Merge "Escape return path extra params to php mail()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 15 Dec 2016 06:05:55 +0000 (06:05 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 15 Dec 2016 06:05:56 +0000 (06:05 +0000)
604 files changed:
.gitignore
RELEASE-NOTES-1.29
autoload.php
composer.json
docs/hooks.txt
includes/AuthPlugin.php
includes/CategoryFinder.php
includes/CategoryViewer.php
includes/DefaultSettings.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/GlobalFunctions.php
includes/MWNamespace.php
includes/Message.php
includes/OutputPage.php
includes/PageProps.php
includes/Preferences.php
includes/PrefixSearch.php
includes/Revision.php
includes/RevisionList.php
includes/Setup.php
includes/WatchedItemQueryService.php
includes/Xml.php
includes/actions/CreditsAction.php
includes/actions/HistoryAction.php
includes/actions/InfoAction.php
includes/actions/MarkpatrolledAction.php
includes/actions/RevisiondeleteAction.php [deleted file]
includes/api/ApiAMCreateAccount.php
includes/api/ApiAuthManagerHelper.php
includes/api/ApiBase.php
includes/api/ApiBlock.php
includes/api/ApiCSPReport.php
includes/api/ApiChangeAuthenticationData.php
includes/api/ApiCheckToken.php
includes/api/ApiClearHasMsg.php
includes/api/ApiClientLogin.php
includes/api/ApiComparePages.php
includes/api/ApiContinuationManager.php
includes/api/ApiDelete.php
includes/api/ApiDisabled.php
includes/api/ApiEditPage.php
includes/api/ApiEmailUser.php
includes/api/ApiErrorFormatter.php
includes/api/ApiExpandTemplates.php
includes/api/ApiFeedContributions.php
includes/api/ApiFeedRecentChanges.php
includes/api/ApiFeedWatchlist.php
includes/api/ApiFileRevert.php
includes/api/ApiFormatJson.php
includes/api/ApiFormatPhp.php
includes/api/ApiFormatRaw.php
includes/api/ApiFormatXml.php
includes/api/ApiImageRotate.php
includes/api/ApiImport.php
includes/api/ApiLinkAccount.php
includes/api/ApiLogin.php
includes/api/ApiLogout.php
includes/api/ApiMain.php
includes/api/ApiManageTags.php
includes/api/ApiMergeHistory.php
includes/api/ApiMessage.php
includes/api/ApiMove.php
includes/api/ApiOpenSearch.php
includes/api/ApiOptions.php
includes/api/ApiPageSet.php
includes/api/ApiParamInfo.php
includes/api/ApiParse.php
includes/api/ApiPatrol.php
includes/api/ApiProtect.php
includes/api/ApiPurge.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/ApiQueryAllImages.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryAllMessages.php
includes/api/ApiQueryAllPages.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBacklinksprop.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryCategories.php
includes/api/ApiQueryCategoryMembers.php
includes/api/ApiQueryDeletedRevisions.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryDisabled.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryIWBacklinks.php
includes/api/ApiQueryIWLinks.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryImages.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLangBacklinks.php
includes/api/ApiQueryLangLinks.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryMyStashedFiles.php
includes/api/ApiQueryQueryPage.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryRevisionsBase.php
includes/api/ApiQuerySearch.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryStashImageInfo.php
includes/api/ApiQueryTokens.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiQueryWatchlistRaw.php
includes/api/ApiRemoveAuthenticationData.php
includes/api/ApiResetPassword.php
includes/api/ApiResult.php
includes/api/ApiRevisionDelete.php
includes/api/ApiRollback.php
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiStashEdit.php
includes/api/ApiTag.php
includes/api/ApiTokens.php
includes/api/ApiUnblock.php
includes/api/ApiUndelete.php
includes/api/ApiUpload.php
includes/api/ApiUsageException.php [new file with mode: 0644]
includes/api/ApiWatch.php
includes/api/i18n/ar.json
includes/api/i18n/be-tarask.json
includes/api/i18n/cs.json
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/hu.json
includes/api/i18n/id.json
includes/api/i18n/it.json
includes/api/i18n/ja.json
includes/api/i18n/ko.json
includes/api/i18n/lb.json
includes/api/i18n/lt.json
includes/api/i18n/mk.json
includes/api/i18n/nb.json
includes/api/i18n/pl.json
includes/api/i18n/pt.json
includes/api/i18n/qqq.json
includes/api/i18n/ru.json
includes/api/i18n/sv.json
includes/api/i18n/te.json
includes/api/i18n/th.json [new file with mode: 0644]
includes/api/i18n/udm.json
includes/api/i18n/uk.json
includes/api/i18n/zh-hans.json
includes/auth/LocalPasswordPrimaryAuthenticationProvider.php
includes/cache/CacheHelper.php
includes/cache/MessageCache.php
includes/changetags/ChangeTagsLogItem.php
includes/content/Content.php
includes/content/WikitextContent.php
includes/db/DatabaseMssql.php
includes/db/DatabaseOracle.php
includes/debug/logger/monolog/LogstashFormatter.php [new file with mode: 0644]
includes/debug/logger/monolog/WikiProcessor.php
includes/deferred/LinksDeletionUpdate.php
includes/diff/DifferenceEngine.php
includes/exception/MWExceptionRenderer.php
includes/export/XmlDumpWriter.php
includes/filerepo/file/File.php
includes/htmlform/fields/HTMLTagFilter.php
includes/http/CurlHttpRequest.php
includes/http/Http.php
includes/http/MWHttpRequest.php
includes/http/PhpHttpRequest.php
includes/installer/DatabaseUpdater.php
includes/installer/MssqlUpdater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/be.json
includes/installer/i18n/bn.json
includes/installer/i18n/cs.json
includes/installer/i18n/en.json
includes/installer/i18n/et.json
includes/installer/i18n/fa.json
includes/installer/i18n/fr.json
includes/installer/i18n/ja.json
includes/installer/i18n/ko.json
includes/installer/i18n/qqq.json
includes/installer/i18n/sd.json
includes/installer/i18n/th.json
includes/installer/i18n/tt-cyrl.json
includes/installer/i18n/zh-hans.json
includes/installer/i18n/zh-hant.json
includes/libs/CookieJar.php
includes/libs/HtmlArmor.php
includes/libs/IEUrlExtension.php
includes/libs/MapCacheLRU.php
includes/libs/Xhprof.php
includes/libs/lockmanager/LockManager.php
includes/libs/mime/XmlTypeCheck.php
includes/libs/objectcache/APCUBagOStuff.php
includes/libs/rdbms/connectionmanager/ConnectionManager.php [new file with mode: 0644]
includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php
includes/libs/stats/NullStatsdDataFactory.php
includes/libs/xmp/XMP.php
includes/logging/BlockLogFormatter.php
includes/logging/ContentModelLogFormatter.php
includes/logging/DeleteLogFormatter.php
includes/logging/LogEntry.php
includes/logging/LogEventsList.php
includes/logging/LogPage.php
includes/media/Bitmap.php
includes/media/DjVu.php
includes/media/Jpeg.php
includes/media/MediaTransformOutput.php
includes/media/SVG.php
includes/media/TransformationalImageHandler.php
includes/objectcache/SqlBagOStuff.php
includes/page/Article.php
includes/page/ImageHistoryPseudoPager.php
includes/page/WikiFilePage.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/parser/ParserOutput.php
includes/password/ParameterizedPassword.php
includes/password/Password.php
includes/password/UserPasswordPolicy.php
includes/poolcounter/PoolWorkArticleView.php
includes/profiler/ProfilerXhprof.php
includes/registration/ExtensionJsonValidationError.php [new file with mode: 0644]
includes/registration/ExtensionJsonValidator.php [new file with mode: 0644]
includes/registration/ExtensionProcessor.php
includes/registration/ExtensionRegistry.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php [deleted file]
includes/revisiondelete/RevDelArchiveItem.php
includes/revisiondelete/RevDelArchivedFileItem.php
includes/revisiondelete/RevDelFileItem.php
includes/revisiondelete/RevDelLogItem.php
includes/revisiondelete/RevDelRevisionItem.php
includes/search/ResultAugmentor.php
includes/search/ResultSetAugmentor.php
includes/search/SearchSuggestion.php
includes/search/SearchSuggestionSet.php
includes/skins/SkinTemplate.php
includes/specialpage/ChangesListSpecialPage.php
includes/specialpage/LoginSignupSpecialPage.php
includes/specialpage/WantedQueryPage.php
includes/specials/SpecialActiveusers.php
includes/specials/SpecialAllPages.php
includes/specials/SpecialAncientpages.php
includes/specials/SpecialApiHelp.php
includes/specials/SpecialBlock.php
includes/specials/SpecialBrokenRedirects.php
includes/specials/SpecialContributions.php
includes/specials/SpecialDeletedContributions.php
includes/specials/SpecialDoubleRedirects.php
includes/specials/SpecialEditTags.php
includes/specials/SpecialEditWatchlist.php
includes/specials/SpecialEmailuser.php
includes/specials/SpecialFewestrevisions.php
includes/specials/SpecialFileDuplicateSearch.php
includes/specials/SpecialImport.php
includes/specials/SpecialListgrants.php
includes/specials/SpecialListgrouprights.php
includes/specials/SpecialMIMEsearch.php
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialMergeHistory.php
includes/specials/SpecialMostcategories.php
includes/specials/SpecialMostinterwikis.php
includes/specials/SpecialMostlinked.php
includes/specials/SpecialMostlinkedtemplates.php
includes/specials/SpecialNewimages.php
includes/specials/SpecialPagesWithProp.php
includes/specials/SpecialPrefixindex.php
includes/specials/SpecialProtectedtitles.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialRevisiondelete.php
includes/specials/SpecialSearch.php
includes/specials/SpecialSpecialpages.php
includes/specials/SpecialStatistics.php
includes/specials/SpecialTrackingCategories.php
includes/specials/SpecialUnblock.php
includes/specials/SpecialUncategorizedcategories.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialUnusedcategories.php
includes/specials/SpecialUnwatchedpages.php
includes/specials/SpecialUserrights.php
includes/specials/SpecialVersion.php
includes/specials/SpecialWantedcategories.php
includes/specials/SpecialWatchlist.php
includes/specials/SpecialWhatlinkshere.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/CategoryPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewFilesPager.php
includes/upload/UploadFromChunks.php
includes/user/User.php
includes/utils/ZipDirectoryReader.php
languages/Language.php
languages/data/ZhConversion.php
languages/i18n/af.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/azb.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bho.json
languages/i18n/bn.json
languages/i18n/bqi.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/dty.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fo.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gu.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/hy.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/khw.json
languages/i18n/kk-cyrl.json
languages/i18n/ko.json
languages/i18n/kri.json
languages/i18n/lb.json
languages/i18n/lij.json
languages/i18n/lki.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/mr.json
languages/i18n/nah.json
languages/i18n/nan.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/oc.json
languages/i18n/pa.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ro.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sah.json
languages/i18n/sd.json
languages/i18n/sgs.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/sv.json
languages/i18n/th.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/udm.json
languages/i18n/uk.json
languages/i18n/vi.json
languages/i18n/wuu.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesBn.php
languages/messages/MessagesMdf.php
languages/messages/MessagesRoa_tara.php
languages/messages/MessagesUk.php
maintenance/archives/patch-externallinks-el_index_60.sql [new file with mode: 0644]
maintenance/backup.inc
maintenance/cdb.php
maintenance/dictionary/mediawiki.dic
maintenance/eval.php
maintenance/jsduck/categories.json
maintenance/language/zhtable/toSimp.manual
maintenance/language/zhtable/toTrad.manual
maintenance/language/zhtable/tradphrases.manual
maintenance/language/zhtable/tradphrases_exclude.manual
maintenance/mergeMessageFileList.php
maintenance/mssql/tables.sql
maintenance/oracle/archives/patch-externallinks-el_index_60.sql [new file with mode: 0644]
maintenance/oracle/tables.sql
maintenance/populateContentModel.php
maintenance/postgres/tables.sql
maintenance/protect.php
maintenance/refreshImageMetadata.php
maintenance/storage/compressOld.php
maintenance/tables.sql
maintenance/validateRegistrationFile.php
mw-config/index.php
resources/Resources.php
resources/lib/oojs-ui/i18n/el.json
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-mediawiki.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-mediawiki.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-mediawiki.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/lib/oojs-ui/themes/mediawiki/icons-alerts.json
resources/lib/oojs-ui/themes/mediawiki/icons-content.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-advanced.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-core.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-list.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-styling.json
resources/lib/oojs-ui/themes/mediawiki/icons-interactions.json
resources/lib/oojs-ui/themes/mediawiki/icons-layout.json
resources/lib/oojs-ui/themes/mediawiki/icons-location.json
resources/lib/oojs-ui/themes/mediawiki/icons-media.json
resources/lib/oojs-ui/themes/mediawiki/icons-moderation.json
resources/lib/oojs-ui/themes/mediawiki/icons-movement.json
resources/lib/oojs-ui/themes/mediawiki/icons-user.json
resources/lib/oojs-ui/themes/mediawiki/icons-wikimedia.json
resources/lib/oojs-ui/themes/mediawiki/icons.json
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/indicators.json
resources/lib/qunitjs/qunit.css
resources/lib/qunitjs/qunit.js
resources/src/jquery/jquery.expandableField.js
resources/src/mediawiki.action/mediawiki.action.history.styles.css
resources/src/mediawiki.action/mediawiki.action.view.filepage.css
resources/src/mediawiki.language/mediawiki.language.numbers.js
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.less/mediawiki.ui/variables.less
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.factory.js
resources/src/mediawiki.skinning/content.css
resources/src/mediawiki.skinning/elements.css
resources/src/mediawiki.skinning/interface.css
resources/src/mediawiki.special/mediawiki.special.apisandbox.js
resources/src/mediawiki.special/mediawiki.special.recentchanges.js
resources/src/mediawiki.special/mediawiki.special.search.styles.css
resources/src/mediawiki.special/mediawiki.special.watchlist.js
resources/src/mediawiki.widgets/MediaSearch/broken-image.png [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsProvider.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsQueue.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceProvider.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceQueue.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.css [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchProvider.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchQueue.js [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.css [new file with mode: 0644]
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js [new file with mode: 0644]
resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
resources/src/mediawiki/api.js
resources/src/mediawiki/api/watch.js
resources/src/mediawiki/mediawiki.notification.js
resources/src/mediawiki/mediawiki.storage.js
resources/src/mediawiki/page/gallery.css
resources/src/mediawiki/page/rollback.js
resources/src/mediawiki/page/watch.js
resources/src/startup.js
tests/common/TestsAutoLoader.php
tests/integration/includes/http/CurlHttpRequestTest.php [new file with mode: 0644]
tests/integration/includes/http/MWHttpRequestTestCase.php [new file with mode: 0644]
tests/integration/includes/http/PhpHttpRequestTest.php [new file with mode: 0644]
tests/parser/parserTests.txt
tests/phan/bin/phan [new file with mode: 0755]
tests/phan/bin/postprocess-phan.php [new file with mode: 0644]
tests/phan/config.php [new file with mode: 0644]
tests/phan/issues/.gitkeep [new file with mode: 0644]
tests/phan/stubs/README [new file with mode: 0644]
tests/phan/stubs/hhvm.php [new file with mode: 0644]
tests/phan/stubs/mail.php [new file with mode: 0644]
tests/phan/stubs/wikidiff.php [new file with mode: 0644]
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/data/localisationcache/ba.json [new file with mode: 0644]
tests/phpunit/data/localisationcache/en.json
tests/phpunit/data/localisationcache/ru.json
tests/phpunit/data/localisationcache/uk.json [deleted file]
tests/phpunit/includes/FormOptionsInitializationTest.php
tests/phpunit/includes/FormOptionsTest.php
tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
tests/phpunit/includes/HttpTest.php [deleted file]
tests/phpunit/includes/MWNamespaceTest.php
tests/phpunit/includes/MessageTest.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/PrefixSearchTest.php
tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php
tests/phpunit/includes/XmlTest.php
tests/phpunit/includes/api/ApiBaseTest.php
tests/phpunit/includes/api/ApiBlockTest.php
tests/phpunit/includes/api/ApiContinuationManagerTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/api/ApiErrorFormatterTest.php
tests/phpunit/includes/api/ApiMainTest.php
tests/phpunit/includes/api/ApiMessageTest.php
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/api/ApiParseTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php
tests/phpunit/includes/api/ApiTestCase.php
tests/phpunit/includes/api/ApiUnblockTest.php
tests/phpunit/includes/api/ApiUploadTest.php
tests/phpunit/includes/api/ApiWatchTest.php
tests/phpunit/includes/api/MockApi.php
tests/phpunit/includes/api/MockApiQueryBase.php
tests/phpunit/includes/api/format/ApiFormatPhpTest.php
tests/phpunit/includes/api/format/ApiFormatXmlTest.php
tests/phpunit/includes/api/query/ApiQueryTest.php
tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php
tests/phpunit/includes/cache/LocalisationCacheTest.php
tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php [new file with mode: 0644]
tests/phpunit/includes/http/HttpTest.php [new file with mode: 0644]
tests/phpunit/includes/installer/DatabaseUpdaterTest.php [deleted file]
tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php [new file with mode: 0644]
tests/phpunit/includes/page/ArticleTest.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/includes/registration/ExtensionRegistryTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
tests/phpunit/includes/search/SearchEnginePrefixTest.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php
tests/phpunit/includes/upload/UploadFromUrlTest.php
tests/phpunit/maintenance/MaintenanceTest.php
tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
tests/phpunit/structure/ExtensionJsonValidationTest.php
tests/phpunit/structure/ResourcesTest.php
tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js
tests/qunit/suites/resources/startup.test.js
thumb.php

index 01a11bf..b2c4d45 100644 (file)
@@ -71,3 +71,4 @@ Thumbs.db
 /tags
 /.htaccess
 /.htpasswd
+/tests/phan/issues
index 23c34df..5ff4ca9 100644 (file)
@@ -14,6 +14,15 @@ production.
   will still be blocked.
 * The resetpassword right and associated password reset capture feature has
   been removed.
+* The $error parameter to the EmailUser hook should be set to a Status object
+  or boolean false. This should be compatible with at least MediaWiki 1.23 if
+  not earlier. Returning a raw HTML string is now deprecated.
+* The $message parameter to the ApiCheckCanExecute hook should be set to an
+  ApiMessage. This is compatible with MediaWiki 1.27 and later. Returning a
+  code for ApiBase::parseMsg() will no longer work.
+* ApiBase::$messageMap is no longer public. Code attempting to access it will
+  result in a PHP fatal error.
+* $wgUserEmailUseReplyTo is now false by default to work around restrictive DMARC policies.
 
 === New features in 1.29 ===
 * (T5233) A cookie can now be set when a user is autoblocked, to track that user if
@@ -22,6 +31,7 @@ production.
 === External library changes in 1.29 ===
 
 ==== Upgraded external libraries ====
+* Updated QUnit from v1.22.0 to v1.23.1.
 
 ==== New external libraries ====
 
@@ -35,8 +45,49 @@ production.
   in the query string is now an error. They should be submitted in the POST
   body instead.
 * The capture option for action=resetpassword has been removed
+* action=clearhasmsg now requires a POST.
+* (T47843) API errors and warnings may be requested in non-English languages
+  using the new 'errorformat', 'errorlang', and 'errorsuselocal' parameters.
+* API error codes may have changed. Most notably, errors from modules using
+  parameter prefixes (e.g. all query submodules) will no longer be prefixed.
+* action=emailuser may return a "Warnings" status, and now returns 'warnings' and
+  'errors' subelements (as applicable) instead of 'message'.
+* action=imagerotate returns an 'errors' subelement rather than 'errormessage'.
+* action=move now reports errors when moving the talk page as an array under
+  key 'talkmove-errors', rather than using 'talkmove-error-code' and
+  'talkmove-error-info'. The format for subpage move errors has also changed.
+* action=revisiondelete no longer includes a "rendered" property on warnings
+  and errors for each item. Use errorformat=wikitext if you're wanting parsed
+  output.
+* action=rollback no longer returns a "messageHtml" property. Use
+  errorformat=html if you're wanting HTML formatting of error messages.
+* action=upload now reports optional stash failures as an array under key
+  'stasherrors' rather than a 'stashfailed' text string.
+* action=watch reports 'errors' and 'warnings' instead of a single 'error', and
+  no longer returns a 'message' on success.
 
 === Action API internal changes in 1.29 ===
+* New methods were added to ApiBase to handle errors and warnings using i18n
+  keys. Methods for using hard-coded English messages were deprecated:
+  * ApiBase::dieUsage() was deprecated
+  * ApiBase::dieUsageMsg() was deprecated
+  * ApiBase::dieUsageMsgOrDebug() was deprecated
+  * ApiBase::getErrorFromStatus() was deprecated
+  * ApiBase::parseMsg() was deprecated
+  * ApiBase::setWarning() was deprecated
+* ApiBase::$messageMap is no longer public. Code attempting to access it will
+  result in a PHP fatal error.
+* The $message parameter to the ApiCheckCanExecute hook should be set to an
+  ApiMessage. This is compatible with MediaWiki 1.27 and later. Returning a
+  code for ApiBase::parseMsg() will no longer work.
+* UsageException is deprecated in favor of ApiUsageException. For the time
+  being ApiUsageException is a subclass of UsageException to allow things that
+  catch only UsageException to still function properly.
+* If, for some strange reason, code was using an ApiErrorFormatter instead of
+  ApiErrorFormatter_BackCompat, note that the result format has changed and
+  various methods now take a module path rather than a module name.
+* ApiMessageTrait::getApiCode() now strips 'apierror-' and 'apiwarn-' prefixes
+  from the message key, and maps some message keys for backwards compatibility.
 
 === Languages updated in 1.29 ===
 
@@ -44,9 +95,30 @@ MediaWiki supports over 350 languages. Many localisations are updated
 regularly. Below only new and removed languages are listed, as well as
 changes to languages because of Phabricator reports.
 
+==== No fallback for Ukrainian ====
+* (T39314) The fallback from Ukrainian to Russian was removed. The Ukrainian
+  language will now use the default fallback language: English. When a translation
+  to Ukrainian is not available, an English string will be shown.
+
 === Other changes in 1.29 ===
 * Database::getSearchEngine() (deprecated in 1.28) was removed. Use
   SearchEngineFactory::getSearchEngineClass() instead.
+* $wgSessionsInMemcached (deprecated in 1.20) was removed. No replacement is
+  required as all sessions are stored in Object Cache now.
+* MWHttpRequest::execute() should be considered to return a StatusValue; the
+  Status return type is deprecated.
+* User::edits() (deprecated in 1.21) was removed.
+* Xml::escapeJsString() (deprecated in 1.21) was removed.
+* Article::getText() and Article::prepareTextForEdit() (deprecated in 1.21)
+  were removed.
+* Article::getAutosummary() and WikiPage::getAutosummary (deprecated in 1.21)
+  were removed.
+* Hooks ArticleViewCustom, EditPageGetDiffText and ShowRawCssJs (deprecated in 1.21)
+  were removed.
+* Class RevisiondeleteAction (deprecated in 1.25) was removed.
+* WikiPage::prepareTextForEdit() (deprecated in 1.21) was removed.
+* WikiPage::getText() (deprecated in 1.21) was removed.
+* Article::fetchContent() (deprecated in 1.21) was removed.
 
 == Compatibility ==
 
index 30ef985..e079686 100644 (file)
@@ -145,6 +145,7 @@ $wgAutoloadLocalClasses = [
        'ApiUnblock' => __DIR__ . '/includes/api/ApiUnblock.php',
        'ApiUndelete' => __DIR__ . '/includes/api/ApiUndelete.php',
        'ApiUpload' => __DIR__ . '/includes/api/ApiUpload.php',
+       'ApiUsageException' => __DIR__ . '/includes/api/ApiUsageException.php',
        'ApiUserrights' => __DIR__ . '/includes/api/ApiUserrights.php',
        'ApiWatch' => __DIR__ . '/includes/api/ApiWatch.php',
        'ArchivedFile' => __DIR__ . '/includes/filerepo/file/ArchivedFile.php',
@@ -428,6 +429,8 @@ $wgAutoloadLocalClasses = [
        'ExplodeIterator' => __DIR__ . '/includes/libs/ExplodeIterator.php',
        'ExportProgressFilter' => __DIR__ . '/maintenance/backup.inc',
        'ExportSites' => __DIR__ . '/maintenance/exportSites.php',
+       'ExtensionJsonValidationError' => __DIR__ . '/includes/registration/ExtensionJsonValidationError.php',
+       'ExtensionJsonValidator' => __DIR__ . '/includes/registration/ExtensionJsonValidator.php',
        'ExtensionLanguages' => __DIR__ . '/maintenance/language/languages.inc',
        'ExtensionProcessor' => __DIR__ . '/includes/registration/ExtensionProcessor.php',
        'ExtensionRegistry' => __DIR__ . '/includes/registration/ExtensionRegistry.php',
@@ -875,6 +878,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Logger\\Monolog\\LegacyFormatter' => __DIR__ . '/includes/debug/logger/monolog/LegacyFormatter.php',
        'MediaWiki\\Logger\\Monolog\\LegacyHandler' => __DIR__ . '/includes/debug/logger/monolog/LegacyHandler.php',
        'MediaWiki\\Logger\\Monolog\\LineFormatter' => __DIR__ . '/includes/debug/logger/monolog/LineFormatter.php',
+       'MediaWiki\\Logger\\Monolog\\LogstashFormatter' => __DIR__ . '/includes/debug/logger/monolog/LogstashFormatter.php',
        'MediaWiki\\Logger\\Monolog\\SyslogHandler' => __DIR__ . '/includes/debug/logger/monolog/SyslogHandler.php',
        'MediaWiki\\Logger\\Monolog\\WikiProcessor' => __DIR__ . '/includes/debug/logger/monolog/WikiProcessor.php',
        'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
@@ -1196,7 +1200,6 @@ $wgAutoloadLocalClasses = [
        'ResourceLoaderSpecialCharacterDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php',
        'ResourceLoaderStartUpModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderStartUpModule.php',
        'ResourceLoaderUploadDialogModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUploadDialogModule.php',
-       'ResourceLoaderUserCSSPrefsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
        'ResourceLoaderUserDefaultsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserDefaultsModule.php',
        'ResourceLoaderUserModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserModule.php',
        'ResourceLoaderUserOptionsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserOptionsModule.php',
@@ -1230,7 +1233,6 @@ $wgAutoloadLocalClasses = [
        'RevisionItemBase' => __DIR__ . '/includes/RevisionList.php',
        'RevisionList' => __DIR__ . '/includes/RevisionList.php',
        'RevisionListBase' => __DIR__ . '/includes/RevisionList.php',
-       'RevisiondeleteAction' => __DIR__ . '/includes/actions/RevisiondeleteAction.php',
        'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
        'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
        'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
@@ -1502,7 +1504,7 @@ $wgAutoloadLocalClasses = [
        'UploadStashWrongOwnerException' => __DIR__ . '/includes/upload/UploadStash.php',
        'UploadStashZeroLengthFileException' => __DIR__ . '/includes/upload/UploadStash.php',
        'UppercaseCollation' => __DIR__ . '/includes/collation/UppercaseCollation.php',
-       'UsageException' => __DIR__ . '/includes/api/ApiMain.php',
+       'UsageException' => __DIR__ . '/includes/api/ApiUsageException.php',
        'User' => __DIR__ . '/includes/user/User.php',
        'UserArray' => __DIR__ . '/includes/user/UserArray.php',
        'UserArrayFromResult' => __DIR__ . '/includes/user/UserArrayFromResult.php',
@@ -1569,6 +1571,8 @@ $wgAutoloadLocalClasses = [
        'WikiRevision' => __DIR__ . '/includes/import/WikiRevision.php',
        'WikiStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'WikiTextStructure' => __DIR__ . '/includes/content/WikiTextStructure.php',
+       'Wikimedia\\Rdbms\\ConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/ConnectionManager.php',
+       'Wikimedia\\Rdbms\\SessionConsistentConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php',
        'WikitextContent' => __DIR__ . '/includes/content/WikitextContent.php',
        'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
        'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
index e1d9f47..f57b9ce 100644 (file)
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.18.0",
+               "oojs/oojs-ui": "0.18.2",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
index a73d50f..1ecc1f8 100644 (file)
@@ -358,8 +358,12 @@ authenticate and authorize API clients before executing the module. Return
 false and set a message to cancel the request.
 $module: Module object
 $user: Current user
-&$message: API usage message to die with, as a message key or array
-  as accepted by ApiBase::dieUsageMsg.
+&$message: API message to die with. Specific values accepted depend on the
+ MediaWiki version:
+ * 1.29+: IApiMessage, Message, string message key, or key+parameters array to
+   pass to ApiBase::dieWithError().
+ * 1.27+: IApiMessage, or a key or key+parameters in ApiBase::$messageMap.
+ * Earlier: A key or key+parameters in ApiBase::$messageMap.
 
 'APIEditBeforeSave': DEPRECATED! Use EditFilterMergedContent instead.
 Before saving a page with api.php?action=edit, after
@@ -774,14 +778,6 @@ $article: the article
 &$sectionanchor: The section anchor link (e.g. "#overview" )
 &$extraq: Extra query parameters which can be added via hooked functions
 
-'ArticleViewCustom': DEPRECATED! Use ArticleContentViewCustom instead.
-Allows to output the text of the article in a different format than wikitext.
-Note that it is preferable to implement proper handing for a custom data type
-using the ContentHandler facility.
-$text: text of the page
-$title: title of the page
-$output: reference to $wgOut
-
 'ArticleViewFooter': After showing the footer section of an ordinary page view
 $article: Article object
 $patrolFooterShown: boolean whether patrol footer is shown
@@ -1194,6 +1190,18 @@ $page: SpecialPage object for DeletedContributions
 $row: the DB row for this line
 &$classes: the classes to add to the surrounding <li>
 
+'DifferenceEngineAfterLoadNewText': called in DifferenceEngine::loadNewText()
+after the new revision's content has been loaded into the class member variable
+$differenceEngine->mNewContent but before returning true from this function.
+$differenceEngine: DifferenceEngine object
+
+'DifferenceEngineLoadTextAfterNewContentIsLoaded': called in
+DifferenceEngine::loadText() after the new revision's content has been loaded
+into the class member variable $differenceEngine->mNewContent but before
+checking if the variable's value is null.
+This hook can be used to inject content into said class member variable.
+$differenceEngine: DifferenceEngine object
+
 'DifferenceEngineMarkPatrolledLink': Allows extensions to change the "mark as patrolled" link
 which is shown both on the diff header as well as on the bottom of a page, usually
 wrapped in a span element which has class="patrollink".
@@ -1272,6 +1280,12 @@ $differenceEngine: DifferenceEngine object
 object into the diff view
 $out: OutputPage object
 
+'DifferenceEngineShowDiffPageMaybeShowMissingRevision': called in
+DifferenceEngine::showDiffPage() when revision data cannot be loaded.
+Return false in order to prevent displaying the missing revision message
+(i.e. to prevent DifferenceEngine::showMissingRevision() from being called).
+$differenceEngine: DifferenceEngine object
+
 'DiffRevisionTools': Override or extend the revision tools available from the
 diff view, i.e. undo, etc.
 $newRev: Revision object of the "new" revision
@@ -1417,13 +1431,6 @@ different data types using the ContentHandler facility.
 $editPage: EditPage object
 &$newtext: wikitext that will be used as "your version"
 
-'EditPageGetDiffText': DEPRECATED! Use EditPageGetDiffContent instead.
-Allow modifying the wikitext that will be used in "Show changes". Note that it
-is preferable to implement diff handling for different data types using the
-ContentHandler facility.
-$editPage: EditPage object
-&$newtext: wikitext that will be used as "your version"
-
 'EditPageGetPreviewContent': Allow modifying the wikitext that will be
 previewed. Note that it is preferable to implement previews for different data
 types using the ContentHandler facility.
@@ -1459,7 +1466,7 @@ true to allow those checks to occur, and false if checking is done.
 &$from: MailAddress object of sending user
 &$subject: subject of the mail
 &$text: text of the mail
-&$error: Out-param for an error
+&$error: Out-param for an error. Should be set to a Status object or boolean false.
 
 'EmailUserCC': Before sending the copy of the email to the author.
 &$to: MailAddress object of receiving user
@@ -2554,8 +2561,6 @@ $user: the User considering the edit
 'PasswordPoliciesForUser': Alter the effective password policy for a user.
 $user: User object whose policy you are modifying
 &$effectivePolicy: Array of policy statements that apply to this user
-$purpose: string indicating purpose of the check, one of 'login', 'create',
-  or 'reset'
 
 'PerformRetroactiveAutoblock': Called before a retroactive autoblock is applied
 to a user.
@@ -2843,12 +2848,6 @@ Special:ShortPages.
 'ShowMissingArticle': Called when generating the output for a non-existent page.
 $article: The article object corresponding to the page
 
-'ShowRawCssJs': DEPRECATED! Use the ContentGetParserOutput hook instead.
-Customise the output of raw CSS and JavaScript in page views.
-$text: Text being shown
-$title: Title of the custom script/stylesheet page
-$output: Current OutputPage object
-
 'ShowSearchHit': Customize display of search hit.
 $searchPage: The SpecialSearch instance.
 $result: The SearchResult to show
index 0b65593..b85e1d6 100644 (file)
@@ -73,7 +73,7 @@ class AuthPlugin {
        /**
         * Modify options in the login template.
         *
-        * @param UserLoginTemplate $template
+        * @param BaseTemplate $template
         * @param string $type 'signup' or 'login'. Added in 1.16.
         */
        public function modifyUITemplate( &$template, &$type ) {
index 4efa2ff..504b35f 100644 (file)
@@ -40,7 +40,6 @@
  *     $a = $cf->run();
  *     print implode( ',' , $a );
  * @endcode
- *
  */
 class CategoryFinder {
        /** @var int[] The original article IDs passed to the seed function */
index b95f274..4c4b8bb 100644 (file)
@@ -197,7 +197,11 @@ class CategoryViewer extends ContextSource {
                $link = null;
                Hooks::run( 'CategoryViewer::generateLink', [ $type, $title, $html, &$link ] );
                if ( $link === null ) {
-                       $link = Linker::link( $title, $html );
+                       $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+                       if ( $html !== null ) {
+                               $html = new HtmlArmor( $html );
+                       }
+                       $link = $linkRenderer->makeLink( $title, $html );
                }
                if ( $isRedirect ) {
                        $link = '<span class="redirect-in-category">' . $link . '</span>';
index eb778b5..e908a16 100644 (file)
@@ -1585,14 +1585,15 @@ $wgEnableEmail = true;
 $wgEnableUserEmail = true;
 
 /**
- * Set to true to put the sending user's email in a Reply-To header
- * instead of From. ($wgPasswordSender will be used as From.)
+ * If true put the sending user's email in a Reply-To header
+ * instead of From (false). ($wgPasswordSender will be used as From.)
  *
  * Some mailers (eg SMTP) set the SMTP envelope sender to the From value,
  * which can cause problems with SPF validation and leak recipient addresses
- * when bounces are sent to the sender.
+ * when bounces are sent to the sender. In addition, DMARC restrictions
+ * can cause emails to fail to be received when false.
  */
-$wgUserEmailUseReplyTo = false;
+$wgUserEmailUseReplyTo = true;
 
 /**
  * Minimum time, in hours, which must elapse between password reminder
@@ -2031,7 +2032,6 @@ $wgDBmysql5 = false;
  * is in this case an unwanted overhead that just slows things down.
  *
  * @warning EXPERIMENTAL!
- *
  */
 $wgDBOracleDRCP = false;
 
@@ -2365,13 +2365,6 @@ $wgMainStash = 'db-replicated';
  */
 $wgParserCacheExpireTime = 86400;
 
-/**
- * Deprecated alias for $wgSessionsInObjectCache.
- *
- * @deprecated since 1.20; Use $wgSessionsInObjectCache
- */
-$wgSessionsInMemcached = true;
-
 /**
  * @deprecated since 1.27, session data is always stored in object cache.
  */
index 745f8de..f37ce34 100644 (file)
@@ -3326,7 +3326,6 @@ HTML
                }
 
                if ( $newContent ) {
-                       ContentHandler::runLegacyHooks( 'EditPageGetDiffText', [ $this, &$newContent ], '1.21' );
                        Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
 
                        $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
index e6223e8..f850152 100644 (file)
@@ -152,7 +152,7 @@ class FileDeleteForm {
         * @param User $user User object performing the request
         * @param array $tags Tags to apply to the deletion action
         * @throws MWException
-        * @return bool|Status
+        * @return Status
         */
        public static function doDelete( &$title, &$file, &$oldimage, $reason,
                $suppress, User $user = null, $tags = []
index b3ccc56..f9f499a 100644 (file)
@@ -3518,7 +3518,7 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
        # Run the extension hook
        $bad = false;
        if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
-               return $bad;
+               return (bool)$bad;
        }
 
        $cache = ObjectCache::getLocalServerInstance( 'hash' );
index 61e34ee..4c5561f 100644 (file)
@@ -28,7 +28,6 @@
  *
  * These are synonyms for the names given in the language file
  * Users and translators should not change them
- *
  */
 class MWNamespace {
 
index 85c78d6..2816aed 100644 (file)
@@ -168,6 +168,17 @@ class Message implements MessageSpecifier, Serializable {
        /** Transform {{..}} constructs, HTML-escape the result */
        const FORMAT_ESCAPED = 'escaped';
 
+       /**
+        * Mapping from Message::listParam() types to Language methods.
+        * @var array
+        */
+       protected static $listTypeMap = [
+               'comma' => 'commaList',
+               'semicolon' => 'semicolonList',
+               'pipe' => 'pipeList',
+               'text' => 'listToText',
+       ];
+
        /**
         * In which language to get this message. True, which is the default,
         * means the current user language, false content language.
@@ -477,18 +488,32 @@ class Message implements MessageSpecifier, Serializable {
         *
         * @since 1.17
         *
-        * @param mixed ... Parameters as strings, or a single argument that is
-        * an array of strings.
+        * @param mixed ... Parameters as strings or arrays from
+        *  Message::numParam() and the like, or a single array of parameters.
         *
         * @return Message $this
         */
        public function params( /*...*/ ) {
                $args = func_get_args();
-               if ( isset( $args[0] ) && is_array( $args[0] ) ) {
-                       $args = $args[0];
+
+               // If $args has only one entry and it's an array, then it's either a
+               // non-varargs call or it happens to be a call with just a single
+               // "special" parameter. Since the "special" parameters don't have any
+               // numeric keys, we'll test that to differentiate the cases.
+               if ( count( $args ) === 1 && isset( $args[0] ) && is_array( $args[0] ) ) {
+                       if ( $args[0] === [] ) {
+                               $args = [];
+                       } else {
+                               foreach ( $args[0] as $key => $value ) {
+                                       if ( is_int( $key ) ) {
+                                               $args = $args[0];
+                                               break;
+                                       }
+                               }
+                       }
                }
-               $args_values = array_values( $args );
-               $this->parameters = array_merge( $this->parameters, $args_values );
+
+               $this->parameters = array_merge( $this->parameters, array_values( $args ) );
                return $this;
        }
 
@@ -1029,9 +1054,9 @@ class Message implements MessageSpecifier, Serializable {
        /**
         * @since 1.22
         *
-        * @param number $period
+        * @param int $period
         *
-        * @return number[] Array with a single "period" key.
+        * @return int[] Array with a single "period" key.
         */
        public static function timeperiodParam( $period ) {
                return [ 'period' => $period ];
@@ -1070,6 +1095,22 @@ class Message implements MessageSpecifier, Serializable {
                return [ 'plaintext' => $plaintext ];
        }
 
+       /**
+        * @since 1.29
+        *
+        * @param array $list
+        * @param string $type 'comma', 'semicolon', 'pipe', 'text'
+        * @return array Array with "list" and "type" keys.
+        */
+       public static function listParam( array $list, $type = 'text' ) {
+               if ( !isset( self::$listTypeMap[$type] ) ) {
+                       throw new InvalidArgumentException(
+                               "Invalid type '$type'. Known types are: " . join( ', ', array_keys( self::$listTypeMap ) )
+                       );
+               }
+               return [ 'list' => $list, 'type' => $type ];
+       }
+
        /**
         * Substitutes any parameters into the message text.
         *
@@ -1123,6 +1164,8 @@ class Message implements MessageSpecifier, Serializable {
                                return [ 'before', $this->getLanguage()->formatBitrate( $param['bitrate'] ) ];
                        } elseif ( isset( $param['plaintext'] ) ) {
                                return [ 'after', $this->formatPlaintext( $param['plaintext'], $format ) ];
+                       } elseif ( isset( $param['list'] ) ) {
+                               return $this->formatListParam( $param['list'], $param['type'], $format );
                        } else {
                                $warning = 'Invalid parameter for message "' . $this->getKey() . '": ' .
                                        htmlspecialchars( serialize( $param ) );
@@ -1251,6 +1294,54 @@ class Message implements MessageSpecifier, Serializable {
 
                }
        }
+
+       /**
+        * Formats a list of parameters as a concatenated string.
+        * @since 1.29
+        * @param array $params
+        * @param string $listType
+        * @param string $format One of the FORMAT_* constants.
+        * @return array Array with the parameter type (either "before" or "after") and the value.
+        */
+       protected function formatListParam( array $params, $listType, $format ) {
+               if ( !isset( self::$listTypeMap[$listType] ) ) {
+                       $warning = 'Invalid list type for message "' . $this->getKey() . '": ' .
+                               htmlspecialchars( serialize( $param ) );
+                       trigger_error( $warning, E_USER_WARNING );
+                       $e = new Exception;
+                       wfDebugLog( 'Bug58676', $warning . "\n" . $e->getTraceAsString() );
+                       return [ 'before', '[INVALID]' ];
+               }
+               $func = self::$listTypeMap[$listType];
+
+               // Handle an empty list sensibly
+               if ( !$params ) {
+                       return [ 'before', $this->getLanguage()->$func( [] ) ];
+               }
+
+               // First, determine what kinds of list items we have
+               $types = [];
+               $vars = [];
+               $list = [];
+               foreach ( $params as $n => $p ) {
+                       list( $type, $value ) = $this->extractParam( $p, $format );
+                       $types[$type] = true;
+                       $list[] = $value;
+                       $vars[] = '$' . ( $n + 1 );
+               }
+
+               // Easy case: all are 'before' or 'after', so just join the
+               // values and use the same type.
+               if ( count( $types ) === 1 ) {
+                       return [ key( $types ), $this->getLanguage()->$func( $list ) ];
+               }
+
+               // Hard case: We need to process each value per its type, then
+               // return the concatenated values as 'after'. We handle this by turning
+               // the list into a RawMessage and processing that as a parameter.
+               $vars = $this->getLanguage()->$func( $vars );
+               return $this->extractParam( new RawMessage( $vars, $params ), $format );
+       }
 }
 
 /**
index 43d71ab..bae871e 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\SessionManager;
 use WrappedString\WrappedString;
 use WrappedString\WrappedStringList;
@@ -1010,8 +1011,9 @@ class OutputPage extends ContextSource {
                if ( $title->isRedirect() ) {
                        $query['redirect'] = 'no';
                }
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                return wfMessage( 'backlinksubtitle' )
-                       ->rawParams( Linker::link( $title, null, [], $query ) );
+                       ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
        }
 
        /**
@@ -1269,6 +1271,7 @@ class OutputPage extends ContextSource {
                        'OutputPageMakeCategoryLinks',
                        [ &$this, $categories, &$this->mCategoryLinks ] )
                ) {
+                       $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                        foreach ( $categories as $category => $type ) {
                                // array keys will cast numeric category names to ints, so cast back to string
                                $category = (string)$category;
@@ -1283,7 +1286,7 @@ class OutputPage extends ContextSource {
                                }
                                $text = $wgContLang->convertHtml( $title->getText() );
                                $this->mCategories[$type][] = $title->getText();
-                               $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
+                               $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
                        }
                }
        }
@@ -2653,8 +2656,10 @@ class OutputPage extends ContextSource {
         * @param array $options Options array to pass to Linker
         */
        public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
+               $linkRenderer = MediaWikiServices::getInstance()
+                       ->getLinkRendererFactory()->createFromLegacyOptions( $options );
                $link = $this->msg( 'returnto' )->rawParams(
-                       Linker::link( $title, $text, [], $query, $options ) )->escaped();
+                       $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
                $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
        }
 
@@ -2735,7 +2740,6 @@ class OutputPage extends ContextSource {
                                'site.styles',
                                'noscript',
                                'user.styles',
-                               'user.cssprefs',
                        ] );
                        $this->getSkin()->setupSkinUserCss( $this );
 
@@ -2848,6 +2852,14 @@ class OutputPage extends ContextSource {
                $bodyClasses[] = $userdir;
                $bodyClasses[] = "sitedir-$sitedir";
 
+               $underline = $this->getUser()->getOption( 'underline' );
+               if ( $underline < 2 ) {
+                       // The following classes can be used here:
+                       // * mw-underline-always
+                       // * mw-underline-never
+                       $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
+               }
+
                if ( $this->getLanguage()->capitalizeAllNouns() ) {
                        # A <body> class is probably not the best way to do this . . .
                        $bodyClasses[] = 'capitalize-all-nouns';
index d09e1fb..382d089 100644 (file)
@@ -25,7 +25,6 @@ use Wikimedia\ScopedCallback;
  * Gives access to properties of a page.
  *
  * @since 1.27
- *
  */
 class PageProps {
 
index d86b19a..cf8e7b8 100644 (file)
@@ -21,6 +21,7 @@
  */
 use MediaWiki\Auth\AuthManager;
 use MediaWiki\Auth\PasswordAuthenticationRequest;
+use MediaWiki\MediaWikiServices;
 
 /**
  * We're now using the HTMLForm object with some customisation to generate the
@@ -253,7 +254,9 @@ class Preferences {
                        'section' => 'personal/info',
                ];
 
-               $editCount = Linker::link( SpecialPage::getTitleFor( "Contributions", $userName ),
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+
+               $editCount = $linkRenderer->makeLink( SpecialPage::getTitleFor( "Contributions", $userName ),
                        $lang->formatNum( $user->getEditCount() ) );
 
                $defaultPreferences['editcount'] = [
@@ -297,8 +300,8 @@ class Preferences {
                if ( $canEditPrivateInfo && $authManager->allowsAuthenticationDataChange(
                        new PasswordAuthenticationRequest(), false )->isGood()
                ) {
-                       $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
-                               $context->msg( 'prefs-resetpass' )->escaped(), [],
+                       $link = $linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
+                               $context->msg( 'prefs-resetpass' )->text(), [],
                                [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
 
                        $defaultPreferences['password'] = [
@@ -448,9 +451,9 @@ class Preferences {
 
                                $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
                                if ( $canEditPrivateInfo && $authManager->allowsPropertyChange( 'emailaddress' ) ) {
-                                       $link = Linker::link(
+                                       $link = $linkRenderer->makeLink(
                                                SpecialPage::getTitleFor( 'ChangeEmail' ),
-                                               $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
+                                               $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
                                                [],
                                                [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
 
@@ -601,14 +604,15 @@ class Preferences {
                        $linkTools = [];
                        $userName = $user->getName();
 
+                       $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                        if ( $allowUserCss ) {
                                $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
-                               $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
+                               $linkTools[] = $linkRenderer->makeLink( $cssPage, $context->msg( 'prefs-custom-css' )->text() );
                        }
 
                        if ( $allowUserJs ) {
                                $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
-                               $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
+                               $linkTools[] = $linkRenderer->makeLink( $jsPage, $context->msg( 'prefs-custom-js' )->text() );
                        }
 
                        $defaultPreferences['commoncssjs'] = [
@@ -1110,6 +1114,8 @@ class Preferences {
                $mptitle = Title::newMainPage();
                $previewtext = $context->msg( 'skin-preview' )->escaped();
 
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+
                # Only show skins that aren't disabled in $wgSkipSkins
                $validSkinNames = Skin::getAllowedSkins();
 
@@ -1145,12 +1151,12 @@ class Preferences {
                        # Create links to user CSS/JS pages
                        if ( $allowUserCss ) {
                                $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
-                               $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
+                               $linkTools[] = $linkRenderer->makeLink( $cssPage, $context->msg( 'prefs-custom-css' )->text() );
                        }
 
                        if ( $allowUserJs ) {
                                $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
-                               $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
+                               $linkTools[] = $linkRenderer->makeLink( $jsPage, $context->msg( 'prefs-custom-js' )->text() );
                        }
 
                        $display = $sn . ' ' . $context->msg( 'parentheses' )
@@ -1212,7 +1218,8 @@ class Preferences {
                $pixels = $context->msg( 'unit-pixel' )->text();
 
                foreach ( $context->getConfig()->get( 'ImageLimits' ) as $index => $limits ) {
-                       $display = "{$limits[0]}×{$limits[1]}" . $pixels;
+                       // Note: A left-to-right marker (\u200e) is inserted, see T144386
+                       $display = "{$limits[0]}" . json_decode( '"\u200e"' ) . "×{$limits[1]}" . $pixels;
                        $ret[$display] = $index;
                }
 
@@ -1624,7 +1631,8 @@ class PreferencesForm extends HTMLForm {
                if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
                        $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
 
-                       $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped(),
+                       $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+                       $html .= "\n" . $linkRenderer->makeLink( $t, $this->msg( 'restoreprefs' )->text(),
                                Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) );
 
                        $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
index f6c4147..04c17e4 100644 (file)
@@ -239,7 +239,7 @@ abstract class PrefixSearch {
                // canonical and alias title forms...
                $keys = [];
                foreach ( SpecialPageFactory::getNames() as $page ) {
-                       $keys[$wgContLang->caseFold( $page )] = $page;
+                       $keys[$wgContLang->caseFold( $page )] = [ 'page' => $page, 'rank' => 0 ];
                }
 
                foreach ( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
@@ -247,33 +247,35 @@ abstract class PrefixSearch {
                                continue;
                        }
 
-                       foreach ( $aliases as $alias ) {
-                               $keys[$wgContLang->caseFold( $alias )] = $alias;
+                       foreach ( $aliases as $key => $alias ) {
+                               $keys[$wgContLang->caseFold( $alias )] = [ 'page' => $alias, 'rank' => $key ];
                        }
                }
                ksort( $keys );
 
-               $srchres = [];
-               $skipped = 0;
+               $matches = [];
                foreach ( $keys as $pageKey => $page ) {
                        if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
                                // bug 27671: Don't use SpecialPage::getTitleFor() here because it
                                // localizes its input leading to searches for e.g. Special:All
                                // returning Spezial:MediaWiki-Systemnachrichten and returning
                                // Spezial:Alle_Seiten twice when $wgLanguageCode == 'de'
-                               if ( $offset > 0 && $skipped < $offset ) {
-                                       $skipped++;
-                                       continue;
+                               $matches[$page['rank']][] = Title::makeTitleSafe( NS_SPECIAL, $page['page'] );
+
+                               if ( isset( $matches[0] ) && count( $matches[0] ) >= $limit + $offset ) {
+                                       // We have enough items in primary rank, no use to continue
+                                       break;
                                }
-                               $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page );
                        }
 
-                       if ( count( $srchres ) >= $limit ) {
-                               break;
-                       }
                }
 
-               return $srchres;
+               // Ensure keys are in order
+               ksort( $matches );
+               // Flatten the array
+               $matches = array_reduce( $matches, 'array_merge', [] );
+
+               return array_slice( $matches, $offset, $limit );
        }
 
        /**
index b812191..81bba55 100644 (file)
@@ -1131,7 +1131,7 @@ class Revision implements IDBAccessObject {
         *
         * @return string The content model id associated with this revision,
         *     see the CONTENT_MODEL_XXX constants.
-        **/
+        */
        public function getContentModel() {
                if ( !$this->mContentModel ) {
                        $title = $this->getTitle();
@@ -1155,7 +1155,7 @@ class Revision implements IDBAccessObject {
         *
         * @return string The content format id associated with this revision,
         *     see the CONTENT_FORMAT_XXX constants.
-        **/
+        */
        public function getContentFormat() {
                if ( !$this->mContentFormat ) {
                        $handler = $this->getContentHandler();
index fb444bd..052fd16 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * List for revision table items for a single page
  */
@@ -268,6 +270,14 @@ abstract class RevisionItemBase {
         * This is used to show the list in HTML form, by the special page.
         */
        abstract public function getHTML();
+
+       /**
+        * Returns an instance of LinkRenderer
+        * @return \MediaWiki\Linker\LinkRenderer
+        */
+       protected function getLinkRenderer() {
+               return MediaWikiServices::getInstance()->getLinkRenderer();
+       }
 }
 
 class RevisionList extends RevisionListBase {
index 357c76d..f6631ea 100644 (file)
@@ -36,8 +36,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 $fname = 'Setup.php';
 $ps_setup = Profiler::instance()->scopedProfileIn( $fname );
 
-// If any extensions are still queued, force load them
+// Load queued extensions
 ExtensionRegistry::getInstance()->loadFromQueue();
+// Don't let any other extensions load
+ExtensionRegistry::getInstance()->finish();
 
 // Check to see if we are at the file scope
 if ( !isset( $wgVersion ) ) {
@@ -462,7 +464,7 @@ if ( $wgMaximalPasswordLength !== false ) {
 }
 
 // Backwards compatibility warning
-if ( !$wgSessionsInObjectCache && !$wgSessionsInMemcached ) {
+if ( !$wgSessionsInObjectCache ) {
        wfDeprecated( '$wgSessionsInObjectCache = false', '1.27' );
        if ( $wgSessionHandler ) {
                wfDeprecated( '$wgSessionsHandler', '1.27' );
index 0c3d52a..cd78b49 100644 (file)
@@ -422,10 +422,7 @@ class WatchedItemQueryService {
                        $ownersToken = $watchlistOwner->getOption( 'watchlisttoken' );
                        $token = $options['watchlistOwnerToken'];
                        if ( $ownersToken == '' || !hash_equals( $ownersToken, $token ) ) {
-                               throw new UsageException(
-                                       'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
-                                       'bad_wltoken'
-                               );
+                               throw ApiUsageException::newWithMessage( null, 'apierror-bad-watchlist-token', 'bad_wltoken' );
                        }
                        return $watchlistOwner->getId();
                }
index 4c6b071..e124c38 100644 (file)
@@ -613,42 +613,6 @@ class Xml {
                                        $content, false );
        }
 
-       /**
-        * Returns an escaped string suitable for inclusion in a string literal
-        * for JavaScript source code.
-        * Illegal control characters are assumed not to be present.
-        *
-        * @deprecated since 1.21; use Xml::encodeJsVar() or Xml::encodeJsCall() instead
-        * @param string $string String to escape
-        * @return string
-        */
-       public static function escapeJsString( $string ) {
-               // See ECMA 262 section 7.8.4 for string literal format
-               $pairs = [
-                       "\\" => "\\\\",
-                       "\"" => "\\\"",
-                       '\'' => '\\\'',
-                       "\n" => "\\n",
-                       "\r" => "\\r",
-
-                       # To avoid closing the element or CDATA section
-                       "<" => "\\x3c",
-                       ">" => "\\x3e",
-
-                       # To avoid any complaints about bad entity refs
-                       "&" => "\\x26",
-
-                       # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
-                       # Encode certain Unicode formatting chars so affected
-                       # versions of Gecko don't misinterpret our strings;
-                       # this is a common problem with Farsi text.
-                       "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
-                       "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
-               ];
-
-               return strtr( $string, $pairs );
-       }
-
        /**
         * Encode a variable of arbitrary type to JavaScript.
         * If the value is an XmlJsCode object, pass through the object's value verbatim.
index 1332ab4..803695a 100644 (file)
@@ -23,6 +23,8 @@
  * @author <evan@wikitravel.org>
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @ingroup Actions
  */
@@ -198,14 +200,15 @@ class CreditsAction extends FormlessAction {
                if ( $this->canShowRealUserName() && !$user->isAnon() ) {
                        $real = $user->getRealName();
                } else {
-                       $real = false;
+                       $real = $user->getName();
                }
 
                $page = $user->isAnon()
                        ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
                        : $user->getUserPage();
 
-               return Linker::link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
+               return MediaWikiServices::getInstance()
+                       ->getLinkRenderer()->makeLink( $page, $real );
        }
 
        /**
@@ -231,9 +234,9 @@ class CreditsAction extends FormlessAction {
         * @return string HTML link
         */
        protected function othersLink() {
-               return Linker::linkKnown(
+               return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                        $this->getTitle(),
-                       $this->msg( 'others' )->escaped(),
+                       $this->msg( 'others' )->text(),
                        [],
                        [ 'action' => 'credits' ]
                );
index c1763fa..767a163 100644 (file)
@@ -23,6 +23,8 @@
  * @ingroup Actions
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * This class handles printing the history page for an article. In order to
  * be efficient, it uses timestamps rather than offsets for paging, to avoid
@@ -58,9 +60,9 @@ class HistoryAction extends FormlessAction {
 
        protected function getDescription() {
                // Creation of a subtitle link pointing to [[Special:Log]]
-               return Linker::linkKnown(
+               return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                        SpecialPage::getTitleFor( 'Log' ),
-                       $this->msg( 'viewpagelogs' )->escaped(),
+                       $this->msg( 'viewpagelogs' )->text(),
                        [],
                        [ 'page' => $this->getTitle()->getPrefixedText() ]
                );
@@ -166,7 +168,7 @@ class HistoryAction extends FormlessAction {
                $year = $request->getInt( 'year' );
                $month = $request->getInt( 'month' );
                $tagFilter = $request->getVal( 'tagfilter' );
-               $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
+               $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter, false, $this->getContext() );
 
                /**
                 * Option to show only revisions that have been (partially) hidden via RevisionDelete
@@ -734,9 +736,9 @@ class HistoryPager extends ReverseChronologicalPager {
                                $undoTooltip = $latest
                                        ? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
                                        : [];
-                               $undolink = Linker::linkKnown(
+                               $undolink = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                                        $this->getTitle(),
-                                       $this->msg( 'editundo' )->escaped(),
+                                       $this->msg( 'editundo' )->text(),
                                        $undoTooltip,
                                        [
                                                'action' => 'edit',
@@ -788,16 +790,15 @@ class HistoryPager extends ReverseChronologicalPager {
         */
        function revLink( $rev ) {
                $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $this->getUser() );
-               $date = htmlspecialchars( $date );
                if ( $rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
-                       $link = Linker::linkKnown(
+                       $link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                                $this->getTitle(),
                                $date,
                                [ 'class' => 'mw-changeslist-date' ],
                                [ 'oldid' => $rev->getId() ]
                        );
                } else {
-                       $link = $date;
+                       $link = htmlspecialchars( $date );
                }
                if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
                        $link = "<span class=\"history-deleted\">$link</span>";
@@ -818,7 +819,7 @@ class HistoryPager extends ReverseChronologicalPager {
                if ( $latest || !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
                        return $cur;
                } else {
-                       return Linker::linkKnown(
+                       return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                                $this->getTitle(),
                                $cur,
                                [],
@@ -847,9 +848,10 @@ class HistoryPager extends ReverseChronologicalPager {
                        return $last;
                }
 
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                if ( $next === 'unknown' ) {
                        # Next row probably exists but is unknown, use an oldid=prev link
-                       return Linker::linkKnown(
+                       return $linkRenderer->makeKnownLink(
                                $this->getTitle(),
                                $last,
                                [],
@@ -868,7 +870,7 @@ class HistoryPager extends ReverseChronologicalPager {
                        return $last;
                }
 
-               return Linker::linkKnown(
+               return $linkRenderer->makeKnownLink(
                        $this->getTitle(),
                        $last,
                        [],
index be3be85..49b9ab7 100644 (file)
@@ -230,11 +230,11 @@ class InfoAction extends FormlessAction {
                if ( $title->isRedirect() ) {
                        $pageInfo['header-basic'][] = [
                                $this->msg( 'pageinfo-redirectsto' ),
-                               Linker::link( $this->page->getRedirectTarget() ) .
+                               $linkRenderer->makeLink( $this->page->getRedirectTarget() ) .
                                $this->msg( 'word-separator' )->escaped() .
-                               $this->msg( 'parentheses' )->rawParams( Linker::link(
+                               $this->msg( 'parentheses' )->rawParams( $linkRenderer->makeLink(
                                        $this->page->getRedirectTarget(),
-                                       $this->msg( 'pageinfo-redirectsto-info' )->escaped(),
+                                       $this->msg( 'pageinfo-redirectsto-info' )->text(),
                                        [],
                                        [ 'action' => 'info' ]
                                ) )->escaped()
@@ -266,9 +266,9 @@ class InfoAction extends FormlessAction {
                ) {
                        // Link to Special:PageLanguage with pre-filled page title if user has permissions
                        $titleObj = SpecialPage::getTitleFor( 'PageLanguage', $title->getPrefixedText() );
-                       $langDisp = Linker::link(
+                       $langDisp = $linkRenderer->makeLink(
                                $titleObj,
-                               $this->msg( 'pageinfo-language' )->escaped()
+                               $this->msg( 'pageinfo-language' )->text()
                        );
                } else {
                        // Display just the message
@@ -360,9 +360,9 @@ class InfoAction extends FormlessAction {
                // Redirects to this page
                $whatLinksHere = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
                $pageInfo['header-basic'][] = [
-                       Linker::link(
+                       $linkRenderer->makeLink(
                                $whatLinksHere,
-                               $this->msg( 'pageinfo-redirects-name' )->escaped(),
+                               $this->msg( 'pageinfo-redirects-name' )->text(),
                                [],
                                [
                                        'hidelinks' => 1,
@@ -436,7 +436,7 @@ class InfoAction extends FormlessAction {
 
                        foreach ( $sources as $sourceTitle ) {
                                $cascadingFrom .= Html::rawElement(
-                                       'li', [], Linker::linkKnown( $sourceTitle ) );
+                                       'li', [], $linkRenderer->makeKnownLink( $sourceTitle ) );
                        }
 
                        $cascadingFrom = Html::rawElement( 'ul', [], $cascadingFrom );
@@ -525,9 +525,9 @@ class InfoAction extends FormlessAction {
                        // Date of page creation
                        $pageInfo['header-edits'][] = [
                                $this->msg( 'pageinfo-firsttime' ),
-                               Linker::linkKnown(
+                               $linkRenderer->makeKnownLink(
                                        $title,
-                                       htmlspecialchars( $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ) ),
+                                       $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
                                        [],
                                        [ 'oldid' => $firstRev->getId() ]
                                )
@@ -544,11 +544,9 @@ class InfoAction extends FormlessAction {
                        // Date of latest edit
                        $pageInfo['header-edits'][] = [
                                $this->msg( 'pageinfo-lasttime' ),
-                               Linker::linkKnown(
+                               $linkRenderer->makeKnownLink(
                                        $title,
-                                       htmlspecialchars(
-                                               $lang->userTimeAndDate( $this->page->getTimestamp(), $user )
-                                       ),
+                                       $lang->userTimeAndDate( $this->page->getTimestamp(), $user ),
                                        [],
                                        [ 'oldid' => $this->page->getLatest() ]
                                )
@@ -655,9 +653,9 @@ class InfoAction extends FormlessAction {
 
                        if ( !$config->get( 'MiserMode' ) && $pageCounts['transclusion']['to'] > 0 ) {
                                if ( $pageCounts['transclusion']['to'] > count( $transcludedTargets ) ) {
-                                       $more = Linker::link(
+                                       $more = $linkRenderer->makeLink(
                                                $whatLinksHere,
-                                               $this->msg( 'moredotdotdot' )->escaped(),
+                                               $this->msg( 'moredotdotdot' )->text(),
                                                [],
                                                [ 'hidelinks' => 1, 'hideredirs' => 1 ]
                                        );
@@ -836,6 +834,7 @@ class InfoAction extends FormlessAction {
                $real_names = [];
                $user_names = [];
                $anon_ips = [];
+               $linkRenderer = MediaWikiServices::getLinkRenderer();
 
                # Sift for real versus user names
                /** @var $user User */
@@ -846,11 +845,11 @@ class InfoAction extends FormlessAction {
 
                        $hiddenPrefs = $this->context->getConfig()->get( 'HiddenPrefs' );
                        if ( $user->getId() == 0 ) {
-                               $anon_ips[] = Linker::link( $page, htmlspecialchars( $user->getName() ) );
+                               $anon_ips[] = $linkRenderer->makeLink( $page, $user->getName() );
                        } elseif ( !in_array( 'realname', $hiddenPrefs ) && $user->getRealName() ) {
-                               $real_names[] = Linker::link( $page, htmlspecialchars( $user->getRealName() ) );
+                               $real_names[] = $linkRenderer->makeLink( $page, $user->getRealName() );
                        } else {
-                               $user_names[] = Linker::link( $page, htmlspecialchars( $user->getName() ) );
+                               $user_names[] = $linkRenderer->makeLink( $page, $user->getName() );
                        }
                }
 
index 8df6044..611e683 100644 (file)
@@ -20,6 +20,8 @@
  * @ingroup Actions
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Mark a revision as patrolled on a page
  *
@@ -56,6 +58,7 @@ class MarkpatrolledAction extends FormAction {
        protected function preText() {
                $rc = $this->getRecentChange();
                $title = $rc->getTitle();
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
 
                // Based on logentry-patrol-patrol (see PatrolLogFormatter)
                $revId = $rc->getAttribute( 'rc_this_oldid' );
@@ -64,8 +67,8 @@ class MarkpatrolledAction extends FormAction {
                        'diff' => $revId,
                        'oldid' => $rc->getAttribute( 'rc_last_oldid' )
                ];
-               $revlink = Linker::link( $title, htmlspecialchars( $revId ), [], $query );
-               $pagelink = Linker::link( $title, htmlspecialchars( $title->getPrefixedText() ) );
+               $revlink = $linkRenderer->makeLink( $title, $revId, [], $query );
+               $pagelink = $linkRenderer->makeLink( $title, $title->getPrefixedText() );
 
                return $this->msg( 'confirm-markpatrolled-top' )->params(
                        $title->getPrefixedText(),
diff --git a/includes/actions/RevisiondeleteAction.php b/includes/actions/RevisiondeleteAction.php
deleted file mode 100644 (file)
index 7df42b3..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-/**
- * An action that just pass the request to Special:RevisionDelete
- *
- * Copyright © 2011 Alexandre Emsenhuber
- *
- * 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
- *
- * @file
- * @ingroup Actions
- * @author Alexandre Emsenhuber
- */
-
-/**
- * An action that just pass the request to Special:RevisionDelete
- *
- * @ingroup Actions
- * @deprecated since 1.25 This class has been replaced by SpecialPageAction, but
- * you really shouldn't have been using it outside core in the first place
- */
-class RevisiondeleteAction extends FormlessAction {
-       public function __construct( Page $page, IContextSource $context = null ) {
-               wfDeprecated( 'RevisiondeleteAction class', '1.25' );
-               parent::__construct( $page, $context );
-       }
-
-       public function getName() {
-               return 'revisiondelete';
-       }
-
-       public function requiresUnblock() {
-               return false;
-       }
-
-       public function getDescription() {
-               return '';
-       }
-
-       public function onView() {
-               return '';
-       }
-
-       public function show() {
-               $special = SpecialPageFactory::getPage( 'Revisiondelete' );
-               $special->setContext( $this->getContext() );
-               $special->getContext()->setTitle( $special->getPageTitle() );
-               $special->run( '' );
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-}
index 2511e3b..5d12590 100644 (file)
@@ -56,8 +56,8 @@ class ApiAMCreateAccount extends ApiBase {
                        $bits = wfParseUrl( $params['returnurl'] );
                        if ( !$bits || $bits['scheme'] === '' ) {
                                $encParamName = $this->encodeParamName( 'returnurl' );
-                               $this->dieUsage(
-                                       "Invalid value '{$params['returnurl']}' for url parameter $encParamName",
+                               $this->dieWithError(
+                                       [ 'apierror-badurl', $encParamName, wfEscapeWikiText( $params['returnurl'] ) ],
                                        "badurl_{$encParamName}"
                                );
                        }
index 6fafebf..5327d7a 100644 (file)
@@ -93,7 +93,7 @@ class ApiAuthManagerHelper {
        /**
         * Call $manager->securitySensitiveOperationStatus()
         * @param string $operation Operation being checked.
-        * @throws UsageException
+        * @throws ApiUsageException
         */
        public function securitySensitiveOperation( $operation ) {
                $status = AuthManager::singleton()->securitySensitiveOperationStatus( $operation );
@@ -102,14 +102,10 @@ class ApiAuthManagerHelper {
                                return;
 
                        case AuthManager::SEC_REAUTH:
-                               $this->module->dieUsage(
-                                       'You have not authenticated recently in this session, please reauthenticate.', 'reauthenticate'
-                               );
+                               $this->module->dieWithError( 'apierror-reauthenticate' );
 
                        case AuthManager::SEC_FAIL:
-                               $this->module->dieUsage(
-                                       'This action is not available as your identify cannot be verified.', 'cannotreauthenticate'
-                               );
+                               $this->module->dieWithError( 'apierror-cannotreauthenticate' );
 
                        default:
                                throw new UnexpectedValueException( "Unknown status \"$status\"" );
index 0cd46e4..a40593f 100644 (file)
@@ -545,7 +545,7 @@ abstract class ApiBase extends ContextSource {
         * @since 1.25
         * @param string $path
         * @return ApiBase|null
-        * @throws UsageException
+        * @throws ApiUsageException
         */
        public function getModuleFromPath( $path ) {
                $module = $this->getMain();
@@ -565,14 +565,14 @@ abstract class ApiBase extends ContextSource {
                        $manager = $parent->getModuleManager();
                        if ( $manager === null ) {
                                $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
-                               $this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
+                               $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
                        }
                        $module = $manager->getModule( $parts[$i] );
 
                        if ( $module === null ) {
                                $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
-                               $this->dieUsage(
-                                       "The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
+                               $this->dieWithError(
+                                       [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
                                        'badmodule'
                                );
                        }
@@ -670,11 +670,18 @@ abstract class ApiBase extends ContextSource {
        /**
         * This method mangles parameter name based on the prefix supplied to the constructor.
         * Override this method to change parameter name during runtime
-        * @param string $paramName Parameter name
-        * @return string Prefixed parameter name
+        * @param string|string[] $paramName Parameter name
+        * @return string|string[] Prefixed parameter name
+        * @since 1.29 accepts an array of strings
         */
        public function encodeParamName( $paramName ) {
-               return $this->mModulePrefix . $paramName;
+               if ( is_array( $paramName ) ) {
+                       return array_map( function ( $name ) {
+                               return $this->mModulePrefix . $name;
+                       }, $paramName );
+               } else {
+                       return $this->mModulePrefix . $paramName;
+               }
        }
 
        /**
@@ -725,20 +732,32 @@ abstract class ApiBase extends ContextSource {
        public function requireOnlyOneParameter( $params, $required /*...*/ ) {
                $required = func_get_args();
                array_shift( $required );
-               $p = $this->getModulePrefix();
 
                $intersection = array_intersect( array_keys( array_filter( $params,
                        [ $this, 'parameterNotEmpty' ] ) ), $required );
 
                if ( count( $intersection ) > 1 ) {
-                       $this->dieUsage(
-                               "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
-                               'invalidparammix' );
+                       $this->dieWithError( [
+                               'apierror-invalidparammix',
+                               Message::listParam( array_map(
+                                       function ( $p ) {
+                                               return '<var>' . $this->encodeParamName( $p ) . '</var>';
+                                       },
+                                       array_values( $intersection )
+                               ) ),
+                               count( $intersection ),
+                       ] );
                } elseif ( count( $intersection ) == 0 ) {
-                       $this->dieUsage(
-                               "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
-                               'missingparam'
-                       );
+                       $this->dieWithError( [
+                               'apierror-missingparam-one-of',
+                               Message::listParam( array_map(
+                                       function ( $p ) {
+                                               return '<var>' . $this->encodeParamName( $p ) . '</var>';
+                                       },
+                                       array_values( $required )
+                               ) ),
+                               count( $required ),
+                       ], 'missingparam' );
                }
        }
 
@@ -751,16 +770,21 @@ abstract class ApiBase extends ContextSource {
        public function requireMaxOneParameter( $params, $required /*...*/ ) {
                $required = func_get_args();
                array_shift( $required );
-               $p = $this->getModulePrefix();
 
                $intersection = array_intersect( array_keys( array_filter( $params,
                        [ $this, 'parameterNotEmpty' ] ) ), $required );
 
                if ( count( $intersection ) > 1 ) {
-                       $this->dieUsage(
-                               "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
-                               'invalidparammix'
-                       );
+                       $this->dieWithError( [
+                               'apierror-invalidparammix',
+                               Message::listParam( array_map(
+                                       function ( $p ) {
+                                               return '<var>' . $this->encodeParamName( $p ) . '</var>';
+                                       },
+                                       array_values( $intersection )
+                               ) ),
+                               count( $intersection ),
+                       ] );
                }
        }
 
@@ -774,7 +798,6 @@ abstract class ApiBase extends ContextSource {
        public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
                $required = func_get_args();
                array_shift( $required );
-               $p = $this->getModulePrefix();
 
                $intersection = array_intersect(
                        array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
@@ -782,8 +805,16 @@ abstract class ApiBase extends ContextSource {
                );
 
                if ( count( $intersection ) == 0 ) {
-                       $this->dieUsage( "At least one of the parameters {$p}" .
-                               implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
+                       $this->dieWithError( [
+                               'apierror-missingparam-at-least-one-of',
+                               Message::listParam( array_map(
+                                       function ( $p ) {
+                                               return '<var>' . $this->encodeParamName( $p ) . '</var>';
+                                       },
+                                       array_values( $required )
+                               ) ),
+                               count( $required ),
+                       ], 'missingparam' );
                }
        }
 
@@ -812,10 +843,8 @@ abstract class ApiBase extends ContextSource {
                }
 
                if ( $badParams ) {
-                       $this->dieUsage(
-                               'The following parameters were found in the query string, but must be in the POST body: '
-                                       . join( ', ', $badParams ),
-                               'mustpostparams'
+                       $this->dieWithError(
+                               [ 'apierror-mustpostparams', join( ', ', $badParams ), count( $badParams ) ]
                        );
                }
        }
@@ -848,10 +877,10 @@ abstract class ApiBase extends ContextSource {
                if ( isset( $params['title'] ) ) {
                        $titleObj = Title::newFromText( $params['title'] );
                        if ( !$titleObj || $titleObj->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
                        }
                        if ( !$titleObj->canExist() ) {
-                               $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
+                               $this->dieWithError( 'apierror-pagecannotexist' );
                        }
                        $pageObj = WikiPage::factory( $titleObj );
                        if ( $load !== false ) {
@@ -863,7 +892,7 @@ abstract class ApiBase extends ContextSource {
                        }
                        $pageObj = WikiPage::newFromID( $params['pageid'], $load );
                        if ( !$pageObj ) {
-                               $this->dieUsageMsg( [ 'nosuchpageid', $params['pageid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
                        }
                }
 
@@ -994,10 +1023,8 @@ abstract class ApiBase extends ContextSource {
                                // accidentally uploaded as a field fails spectacularly)
                                $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
                                if ( $value !== null ) {
-                                       $this->dieUsage(
-                                               "File upload param $encParamName is not a file upload; " .
-                                                       'be sure to use multipart/form-data for your POST and include ' .
-                                                       'a filename in the Content-Disposition header.',
+                                       $this->dieWithError(
+                                               [ 'apierror-badupload', $encParamName ],
                                                "badupload_{$encParamName}"
                                        );
                                }
@@ -1032,10 +1059,7 @@ abstract class ApiBase extends ContextSource {
                                        // done by WebRequest for $_GET. Let's call that a feature.
                                        $value = join( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
                                } else {
-                                       $this->dieUsage(
-                                               "U+001F multi-value separation may only be used for multi-valued parameters.",
-                                               'badvalue_notmultivalue'
-                                       );
+                                       $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
                                }
                        }
 
@@ -1072,7 +1096,7 @@ abstract class ApiBase extends ContextSource {
                                        case 'text':
                                        case 'password':
                                                if ( $required && $value === '' ) {
-                                                       $this->dieUsageMsg( [ 'missingparam', $paramName ] );
+                                                       $this->dieWithError( [ 'apierror-missingparam', $paramName ] );
                                                }
                                                break;
                                        case 'integer': // Force everything using intval() and optionally validate limits
@@ -1175,8 +1199,6 @@ abstract class ApiBase extends ContextSource {
 
                        // Set a warning if a deprecated parameter has been passed
                        if ( $deprecated && $value !== false ) {
-                               $this->setWarning( "The $encParamName parameter has been deprecated." );
-
                                $feature = $encParamName;
                                $m = $this;
                                while ( !$m->isMain() ) {
@@ -1186,10 +1208,10 @@ abstract class ApiBase extends ContextSource {
                                        $feature = "{$param}={$name}&{$feature}";
                                        $m = $p;
                                }
-                               $this->logFeatureUsage( $feature );
+                               $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
                        }
                } elseif ( $required ) {
-                       $this->dieUsageMsg( [ 'missingparam', $paramName ] );
+                       $this->dieWithError( [ 'apierror-missingparam', $paramName ] );
                }
 
                return $value;
@@ -1204,11 +1226,7 @@ abstract class ApiBase extends ContextSource {
         */
        protected function handleParamNormalization( $paramName, $value, $rawValue ) {
                $encParamName = $this->encodeParamName( $paramName );
-               $this->setWarning(
-                       "The value passed for '$encParamName' contains invalid or non-normalized data. "
-                       . 'Textual data should be valid, NFC-normalized Unicode without '
-                       . 'C0 control characters other than HT (\\t), LF (\\n), and CR (\\r).'
-               );
+               $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
        }
 
        /**
@@ -1265,9 +1283,10 @@ abstract class ApiBase extends ContextSource {
                }
 
                if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
-                       $this->logFeatureUsage( "too-many-$valueName-for-{$this->getModulePath()}" );
-                       $this->setWarning( "Too many values supplied for parameter '$valueName': " .
-                               "the limit is $sizeLimit" );
+                       $this->addDeprecation(
+                               [ 'apiwarn-toomanyvalues', $valueName, $sizeLimit ],
+                               "too-many-$valueName-for-{$this->getModulePath()}"
+                       );
                }
 
                if ( !$allowMultiple && count( $valuesList ) != 1 ) {
@@ -1276,26 +1295,38 @@ abstract class ApiBase extends ContextSource {
                                return $value;
                        }
 
-                       $possibleValues = is_array( $allowedValues )
-                               ? "of '" . implode( "', '", $allowedValues ) . "'"
-                               : '';
-                       $this->dieUsage(
-                               "Only one $possibleValues is allowed for parameter '$valueName'",
-                               "multival_$valueName"
-                       );
+                       if ( is_array( $allowedValues ) ) {
+                               $values = array_map( function ( $v ) {
+                                       return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
+                               }, $allowedValues );
+                               $this->dieWithError( [
+                                       'apierror-multival-only-one-of',
+                                       $valueName,
+                                       Message::listParam( $values ),
+                                       count( $values ),
+                               ], "multival_$valueName" );
+                       } else {
+                               $this->dieWithError( [
+                                       'apierror-multival-only-one',
+                                       $valueName,
+                               ], "multival_$valueName" );
+                       }
                }
 
                if ( is_array( $allowedValues ) ) {
                        // Check for unknown values
-                       $unknown = array_diff( $valuesList, $allowedValues );
+                       $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
                        if ( count( $unknown ) ) {
                                if ( $allowMultiple ) {
-                                       $s = count( $unknown ) > 1 ? 's' : '';
-                                       $vals = implode( ', ', $unknown );
-                                       $this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
+                                       $this->addWarning( [
+                                               'apiwarn-unrecognizedvalues',
+                                               $valueName,
+                                               Message::listParam( $unknown, 'comma' ),
+                                               count( $unknown ),
+                                       ] );
                                } else {
-                                       $this->dieUsage(
-                                               "Unrecognized value for parameter '$valueName': {$valuesList[0]}",
+                                       $this->dieWithError(
+                                               [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
                                                "unknown_$valueName"
                                        );
                                }
@@ -1321,7 +1352,12 @@ abstract class ApiBase extends ContextSource {
                $enforceLimits = false
        ) {
                if ( !is_null( $min ) && $value < $min ) {
-                       $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
+                       $msg = ApiMessage::create(
+                               [ 'apierror-integeroutofrange-belowminimum',
+                                       $this->encodeParamName( $paramName ), $min, $value ],
+                               'integeroutofrange',
+                               [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
+                       );
                        $this->warnOrDie( $msg, $enforceLimits );
                        $value = $min;
                }
@@ -1337,13 +1373,22 @@ abstract class ApiBase extends ContextSource {
                if ( !is_null( $max ) && $value > $max ) {
                        if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
                                if ( $value > $botMax ) {
-                                       $msg = $this->encodeParamName( $paramName ) .
-                                               " may not be over $botMax (set to $value) for bots or sysops";
+                                       $msg = ApiMessage::create(
+                                               [ 'apierror-integeroutofrange-abovebotmax',
+                                                       $this->encodeParamName( $paramName ), $botMax, $value ],
+                                               'integeroutofrange',
+                                               [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
+                                       );
                                        $this->warnOrDie( $msg, $enforceLimits );
                                        $value = $botMax;
                                }
                        } else {
-                               $msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
+                               $msg = ApiMessage::create(
+                                       [ 'apierror-integeroutofrange-abovemax',
+                                               $this->encodeParamName( $paramName ), $max, $value ],
+                                       'integeroutofrange',
+                                       [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
+                               );
                                $this->warnOrDie( $msg, $enforceLimits );
                                $value = $max;
                        }
@@ -1361,11 +1406,9 @@ abstract class ApiBase extends ContextSource {
                // (wfTimestamp() also accepts various non-strings and the string of 14
                // ASCII NUL bytes, but those can't get here)
                if ( !$value ) {
-                       $this->logFeatureUsage( 'unclear-"now"-timestamp' );
-                       $this->setWarning(
-                               "Passing '$value' for timestamp parameter $encParamName has been deprecated." .
-                                       ' If for some reason you need to explicitly specify the current time without' .
-                                       ' calculating it client-side, use "now".'
+                       $this->addDeprecation(
+                               [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
+                               'unclear-"now"-timestamp'
                        );
                        return wfTimestamp( TS_MW );
                }
@@ -1377,8 +1420,8 @@ abstract class ApiBase extends ContextSource {
 
                $unixTimestamp = wfTimestamp( TS_UNIX, $value );
                if ( $unixTimestamp === false ) {
-                       $this->dieUsage(
-                               "Invalid value '$value' for timestamp parameter $encParamName",
+                       $this->dieWithError(
+                               [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
                                "badtimestamp_{$encParamName}"
                        );
                }
@@ -1433,8 +1476,8 @@ abstract class ApiBase extends ContextSource {
        private function validateUser( $value, $encParamName ) {
                $title = Title::makeTitleSafe( NS_USER, $value );
                if ( $title === null || $title->hasFragment() ) {
-                       $this->dieUsage(
-                               "Invalid value '$value' for user parameter $encParamName",
+                       $this->dieWithError(
+                               [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
                                "baduser_{$encParamName}"
                        );
                }
@@ -1490,22 +1533,19 @@ abstract class ApiBase extends ContextSource {
                if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
                        $user = User::newFromName( $params['owner'], false );
                        if ( !( $user && $user->getId() ) ) {
-                               $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
+                               $this->dieWithError(
+                                       [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
+                               );
                        }
                        $token = $user->getOption( 'watchlisttoken' );
                        if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
-                               $this->dieUsage(
-                                       'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
-                                       'bad_wltoken'
-                               );
+                               $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
                        }
                } else {
                        if ( !$this->getUser()->isLoggedIn() ) {
-                               $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
-                       }
-                       if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
-                               $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
+                               $this->dieWithError( 'watchlistanontext', 'notloggedin' );
                        }
+                       $this->checkUserRightsAny( 'viewmywatchlist' );
                        $user = $this->getUser();
                }
 
@@ -1561,6 +1601,39 @@ abstract class ApiBase extends ContextSource {
                return $msg;
        }
 
+       /**
+        * Turn an array of message keys or key+param arrays into a Status
+        * @since 1.29
+        * @param array $errors
+        * @param User|null $user
+        * @return Status
+        */
+       public function errorArrayToStatus( array $errors, User $user = null ) {
+               if ( $user === null ) {
+                       $user = $this->getUser();
+               }
+
+               $status = Status::newGood();
+               foreach ( $errors as $error ) {
+                       if ( is_array( $error ) && $error[0] === 'blockedtext' && $user->getBlock() ) {
+                               $status->fatal( ApiMessage::create(
+                                       'apierror-blocked',
+                                       'blocked',
+                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
+                               ) );
+                       } elseif ( is_array( $error ) && $error[0] === 'autoblockedtext' && $user->getBlock() ) {
+                               $status->fatal( ApiMessage::create(
+                                       'apierror-autoblocked',
+                                       'autoblocked',
+                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
+                               ) );
+                       } else {
+                               call_user_func_array( [ $status, 'fatal' ], (array)$error );
+                       }
+               }
+               return $status;
+       }
+
        /**@}*/
 
        /************************************************************************//**
@@ -1569,745 +1642,227 @@ abstract class ApiBase extends ContextSource {
         */
 
        /**
-        * Set warning section for this module. Users should monitor this
-        * section to notice any changes in API. Multiple calls to this
-        * function will result in the warning messages being separated by
-        * newlines
-        * @param string $warning Warning message
+        * Add a warning for this module.
+        *
+        * Users should monitor this section to notice any changes in API. Multiple
+        * calls to this function will result in multiple warning messages.
+        *
+        * If $msg is not an ApiMessage, the message code will be derived from the
+        * message key by stripping any "apiwarn-" or "apierror-" prefix.
+        *
+        * @since 1.29
+        * @param string|array|Message $msg See ApiErrorFormatter::addWarning()
+        * @param string|null $code See ApiErrorFormatter::addWarning()
+        * @param array|null $data See ApiErrorFormatter::addWarning()
         */
-       public function setWarning( $warning ) {
-               $msg = new ApiRawMessage( $warning, 'warning' );
-               $this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg );
+       public function addWarning( $msg, $code = null, $data = null ) {
+               $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
        }
 
        /**
-        * Adds a warning to the output, else dies
+        * Add a deprecation warning for this module.
         *
-        * @param string $msg Message to show as a warning, or error message if dying
-        * @param bool $enforceLimits Whether this is an enforce (die)
+        * A combination of $this->addWarning() and $this->logFeatureUsage()
+        *
+        * @since 1.29
+        * @param string|array|Message $msg See ApiErrorFormatter::addWarning()
+        * @param string|null $feature See ApiBase::logFeatureUsage()
+        * @param array|null $data See ApiErrorFormatter::addWarning()
         */
-       private function warnOrDie( $msg, $enforceLimits = false ) {
-               if ( $enforceLimits ) {
-                       $this->dieUsage( $msg, 'integeroutofrange' );
+       public function addDeprecation( $msg, $feature, $data = [] ) {
+               $data = (array)$data;
+               if ( $feature !== null ) {
+                       $data['feature'] = $feature;
+                       $this->logFeatureUsage( $feature );
                }
+               $this->addWarning( $msg, 'deprecation', $data );
+       }
 
-               $this->setWarning( $msg );
+       /**
+        * Add an error for this module without aborting
+        *
+        * If $msg is not an ApiMessage, the message code will be derived from the
+        * message key by stripping any "apiwarn-" or "apierror-" prefix.
+        *
+        * @note If you want to abort processing, use self::dieWithError() instead.
+        * @since 1.29
+        * @param string|array|Message $msg See ApiErrorFormatter::addError()
+        * @param string|null $code See ApiErrorFormatter::addError()
+        * @param array|null $data See ApiErrorFormatter::addError()
+        */
+       public function addError( $msg, $code = null, $data = null ) {
+               $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
        }
 
        /**
-        * Throw a UsageException, which will (if uncaught) call the main module's
-        * error handler and die with an error message.
+        * Add warnings and/or errors from a Status
         *
-        * @param string $description One-line human-readable description of the
-        *   error condition, e.g., "The API requires a valid action parameter"
-        * @param string $errorCode Brief, arbitrary, stable string to allow easy
-        *   automated identification of the error, e.g., 'unknown_action'
-        * @param int $httpRespCode HTTP response code
-        * @param array|null $extradata Data to add to the "<error>" element; array in ApiResult format
-        * @throws UsageException always
+        * @note If you want to abort processing, use self::dieStatus() instead.
+        * @since 1.29
+        * @param StatusValue $status
+        * @param string[] $types 'warning' and/or 'error'
         */
-       public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
-               throw new UsageException(
-                       $description,
-                       $this->encodeParamName( $errorCode ),
-                       $httpRespCode,
-                       $extradata
-               );
+       public function addMessagesFromStatus( StatusValue $status, $types = [ 'warning', 'error' ] ) {
+               $this->getErrorFormatter()->addMessagesFromStatus( $this->getModulePath(), $status, $types );
+       }
+
+       /**
+        * Abort execution with an error
+        *
+        * If $msg is not an ApiMessage, the message code will be derived from the
+        * message key by stripping any "apiwarn-" or "apierror-" prefix.
+        *
+        * @since 1.29
+        * @param string|array|Message $msg See ApiErrorFormatter::addError()
+        * @param string|null $code See ApiErrorFormatter::addError()
+        * @param array|null $data See ApiErrorFormatter::addError()
+        * @param int|null $httpCode HTTP error code to use
+        * @throws ApiUsageException always
+        */
+       public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
+               throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
        }
 
        /**
-        * Throw a UsageException, which will (if uncaught) call the main module's
+        * Adds a warning to the output, else dies
+        *
+        * @param ApiMessage $msg Message to show as a warning, or error message if dying
+        * @param bool $enforceLimits Whether this is an enforce (die)
+        */
+       private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
+               if ( $enforceLimits ) {
+                       $this->dieWithError( $msg );
+               } else {
+                       $this->addWarning( $msg );
+               }
+       }
+
+       /**
+        * Throw an ApiUsageException, which will (if uncaught) call the main module's
         * error handler and die with an error message including block info.
         *
         * @since 1.27
-        * @param Block $block The block used to generate the UsageException
-        * @throws UsageException always
+        * @param Block $block The block used to generate the ApiUsageException
+        * @throws ApiUsageException always
         */
        public function dieBlocked( Block $block ) {
                // Die using the appropriate message depending on block type
                if ( $block->getType() == Block::TYPE_AUTO ) {
-                       $this->dieUsage(
-                               'Your IP address has been blocked automatically, because it was used by a blocked user',
+                       $this->dieWithError(
+                               'apierror-autoblocked',
                                'autoblocked',
-                               0,
                                [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
                        );
                } else {
-                       $this->dieUsage(
-                               'You have been blocked from editing',
+                       $this->dieWithError(
+                               'apierror-blocked',
                                'blocked',
-                               0,
                                [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
                        );
                }
        }
 
        /**
-        * Get error (as code, string) from a Status object.
+        * Throw an ApiUsageException based on the Status object.
         *
-        * @since 1.23
-        * @param Status $status
-        * @param array|null &$extraData Set if extra data from IApiMessage is available (since 1.27)
-        * @return array Array of code and error string
-        * @throws MWException
+        * @since 1.22
+        * @since 1.29 Accepts a StatusValue
+        * @param StatusValue $status
+        * @throws ApiUsageException always
         */
-       public function getErrorFromStatus( $status, &$extraData = null ) {
+       public function dieStatus( StatusValue $status ) {
                if ( $status->isGood() ) {
                        throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
                }
 
-               $errors = $status->getErrorsByType( 'error' );
-               if ( !$errors ) {
-                       // No errors? Assume the warnings should be treated as errors
-                       $errors = $status->getErrorsByType( 'warning' );
-               }
-               if ( !$errors ) {
-                       // Still no errors? Punt
-                       $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
-               }
-
-               // Cannot use dieUsageMsg() because extensions might return custom
-               // error messages.
-               if ( $errors[0]['message'] instanceof Message ) {
-                       $msg = $errors[0]['message'];
-                       if ( $msg instanceof IApiMessage ) {
-                               $extraData = $msg->getApiData();
-                               $code = $msg->getApiCode();
-                       } else {
-                               $code = $msg->getKey();
-                       }
-               } else {
-                       $code = $errors[0]['message'];
-                       $msg = wfMessage( $code, $errors[0]['params'] );
-               }
-               if ( isset( ApiBase::$messageMap[$code] ) ) {
-                       // Translate message to code, for backwards compatibility
-                       $code = ApiBase::$messageMap[$code]['code'];
-               }
-
-               return [ $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() ];
+               throw new ApiUsageException( $this, $status );
        }
 
-       /**
-        * Throw a UsageException based on the errors in the Status object.
-        *
-        * @since 1.22
-        * @param Status $status
-        * @throws UsageException always
-        */
-       public function dieStatus( $status ) {
-               $extraData = null;
-               list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
-               $this->dieUsage( $msg, $code, 0, $extraData );
-       }
-
-       // @codingStandardsIgnoreStart Allow long lines. Cannot split these.
-       /**
-        * Array that maps message keys to error messages. $1 and friends are replaced.
-        */
-       public static $messageMap = [
-               // This one MUST be present, or dieUsageMsg() will recurse infinitely
-               'unknownerror' => [ 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ],
-               'unknownerror-nocode' => [ 'code' => 'unknownerror', 'info' => 'Unknown error' ],
-
-               // Messages from Title::getUserPermissionsErrors()
-               'ns-specialprotected' => [
-                       'code' => 'unsupportednamespace',
-                       'info' => "Pages in the Special namespace can't be edited"
-               ],
-               'protectedinterface' => [
-                       'code' => 'protectednamespace-interface',
-                       'info' => "You're not allowed to edit interface messages"
-               ],
-               'namespaceprotected' => [
-                       'code' => 'protectednamespace',
-                       'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
-               ],
-               'customcssprotected' => [
-                       'code' => 'customcssprotected',
-                       'info' => "You're not allowed to edit custom CSS pages"
-               ],
-               'customjsprotected' => [
-                       'code' => 'customjsprotected',
-                       'info' => "You're not allowed to edit custom JavaScript pages"
-               ],
-               'cascadeprotected' => [
-                       'code' => 'cascadeprotected',
-                       'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
-               ],
-               'protectedpagetext' => [
-                       'code' => 'protectedpage',
-                       'info' => "The \"\$1\" right is required to edit this page"
-               ],
-               'protect-cantedit' => [
-                       'code' => 'cantedit',
-                       'info' => "You can't protect this page because you can't edit it"
-               ],
-               'deleteprotected' => [
-                       'code' => 'cantedit',
-                       'info' => "You can't delete this page because it has been protected"
-               ],
-               'badaccess-group0' => [
-                       'code' => 'permissiondenied',
-                       'info' => 'Permission denied'
-               ], // Generic permission denied message
-               'badaccess-groups' => [
-                       'code' => 'permissiondenied',
-                       'info' => 'Permission denied'
-               ],
-               'titleprotected' => [
-                       'code' => 'protectedtitle',
-                       'info' => 'This title has been protected from creation'
-               ],
-               'nocreate-loggedin' => [
-                       'code' => 'cantcreate',
-                       'info' => "You don't have permission to create new pages"
-               ],
-               'nocreatetext' => [
-                       'code' => 'cantcreate-anon',
-                       'info' => "Anonymous users can't create new pages"
-               ],
-               'movenologintext' => [
-                       'code' => 'cantmove-anon',
-                       'info' => "Anonymous users can't move pages"
-               ],
-               'movenotallowed' => [
-                       'code' => 'cantmove',
-                       'info' => "You don't have permission to move pages"
-               ],
-               'confirmedittext' => [
-                       'code' => 'confirmemail',
-                       'info' => 'You must confirm your email address before you can edit'
-               ],
-               'blockedtext' => [
-                       'code' => 'blocked',
-                       'info' => 'You have been blocked from editing'
-               ],
-               'autoblockedtext' => [
-                       'code' => 'autoblocked',
-                       'info' => 'Your IP address has been blocked automatically, because it was used by a blocked user'
-               ],
-
-               // Miscellaneous interface messages
-               'actionthrottledtext' => [
-                       'code' => 'ratelimited',
-                       'info' => "You've exceeded your rate limit. Please wait some time and try again"
-               ],
-               'alreadyrolled' => [
-                       'code' => 'alreadyrolled',
-                       'info' => 'The page you tried to rollback was already rolled back'
-               ],
-               'cantrollback' => [
-                       'code' => 'onlyauthor',
-                       'info' => 'The page you tried to rollback only has one author'
-               ],
-               'readonlytext' => [
-                       'code' => 'readonly',
-                       'info' => 'The wiki is currently in read-only mode'
-               ],
-               'sessionfailure' => [
-                       'code' => 'badtoken',
-                       'info' => 'Invalid token' ],
-               'cannotdelete' => [
-                       'code' => 'cantdelete',
-                       'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
-               ],
-               'notanarticle' => [
-                       'code' => 'missingtitle',
-                       'info' => "The page you requested doesn't exist"
-               ],
-               'selfmove' => [ 'code' => 'selfmove', 'info' => "Can't move a page to itself"
-               ],
-               'immobile_namespace' => [
-                       'code' => 'immobilenamespace',
-                       'info' => 'You tried to move pages from or to a namespace that is protected from moving'
-               ],
-               'articleexists' => [
-                       'code' => 'articleexists',
-                       'info' => 'The destination article already exists and is not a redirect to the source article'
-               ],
-               'protectedpage' => [
-                       'code' => 'protectedpage',
-                       'info' => "You don't have permission to perform this move"
-               ],
-               'hookaborted' => [
-                       'code' => 'hookaborted',
-                       'info' => 'The modification you tried to make was aborted by an extension hook'
-               ],
-               'cantmove-titleprotected' => [
-                       'code' => 'protectedtitle',
-                       'info' => 'The destination article has been protected from creation'
-               ],
-               'imagenocrossnamespace' => [
-                       'code' => 'nonfilenamespace',
-                       'info' => "Can't move a file to a non-file namespace"
-               ],
-               'imagetypemismatch' => [
-                       'code' => 'filetypemismatch',
-                       'info' => "The new file extension doesn't match its type"
-               ],
-               // 'badarticleerror' => shouldn't happen
-               // 'badtitletext' => shouldn't happen
-               'ip_range_invalid' => [ 'code' => 'invalidrange', 'info' => 'Invalid IP range' ],
-               'range_block_disabled' => [
-                       'code' => 'rangedisabled',
-                       'info' => 'Blocking IP ranges has been disabled'
-               ],
-               'nosuchusershort' => [
-                       'code' => 'nosuchuser',
-                       'info' => "The user you specified doesn't exist"
-               ],
-               'badipaddress' => [ 'code' => 'invalidip', 'info' => 'Invalid IP address specified' ],
-               'ipb_expiry_invalid' => [ 'code' => 'invalidexpiry', 'info' => 'Invalid expiry time' ],
-               'ipb_already_blocked' => [
-                       'code' => 'alreadyblocked',
-                       'info' => 'The user you tried to block was already blocked'
-               ],
-               'ipb_blocked_as_range' => [
-                       'code' => 'blockedasrange',
-                       'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole."
-               ],
-               'ipb_cant_unblock' => [
-                       'code' => 'cantunblock',
-                       'info' => 'The block you specified was not found. It may have been unblocked already'
-               ],
-               'mailnologin' => [
-                       'code' => 'cantsend',
-                       'info' => 'You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email'
-               ],
-               'ipbblocked' => [
-                       'code' => 'ipbblocked',
-                       'info' => 'You cannot block or unblock users while you are yourself blocked'
-               ],
-               'ipbnounblockself' => [
-                       'code' => 'ipbnounblockself',
-                       'info' => 'You are not allowed to unblock yourself'
-               ],
-               'usermaildisabled' => [
-                       'code' => 'usermaildisabled',
-                       'info' => 'User email has been disabled'
-               ],
-               'blockedemailuser' => [
-                       'code' => 'blockedfrommail',
-                       'info' => 'You have been blocked from sending email'
-               ],
-               'notarget' => [
-                       'code' => 'notarget',
-                       'info' => 'You have not specified a valid target for this action'
-               ],
-               'noemail' => [
-                       'code' => 'noemail',
-                       'info' => 'The user has not specified a valid email address, or has chosen not to receive email from other users'
-               ],
-               'rcpatroldisabled' => [
-                       'code' => 'patroldisabled',
-                       'info' => 'Patrolling is disabled on this wiki'
-               ],
-               'markedaspatrollederror-noautopatrol' => [
-                       'code' => 'noautopatrol',
-                       'info' => "You don't have permission to patrol your own changes"
-               ],
-               'delete-toobig' => [
-                       'code' => 'bigdelete',
-                       'info' => "You can't delete this page because it has more than \$1 revisions"
-               ],
-               'movenotallowedfile' => [
-                       'code' => 'cantmovefile',
-                       'info' => "You don't have permission to move files"
-               ],
-               'userrights-no-interwiki' => [
-                       'code' => 'nointerwikiuserrights',
-                       'info' => "You don't have permission to change user rights on other wikis"
-               ],
-               'userrights-nodatabase' => [
-                       'code' => 'nosuchdatabase',
-                       'info' => "Database \"\$1\" does not exist or is not local"
-               ],
-               'nouserspecified' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
-               'noname' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
-               'summaryrequired' => [ 'code' => 'summaryrequired', 'info' => 'Summary required' ],
-               'import-rootpage-invalid' => [
-                       'code' => 'import-rootpage-invalid',
-                       'info' => 'Root page is an invalid title'
-               ],
-               'import-rootpage-nosubpage' => [
-                       'code' => 'import-rootpage-nosubpage',
-                       'info' => 'Namespace "$1" of the root page does not allow subpages'
-               ],
-
-               // API-specific messages
-               'readrequired' => [
-                       'code' => 'readapidenied',
-                       'info' => 'You need read permission to use this module'
-               ],
-               'writedisabled' => [
-                       'code' => 'noapiwrite',
-                       'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"
-               ],
-               'writerequired' => [
-                       'code' => 'writeapidenied',
-                       'info' => "You're not allowed to edit this wiki through the API"
-               ],
-               'missingparam' => [ 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ],
-               'invalidtitle' => [ 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ],
-               'nosuchpageid' => [ 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ],
-               'nosuchrevid' => [ 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ],
-               'nosuchuser' => [ 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ],
-               'invaliduser' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
-               'invalidexpiry' => [ 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ],
-               'pastexpiry' => [ 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ],
-               'create-titleexists' => [
-                       'code' => 'create-titleexists',
-                       'info' => "Existing titles can't be protected with 'create'"
-               ],
-               'missingtitle-createonly' => [
-                       'code' => 'missingtitle-createonly',
-                       'info' => "Missing titles can only be protected with 'create'"
-               ],
-               'cantblock' => [ 'code' => 'cantblock',
-                       'info' => "You don't have permission to block users"
-               ],
-               'canthide' => [
-                       'code' => 'canthide',
-                       'info' => "You don't have permission to hide user names from the block log"
-               ],
-               'cantblock-email' => [
-                       'code' => 'cantblock-email',
-                       'info' => "You don't have permission to block users from sending email through the wiki"
-               ],
-               'unblock-notarget' => [
-                       'code' => 'notarget',
-                       'info' => 'Either the id or the user parameter must be set'
-               ],
-               'unblock-idanduser' => [
-                       'code' => 'idanduser',
-                       'info' => "The id and user parameters can't be used together"
-               ],
-               'cantunblock' => [
-                       'code' => 'permissiondenied',
-                       'info' => "You don't have permission to unblock users"
-               ],
-               'cannotundelete' => [
-                       'code' => 'cantundelete',
-                       'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
-               ],
-               'permdenied-undelete' => [
-                       'code' => 'permissiondenied',
-                       'info' => "You don't have permission to restore deleted revisions"
-               ],
-               'createonly-exists' => [
-                       'code' => 'articleexists',
-                       'info' => 'The article you tried to create has been created already'
-               ],
-               'nocreate-missing' => [
-                       'code' => 'missingtitle',
-                       'info' => "The article you tried to edit doesn't exist"
-               ],
-               'cantchangecontentmodel' => [
-                       'code' => 'cantchangecontentmodel',
-                       'info' => "You don't have permission to change the content model of a page"
-               ],
-               'nosuchrcid' => [
-                       'code' => 'nosuchrcid',
-                       'info' => "There is no change with rcid \"\$1\""
-               ],
-               'nosuchlogid' => [
-                       'code' => 'nosuchlogid',
-                       'info' => "There is no log entry with ID \"\$1\""
-               ],
-               'protect-invalidaction' => [
-                       'code' => 'protect-invalidaction',
-                       'info' => "Invalid protection type \"\$1\""
-               ],
-               'protect-invalidlevel' => [
-                       'code' => 'protect-invalidlevel',
-                       'info' => "Invalid protection level \"\$1\""
-               ],
-               'toofewexpiries' => [
-                       'code' => 'toofewexpiries',
-                       'info' => "\$1 expiry timestamps were provided where \$2 were needed"
-               ],
-               'cantimport' => [
-                       'code' => 'cantimport',
-                       'info' => "You don't have permission to import pages"
-               ],
-               'cantimport-upload' => [
-                       'code' => 'cantimport-upload',
-                       'info' => "You don't have permission to import uploaded pages"
-               ],
-               'importnofile' => [ 'code' => 'nofile', 'info' => "You didn't upload a file" ],
-               'importuploaderrorsize' => [
-                       'code' => 'filetoobig',
-                       'info' => 'The file you uploaded is bigger than the maximum upload size'
-               ],
-               'importuploaderrorpartial' => [
-                       'code' => 'partialupload',
-                       'info' => 'The file was only partially uploaded'
-               ],
-               'importuploaderrortemp' => [
-                       'code' => 'notempdir',
-                       'info' => 'The temporary upload directory is missing'
-               ],
-               'importcantopen' => [
-                       'code' => 'cantopenfile',
-                       'info' => "Couldn't open the uploaded file"
-               ],
-               'import-noarticle' => [
-                       'code' => 'badinterwiki',
-                       'info' => 'Invalid interwiki title specified'
-               ],
-               'importbadinterwiki' => [
-                       'code' => 'badinterwiki',
-                       'info' => 'Invalid interwiki title specified'
-               ],
-               'import-unknownerror' => [
-                       'code' => 'import-unknownerror',
-                       'info' => "Unknown error on import: \"\$1\""
-               ],
-               'cantoverwrite-sharedfile' => [
-                       'code' => 'cantoverwrite-sharedfile',
-                       'info' => 'The target file exists on a shared repository and you do not have permission to override it'
-               ],
-               'sharedfile-exists' => [
-                       'code' => 'fileexists-sharedrepo-perm',
-                       'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
-               ],
-               'mustbeposted' => [
-                       'code' => 'mustbeposted',
-                       'info' => "The \$1 module requires a POST request"
-               ],
-               'show' => [
-                       'code' => 'show',
-                       'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
-               ],
-               'specialpage-cantexecute' => [
-                       'code' => 'specialpage-cantexecute',
-                       'info' => "You don't have permission to view the results of this special page"
-               ],
-               'invalidoldimage' => [
-                       'code' => 'invalidoldimage',
-                       'info' => 'The oldimage parameter has invalid format'
-               ],
-               'nodeleteablefile' => [
-                       'code' => 'nodeleteablefile',
-                       'info' => 'No such old version of the file'
-               ],
-               'fileexists-forbidden' => [
-                       'code' => 'fileexists-forbidden',
-                       'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
-               ],
-               'fileexists-shared-forbidden' => [
-                       'code' => 'fileexists-shared-forbidden',
-                       'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
-               ],
-               'filerevert-badversion' => [
-                       'code' => 'filerevert-badversion',
-                       'info' => 'There is no previous local version of this file with the provided timestamp.'
-               ],
-
-               // ApiEditPage messages
-               'noimageredirect-anon' => [
-                       'code' => 'noimageredirect-anon',
-                       'info' => "Anonymous users can't create image redirects"
-               ],
-               'noimageredirect-logged' => [
-                       'code' => 'noimageredirect',
-                       'info' => "You don't have permission to create image redirects"
-               ],
-               'spamdetected' => [
-                       'code' => 'spamdetected',
-                       'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
-               ],
-               'contenttoobig' => [
-                       'code' => 'contenttoobig',
-                       'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
-               ],
-               'noedit-anon' => [ 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ],
-               'noedit' => [ 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ],
-               'wasdeleted' => [
-                       'code' => 'pagedeleted',
-                       'info' => 'The page has been deleted since you fetched its timestamp'
-               ],
-               'blankpage' => [
-                       'code' => 'emptypage',
-                       'info' => 'Creating new, empty pages is not allowed'
-               ],
-               'editconflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
-               'hashcheckfailed' => [ 'code' => 'badmd5', 'info' => 'The supplied MD5 hash was incorrect' ],
-               'missingtext' => [
-                       'code' => 'notext',
-                       'info' => 'One of the text, appendtext, prependtext and undo parameters must be set'
-               ],
-               'emptynewsection' => [
-                       'code' => 'emptynewsection',
-                       'info' => 'Creating empty new sections is not possible.'
-               ],
-               'revwrongpage' => [
-                       'code' => 'revwrongpage',
-                       'info' => "r\$1 is not a revision of \"\$2\""
-               ],
-               'undo-failure' => [
-                       'code' => 'undofailure',
-                       'info' => 'Undo failed due to conflicting intermediate edits'
-               ],
-               'content-not-allowed-here' => [
-                       'code' => 'contentnotallowedhere',
-                       'info' => 'Content model "$1" is not allowed at title "$2"'
-               ],
-
-               // Messages from WikiPage::doEit(]
-               'edit-hook-aborted' => [
-                       'code' => 'edit-hook-aborted',
-                       'info' => 'Your edit was aborted by an ArticleSave hook'
-               ],
-               'edit-gone-missing' => [
-                       'code' => 'edit-gone-missing',
-                       'info' => "The page you tried to edit doesn't seem to exist anymore"
-               ],
-               'edit-conflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
-               'edit-already-exists' => [
-                       'code' => 'edit-already-exists',
-                       'info' => 'It seems the page you tried to create already exist'
-               ],
-
-               // uploadMsgs
-               'invalid-file-key' => [ 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ],
-               'nouploadmodule' => [ 'code' => 'nouploadmodule', 'info' => 'No upload module set' ],
-               'uploaddisabled' => [
-                       'code' => 'uploaddisabled',
-                       'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
-               ],
-               'copyuploaddisabled' => [
-                       'code' => 'copyuploaddisabled',
-                       'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
-               ],
-               'copyuploadbaddomain' => [
-                       'code' => 'copyuploadbaddomain',
-                       'info' => 'Uploads by URL are not allowed from this domain.'
-               ],
-               'copyuploadbadurl' => [
-                       'code' => 'copyuploadbadurl',
-                       'info' => 'Upload not allowed from this URL.'
-               ],
-
-               'filename-tooshort' => [
-                       'code' => 'filename-tooshort',
-                       'info' => 'The filename is too short'
-               ],
-               'filename-toolong' => [ 'code' => 'filename-toolong', 'info' => 'The filename is too long' ],
-               'illegal-filename' => [
-                       'code' => 'illegal-filename',
-                       'info' => 'The filename is not allowed'
-               ],
-               'filetype-missing' => [
-                       'code' => 'filetype-missing',
-                       'info' => 'The file is missing an extension'
-               ],
-
-               'mustbeloggedin' => [ 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' ]
-       ];
-       // @codingStandardsIgnoreEnd
-
        /**
         * Helper function for readonly errors
         *
-        * @throws UsageException always
+        * @throws ApiUsageException always
         */
        public function dieReadOnly() {
-               $parsed = $this->parseMsg( [ 'readonlytext' ] );
-               $this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
-                       [ 'readonlyreason' => wfReadOnlyReason() ] );
+               $this->dieWithError(
+                       'apierror-readonly',
+                       'readonly',
+                       [ 'readonlyreason' => wfReadOnlyReason() ]
+               );
        }
 
        /**
-        * Output the error message related to a certain array
-        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
-        * @throws UsageException always
+        * Helper function for permission-denied errors
+        * @since 1.29
+        * @param string|string[] $rights
+        * @param User|null $user
+        * @throws ApiUsageException if the user doesn't have any of the rights.
+        *  The error message is based on $rights[0].
         */
-       public function dieUsageMsg( $error ) {
-               # most of the time we send a 1 element, so we might as well send it as
-               # a string and make this an array here.
-               if ( is_string( $error ) ) {
-                       $error = [ $error ];
+       public function checkUserRightsAny( $rights, $user = null ) {
+               if ( !$user ) {
+                       $user = $this->getUser();
+               }
+               $rights = (array)$rights;
+               if ( !call_user_func_array( [ $user, 'isAllowedAny' ], $rights ) ) {
+                       $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
                }
-               $parsed = $this->parseMsg( $error );
-               $extraData = isset( $parsed['data'] ) ? $parsed['data'] : null;
-               $this->dieUsage( $parsed['info'], $parsed['code'], 0, $extraData );
        }
 
        /**
-        * Will only set a warning instead of failing if the global $wgDebugAPI
-        * is set to true. Otherwise behaves exactly as dieUsageMsg().
-        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
-        * @throws UsageException
-        * @since 1.21
+        * Helper function for permission-denied errors
+        * @since 1.29
+        * @param Title $title
+        * @param string|string[] $actions
+        * @param User|null $user
+        * @throws ApiUsageException if the user doesn't have all of the rights.
         */
-       public function dieUsageMsgOrDebug( $error ) {
-               if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
-                       $this->dieUsageMsg( $error );
+       public function checkTitleUserPermissions( Title $title, $actions, $user = null ) {
+               if ( !$user ) {
+                       $user = $this->getUser();
                }
 
-               if ( is_string( $error ) ) {
-                       $error = [ $error ];
+               $errors = [];
+               foreach ( (array)$actions as $action ) {
+                       $errors = array_merge( $errors, $title->getUserPermissionsErrors( $action, $user ) );
+               }
+               if ( $errors ) {
+                       $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
                }
-               $parsed = $this->parseMsg( $error );
-               $this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
        }
 
        /**
-        * Die with the $prefix.'badcontinue' error. This call is common enough to
-        * make it into the base method.
-        * @param bool $condition Will only die if this value is true
-        * @throws UsageException
-        * @since 1.21
+        * Will only set a warning instead of failing if the global $wgDebugAPI
+        * is set to true. Otherwise behaves exactly as self::dieWithError().
+        *
+        * @since 1.29
+        * @param string|array|Message $msg
+        * @param string|null $code
+        * @param array|null $data
+        * @param int|null $httpCode
+        * @throws ApiUsageException
         */
-       protected function dieContinueUsageIf( $condition ) {
-               if ( $condition ) {
-                       $this->dieUsage(
-                               'Invalid continue param. You should pass the original value returned by the previous query',
-                               'badcontinue' );
+       public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
+               if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
+                       $this->dieWithError( $msg, $code, $data, $httpCode );
+               } else {
+                       $this->addWarning( $msg, $code, $data );
                }
        }
 
        /**
-        * Return the error message related to a certain array
-        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
-        * @return [ 'code' => code, 'info' => info ]
+        * Die with the 'badcontinue' error.
+        *
+        * This call is common enough to make it into the base method.
+        *
+        * @param bool $condition Will only die if this value is true
+        * @throws ApiUsageException
+        * @since 1.21
         */
-       public function parseMsg( $error ) {
-               // Check whether someone passed the whole array, instead of one element as
-               // documented. This breaks if it's actually an array of fallback keys, but
-               // that's long-standing misbehavior introduced in r87627 to incorrectly
-               // fix T30797.
-               if ( is_array( $error ) ) {
-                       $first = reset( $error );
-                       if ( is_array( $first ) ) {
-                               wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
-                               $error = $first;
-                       }
-               }
-
-               $msg = Message::newFromSpecifier( $error );
-
-               if ( $msg instanceof IApiMessage ) {
-                       return [
-                               'code' => $msg->getApiCode(),
-                               'info' => $msg->inLanguage( 'en' )->useDatabase( false )->text(),
-                               'data' => $msg->getApiData()
-                       ];
-               }
-
-               $key = $msg->getKey();
-               if ( isset( self::$messageMap[$key] ) ) {
-                       $params = $msg->getParams();
-                       return [
-                               'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $params ),
-                               'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $params )
-                       ];
+       protected function dieContinueUsageIf( $condition ) {
+               if ( $condition ) {
+                       $this->dieWithError( 'apierror-badcontinue' );
                }
-
-               // If the key isn't present, throw an "unknown error"
-               return $this->parseMsg( [ 'unknownerror', $key ] );
        }
 
        /**
@@ -2323,6 +1878,7 @@ abstract class ApiBase extends ContextSource {
        /**
         * Write logging information for API features to a debug log, for usage
         * analysis.
+        * @note Consider using $this->addDeprecation() instead to both warn and log.
         * @param string $feature Feature being used.
         */
        public function logFeatureUsage( $feature ) {
@@ -2790,6 +2346,300 @@ abstract class ApiBase extends ContextSource {
                }
        }
 
+       /**
+        * @deprecated since 1.29, use ApiBase::addWarning() instead
+        * @param string $warning Warning message
+        */
+       public function setWarning( $warning ) {
+               $msg = new ApiRawMessage( $warning, 'warning' );
+               $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg );
+       }
+
+       /**
+        * Throw an ApiUsageException, which will (if uncaught) call the main module's
+        * error handler and die with an error message.
+        *
+        * @deprecated since 1.29, use self::dieWithError() instead
+        * @param string $description One-line human-readable description of the
+        *   error condition, e.g., "The API requires a valid action parameter"
+        * @param string $errorCode Brief, arbitrary, stable string to allow easy
+        *   automated identification of the error, e.g., 'unknown_action'
+        * @param int $httpRespCode HTTP response code
+        * @param array|null $extradata Data to add to the "<error>" element; array in ApiResult format
+        * @throws ApiUsageException always
+        */
+       public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
+               $this->dieWithError(
+                       new RawMessage( '$1', [ $description ] ),
+                       $errorCode,
+                       $extradata,
+                       $httpRespCode
+               );
+       }
+
+       /**
+        * Get error (as code, string) from a Status object.
+        *
+        * @since 1.23
+        * @deprecated since 1.29, use ApiErrorFormatter::arrayFromStatus instead
+        * @param Status $status
+        * @param array|null &$extraData Set if extra data from IApiMessage is available (since 1.27)
+        * @return array Array of code and error string
+        * @throws MWException
+        */
+       public function getErrorFromStatus( $status, &$extraData = null ) {
+               if ( $status->isGood() ) {
+                       throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
+               }
+
+               $errors = $status->getErrorsByType( 'error' );
+               if ( !$errors ) {
+                       // No errors? Assume the warnings should be treated as errors
+                       $errors = $status->getErrorsByType( 'warning' );
+               }
+               if ( !$errors ) {
+                       // Still no errors? Punt
+                       $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
+               }
+
+               if ( $errors[0]['message'] instanceof MessageSpecifier ) {
+                       $msg = $errors[0]['message'];
+               } else {
+                       $msg = new Message( $errors[0]['message'], $errors[0]['params'] );
+               }
+               if ( !$msg instanceof IApiMessage ) {
+                       $key = $msg->getKey();
+                       $params = $msg->getParams();
+                       array_unshift( $params, isset( self::$messageMap[$key] ) ? self::$messageMap[$key] : $key );
+                       $msg = ApiMessage::create( $params );
+               }
+
+               return [
+                       $msg->getApiCode(),
+                       ApiErrorFormatter::stripMarkup( $msg->inLanguage( 'en' )->useDatabase( false )->text() )
+               ];
+       }
+
+       /**
+        * @deprecated since 1.29. Prior to 1.29, this was a public mapping from
+        *  arbitrary strings (often message keys used elsewhere in MediaWiki) to
+        *  API codes and message texts, and a few interfaces required poking
+        *  something in here. Now we're repurposing it to map those same strings
+        *  to i18n messages, and declaring that any interface that requires poking
+        *  at this is broken and needs replacing ASAP.
+        */
+       private static $messageMap = [
+               'unknownerror' => 'apierror-unknownerror',
+               'unknownerror-nocode' => 'apierror-unknownerror-nocode',
+               'ns-specialprotected' => 'ns-specialprotected',
+               'protectedinterface' => 'protectedinterface',
+               'namespaceprotected' => 'namespaceprotected',
+               'customcssprotected' => 'customcssprotected',
+               'customjsprotected' => 'customjsprotected',
+               'cascadeprotected' => 'cascadeprotected',
+               'protectedpagetext' => 'protectedpagetext',
+               'protect-cantedit' => 'protect-cantedit',
+               'deleteprotected' => 'deleteprotected',
+               'badaccess-group0' => 'badaccess-group0',
+               'badaccess-groups' => 'badaccess-groups',
+               'titleprotected' => 'titleprotected',
+               'nocreate-loggedin' => 'nocreate-loggedin',
+               'nocreatetext' => 'nocreatetext',
+               'movenologintext' => 'movenologintext',
+               'movenotallowed' => 'movenotallowed',
+               'confirmedittext' => 'confirmedittext',
+               'blockedtext' => 'apierror-blocked',
+               'autoblockedtext' => 'apierror-autoblocked',
+               'actionthrottledtext' => 'apierror-ratelimited',
+               'alreadyrolled' => 'alreadyrolled',
+               'cantrollback' => 'cantrollback',
+               'readonlytext' => 'readonlytext',
+               'sessionfailure' => 'sessionfailure',
+               'cannotdelete' => 'cannotdelete',
+               'notanarticle' => 'apierror-missingtitle',
+               'selfmove' => 'selfmove',
+               'immobile_namespace' => 'apierror-immobilenamespace',
+               'articleexists' => 'articleexists',
+               'hookaborted' => 'hookaborted',
+               'cantmove-titleprotected' => 'cantmove-titleprotected',
+               'imagenocrossnamespace' => 'imagenocrossnamespace',
+               'imagetypemismatch' => 'imagetypemismatch',
+               'ip_range_invalid' => 'ip_range_invalid',
+               'range_block_disabled' => 'range_block_disabled',
+               'nosuchusershort' => 'nosuchusershort',
+               'badipaddress' => 'badipaddress',
+               'ipb_expiry_invalid' => 'ipb_expiry_invalid',
+               'ipb_already_blocked' => 'ipb_already_blocked',
+               'ipb_blocked_as_range' => 'ipb_blocked_as_range',
+               'ipb_cant_unblock' => 'ipb_cant_unblock',
+               'mailnologin' => 'apierror-cantsend',
+               'ipbblocked' => 'ipbblocked',
+               'ipbnounblockself' => 'ipbnounblockself',
+               'usermaildisabled' => 'usermaildisabled',
+               'blockedemailuser' => 'apierror-blockedfrommail',
+               'notarget' => 'apierror-notarget',
+               'noemail' => 'noemail',
+               'rcpatroldisabled' => 'rcpatroldisabled',
+               'markedaspatrollederror-noautopatrol' => 'markedaspatrollederror-noautopatrol',
+               'delete-toobig' => 'delete-toobig',
+               'movenotallowedfile' => 'movenotallowedfile',
+               'userrights-no-interwiki' => 'userrights-no-interwiki',
+               'userrights-nodatabase' => 'userrights-nodatabase',
+               'nouserspecified' => 'nouserspecified',
+               'noname' => 'noname',
+               'summaryrequired' => 'apierror-summaryrequired',
+               'import-rootpage-invalid' => 'import-rootpage-invalid',
+               'import-rootpage-nosubpage' => 'import-rootpage-nosubpage',
+               'readrequired' => 'apierror-readapidenied',
+               'writedisabled' => 'apierror-noapiwrite',
+               'writerequired' => 'apierror-writeapidenied',
+               'missingparam' => 'apierror-missingparam',
+               'invalidtitle' => 'apierror-invalidtitle',
+               'nosuchpageid' => 'apierror-nosuchpageid',
+               'nosuchrevid' => 'apierror-nosuchrevid',
+               'nosuchuser' => 'nosuchusershort',
+               'invaliduser' => 'apierror-invaliduser',
+               'invalidexpiry' => 'apierror-invalidexpiry',
+               'pastexpiry' => 'apierror-pastexpiry',
+               'create-titleexists' => 'apierror-create-titleexists',
+               'missingtitle-createonly' => 'apierror-missingtitle-createonly',
+               'cantblock' => 'apierror-cantblock',
+               'canthide' => 'apierror-canthide',
+               'cantblock-email' => 'apierror-cantblock-email',
+               'cantunblock' => 'apierror-permissiondenied-generic',
+               'cannotundelete' => 'cannotundelete',
+               'permdenied-undelete' => 'apierror-permissiondenied-generic',
+               'createonly-exists' => 'apierror-articleexists',
+               'nocreate-missing' => 'apierror-missingtitle',
+               'cantchangecontentmodel' => 'apierror-cantchangecontentmodel',
+               'nosuchrcid' => 'apierror-nosuchrcid',
+               'nosuchlogid' => 'apierror-nosuchlogid',
+               'protect-invalidaction' => 'apierror-protect-invalidaction',
+               'protect-invalidlevel' => 'apierror-protect-invalidlevel',
+               'toofewexpiries' => 'apierror-toofewexpiries',
+               'cantimport' => 'apierror-cantimport',
+               'cantimport-upload' => 'apierror-cantimport-upload',
+               'importnofile' => 'importnofile',
+               'importuploaderrorsize' => 'importuploaderrorsize',
+               'importuploaderrorpartial' => 'importuploaderrorpartial',
+               'importuploaderrortemp' => 'importuploaderrortemp',
+               'importcantopen' => 'importcantopen',
+               'import-noarticle' => 'import-noarticle',
+               'importbadinterwiki' => 'importbadinterwiki',
+               'import-unknownerror' => 'apierror-import-unknownerror',
+               'cantoverwrite-sharedfile' => 'apierror-cantoverwrite-sharedfile',
+               'sharedfile-exists' => 'apierror-fileexists-sharedrepo-perm',
+               'mustbeposted' => 'apierror-mustbeposted',
+               'show' => 'apierror-show',
+               'specialpage-cantexecute' => 'apierror-specialpage-cantexecute',
+               'invalidoldimage' => 'apierror-invalidoldimage',
+               'nodeleteablefile' => 'apierror-nodeleteablefile',
+               'fileexists-forbidden' => 'fileexists-forbidden',
+               'fileexists-shared-forbidden' => 'fileexists-shared-forbidden',
+               'filerevert-badversion' => 'filerevert-badversion',
+               'noimageredirect-anon' => 'apierror-noimageredirect-anon',
+               'noimageredirect-logged' => 'apierror-noimageredirect',
+               'spamdetected' => 'apierror-spamdetected',
+               'contenttoobig' => 'apierror-contenttoobig',
+               'noedit-anon' => 'apierror-noedit-anon',
+               'noedit' => 'apierror-noedit',
+               'wasdeleted' => 'apierror-pagedeleted',
+               'blankpage' => 'apierror-emptypage',
+               'editconflict' => 'editconflict',
+               'hashcheckfailed' => 'apierror-badmd5',
+               'missingtext' => 'apierror-notext',
+               'emptynewsection' => 'apierror-emptynewsection',
+               'revwrongpage' => 'apierror-revwrongpage',
+               'undo-failure' => 'undo-failure',
+               'content-not-allowed-here' => 'content-not-allowed-here',
+               'edit-hook-aborted' => 'edit-hook-aborted',
+               'edit-gone-missing' => 'edit-gone-missing',
+               'edit-conflict' => 'edit-conflict',
+               'edit-already-exists' => 'edit-already-exists',
+               'invalid-file-key' => 'apierror-invalid-file-key',
+               'nouploadmodule' => 'apierror-nouploadmodule',
+               'uploaddisabled' => 'uploaddisabled',
+               'copyuploaddisabled' => 'copyuploaddisabled',
+               'copyuploadbaddomain' => 'apierror-copyuploadbaddomain',
+               'copyuploadbadurl' => 'apierror-copyuploadbadurl',
+               'filename-tooshort' => 'filename-tooshort',
+               'filename-toolong' => 'filename-toolong',
+               'illegal-filename' => 'illegal-filename',
+               'filetype-missing' => 'filetype-missing',
+               'mustbeloggedin' => 'apierror-mustbeloggedin',
+       ];
+
+       /**
+        * @deprecated do not use
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
+        * @return ApiMessage
+        */
+       private function parseMsgInternal( $error ) {
+               $msg = Message::newFromSpecifier( $error );
+               if ( !$msg instanceof IApiMessage ) {
+                       $key = $msg->getKey();
+                       if ( isset( self::$messageMap[$key] ) ) {
+                               $params = $msg->getParams();
+                               array_unshift( $params, self::$messageMap[$key] );
+                       } else {
+                               $params = [ 'apierror-unknownerror', wfEscapeWikiText( $key ) ];
+                       }
+                       $msg = ApiMessage::create( $params );
+               }
+               return $msg;
+       }
+
+       /**
+        * Return the error message related to a certain array
+        * @deprecated since 1.29
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
+        * @return [ 'code' => code, 'info' => info ]
+        */
+       public function parseMsg( $error ) {
+               // Check whether someone passed the whole array, instead of one element as
+               // documented. This breaks if it's actually an array of fallback keys, but
+               // that's long-standing misbehavior introduced in r87627 to incorrectly
+               // fix T30797.
+               if ( is_array( $error ) ) {
+                       $first = reset( $error );
+                       if ( is_array( $first ) ) {
+                               wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
+                               $error = $first;
+                       }
+               }
+
+               $msg = $this->parseMsgInternal( $error );
+               return [
+                       'code' => $msg->getApiCode(),
+                       'info' => ApiErrorFormatter::stripMarkup(
+                               $msg->inLanguage( 'en' )->useDatabase( false )->text()
+                       ),
+                       'data' => $msg->getApiData()
+               ];
+       }
+
+       /**
+        * Output the error message related to a certain array
+        * @deprecated since 1.29, use ApiBase::dieWithError() instead
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
+        * @throws ApiUsageException always
+        */
+       public function dieUsageMsg( $error ) {
+               $this->dieWithError( $this->parseMsgInternal( $error ) );
+       }
+
+       /**
+        * Will only set a warning instead of failing if the global $wgDebugAPI
+        * is set to true. Otherwise behaves exactly as dieUsageMsg().
+        * @deprecated since 1.29, use ApiBase::dieWithErrorOrDebug() instead
+        * @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array
+        * @throws ApiUsageException
+        * @since 1.21
+        */
+       public function dieUsageMsgOrDebug( $error ) {
+               $this->dieWithErrorOrDebug( $this->parseMsgInternal( $error ) );
+       }
+
        /**@}*/
 }
 
index e4c9d0a..3774f09 100644 (file)
@@ -41,41 +41,50 @@ class ApiBlock extends ApiBase {
        public function execute() {
                global $wgContLang;
 
+               $this->checkUserRightsAny( 'block' );
+
                $user = $this->getUser();
                $params = $this->extractRequestParams();
 
-               if ( !$user->isAllowed( 'block' ) ) {
-                       $this->dieUsageMsg( 'cantblock' );
-               }
+               $this->requireOnlyOneParameter( $params, 'user', 'userid' );
 
                # bug 15810: blocked admins should have limited access here
                if ( $user->isBlocked() ) {
                        $status = SpecialBlock::checkUnblockSelf( $params['user'], $user );
                        if ( $status !== true ) {
-                               $msg = $this->parseMsg( $status );
-                               $this->dieUsage(
-                                       $msg['info'],
-                                       $msg['code'],
-                                       0,
+                               $this->dieWithError(
+                                       $status,
+                                       null,
                                        [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
                                );
                        }
                }
 
-               $target = User::newFromName( $params['user'] );
-               // Bug 38633 - if the target is a user (not an IP address), but it
-               // doesn't exist or is unusable, error.
-               if ( $target instanceof User &&
-                       ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) )
-               ) {
-                       $this->dieUsageMsg( [ 'nosuchuser', $params['user'] ] );
+               if ( $params['userid'] !== null ) {
+                       $username = User::whoIs( $params['userid'] );
+
+                       if ( $username === false ) {
+                               $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
+                       } else {
+                               $params['user'] = $username;
+                       }
+               } else {
+                       $target = User::newFromName( $params['user'] );
+
+                       // Bug 38633 - if the target is a user (not an IP address), but it
+                       // doesn't exist or is unusable, error.
+                       if ( $target instanceof User &&
+                               ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) )
+                       ) {
+                               $this->dieWithError( [ 'nosuchusershort', $params['user'] ], 'nosuchuser' );
+                       }
                }
 
                if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) {
-                       $this->dieUsageMsg( 'canthide' );
+                       $this->dieWithError( 'apierror-canthide' );
                }
                if ( $params['noemail'] && !SpecialBlock::canBlockEmail( $user ) ) {
-                       $this->dieUsageMsg( 'cantblock-email' );
+                       $this->dieWithError( 'apierror-cantblock-email' );
                }
 
                $data = [
@@ -100,8 +109,7 @@ class ApiBlock extends ApiBase {
 
                $retval = SpecialBlock::processForm( $data, $this->getContext() );
                if ( $retval !== true ) {
-                       // We don't care about multiple errors, just report one of them
-                       $this->dieUsageMsg( $retval );
+                       $this->dieStatus( $this->errorArrayToStatus( $retval ) );
                }
 
                list( $target, /*...*/ ) = SpecialBlock::getTargetAndType( $params['user'] );
@@ -142,7 +150,9 @@ class ApiBlock extends ApiBase {
                return [
                        'user' => [
                                ApiBase::PARAM_TYPE => 'user',
-                               ApiBase::PARAM_REQUIRED => true
+                       ],
+                       'userid' => [
+                               ApiBase::PARAM_TYPE => 'integer',
                        ],
                        'expiry' => 'never',
                        'reason' => '',
index 5a0edfc..4139019 100644 (file)
@@ -137,8 +137,11 @@ class ApiCSPReport extends ApiBase {
                }
                $status = FormatJson::parse( $postBody, FormatJson::FORCE_ASSOC );
                if ( !$status->isGood() ) {
-                       list( $code, ) = $this->getErrorFromStatus( $status );
-                       $this->error( $code, __METHOD__ );
+                       $msg = $status->getErrors()[0]['message'];
+                       if ( $msg instanceof Message ) {
+                               $msg = $msg->getKey();
+                       }
+                       $this->error( $msg, __METHOD__ );
                }
 
                $report = $status->getValue();
@@ -176,7 +179,7 @@ class ApiCSPReport extends ApiBase {
         *
         * @param $code String error code
         * @param $method String method that made error
-        * @throws UsageException Always
+        * @throws ApiUsageException Always
         */
        private function error( $code, $method ) {
                $this->log->info( 'Error reading CSP report: ' . $code, [
@@ -184,7 +187,9 @@ class ApiCSPReport extends ApiBase {
                        'user-agent' => $this->getRequest()->getHeader( 'user-agent' )
                ] );
                // 500 so it shows up in browser's developer console.
-               $this->dieUsage( "Error processing CSP report: $code", 'cspreport-' . $code, 500 );
+               $this->dieWithError(
+                       [ 'apierror-csp-report', wfEscapeWikiText( $code ) ], 'cspreport-' . $code, [], 500
+               );
        }
 
        public function getAllowedParams() {
index aea2819..c25920e 100644 (file)
@@ -35,7 +35,7 @@ class ApiChangeAuthenticationData extends ApiBase {
 
        public function execute() {
                if ( !$this->getUser()->isLoggedIn() ) {
-                       $this->dieUsage( 'Must be logged in to change authentication data', 'notloggedin' );
+                       $this->dieWithError( 'apierror-mustbeloggedin-changeauthenticationdata', 'notloggedin' );
                }
 
                $helper = new ApiAuthManagerHelper( $this );
@@ -50,7 +50,7 @@ class ApiChangeAuthenticationData extends ApiBase {
                        $this->getConfig()->get( 'ChangeCredentialsBlacklist' )
                );
                if ( count( $reqs ) !== 1 ) {
-                       $this->dieUsage( 'Failed to create change request', 'badrequest' );
+                       $this->dieWithError( 'apierror-changeauth-norequest', 'badrequest' );
                }
                $req = reset( $reqs );
 
index 3d2159c..3cc7a8a 100644 (file)
@@ -22,6 +22,8 @@
  * @file
  */
 
+use MediaWiki\Session\Token;
+
 /**
  * @since 1.25
  * @ingroup API
@@ -39,6 +41,11 @@ class ApiCheckToken extends ApiBase {
                $tokenObj = ApiQueryTokens::getToken(
                        $this->getUser(), $this->getRequest()->getSession(), $salts[$params['type']]
                );
+
+               if ( substr( $token, -strlen( urldecode( Token::SUFFIX ) ) ) === urldecode( Token::SUFFIX ) ) {
+                       $this->addWarning( 'apiwarn-checktoken-percentencoding' );
+               }
+
                if ( $tokenObj->match( $token, $maxage ) ) {
                        $res['result'] = 'valid';
                } elseif ( $maxage !== null && $tokenObj->match( $token ) ) {
@@ -47,7 +54,7 @@ class ApiCheckToken extends ApiBase {
                        $res['result'] = 'invalid';
                }
 
-               $ts = MediaWiki\Session\Token::getTimestamp( $token );
+               $ts = Token::getTimestamp( $token );
                if ( $ts !== null ) {
                        $mwts = new MWTimestamp();
                        $mwts->timestamp->setTimestamp( $ts );
index 13b3577..99242a8 100644 (file)
@@ -45,7 +45,7 @@ class ApiClearHasMsg extends ApiBase {
        }
 
        public function mustBePosted() {
-               return false;
+               return true;
        }
 
        protected function getExamplesMessages() {
index cbb1524..3f5bc0c 100644 (file)
@@ -57,8 +57,8 @@ class ApiClientLogin extends ApiBase {
                        $bits = wfParseUrl( $params['returnurl'] );
                        if ( !$bits || $bits['scheme'] === '' ) {
                                $encParamName = $this->encodeParamName( 'returnurl' );
-                               $this->dieUsage(
-                                       "Invalid value '{$params['returnurl']}' for url parameter $encParamName",
+                               $this->dieWithError(
+                                       [ 'apierror-badurl', $encParamName, wfEscapeWikiText( $params['returnurl'] ) ],
                                        "badurl_{$encParamName}"
                                );
                        }
index 7eb0bf3..d6867eb 100644 (file)
@@ -34,8 +34,7 @@ class ApiComparePages extends ApiBase {
                $revision = Revision::newFromId( $rev1 );
 
                if ( !$revision ) {
-                       $this->dieUsage( 'The diff cannot be retrieved, ' .
-                               'one revision does not exist or you do not have permission to view it.', 'baddiff' );
+                       $this->dieWithError( 'apierror-baddiff' );
                }
 
                $contentHandler = $revision->getContentHandler();
@@ -65,11 +64,7 @@ class ApiComparePages extends ApiBase {
                $difftext = $de->getDiffBody();
 
                if ( $difftext === false ) {
-                       $this->dieUsage(
-                               'The diff cannot be retrieved. Maybe one or both revisions do ' .
-                                       'not exist or you do not have permission to view them.',
-                               'baddiff'
-                       );
+                       $this->dieWithError( 'apierror-baddiff' );
                }
 
                ApiResult::setContentValue( $vals, 'body', $difftext );
@@ -89,22 +84,19 @@ class ApiComparePages extends ApiBase {
                } elseif ( $titleText ) {
                        $title = Title::newFromText( $titleText );
                        if ( !$title || $title->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $titleText ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titleText ) ] );
                        }
 
                        return $title->getLatestRevID();
                } elseif ( $titleId ) {
                        $title = Title::newFromID( $titleId );
                        if ( !$title ) {
-                               $this->dieUsageMsg( [ 'nosuchpageid', $titleId ] );
+                               $this->dieWithError( [ 'apierror-nosuchpageid', $titleId ] );
                        }
 
                        return $title->getLatestRevID();
                }
-               $this->dieUsage(
-                       'A title, a page ID, or a revision number is needed for both the from and the to parameters',
-                       'inputneeded'
-               );
+               $this->dieWithError( 'apierror-compare-inputneeded', 'inputneeded' );
        }
 
        public function getAllowedParams() {
index 19e2453..7da8ed9 100644 (file)
@@ -40,7 +40,7 @@ class ApiContinuationManager {
         * @param ApiBase $module Module starting the continuation
         * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
         * @param array $generatedModules Names of modules that depend on the generator
-        * @throws UsageException
+        * @throws ApiUsageException
         */
        public function __construct(
                ApiBase $module, array $allModules = [], array $generatedModules = []
@@ -57,10 +57,7 @@ class ApiContinuationManager {
                if ( $continue !== '' ) {
                        $continue = explode( '||', $continue );
                        if ( count( $continue ) !== 2 ) {
-                               throw new UsageException(
-                                       'Invalid continue param. You should pass the original value returned by the previous query',
-                                       'badcontinue'
-                               );
+                               throw ApiUsageException::newWithMessage( $module->getMain(), 'apierror-badcontinue' );
                        }
                        $this->generatorDone = ( $continue[0] === '-' );
                        $skip = explode( '|', $continue[1] );
index 993c23e..50c24ae 100644 (file)
@@ -45,7 +45,7 @@ class ApiDelete extends ApiBase {
 
                $pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
                if ( !$pageObj->exists() ) {
-                       $this->dieUsageMsg( 'notanarticle' );
+                       $this->dieWithError( 'apierror-missingtitle' );
                }
 
                $titleObj = $pageObj->getTitle();
@@ -53,10 +53,7 @@ class ApiDelete extends ApiBase {
                $user = $this->getUser();
 
                // Check that the user is allowed to carry out the deletion
-               $errors = $titleObj->getUserPermissionsErrors( 'delete', $user );
-               if ( count( $errors ) ) {
-                       $this->dieUsageMsg( $errors[0] );
-               }
+               $this->checkTitleUserPermissions( $titleObj, 'delete' );
 
                // If change tagging was requested, check that the user is allowed to tag,
                // and the tags are valid
@@ -80,9 +77,6 @@ class ApiDelete extends ApiBase {
                        $status = self::delete( $pageObj, $user, $reason, $params['tags'] );
                }
 
-               if ( is_array( $status ) ) {
-                       $this->dieUsageMsg( $status[0] );
-               }
                if ( !$status->isGood() ) {
                        $this->dieStatus( $status );
                }
@@ -112,7 +106,7 @@ class ApiDelete extends ApiBase {
         * @param User $user User doing the action
         * @param string|null $reason Reason for the deletion. Autogenerated if null
         * @param array $tags Tags to tag the deletion with
-        * @return Status|array
+        * @return Status
         */
        protected static function delete( Page $page, User $user, &$reason = null, $tags = [] ) {
                $title = $page->getTitle();
@@ -124,7 +118,7 @@ class ApiDelete extends ApiBase {
                        $hasHistory = false;
                        $reason = $page->getAutoDeleteReason( $hasHistory );
                        if ( $reason === false ) {
-                               return [ [ 'cannotdelete', $title->getPrefixedText() ] ];
+                               return Status::newFatal( 'cannotdelete', $title->getPrefixedText() );
                        }
                }
 
@@ -141,7 +135,7 @@ class ApiDelete extends ApiBase {
         * @param string $reason Reason for the deletion. Autogenerated if null.
         * @param bool $suppress Whether to mark all deleted versions as restricted
         * @param array $tags Tags to tag the deletion with
-        * @return Status|array
+        * @return Status
         */
        protected static function deleteFile( Page $page, User $user, $oldimage,
                &$reason = null, $suppress = false, $tags = []
@@ -155,11 +149,11 @@ class ApiDelete extends ApiBase {
 
                if ( $oldimage ) {
                        if ( !FileDeleteForm::isValidOldSpec( $oldimage ) ) {
-                               return [ [ 'invalidoldimage' ] ];
+                               return Status::newFatal( 'invalidoldimage' );
                        }
                        $oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $oldimage );
                        if ( !$oldfile->exists() || !$oldfile->isLocal() || $oldfile->getRedirected() ) {
-                               return [ [ 'nodeleteablefile' ] ];
+                               return Status::newFatal( 'nodeleteablefile' );
                        }
                }
 
index fc97522..41bf9b6 100644 (file)
@@ -37,7 +37,7 @@
 class ApiDisabled extends ApiBase {
 
        public function execute() {
-               $this->dieUsage( "The \"{$this->getModuleName()}\" module has been disabled.", 'moduledisabled' );
+               $this->dieWithError( [ 'apierror-moduledisabled', $this->getModuleName() ] );
        }
 
        public function isReadMode() {
index d6de834..6b56870 100644 (file)
@@ -40,12 +40,7 @@ class ApiEditPage extends ApiBase {
                $user = $this->getUser();
                $params = $this->extractRequestParams();
 
-               if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
-                       is_null( $params['prependtext'] ) &&
-                       $params['undo'] == 0
-               ) {
-                       $this->dieUsageMsg( 'missingtext' );
-               }
+               $this->requireAtLeastOneParameter( $params, 'text', 'appendtext', 'prependtext', 'undo' );
 
                $pageObj = $this->getTitleOrPageId( $params );
                $titleObj = $pageObj->getTitle();
@@ -55,9 +50,7 @@ class ApiEditPage extends ApiBase {
                        if ( $params['prependtext'] === null && $params['appendtext'] === null
                                && $params['section'] !== 'new'
                        ) {
-                               $this->dieUsage( 'You have attempted to edit using the "redirect"-following'
-                                       . ' mode, which must be used in conjuction with section=new, prependtext'
-                                       . ', or appendtext.', 'redirect-appendonly' );
+                               $this->dieWithError( 'apierror-redirect-appendonly' );
                        }
                        if ( $titleObj->isRedirect() ) {
                                $oldTitle = $titleObj;
@@ -105,10 +98,7 @@ class ApiEditPage extends ApiBase {
                if ( $params['undo'] > 0 ) {
                        // allow undo via api
                } elseif ( $contentHandler->supportsDirectApiEditing() === false ) {
-                       $this->dieUsage(
-                               "Direct editing via API is not supported for content model $model used by $name",
-                               'no-direct-editing'
-                       );
+                       $this->dieWithError( [ 'apierror-no-direct-editing', $model, $name ] );
                }
 
                if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
@@ -118,49 +108,21 @@ class ApiEditPage extends ApiBase {
                }
 
                if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
-
-                       $this->dieUsage( "The requested format $contentFormat is not supported for content model " .
-                               " $model used by $name", 'badformat' );
+                       $this->dieWithError( [ 'apierror-badformat', $contentFormat, $model, $name ] );
                }
 
                if ( $params['createonly'] && $titleObj->exists() ) {
-                       $this->dieUsageMsg( 'createonly-exists' );
+                       $this->dieWithError( 'apierror-articleexists' );
                }
                if ( $params['nocreate'] && !$titleObj->exists() ) {
-                       $this->dieUsageMsg( 'nocreate-missing' );
+                       $this->dieWithError( 'apierror-missingtitle' );
                }
 
                // Now let's check whether we're even allowed to do this
-               $errors = $titleObj->getUserPermissionsErrors( 'edit', $user );
-               if ( !$titleObj->exists() ) {
-                       $errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $user ) );
-               }
-               if ( count( $errors ) ) {
-                       if ( is_array( $errors[0] ) ) {
-                               switch ( $errors[0][0] ) {
-                                       case 'blockedtext':
-                                               $this->dieUsage(
-                                                       'You have been blocked from editing',
-                                                       'blocked',
-                                                       0,
-                                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
-                                               );
-                                               break;
-                                       case 'autoblockedtext':
-                                               $this->dieUsage(
-                                                       'Your IP address has been blocked automatically, because it was used by a blocked user',
-                                                       'autoblocked',
-                                                       0,
-                                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
-                                               );
-                                               break;
-                                       default:
-                                               $this->dieUsageMsg( $errors[0] );
-                               }
-                       } else {
-                               $this->dieUsageMsg( $errors[0] );
-                       }
-               }
+               $this->checkTitleUserPermissions(
+                       $titleObj,
+                       $titleObj->exists() ? 'edit' : [ 'edit', 'create' ]
+               );
 
                $toMD5 = $params['text'];
                if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) ) {
@@ -178,8 +140,11 @@ class ApiEditPage extends ApiBase {
                                        try {
                                                $content = ContentHandler::makeContent( $text, $this->getTitle() );
                                        } catch ( MWContentSerializationException $ex ) {
-                                               $this->dieUsage( $ex->getMessage(), 'parseerror' );
-
+                                               // @todo: Internationalize MWContentSerializationException
+                                               $this->dieWithError(
+                                                       [ 'apierror-contentserializationexception', wfEscapeWikiText( $ex->getMessage() ) ],
+                                                       'parseerror'
+                                               );
                                                return;
                                        }
                                } else {
@@ -191,17 +156,14 @@ class ApiEditPage extends ApiBase {
                        // @todo Add support for appending/prepending to the Content interface
 
                        if ( !( $content instanceof TextContent ) ) {
-                               $mode = $contentHandler->getModelID();
-                               $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
+                               $modelName = $contentHandler->getModelID();
+                               $this->dieWithError( [ 'apierror-appendnotsupported', $modelName ] );
                        }
 
                        if ( !is_null( $params['section'] ) ) {
                                if ( !$contentHandler->supportsSections() ) {
                                        $modelName = $contentHandler->getModelID();
-                                       $this->dieUsage(
-                                               "Sections are not supported for this content model: $modelName.",
-                                               'sectionsnotsupported'
-                                       );
+                                       $this->dieWithError( [ 'apierror-sectionsnotsupported', $modelName ] );
                                }
 
                                if ( $params['section'] == 'new' ) {
@@ -213,7 +175,7 @@ class ApiEditPage extends ApiBase {
                                        $content = $content->getSection( $section );
 
                                        if ( !$content ) {
-                                               $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
+                                               $this->dieWithError( [ 'apierror-nosuchsection', wfEscapeWikiText( $section ) ] );
                                        }
                                }
                        }
@@ -238,22 +200,22 @@ class ApiEditPage extends ApiBase {
                        }
                        $undoRev = Revision::newFromId( $params['undo'] );
                        if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) ) {
-                               $this->dieUsageMsg( [ 'nosuchrevid', $params['undo'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchrevid', $params['undo'] ] );
                        }
 
                        if ( $params['undoafter'] == 0 ) {
                                $undoafterRev = $undoRev->getPrevious();
                        }
                        if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( Revision::DELETED_TEXT ) ) {
-                               $this->dieUsageMsg( [ 'nosuchrevid', $params['undoafter'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchrevid', $params['undoafter'] ] );
                        }
 
                        if ( $undoRev->getPage() != $pageObj->getId() ) {
-                               $this->dieUsageMsg( [ 'revwrongpage', $undoRev->getId(),
+                               $this->dieWithError( [ 'apierror-revwrongpage', $undoRev->getId(),
                                        $titleObj->getPrefixedText() ] );
                        }
                        if ( $undoafterRev->getPage() != $pageObj->getId() ) {
-                               $this->dieUsageMsg( [ 'revwrongpage', $undoafterRev->getId(),
+                               $this->dieWithError( [ 'apierror-revwrongpage', $undoafterRev->getId(),
                                        $titleObj->getPrefixedText() ] );
                        }
 
@@ -264,7 +226,7 @@ class ApiEditPage extends ApiBase {
                        );
 
                        if ( !$newContent ) {
-                               $this->dieUsageMsg( 'undo-failure' );
+                               $this->dieWithError( 'undo-failure', 'undofailure' );
                        }
                        if ( empty( $params['contentmodel'] )
                                && empty( $params['contentformat'] )
@@ -293,7 +255,7 @@ class ApiEditPage extends ApiBase {
 
                // See if the MD5 hash checks out
                if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) {
-                       $this->dieUsageMsg( 'hashcheckfailed' );
+                       $this->dieWithError( 'apierror-badmd5' );
                }
 
                // EditPage wants to parse its stuff from a WebRequest
@@ -347,14 +309,13 @@ class ApiEditPage extends ApiBase {
                if ( !is_null( $params['section'] ) ) {
                        $section = $params['section'];
                        if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
-                               $this->dieUsage( "The section parameter must be a valid section id or 'new'",
-                                       'invalidsection' );
+                               $this->dieWithError( 'apierror-invalidsection' );
                        }
                        $content = $pageObj->getContent();
                        if ( $section !== '0' && $section != 'new'
                                && ( !$content || !$content->getSection( $section ) )
                        ) {
-                               $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
+                               $this->dieWithError( [ 'apierror-nosuchsection', $section ] );
                        }
                        $requestArray['wpSection'] = $params['section'];
                } else {
@@ -423,7 +384,7 @@ class ApiEditPage extends ApiBase {
                                return;
                        }
 
-                       $this->dieUsageMsg( 'hookaborted' );
+                       $this->dieWithError( 'hookaborted' );
                }
 
                // Do the actual save
@@ -445,67 +406,22 @@ class ApiEditPage extends ApiBase {
                                        $r['result'] = 'Failure';
                                        $apiResult->addValue( null, $this->getModuleName(), $r );
                                        return;
-                               } else {
-                                       $this->dieUsageMsg( 'hookaborted' );
                                }
-
-                       case EditPage::AS_PARSE_ERROR:
-                               $this->dieUsage( $status->getMessage(), 'parseerror' );
-
-                       case EditPage::AS_IMAGE_REDIRECT_ANON:
-                               $this->dieUsageMsg( 'noimageredirect-anon' );
-
-                       case EditPage::AS_IMAGE_REDIRECT_LOGGED:
-                               $this->dieUsageMsg( 'noimageredirect-logged' );
-
-                       case EditPage::AS_SPAM_ERROR:
-                               $this->dieUsageMsg( [ 'spamdetected', $result['spam'] ] );
+                               if ( !$status->getErrors() ) {
+                                       $status->fatal( 'hookaborted' );
+                               }
+                               $this->dieStatus( $status );
 
                        case EditPage::AS_BLOCKED_PAGE_FOR_USER:
-                               $this->dieUsage(
-                                       'You have been blocked from editing',
+                               $this->dieWithError(
+                                       'apierror-blocked',
                                        'blocked',
-                                       0,
                                        [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
                                );
 
-                       case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
-                       case EditPage::AS_CONTENT_TOO_BIG:
-                               $this->dieUsageMsg( [ 'contenttoobig', $this->getConfig()->get( 'MaxArticleSize' ) ] );
-
-                       case EditPage::AS_READ_ONLY_PAGE_ANON:
-                               $this->dieUsageMsg( 'noedit-anon' );
-
-                       case EditPage::AS_READ_ONLY_PAGE_LOGGED:
-                               $this->dieUsageMsg( 'noedit' );
-
                        case EditPage::AS_READ_ONLY_PAGE:
                                $this->dieReadOnly();
 
-                       case EditPage::AS_RATE_LIMITED:
-                               $this->dieUsageMsg( 'actionthrottledtext' );
-
-                       case EditPage::AS_ARTICLE_WAS_DELETED:
-                               $this->dieUsageMsg( 'wasdeleted' );
-
-                       case EditPage::AS_NO_CREATE_PERMISSION:
-                               $this->dieUsageMsg( 'nocreate-loggedin' );
-
-                       case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
-                               $this->dieUsageMsg( 'cantchangecontentmodel' );
-
-                       case EditPage::AS_BLANK_ARTICLE:
-                               $this->dieUsageMsg( 'blankpage' );
-
-                       case EditPage::AS_CONFLICT_DETECTED:
-                               $this->dieUsageMsg( 'editconflict' );
-
-                       case EditPage::AS_TEXTBOX_EMPTY:
-                               $this->dieUsageMsg( 'emptynewsection' );
-
-                       case EditPage::AS_CHANGE_TAG_ERROR:
-                               $this->dieStatus( $status );
-
                        case EditPage::AS_SUCCESS_NEW_ARTICLE:
                                $r['new'] = true;
                                // fall-through
@@ -526,15 +442,39 @@ class ApiEditPage extends ApiBase {
                                }
                                break;
 
-                       case EditPage::AS_SUMMARY_NEEDED:
-                               // Shouldn't happen since we set wpIgnoreBlankSummary, but just in case
-                               $this->dieUsageMsg( 'summaryrequired' );
-
-                       case EditPage::AS_END:
                        default:
-                               // $status came from WikiPage::doEditContent()
-                               $errors = $status->getErrorsArray();
-                               $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
+                               // EditPage sometimes only sets the status code without setting
+                               // any actual error messages. Supply defaults for those cases.
+                               $maxArticleSize = $this->getConfig()->get( 'MaxArticleSize' );
+                               $defaultMessages = [
+                                       // Currently needed
+                                       EditPage::AS_IMAGE_REDIRECT_ANON => [ 'apierror-noimageredirect-anon' ],
+                                       EditPage::AS_IMAGE_REDIRECT_LOGGED => [ 'apierror-noimageredirect-logged' ],
+                                       EditPage::AS_CONTENT_TOO_BIG => [ 'apierror-contenttoobig', $maxArticleSize ],
+                                       EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED => [ 'apierror-contenttoobig', $maxArticleSize ],
+                                       EditPage::AS_READ_ONLY_PAGE_ANON => [ 'apierror-noedit-anon' ],
+                                       EditPage::AS_NO_CHANGE_CONTENT_MODEL => [ 'apierror-cantchangecontentmodel' ],
+                                       EditPage::AS_ARTICLE_WAS_DELETED => [ 'apierror-pagedeleted' ],
+                                       EditPage::AS_CONFLICT_DETECTED => [ 'editconflict' ],
+
+                                       // Currently shouldn't be needed
+                                       EditPage::AS_SPAM_ERROR => [ 'apierror-spamdetected', wfEscapeWikiText( $result['spam'] ) ],
+                                       EditPage::AS_READ_ONLY_PAGE_LOGGED => [ 'apierror-noedit' ],
+                                       EditPage::AS_RATE_LIMITED => [ 'apierror-ratelimited' ],
+                                       EditPage::AS_NO_CREATE_PERMISSION => [ 'nocreate-loggedin' ],
+                                       EditPage::AS_BLANK_ARTICLE => [ 'apierror-emptypage' ],
+                                       EditPage::AS_TEXTBOX_EMPTY => [ 'apierror-emptynewsection' ],
+                                       EditPage::AS_SUMMARY_NEEDED => [ 'apierror-summaryrequired' ],
+                               ];
+                               if ( !$status->getErrors() ) {
+                                       if ( isset( $defaultMessages[$status->value] ) ) {
+                                               call_user_func_array( [ $status, 'fatal' ], $defaultMessages[$status->value] );
+                                       } else {
+                                               wfWarn( __METHOD__ . ": Unknown EditPage code {$status->value} with no message" );
+                                               $status->fatal( 'apierror-unknownerror-editpage', $status->value );
+                                       }
+                               }
+                               $this->dieStatus( $status );
                                break;
                }
                $apiResult->addValue( null, $this->getModuleName(), $r );
index 192378e..8aff6f8 100644 (file)
@@ -36,7 +36,16 @@ class ApiEmailUser extends ApiBase {
                // Validate target
                $targetUser = SpecialEmailUser::getTarget( $params['target'] );
                if ( !( $targetUser instanceof User ) ) {
-                       $this->dieUsageMsg( [ $targetUser ] );
+                       switch ( $targetUser ) {
+                               case 'notarget':
+                                       $this->dieWithError( 'apierror-notarget' );
+                               case 'noemail':
+                                       $this->dieWithError( [ 'noemail', $params['target'] ] );
+                               case 'nowikiemail':
+                                       $this->dieWithError( 'nowikiemailtext', 'nowikiemail' );
+                               default:
+                                       $this->dieWithError( [ 'apierror-unknownerror', $targetUser ] );
+                       }
                }
 
                // Check permissions and errors
@@ -46,7 +55,7 @@ class ApiEmailUser extends ApiBase {
                        $this->getConfig()
                );
                if ( $error ) {
-                       $this->dieUsageMsg( [ $error ] );
+                       $this->dieWithError( $error );
                }
 
                $data = [
@@ -56,25 +65,16 @@ class ApiEmailUser extends ApiBase {
                        'CCMe' => $params['ccme'],
                ];
                $retval = SpecialEmailUser::submit( $data, $this->getContext() );
-
-               if ( $retval instanceof Status ) {
-                       // SpecialEmailUser sometimes returns a status
-                       // sometimes it doesn't.
-                       if ( $retval->isGood() ) {
-                               $retval = true;
-                       } else {
-                               $retval = $retval->getErrorsArray();
-                       }
+               if ( !$retval instanceof Status ) {
+                       // This is probably the reason
+                       $retval = Status::newFatal( 'hookaborted' );
                }
 
-               if ( $retval === true ) {
-                       $result = [ 'result' => 'Success' ];
-               } else {
-                       $result = [
-                               'result' => 'Failure',
-                               'message' => $retval
-                       ];
-               }
+               $result = array_filter( [
+                       'result' => $retval->isGood() ? 'Success' : $retval->isOk() ? 'Warnings' : 'Failure',
+                       'warnings' => $this->getErrorFormatter()->arrayFromStatus( $retval, 'warning' ),
+                       'errors' => $this->getErrorFormatter()->arrayFromStatus( $retval, 'error' ),
+               ] );
 
                $this->getResult()->addValue( null, $this->getModuleName(), $result );
        }
index 6d9184f..4fb19b8 100644 (file)
@@ -43,7 +43,9 @@ class ApiErrorFormatter {
         * @param ApiResult $result Into which data will be added
         * @param Language $lang Used for i18n
         * @param string $format
-        *  - text: Error message as wikitext
+        *  - plaintext: Error message as something vaguely like plaintext
+        *    (it's basically wikitext with HTML tags stripped and entities decoded)
+        *  - wikitext: Error message as wikitext
         *  - html: Error message as HTML
         *  - raw: Raw message key and parameters, no human-readable text
         *  - none: Code and data only, no human-readable text
@@ -56,6 +58,15 @@ class ApiErrorFormatter {
                $this->format = $format;
        }
 
+       /**
+        * Fetch the Language for this formatter
+        * @since 1.29
+        * @return Language
+        */
+       public function getLanguage() {
+               return $this->lang;
+       }
+
        /**
         * Fetch a dummy title to set on Messages
         * @return Title
@@ -69,53 +80,49 @@ class ApiErrorFormatter {
 
        /**
         * Add a warning to the result
-        * @param string $moduleName
-        * @param MessageSpecifier|array|string $msg i18n message for the warning
-        * @param string $code Machine-readable code for the warning. Defaults as
-        *   for IApiMessage::getApiCode().
-        * @param array $data Machine-readable data for the warning, if any.
-        *   Uses IApiMessage::getApiData() if $msg implements that interface.
+        * @param string|null $modulePath
+        * @param Message|array|string $msg Warning message. See ApiMessage::create().
+        * @param string|null $code See ApiMessage::create().
+        * @param array|null $data See ApiMessage::create().
         */
-       public function addWarning( $moduleName, $msg, $code = null, $data = null ) {
+       public function addWarning( $modulePath, $msg, $code = null, $data = null ) {
                $msg = ApiMessage::create( $msg, $code, $data )
                        ->inLanguage( $this->lang )
                        ->title( $this->getDummyTitle() )
                        ->useDatabase( $this->useDB );
-               $this->addWarningOrError( 'warning', $moduleName, $msg );
+               $this->addWarningOrError( 'warning', $modulePath, $msg );
        }
 
        /**
         * Add an error to the result
-        * @param string $moduleName
-        * @param MessageSpecifier|array|string $msg i18n message for the error
-        * @param string $code Machine-readable code for the warning. Defaults as
-        *   for IApiMessage::getApiCode().
-        * @param array $data Machine-readable data for the warning, if any.
-        *   Uses IApiMessage::getApiData() if $msg implements that interface.
+        * @param string|null $modulePath
+        * @param Message|array|string $msg Warning message. See ApiMessage::create().
+        * @param string|null $code See ApiMessage::create().
+        * @param array|null $data See ApiMessage::create().
         */
-       public function addError( $moduleName, $msg, $code = null, $data = null ) {
+       public function addError( $modulePath, $msg, $code = null, $data = null ) {
                $msg = ApiMessage::create( $msg, $code, $data )
                        ->inLanguage( $this->lang )
                        ->title( $this->getDummyTitle() )
                        ->useDatabase( $this->useDB );
-               $this->addWarningOrError( 'error', $moduleName, $msg );
+               $this->addWarningOrError( 'error', $modulePath, $msg );
        }
 
        /**
-        * Add warnings and errors from a Status object to the result
-        * @param string $moduleName
-        * @param Status $status
+        * Add warnings and errors from a StatusValue object to the result
+        * @param string|null $modulePath
+        * @param StatusValue $status
         * @param string[] $types 'warning' and/or 'error'
         */
        public function addMessagesFromStatus(
-               $moduleName, Status $status, $types = [ 'warning', 'error' ]
+               $modulePath, StatusValue $status, $types = [ 'warning', 'error' ]
        ) {
-               if ( $status->isGood() || !$status->errors ) {
+               if ( $status->isGood() || !$status->getErrors() ) {
                        return;
                }
 
                $types = (array)$types;
-               foreach ( $status->errors as $error ) {
+               foreach ( $status->getErrors() as $error ) {
                        if ( !in_array( $error['type'], $types, true ) ) {
                                continue;
                        }
@@ -127,40 +134,37 @@ class ApiErrorFormatter {
                                $tag = 'warning';
                        }
 
-                       if ( is_array( $error ) && isset( $error['message'] ) ) {
-                               // Normal case
-                               if ( $error['message'] instanceof Message ) {
-                                       $msg = ApiMessage::create( $error['message'], null, [] );
-                               } else {
-                                       $args = isset( $error['params'] ) ? $error['params'] : [];
-                                       array_unshift( $args, $error['message'] );
-                                       $error += [ 'params' => [] ];
-                                       $msg = ApiMessage::create( $args, null, [] );
-                               }
-                       } elseif ( is_array( $error ) ) {
-                               // Weird case handled by Message::getErrorMessage
-                               $msg = ApiMessage::create( $error, null, [] );
-                       } else {
-                               // Another weird case handled by Message::getErrorMessage
-                               $msg = ApiMessage::create( $error, null, [] );
-                       }
-
-                       $msg->inLanguage( $this->lang )
+                       $msg = ApiMessage::create( $error )
+                               ->inLanguage( $this->lang )
                                ->title( $this->getDummyTitle() )
                                ->useDatabase( $this->useDB );
-                       $this->addWarningOrError( $tag, $moduleName, $msg );
+                       $this->addWarningOrError( $tag, $modulePath, $msg );
                }
        }
 
        /**
-        * Format messages from a Status as an array
-        * @param Status $status
+        * Format a message as an array
+        * @param Message|array|string $msg Message. See ApiMessage::create().
+        * @param string|null $format
+        * @return array
+        */
+       public function formatMessage( $msg, $format = null ) {
+               $msg = ApiMessage::create( $msg )
+                       ->inLanguage( $this->lang )
+                       ->title( $this->getDummyTitle() )
+                       ->useDatabase( $this->useDB );
+               return $this->formatMessageInternal( $msg, $format ?: $this->format );
+       }
+
+       /**
+        * Format messages from a StatusValue as an array
+        * @param StatusValue $status
         * @param string $type 'warning' or 'error'
         * @param string|null $format
         * @return array
         */
-       public function arrayFromStatus( Status $status, $type = 'error', $format = null ) {
-               if ( $status->isGood() || !$status->errors ) {
+       public function arrayFromStatus( StatusValue $status, $type = 'error', $format = null ) {
+               if ( $status->isGood() || !$status->getErrors() ) {
                        return [];
                }
 
@@ -168,24 +172,69 @@ class ApiErrorFormatter {
                $formatter = new ApiErrorFormatter(
                        $result, $this->lang, $format ?: $this->format, $this->useDB
                );
-               $formatter->addMessagesFromStatus( 'dummy', $status, [ $type ] );
+               $formatter->addMessagesFromStatus( null, $status, [ $type ] );
                switch ( $type ) {
                        case 'error':
-                               return (array)$result->getResultData( [ 'errors', 'dummy' ] );
+                               return (array)$result->getResultData( [ 'errors' ] );
                        case 'warning':
-                               return (array)$result->getResultData( [ 'warnings', 'dummy' ] );
+                               return (array)$result->getResultData( [ 'warnings' ] );
                }
        }
 
        /**
-        * Actually add the warning or error to the result
-        * @param string $tag 'warning' or 'error'
-        * @param string $moduleName
+        * Turn wikitext into something resembling plaintext
+        * @since 1.29
+        * @param string $text
+        * @return string
+        */
+       public static function stripMarkup( $text ) {
+               // Turn semantic quoting tags to quotes
+               $ret = preg_replace( '!</?(var|kbd|samp|code)>!', '"', $text );
+
+               // Strip tags and decode.
+               $ret = html_entity_decode( strip_tags( $ret ), ENT_QUOTES | ENT_HTML5 );
+
+               return $ret;
+       }
+
+       /**
+        * Format a Message object for raw format
+        * @param MessageSpecifier $msg
+        * @return array
+        */
+       private function formatRawMessage( MessageSpecifier $msg ) {
+               $ret = [
+                       'key' => $msg->getKey(),
+                       'params' => $msg->getParams(),
+               ];
+               ApiResult::setIndexedTagName( $ret['params'], 'param' );
+
+               // Transform Messages as parameters in the style of Message::fooParam().
+               foreach ( $ret['params'] as $i => $param ) {
+                       if ( $param instanceof MessageSpecifier ) {
+                               $ret['params'][$i] = [ 'message' => $this->formatRawMessage( $param ) ];
+                       }
+               }
+               return $ret;
+       }
+
+       /**
+        * Format a message as an array
+        * @since 1.29
         * @param ApiMessage|ApiRawMessage $msg
+        * @param string|null $format
+        * @return array
         */
-       protected function addWarningOrError( $tag, $moduleName, $msg ) {
+       protected function formatMessageInternal( $msg, $format ) {
                $value = [ 'code' => $msg->getApiCode() ];
-               switch ( $this->format ) {
+               switch ( $format ) {
+                       case 'plaintext':
+                               $value += [
+                                       'text' => self::stripMarkup( $msg->text() ),
+                                       ApiResult::META_CONTENT => 'text',
+                               ];
+                               break;
+
                        case 'wikitext':
                                $value += [
                                        'text' => $msg->text(),
@@ -201,19 +250,34 @@ class ApiErrorFormatter {
                                break;
 
                        case 'raw':
-                               $value += [
-                                       'key' => $msg->getKey(),
-                                       'params' => $msg->getParams(),
-                               ];
-                               ApiResult::setIndexedTagName( $value['params'], 'param' );
+                               $value += $this->formatRawMessage( $msg );
                                break;
 
                        case 'none':
                                break;
                }
-               $value += $msg->getApiData();
+               $data = $msg->getApiData();
+               if ( $data ) {
+                       $value['data'] = $msg->getApiData() + [
+                               ApiResult::META_TYPE => 'assoc',
+                       ];
+               }
+               return $value;
+       }
 
-               $path = [ $tag . 's', $moduleName ];
+       /**
+        * Actually add the warning or error to the result
+        * @param string $tag 'warning' or 'error'
+        * @param string|null $modulePath
+        * @param ApiMessage|ApiRawMessage $msg
+        */
+       protected function addWarningOrError( $tag, $modulePath, $msg ) {
+               $value = $this->formatMessageInternal( $msg, $this->format );
+               if ( $modulePath !== null ) {
+                       $value += [ 'module' => $modulePath ];
+               }
+
+               $path = [ $tag . 's' ];
                $existing = $this->result->getResultData( $path );
                if ( $existing === null || !in_array( $value, $existing ) ) {
                        $flags = ApiResult::NO_SIZE_CHECK;
@@ -243,19 +307,19 @@ class ApiErrorFormatter_BackCompat extends ApiErrorFormatter {
                parent::__construct( $result, Language::factory( 'en' ), 'none', false );
        }
 
-       public function arrayFromStatus( Status $status, $type = 'error', $format = null ) {
-               if ( $status->isGood() || !$status->errors ) {
+       public function arrayFromStatus( StatusValue $status, $type = 'error', $format = null ) {
+               if ( $status->isGood() || !$status->getErrors() ) {
                        return [];
                }
 
                $result = [];
                foreach ( $status->getErrorsByType( $type ) as $error ) {
-                       if ( $error['message'] instanceof Message ) {
-                               $error = [
-                                       'message' => $error['message']->getKey(),
-                                       'params' => $error['message']->getParams(),
-                               ] + $error;
-                       }
+                       $msg = ApiMessage::create( $error );
+                       $error = [
+                               'message' => $msg->getKey(),
+                               'params' => $msg->getParams(),
+                               'code' => $msg->getApiCode(),
+                       ] + $error;
                        ApiResult::setIndexedTagName( $error['params'], 'param' );
                        $result[] = $error;
                }
@@ -264,24 +328,32 @@ class ApiErrorFormatter_BackCompat extends ApiErrorFormatter {
                return $result;
        }
 
-       protected function addWarningOrError( $tag, $moduleName, $msg ) {
-               $value = $msg->plain();
+       protected function formatMessageInternal( $msg, $format ) {
+               return [
+                       'code' => $msg->getApiCode(),
+                       'info' => $msg->text(),
+               ] + $msg->getApiData();
+       }
+
+       protected function addWarningOrError( $tag, $modulePath, $msg ) {
+               $value = self::stripMarkup( $msg->text() );
 
                if ( $tag === 'error' ) {
                        // In BC mode, only one error
-                       $code = $msg->getApiCode();
-                       if ( isset( ApiBase::$messageMap[$code] ) ) {
-                               // Backwards compatibility
-                               $code = ApiBase::$messageMap[$code]['code'];
-                       }
-
                        $value = [
-                               'code' => $code,
+                               'code' => $msg->getApiCode(),
                                'info' => $value,
                        ] + $msg->getApiData();
                        $this->result->addValue( null, 'error', $value,
                                ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
                } else {
+                       if ( $modulePath === null ) {
+                               $moduleName = 'unknown';
+                       } else {
+                               $i = strrpos( $modulePath, '+' );
+                               $moduleName = $i === false ? $modulePath : substr( $modulePath, $i + 1 );
+                       }
+
                        // Don't add duplicate warnings
                        $tag .= 's';
                        $path = [ $tag, $moduleName ];
index 10fb182..6f7cf65 100644 (file)
@@ -42,11 +42,9 @@ class ApiExpandTemplates extends ApiBase {
                $this->requireMaxOneParameter( $params, 'prop', 'generatexml' );
 
                if ( $params['prop'] === null ) {
-                       $this->logFeatureUsage( 'action=expandtemplates&!prop' );
-                       $this->setWarning( 'Because no values have been specified for the prop parameter, a ' .
-                               'legacy format has been used for the output. This format is deprecated, and in ' .
-                               'the future, a default value will be set for the prop parameter, causing the new' .
-                               'format to always be used.' );
+                       $this->addDeprecation(
+                               'apiwarn-deprecation-expandtemplates-prop', 'action=expandtemplates&!prop'
+                       );
                        $prop = [];
                } else {
                        $prop = array_flip( $params['prop'] );
@@ -57,13 +55,13 @@ class ApiExpandTemplates extends ApiBase {
                if ( $revid !== null ) {
                        $rev = Revision::newFromId( $revid );
                        if ( !$rev ) {
-                               $this->dieUsage( "There is no revision ID $revid", 'missingrev' );
+                               $this->dieWithError( [ 'apierror-nosuchrevid', $revid ] );
                        }
                        $title_obj = $rev->getTitle();
                } else {
                        $title_obj = Title::newFromText( $params['title'] );
                        if ( !$title_obj || $title_obj->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
                        }
                }
 
@@ -161,9 +159,7 @@ class ApiExpandTemplates extends ApiBase {
                                }
                                if ( isset( $prop['modules'] ) &&
                                        !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
-                                       $this->setWarning( "Property 'modules' was set but not 'jsconfigvars' " .
-                                               "or 'encodedjsconfigvars'. Configuration variables are necessary " .
-                                               'for proper module usage.' );
+                                       $this->addWarning( 'apiwarn-moduleswithoutvars' );
                                }
                        }
                }
index c7dc303..97720c6 100644 (file)
@@ -43,16 +43,16 @@ class ApiFeedContributions extends ApiBase {
 
                $config = $this->getConfig();
                if ( !$config->get( 'Feed' ) ) {
-                       $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+                       $this->dieWithError( 'feed-unavailable' );
                }
 
                $feedClasses = $config->get( 'FeedClasses' );
                if ( !isset( $feedClasses[$params['feedformat']] ) ) {
-                       $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+                       $this->dieWithError( 'feed-invalid' );
                }
 
                if ( $params['showsizediff'] && $this->getConfig()->get( 'MiserMode' ) ) {
-                       $this->dieUsage( 'Size difference is disabled in Miser Mode', 'sizediffdisabled' );
+                       $this->dieWithError( 'apierror-sizediffdisabled' );
                }
 
                $msg = wfMessage( 'Contributions' )->inContentLanguage()->text();
index 813ac01..e0e50ed 100644 (file)
@@ -47,12 +47,12 @@ class ApiFeedRecentChanges extends ApiBase {
                $this->params = $this->extractRequestParams();
 
                if ( !$config->get( 'Feed' ) ) {
-                       $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+                       $this->dieWithError( 'feed-unavailable' );
                }
 
                $feedClasses = $config->get( 'FeedClasses' );
                if ( !isset( $feedClasses[$this->params['feedformat']] ) ) {
-                       $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+                       $this->dieWithError( 'feed-invalid' );
                }
 
                $this->getMain()->setCacheMode( 'public' );
@@ -98,7 +98,7 @@ class ApiFeedRecentChanges extends ApiBase {
                if ( $specialClass === 'SpecialRecentchangeslinked' ) {
                        $title = Title::newFromText( $this->params['target'] );
                        if ( !$title ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $this->params['target'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $this->params['target'] ) ] );
                        }
 
                        $feed = new ChangesFeed( $feedFormat, false );
index af5b1af..b9bb761 100644 (file)
@@ -56,11 +56,11 @@ class ApiFeedWatchlist extends ApiBase {
                        $params = $this->extractRequestParams();
 
                        if ( !$config->get( 'Feed' ) ) {
-                               $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+                               $this->dieWithError( 'feed-unavailable' );
                        }
 
                        if ( !isset( $feedClasses[$params['feedformat']] ) ) {
-                               $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+                               $this->dieWithError( 'feed-invalid' );
                        }
 
                        // limit to the number of hours going from now back
@@ -151,15 +151,26 @@ class ApiFeedWatchlist extends ApiBase {
                        $msg = wfMessage( 'watchlist' )->inContentLanguage()->escaped();
                        $feed = new $feedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
 
-                       if ( $e instanceof UsageException ) {
-                               $errorCode = $e->getCodeString();
+                       if ( $e instanceof ApiUsageException ) {
+                               foreach ( $e->getStatusValue()->getErrors() as $error ) {
+                                       $msg = ApiMessage::create( $error )
+                                               ->inLanguage( $this->getLanguage() );
+                                       $errorTitle = $this->msg( 'api-feed-error-title', $msg->getApiCode() );
+                                       $errorText = $msg->text();
+                                       $feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
+                               }
                        } else {
-                               // Something is seriously wrong
-                               $errorCode = 'internal_api_error';
+                               if ( $e instanceof UsageException ) {
+                                       $errorCode = $e->getCodeString();
+                               } else {
+                                       // Something is seriously wrong
+                                       $errorCode = 'internal_api_error';
+                               }
+                               $errorTitle = $this->msg( 'api-feed-error-title', $msg->getApiCode() );
+                               $errorText = $e->getMessage();
+                               $feedItems[] = new FeedItem( $errorTitle, $errorText, '', '', '' );
                        }
 
-                       $errorText = $e->getMessage();
-                       $feedItems[] = new FeedItem( "Error ($errorCode)", $errorText, '', '', '' );
                        ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
                }
        }
index 97464d6..736898e 100644 (file)
@@ -45,7 +45,7 @@ class ApiFileRevert extends ApiBase {
                $this->validateParameters();
 
                // Check whether we're allowed to revert this file
-               $this->checkPermissions( $this->getUser() );
+               $this->checkTitleUserPermissions( $this->file->getTitle(), [ 'edit', 'upload' ] );
 
                $sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
                $status = $this->file->upload(
@@ -70,23 +70,6 @@ class ApiFileRevert extends ApiBase {
                $this->getResult()->addValue( null, $this->getModuleName(), $result );
        }
 
-       /**
-        * Checks that the user has permissions to perform this revert.
-        * Dies with usage message on inadequate permissions.
-        * @param User $user The user to check.
-        */
-       protected function checkPermissions( $user ) {
-               $title = $this->file->getTitle();
-               $permissionErrors = array_merge(
-                       $title->getUserPermissionsErrors( 'edit', $user ),
-                       $title->getUserPermissionsErrors( 'upload', $user )
-               );
-
-               if ( $permissionErrors ) {
-                       $this->dieUsageMsg( $permissionErrors[0] );
-               }
-       }
-
        /**
         * Validate the user parameters and set $this->archiveName and $this->file.
         * Throws an error if validation fails
@@ -95,21 +78,23 @@ class ApiFileRevert extends ApiBase {
                // Validate the input title
                $title = Title::makeTitleSafe( NS_FILE, $this->params['filename'] );
                if ( is_null( $title ) ) {
-                       $this->dieUsageMsg( [ 'invalidtitle', $this->params['filename'] ] );
+                       $this->dieWithError(
+                               [ 'apierror-invalidtitle', wfEscapeWikiText( $this->params['filename'] ) ]
+                       );
                }
                $localRepo = RepoGroup::singleton()->getLocalRepo();
 
                // Check if the file really exists
                $this->file = $localRepo->newFile( $title );
                if ( !$this->file->exists() ) {
-                       $this->dieUsageMsg( 'notanarticle' );
+                       $this->dieWithError( 'apierror-missingtitle' );
                }
 
                // Check if the archivename is valid for this file
                $this->archiveName = $this->params['archivename'];
                $oldFile = $localRepo->newFromArchiveName( $title, $this->archiveName );
                if ( !$oldFile->exists() ) {
-                       $this->dieUsageMsg( 'filerevert-badversion' );
+                       $this->dieWithError( 'filerevert-badversion' );
                }
        }
 
index 2e917e1..8ebfe48 100644 (file)
@@ -84,8 +84,8 @@ class ApiFormatJson extends ApiFormatBase {
                                        break;
 
                                default:
-                                       $this->dieUsage( __METHOD__ .
-                                               ': Unknown value for \'formatversion\'', 'unknownformatversion' );
+                                       // Should have been caught during parameter validation
+                                       $this->dieDebug( __METHOD__, 'Unknown value for \'formatversion\'' );
                        }
                }
                $data = $this->getResult()->getResultData( null, $transform );
index fc25f47..a744f57 100644 (file)
@@ -55,7 +55,8 @@ class ApiFormatPhp extends ApiFormatBase {
                                break;
 
                        default:
-                               $this->dieUsage( __METHOD__ . ': Unknown value for \'formatversion\'', 'unknownformatversion' );
+                               // Should have been caught during parameter validation
+                               $this->dieDebug( __METHOD__, 'Unknown value for \'formatversion\'' );
                }
                $text = serialize( $this->getResult()->getResultData( null, $transforms ) );
 
@@ -67,11 +68,7 @@ class ApiFormatPhp extends ApiFormatBase {
                        in_array( 'wfOutputHandler', ob_list_handlers(), true ) &&
                        preg_match( '/\<\s*cross-domain-policy(?=\s|\>)/i', $text )
                ) {
-                       $this->dieUsage(
-                               'This response cannot be represented using format=php. ' .
-                               'See https://phabricator.wikimedia.org/T68776',
-                               'internalerror'
-                       );
+                       $this->dieWithError( 'apierror-formatphp', 'internalerror' );
                }
 
                $this->printText( $text );
index 9da040c..228b47e 100644 (file)
@@ -49,7 +49,7 @@ class ApiFormatRaw extends ApiFormatBase {
        public function getMimeType() {
                $data = $this->getResult()->getResultData();
 
-               if ( isset( $data['error'] ) ) {
+               if ( isset( $data['error'] ) || isset( $data['errors'] ) ) {
                        return $this->errorFallback->getMimeType();
                }
 
@@ -62,7 +62,7 @@ class ApiFormatRaw extends ApiFormatBase {
 
        public function initPrinter( $unused = false ) {
                $data = $this->getResult()->getResultData();
-               if ( isset( $data['error'] ) ) {
+               if ( isset( $data['error'] ) || isset( $data['errors'] ) ) {
                        $this->errorFallback->initPrinter( $unused );
                        if ( $this->mFailWithHTTPError ) {
                                $this->getMain()->getRequest()->response()->statusHeader( 400 );
@@ -74,7 +74,7 @@ class ApiFormatRaw extends ApiFormatBase {
 
        public function closePrinter() {
                $data = $this->getResult()->getResultData();
-               if ( isset( $data['error'] ) ) {
+               if ( isset( $data['error'] ) || isset( $data['errors'] ) ) {
                        $this->errorFallback->closePrinter();
                } else {
                        parent::closePrinter();
@@ -83,7 +83,7 @@ class ApiFormatRaw extends ApiFormatBase {
 
        public function execute() {
                $data = $this->getResult()->getResultData();
-               if ( isset( $data['error'] ) ) {
+               if ( isset( $data['error'] ) || isset( $data['errors'] ) ) {
                        $this->errorFallback->execute();
                        return;
                }
index a45dbeb..e4dfda0 100644 (file)
@@ -269,17 +269,17 @@ class ApiFormatXml extends ApiFormatBase {
        protected function addXslt() {
                $nt = Title::newFromText( $this->mXslt );
                if ( is_null( $nt ) || !$nt->exists() ) {
-                       $this->setWarning( 'Invalid or non-existent stylesheet specified' );
+                       $this->addWarning( 'apiwarn-invalidxmlstylesheet' );
 
                        return;
                }
                if ( $nt->getNamespace() != NS_MEDIAWIKI ) {
-                       $this->setWarning( 'Stylesheet should be in the MediaWiki namespace.' );
+                       $this->addWarning( 'apiwarn-invalidxmlstylesheetns' );
 
                        return;
                }
                if ( substr( $nt->getText(), -4 ) !== '.xsl' ) {
-                       $this->setWarning( 'Stylesheet should have .xsl extension.' );
+                       $this->addWarning( 'apiwarn-invalidxmlstylesheetext' );
 
                        return;
                }
index 37cb80a..72fb16d 100644 (file)
@@ -56,23 +56,29 @@ class ApiImageRotate extends ApiBase {
                        $file = wfFindFile( $title, [ 'latest' => true ] );
                        if ( !$file ) {
                                $r['result'] = 'Failure';
-                               $r['errormessage'] = 'File does not exist';
+                               $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
+                                       Status::newFatal( 'apierror-filedoesnotexist' )
+                               );
                                $result[] = $r;
                                continue;
                        }
                        $handler = $file->getHandler();
                        if ( !$handler || !$handler->canRotate() ) {
                                $r['result'] = 'Failure';
-                               $r['errormessage'] = 'File type cannot be rotated';
+                               $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
+                                       Status::newFatal( 'apierror-filetypecannotberotated' )
+                               );
                                $result[] = $r;
                                continue;
                        }
 
                        // Check whether we're allowed to rotate this file
-                       $permError = $this->checkPermissions( $this->getUser(), $file->getTitle() );
-                       if ( $permError !== null ) {
+                       $permError = $this->checkTitleUserPermissions( $file->getTitle(), [ 'edit', 'upload' ] );
+                       if ( $permError ) {
                                $r['result'] = 'Failure';
-                               $r['errormessage'] = $permError;
+                               $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
+                                       $this->errorArrayToStatus( $permError )
+                               );
                                $result[] = $r;
                                continue;
                        }
@@ -80,7 +86,9 @@ class ApiImageRotate extends ApiBase {
                        $srcPath = $file->getLocalRefPath();
                        if ( $srcPath === false ) {
                                $r['result'] = 'Failure';
-                               $r['errormessage'] = 'Cannot get local file path';
+                               $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
+                                       Status::newFatal( 'apierror-filenopath' )
+                               );
                                $result[] = $r;
                                continue;
                        }
@@ -102,11 +110,13 @@ class ApiImageRotate extends ApiBase {
                                        $r['result'] = 'Success';
                                } else {
                                        $r['result'] = 'Failure';
-                                       $r['errormessage'] = $this->getErrorFormatter()->arrayFromStatus( $status );
+                                       $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
                                }
                        } else {
                                $r['result'] = 'Failure';
-                               $r['errormessage'] = $err->toText();
+                               $r['errors'] = $this->getErrorFormatter()->arrayFromStatus(
+                                       Status::newFatal( ApiMessage::create( $err->getMsg() ) )
+                               );
                        }
                        $result[] = $r;
                }
@@ -130,28 +140,6 @@ class ApiImageRotate extends ApiBase {
                return $this->mPageSet;
        }
 
-       /**
-        * Checks that the user has permissions to perform rotations.
-        * @param User $user The user to check
-        * @param Title $title
-        * @return string|null Permission error message, or null if there is no error
-        */
-       protected function checkPermissions( $user, $title ) {
-               $permissionErrors = array_merge(
-                       $title->getUserPermissionsErrors( 'edit', $user ),
-                       $title->getUserPermissionsErrors( 'upload', $user )
-               );
-
-               if ( $permissionErrors ) {
-                       // Just return the first error
-                       $msg = $this->parseMsg( $permissionErrors[0] );
-
-                       return $msg['info'];
-               }
-
-               return null;
-       }
-
        public function mustBePosted() {
                return true;
        }
index 10106ff..3f48f38 100644 (file)
@@ -42,10 +42,10 @@ class ApiImport extends ApiBase {
                $isUpload = false;
                if ( isset( $params['interwikisource'] ) ) {
                        if ( !$user->isAllowed( 'import' ) ) {
-                               $this->dieUsageMsg( 'cantimport' );
+                               $this->dieWithError( 'apierror-cantimport' );
                        }
                        if ( !isset( $params['interwikipage'] ) ) {
-                               $this->dieUsageMsg( [ 'missingparam', 'interwikipage' ] );
+                               $this->dieWithError( [ 'apierror-missingparam', 'interwikipage' ] );
                        }
                        $source = ImportStreamSource::newFromInterwiki(
                                $params['interwikisource'],
@@ -56,7 +56,7 @@ class ApiImport extends ApiBase {
                } else {
                        $isUpload = true;
                        if ( !$user->isAllowed( 'importupload' ) ) {
-                               $this->dieUsageMsg( 'cantimport-upload' );
+                               $this->dieWithError( 'apierror-cantimport-upload' );
                        }
                        $source = ImportStreamSource::newFromUpload( 'xml' );
                }
@@ -83,7 +83,7 @@ class ApiImport extends ApiBase {
                try {
                        $importer->doImport();
                } catch ( Exception $e ) {
-                       $this->dieUsageMsg( [ 'import-unknownerror', $e->getMessage() ] );
+                       $this->dieWithError( [ 'apierror-import-unknownerror', wfEscapeWikiText( $e->getMessage() ) ] );
                }
 
                $resultData = $reporter->getData();
index 1017607..9a21e76 100644 (file)
@@ -49,7 +49,7 @@ class ApiLinkAccount extends ApiBase {
 
        public function execute() {
                if ( !$this->getUser()->isLoggedIn() ) {
-                       $this->dieUsage( 'Must be logged in to link accounts', 'notloggedin' );
+                       $this->dieWithError( 'apierror-mustbeloggedin-linkaccounts', 'notloggedin' );
                }
 
                $params = $this->extractRequestParams();
@@ -60,8 +60,8 @@ class ApiLinkAccount extends ApiBase {
                        $bits = wfParseUrl( $params['returnurl'] );
                        if ( !$bits || $bits['scheme'] === '' ) {
                                $encParamName = $this->encodeParamName( 'returnurl' );
-                               $this->dieUsage(
-                                       "Invalid value '{$params['returnurl']}' for url parameter $encParamName",
+                               $this->dieWithError(
+                                       [ 'apierror-badurl', $encParamName, wfEscapeWikiText( $params['returnurl'] ) ],
                                        "badurl_{$encParamName}"
                                );
                        }
index 6ac261d..6cf1fad 100644 (file)
@@ -72,10 +72,11 @@ class ApiLogin extends ApiBase {
 
                try {
                        $this->requirePostedParameters( [ 'password', 'token' ] );
-               } catch ( UsageException $ex ) {
+               } catch ( ApiUsageException $ex ) {
                        // Make this a warning for now, upgrade to an error in 1.29.
-                       $this->setWarning( $ex->getMessage() );
-                       $this->logFeatureUsage( 'login-params-in-query-string' );
+                       foreach ( $ex->getStatusValue()->getErrors() as $error ) {
+                               $this->addDeprecation( $error, 'login-params-in-query-string' );
+                       }
                }
 
                $params = $this->extractRequestParams();
@@ -146,15 +147,10 @@ class ApiLogin extends ApiBase {
                        switch ( $res->status ) {
                                case AuthenticationResponse::PASS:
                                        if ( $this->getConfig()->get( 'EnableBotPasswords' ) ) {
-                                               $warn = 'Main-account login via action=login is deprecated and may stop working ' .
-                                                       'without warning.';
-                                               $warn .= ' To continue login with action=login, see [[Special:BotPasswords]].';
-                                               $warn .= ' To safely continue using main-account login, see action=clientlogin.';
+                                               $this->addDeprecation( 'apiwarn-deprecation-login-botpw', 'main-account-login' );
                                        } else {
-                                               $warn = 'Login via action=login is deprecated and may stop working without warning.';
-                                               $warn .= ' To safely log in, see action=clientlogin.';
+                                               $this->addDeprecation( 'apiwarn-deprecation-login-nobotpw', 'main-account-login' );
                                        }
-                                       $this->setWarning( $warn );
                                        $authRes = 'Success';
                                        $loginType = 'AuthManager';
                                        break;
@@ -194,16 +190,21 @@ class ApiLogin extends ApiBase {
 
                        case 'NeedToken':
                                $result['token'] = $token->toString();
-                               $this->setWarning( 'Fetching a token via action=login is deprecated. ' .
-                                  'Use action=query&meta=tokens&type=login instead.' );
-                               $this->logFeatureUsage( 'action=login&!lgtoken' );
+                               $this->addDeprecation( 'apiwarn-deprecation-login-token', 'action=login&!lgtoken' );
                                break;
 
                        case 'WrongToken':
                                break;
 
                        case 'Failed':
-                               $result['reason'] = $message->useDatabase( 'false' )->inLanguage( 'en' )->text();
+                               $errorFormatter = $this->getErrorFormatter();
+                               if ( $errorFormatter instanceof ApiErrorFormatter_BackCompat ) {
+                                       $result['reason'] = ApiErrorFormatter::stripMarkup(
+                                               $message->useDatabase( false )->inLanguage( 'en' )->text()
+                                       );
+                               } else {
+                                       $result['reason'] = $errorFormatter->formatMessage( $message );
+                               }
                                break;
 
                        case 'Aborted':
index 6a26e2e..d5c28f1 100644 (file)
@@ -45,9 +45,11 @@ class ApiLogout extends ApiBase {
 
                // Make sure it's possible to log out
                if ( !$session->canSetUser() ) {
-                       $this->dieUsage(
-                               'Cannot log out when using ' .
-                                       $session->getProvider()->describe( Language::factory( 'en' ) ),
+                       $this->dieWithError(
+                               [
+                                       'cannotlogoutnow-text',
+                                       $session->getProvider()->describe( $this->getErrorFormatter()->getLanguage() )
+                               ],
                                'cannotlogout'
                        );
                }
index 38299b4..fe6ed41 100644 (file)
@@ -46,6 +46,11 @@ class ApiMain extends ApiBase {
         */
        const API_DEFAULT_FORMAT = 'jsonfm';
 
+       /**
+        * When no uselang parameter is given, this language will be used
+        */
+       const API_DEFAULT_USELANG = 'user';
+
        /**
         * List of available modules: action name => module class
         */
@@ -140,7 +145,7 @@ class ApiMain extends ApiBase {
         */
        private $mPrinter;
 
-       private $mModuleMgr, $mResult, $mErrorFormatter;
+       private $mModuleMgr, $mResult, $mErrorFormatter = null;
        /** @var ApiContinuationManager|null */
        private $mContinuationManager;
        private $mAction;
@@ -229,7 +234,11 @@ class ApiMain extends ApiBase {
                        }
                }
 
-               $uselang = $this->getParameter( 'uselang' );
+               $this->mResult = new ApiResult( $this->getConfig()->get( 'APIMaxResultSize' ) );
+
+               // Setup uselang. This doesn't use $this->getParameter()
+               // because we're not ready to handle errors yet.
+               $uselang = $request->getVal( 'uselang', self::API_DEFAULT_USELANG );
                if ( $uselang === 'user' ) {
                        // Assume the parent context is going to return the user language
                        // for uselang=user (see T85635).
@@ -247,6 +256,29 @@ class ApiMain extends ApiBase {
                        }
                }
 
+               // Set up the error formatter. This doesn't use $this->getParameter()
+               // because we're not ready to handle errors yet.
+               $errorFormat = $request->getVal( 'errorformat', 'bc' );
+               $errorLangCode = $request->getVal( 'errorlang', 'uselang' );
+               $errorsUseDB = $request->getCheck( 'errorsuselocal' );
+               if ( in_array( $errorFormat, [ 'plaintext', 'wikitext', 'html', 'raw', 'none' ], true ) ) {
+                       if ( $errorLangCode === 'uselang' ) {
+                               $errorLang = $this->getLanguage();
+                       } elseif ( $errorLangCode === 'content' ) {
+                               global $wgContLang;
+                               $errorLang = $wgContLang;
+                       } else {
+                               $errorLangCode = RequestContext::sanitizeLangCode( $errorLangCode );
+                               $errorLang = Language::factory( $errorLangCode );
+                       }
+                       $this->mErrorFormatter = new ApiErrorFormatter(
+                               $this->mResult, $errorLang, $errorFormat, $errorsUseDB
+                       );
+               } else {
+                       $this->mErrorFormatter = new ApiErrorFormatter_BackCompat( $this->mResult );
+               }
+               $this->mResult->setErrorFormatter( $this->getErrorFormatter() );
+
                $this->mModuleMgr = new ApiModuleManager( $this );
                $this->mModuleMgr->addModules( self::$Modules, 'action' );
                $this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' );
@@ -255,9 +287,6 @@ class ApiMain extends ApiBase {
 
                Hooks::run( 'ApiMain::moduleManager', [ $this->mModuleMgr ] );
 
-               $this->mResult = new ApiResult( $this->getConfig()->get( 'APIMaxResultSize' ) );
-               $this->mErrorFormatter = new ApiErrorFormatter_BackCompat( $this->mResult );
-               $this->mResult->setErrorFormatter( $this->mErrorFormatter );
                $this->mContinuationManager = null;
                $this->mEnableWrite = $enableWrite;
 
@@ -464,7 +493,9 @@ class ApiMain extends ApiBase {
        public function createPrinterByName( $format ) {
                $printer = $this->mModuleMgr->getModule( $format, 'format' );
                if ( $printer === null ) {
-                       $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
+                       $this->dieWithError(
+                               [ 'apierror-unknownformat', wfEscapeWikiText( $format ) ], 'unknown_format'
+                       );
                }
 
                return $printer;
@@ -542,7 +573,7 @@ class ApiMain extends ApiBase {
         */
        protected function handleException( Exception $e ) {
                // Bug 63145: Rollback any open database transactions
-               if ( !( $e instanceof UsageException ) ) {
+               if ( !( $e instanceof ApiUsageException || $e instanceof UsageException ) ) {
                        // UsageExceptions are intentional, so don't rollback if that's the case
                        try {
                                MWExceptionHandler::rollbackMasterChangesAndLog( $e );
@@ -557,7 +588,7 @@ class ApiMain extends ApiBase {
                Hooks::run( 'ApiMain::onException', [ $this, $e ] );
 
                // Log it
-               if ( !( $e instanceof UsageException ) ) {
+               if ( !( $e instanceof ApiUsageException || $e instanceof UsageException ) ) {
                        MWExceptionHandler::logException( $e );
                }
 
@@ -565,13 +596,13 @@ class ApiMain extends ApiBase {
                // If this fails, an unhandled exception should be thrown so that global error
                // handler will process and log it.
 
-               $errCode = $this->substituteResultWithError( $e );
+               $errCodes = $this->substituteResultWithError( $e );
 
                // Error results should not be cached
                $this->setCacheMode( 'private' );
 
                $response = $this->getRequest()->response();
-               $headerStr = 'MediaWiki-API-Error: ' . $errCode;
+               $headerStr = 'MediaWiki-API-Error: ' . join( ', ', $errCodes );
                $response->header( $headerStr );
 
                // Reset and print just the error message
@@ -580,14 +611,31 @@ class ApiMain extends ApiBase {
                // Printer may not be initialized if the extractRequestParams() fails for the main module
                $this->createErrorPrinter();
 
+               $failed = false;
                try {
                        $this->printResult( $e->getCode() );
+               } catch ( ApiUsageException $ex ) {
+                       // The error printer itself is failing. Try suppressing its request
+                       // parameters and redo.
+                       $failed = true;
+                       $this->addWarning( 'apiwarn-errorprinterfailed' );
+                       foreach ( $ex->getStatusValue()->getErrors() as $error ) {
+                               try {
+                                       $this->mPrinter->addWarning( $error );
+                               } catch ( Exception $ex2 ) {
+                                       // WTF?
+                                       $this->addWarning( $error );
+                               }
+                       }
                } catch ( UsageException $ex ) {
                        // The error printer itself is failing. Try suppressing its request
                        // parameters and redo.
-                       $this->setWarning(
-                               'Error printer failed (will retry without params): ' . $ex->getMessage()
+                       $failed = true;
+                       $this->addWarning(
+                               [ 'apiwarn-errorprinterfailed-ex', $ex->getMessage() ], 'errorprinterfailed'
                        );
+               }
+               if ( $failed ) {
                        $this->mPrinter = null;
                        $this->createErrorPrinter();
                        $this->mPrinter->forceDefaultParams();
@@ -958,99 +1006,145 @@ class ApiMain extends ApiBase {
        /**
         * Create an error message for the given exception.
         *
-        * If the exception is a UsageException then
-        * UsageException::getMessageArray() will be called to create the message.
+        * If an ApiUsageException, errors/warnings will be extracted from the
+        * embedded StatusValue.
+        *
+        * If a base UsageException, the getMessageArray() method will be used to
+        * extract the code and English message for a single error (no warnings).
+        *
+        * Any other exception will be returned with a generic code and wrapper
+        * text around the exception's (presumably English) message as a single
+        * error (no warnings).
         *
         * @param Exception $e
-        * @return array ['code' => 'some string', 'info' => 'some other string']
+        * @param string $type 'error' or 'warning'
+        * @return ApiMessage[]
         * @since 1.27
         */
-       protected function errorMessageFromException( $e ) {
-               if ( $e instanceof UsageException ) {
+       protected function errorMessagesFromException( $e, $type = 'error' ) {
+               $messages = [];
+               if ( $e instanceof ApiUsageException ) {
+                       foreach ( $e->getStatusValue()->getErrorsByType( $type ) as $error ) {
+                               $messages[] = ApiMessage::create( $error );
+                       }
+               } elseif ( $type !== 'error' ) {
+                       // None of the rest have any messages for non-error types
+               } elseif ( $e instanceof UsageException ) {
                        // User entered incorrect parameters - generate error response
-                       $errMessage = $e->getMessageArray();
+                       $data = $e->getMessageArray();
+                       $code = $data['code'];
+                       $info = $data['info'];
+                       unset( $data['code'], $data['info'] );
+                       $messages[] = new ApiRawMessage( [ '$1', $info ], $code, $data );
                } else {
-                       $config = $this->getConfig();
                        // Something is seriously wrong
+                       $config = $this->getConfig();
+                       $code = 'internal_api_error_' . get_class( $e );
                        if ( ( $e instanceof DBQueryError ) && !$config->get( 'ShowSQLErrors' ) ) {
-                               $info = 'Database query error';
+                               $params = [ 'apierror-databaseerror', WebRequest::getRequestId() ];
                        } else {
-                               $info = "Exception Caught: {$e->getMessage()}";
+                               $params = [
+                                       'apierror-exceptioncaught',
+                                       WebRequest::getRequestId(),
+                                       wfEscapeWikiText( $e->getMessage() )
+                               ];
                        }
-
-                       $errMessage = [
-                               'code' => 'internal_api_error_' . get_class( $e ),
-                               'info' => '[' . WebRequest::getRequestId() . '] ' . $info,
-                       ];
+                       $messages[] = ApiMessage::create( $params, $code );
                }
-               return $errMessage;
+               return $messages;
        }
 
        /**
         * Replace the result data with the information about an exception.
-        * Returns the error code
         * @param Exception $e
-        * @return string
+        * @return string[] Error codes
         */
        protected function substituteResultWithError( $e ) {
                $result = $this->getResult();
+               $formatter = $this->getErrorFormatter();
                $config = $this->getConfig();
+               $errorCodes = [];
 
-               $errMessage = $this->errorMessageFromException( $e );
-               if ( $e instanceof UsageException ) {
-                       // User entered incorrect parameters - generate error response
+               // Remember existing warnings and errors across the reset
+               $errors = $result->getResultData( [ 'errors' ] );
+               $warnings = $result->getResultData( [ 'warnings' ] );
+               $result->reset();
+               if ( $warnings !== null ) {
+                       $result->addValue( null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK );
+               }
+               if ( $errors !== null ) {
+                       $result->addValue( null, 'errors', $errors, ApiResult::NO_SIZE_CHECK );
+
+                       // Collect the copied error codes for the return value
+                       foreach ( $errors as $error ) {
+                               if ( isset( $error['code'] ) ) {
+                                       $errorCodes[$error['code']] = true;
+                               }
+                       }
+               }
+
+               // Add errors from the exception
+               $modulePath = $e instanceof ApiUsageException ? $e->getModulePath() : null;
+               foreach ( $this->errorMessagesFromException( $e, 'error' ) as $msg ) {
+                       $errorCodes[$msg->getApiCode()] = true;
+                       $formatter->addError( $modulePath, $msg );
+               }
+               foreach ( $this->errorMessagesFromException( $e, 'warning' ) as $msg ) {
+                       $formatter->addWarning( $modulePath, $msg );
+               }
+
+               // Add additional data. Path depends on whether we're in BC mode or not.
+               // Data depends on the type of exception.
+               if ( $formatter instanceof ApiErrorFormatter_BackCompat ) {
+                       $path = [ 'error' ];
+               } else {
+                       $path = null;
+               }
+               if ( $e instanceof ApiUsageException || $e instanceof UsageException ) {
                        $link = wfExpandUrl( wfScript( 'api' ) );
-                       ApiResult::setContentValue( $errMessage, 'docref', "See $link for API usage" );
+                       $result->addContentValue(
+                               $path,
+                               'docref',
+                               $this->msg( 'api-usage-docref', $link )->inLanguage( $formatter->getLanguage() )->text()
+                       );
                } else {
-                       // Something is seriously wrong
                        if ( $config->get( 'ShowExceptionDetails' ) ) {
-                               ApiResult::setContentValue(
-                                       $errMessage,
+                               $result->addContentValue(
+                                       $path,
                                        'trace',
-                                       MWExceptionHandler::getRedactedTraceAsString( $e )
+                                       $this->msg( 'api-exception-trace',
+                                               get_class( $e ),
+                                               $e->getFile(),
+                                               $e->getLine(),
+                                               MWExceptionHandler::getRedactedTraceAsString( $e )
+                                       )->inLanguage( $formatter->getLanguage() )->text()
                                );
                        }
                }
 
-               // Remember all the warnings to re-add them later
-               $warnings = $result->getResultData( [ 'warnings' ] );
+               // Add the id and such
+               $this->addRequestedFields( [ 'servedby' ] );
 
-               $result->reset();
-               // Re-add the id
-               $requestid = $this->getParameter( 'requestid' );
-               if ( !is_null( $requestid ) ) {
-                       $result->addValue( null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK );
-               }
-               if ( $config->get( 'ShowHostnames' ) ) {
-                       // servedby is especially useful when debugging errors
-                       $result->addValue( null, 'servedby', wfHostname(), ApiResult::NO_SIZE_CHECK );
-               }
-               if ( $warnings !== null ) {
-                       $result->addValue( null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK );
-               }
-
-               $result->addValue( null, 'error', $errMessage, ApiResult::NO_SIZE_CHECK );
-
-               return $errMessage['code'];
+               return array_keys( $errorCodes );
        }
 
        /**
-        * Set up for the execution.
-        * @return array
+        * Add requested fields to the result
+        * @param string[] $force Which fields to force even if not requested. Accepted values are:
+        *  - servedby
         */
-       protected function setupExecuteAction() {
-               // First add the id to the top element
+       protected function addRequestedFields( $force = [] ) {
                $result = $this->getResult();
+
                $requestid = $this->getParameter( 'requestid' );
-               if ( !is_null( $requestid ) ) {
-                       $result->addValue( null, 'requestid', $requestid );
+               if ( $requestid !== null ) {
+                       $result->addValue( null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK );
                }
 
-               if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
-                       $servedby = $this->getParameter( 'servedby' );
-                       if ( $servedby ) {
-                               $result->addValue( null, 'servedby', wfHostname() );
-                       }
+               if ( $this->getConfig()->get( 'ShowHostnames' ) && (
+                       in_array( 'servedby', $force, true ) || $this->getParameter( 'servedby' )
+               ) ) {
+                       $result->addValue( null, 'servedby', wfHostname(), ApiResult::NO_SIZE_CHECK );
                }
 
                if ( $this->getParameter( 'curtimestamp' ) ) {
@@ -1058,13 +1152,23 @@ class ApiMain extends ApiBase {
                                ApiResult::NO_SIZE_CHECK );
                }
 
-               $params = $this->extractRequestParams();
+               if ( $this->getParameter( 'responselanginfo' ) ) {
+                       $result->addValue( null, 'uselang', $this->getLanguage()->getCode(),
+                               ApiResult::NO_SIZE_CHECK );
+                       $result->addValue( null, 'errorlang', $this->getErrorFormatter()->getLanguage()->getCode(),
+                               ApiResult::NO_SIZE_CHECK );
+               }
+       }
 
-               $this->mAction = $params['action'];
+       /**
+        * Set up for the execution.
+        * @return array
+        */
+       protected function setupExecuteAction() {
+               $this->addRequestedFields();
 
-               if ( !is_string( $this->mAction ) ) {
-                       $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
-               }
+               $params = $this->extractRequestParams();
+               $this->mAction = $params['action'];
 
                return $params;
        }
@@ -1073,13 +1177,15 @@ class ApiMain extends ApiBase {
         * Set up the module for response
         * @return ApiBase The module that will handle this action
         * @throws MWException
-        * @throws UsageException
+        * @throws ApiUsageException
         */
        protected function setupModule() {
                // Instantiate the module requested by the user
                $module = $this->mModuleMgr->getModule( $this->mAction, 'action' );
                if ( $module === null ) {
-                       $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
+                       $this->dieWithError(
+                               [ 'apierror-unknownaction', wfEscapeWikiText( $this->mAction ) ], 'unknown_action'
+                       );
                }
                $moduleParams = $module->extractRequestParams();
 
@@ -1098,13 +1204,13 @@ class ApiMain extends ApiBase {
                        }
 
                        if ( !isset( $moduleParams['token'] ) ) {
-                               $this->dieUsageMsg( [ 'missingparam', 'token' ] );
+                               $module->dieWithError( [ 'apierror-missingparam', 'token' ] );
                        }
 
                        $module->requirePostedParameters( [ 'token' ] );
 
                        if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) {
-                               $this->dieUsageMsg( 'sessionfailure' );
+                               $module->dieWithError( 'apierror-badtoken' );
                        }
                }
 
@@ -1128,10 +1234,10 @@ class ApiMain extends ApiBase {
                                $response->header( 'X-Database-Lag: ' . intval( $lag ) );
 
                                if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
-                                       $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
+                                       $this->dieWithError( [ 'apierror-maxlag', $lag, $host ] );
                                }
 
-                               $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
+                               $this->dieWithError( [ 'apierror-maxlag-generic', $lag ], 'maxlag' );
                        }
                }
 
@@ -1262,19 +1368,16 @@ class ApiMain extends ApiBase {
                if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) &&
                        !$user->isAllowed( 'read' )
                ) {
-                       $this->dieUsageMsg( 'readrequired' );
+                       $this->dieWithError( 'apierror-readapidenied' );
                }
 
                if ( $module->isWriteMode() ) {
                        if ( !$this->mEnableWrite ) {
-                               $this->dieUsageMsg( 'writedisabled' );
+                               $this->dieWithError( 'apierror-noapiwrite' );
                        } elseif ( !$user->isAllowed( 'writeapi' ) ) {
-                               $this->dieUsageMsg( 'writerequired' );
+                               $this->dieWithError( 'apierror-writeapidenied' );
                        } elseif ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) {
-                               $this->dieUsage(
-                                       'Promise-Non-Write-API-Action HTTP header cannot be sent to write API modules',
-                                       'promised-nonwrite-api'
-                               );
+                               $this->dieWithError( 'apierror-promised-nonwrite-api' );
                        }
 
                        $this->checkReadOnly( $module );
@@ -1283,7 +1386,7 @@ class ApiMain extends ApiBase {
                // Allow extensions to stop execution for arbitrary reasons.
                $message = false;
                if ( !Hooks::run( 'ApiCheckCanExecute', [ $module, $user, &$message ] ) ) {
-                       $this->dieUsageMsg( $message );
+                       $this->dieWithError( $message );
                }
        }
 
@@ -1329,12 +1432,9 @@ class ApiMain extends ApiBase {
                                "Api request failed as read only because the following DBs are lagged: $laggedServers"
                        );
 
-                       $parsed = $this->parseMsg( [ 'readonlytext' ] );
-                       $this->dieUsage(
-                               $parsed['info'],
-                               $parsed['code'],
-                               /* http error */
-                               0,
+                       $this->dieWithError(
+                               'readonly_lag',
+                               'readonly',
                                [ 'readonlyreason' => "Waiting for $numLagged lagged database(s)" ]
                        );
                }
@@ -1350,12 +1450,12 @@ class ApiMain extends ApiBase {
                        switch ( $params['assert'] ) {
                                case 'user':
                                        if ( $user->isAnon() ) {
-                                               $this->dieUsage( 'Assertion that the user is logged in failed', 'assertuserfailed' );
+                                               $this->dieWithError( 'apierror-assertuserfailed' );
                                        }
                                        break;
                                case 'bot':
                                        if ( !$user->isAllowed( 'bot' ) ) {
-                                               $this->dieUsage( 'Assertion that the user has the bot right failed', 'assertbotfailed' );
+                                               $this->dieWithError( 'apierror-assertbotfailed' );
                                        }
                                        break;
                        }
@@ -1363,9 +1463,8 @@ class ApiMain extends ApiBase {
                if ( isset( $params['assertuser'] ) ) {
                        $assertUser = User::newFromName( $params['assertuser'], false );
                        if ( !$assertUser || !$this->getUser()->equals( $assertUser ) ) {
-                               $this->dieUsage(
-                                       'Assertion that the user is "' . $params['assertuser'] . '" failed',
-                                       'assertnameduserfailed'
+                               $this->dieWithError(
+                                       [ 'apierror-assertnameduserfailed', wfEscapeWikiText( $params['assertuser'] ) ]
                                );
                        }
                }
@@ -1381,7 +1480,7 @@ class ApiMain extends ApiBase {
                if ( !$request->wasPosted() && $module->mustBePosted() ) {
                        // Module requires POST. GET request might still be allowed
                        // if $wgDebugApi is true, otherwise fail.
-                       $this->dieUsageMsgOrDebug( [ 'mustbeposted', $this->mAction ] );
+                       $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $this->mAction ] );
                }
 
                // See if custom printer is used
@@ -1396,8 +1495,7 @@ class ApiMain extends ApiBase {
                        ( $this->getUser()->isLoggedIn() &&
                                $this->getUser()->requiresHTTPS() )
                ) ) {
-                       $this->logFeatureUsage( 'https-expected' );
-                       $this->setWarning( 'HTTP used when HTTPS was expected' );
+                       $this->addDeprecation( 'apiwarn-deprecation-httpsexpected', 'https-expected' );
                }
        }
 
@@ -1481,7 +1579,9 @@ class ApiMain extends ApiBase {
                ];
 
                if ( $e ) {
-                       $logCtx['errorCodes'][] = $this->errorMessageFromException( $e )['code'];
+                       foreach ( $this->errorMessagesFromException( $e ) as $msg ) {
+                               $logCtx['errorCodes'][] = $msg->getApiCode();
+                       }
                }
 
                // Construct space separated message for 'api' log channel
@@ -1560,9 +1660,7 @@ class ApiMain extends ApiBase {
                        if ( $this->getRequest()->getArray( $name ) !== null ) {
                                // See bug 10262 for why we don't just implode( '|', ... ) the
                                // array.
-                               $this->setWarning(
-                                       "Parameter '$name' uses unsupported PHP array syntax"
-                               );
+                               $this->addWarning( [ 'apiwarn-unsupportedarray', $name ] );
                        }
                        $ret = $default;
                }
@@ -1602,8 +1700,7 @@ class ApiMain extends ApiBase {
 
                if ( !$this->mInternalMode ) {
                        // Printer has not yet executed; don't warn that its parameters are unused
-                       $printerParams = array_map(
-                               [ $this->mPrinter, 'encodeParamName' ],
+                       $printerParams = $this->mPrinter->encodeParamName(
                                array_keys( $this->mPrinter->getFinalParams() ?: [] )
                        );
                        $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams );
@@ -1612,8 +1709,11 @@ class ApiMain extends ApiBase {
                }
 
                if ( count( $unusedParams ) ) {
-                       $s = count( $unusedParams ) > 1 ? 's' : '';
-                       $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" );
+                       $this->addWarning( [
+                               'apierror-unrecognizedparams',
+                               Message::listParam( array_map( 'wfEscapeWikiText', $unusedParams ), 'comma' ),
+                               count( $unusedParams )
+                       ] );
                }
        }
 
@@ -1624,7 +1724,7 @@ class ApiMain extends ApiBase {
         */
        protected function printResult( $httpCode = 0 ) {
                if ( $this->getConfig()->get( 'DebugAPI' ) !== false ) {
-                       $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
+                       $this->addWarning( 'apiwarn-wgDebugAPI' );
                }
 
                $printer = $this->mPrinter;
@@ -1678,9 +1778,20 @@ class ApiMain extends ApiBase {
                        'requestid' => null,
                        'servedby' => false,
                        'curtimestamp' => false,
+                       'responselanginfo' => false,
                        'origin' => null,
                        'uselang' => [
-                               ApiBase::PARAM_DFLT => 'user',
+                               ApiBase::PARAM_DFLT => self::API_DEFAULT_USELANG,
+                       ],
+                       'errorformat' => [
+                               ApiBase::PARAM_TYPE => [ 'plaintext', 'wikitext', 'html', 'raw', 'none', 'bc' ],
+                               ApiBase::PARAM_DFLT => 'bc',
+                       ],
+                       'errorlang' => [
+                               ApiBase::PARAM_DFLT => 'uselang',
+                       ],
+                       'errorsuselocal' => [
+                               ApiBase::PARAM_DFLT => false,
                        ],
                ];
        }
@@ -1732,7 +1843,7 @@ class ApiMain extends ApiBase {
                        $help['permissions'] .= Html::rawElement( 'dd', null,
                                $this->msg( 'api-help-permissions-granted-to' )
                                        ->numParams( count( $groups ) )
-                                       ->params( $this->getLanguage()->commaList( $groups ) )
+                                       ->params( Message::listParam( $groups ) )
                                        ->parse()
                        );
                }
@@ -1831,70 +1942,6 @@ class ApiMain extends ApiBase {
        }
 }
 
-/**
- * This exception will be thrown when dieUsage is called to stop module execution.
- *
- * @ingroup API
- */
-class UsageException extends MWException {
-
-       private $mCodestr;
-
-       /**
-        * @var null|array
-        */
-       private $mExtraData;
-
-       /**
-        * @param string $message
-        * @param string $codestr
-        * @param int $code
-        * @param array|null $extradata
-        */
-       public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
-               parent::__construct( $message, $code );
-               $this->mCodestr = $codestr;
-               $this->mExtraData = $extradata;
-
-               // This should never happen, so throw an exception about it that will
-               // hopefully get logged with a backtrace (T138585)
-               if ( !is_string( $codestr ) || $codestr === '' ) {
-                       throw new InvalidArgumentException( 'Invalid $codestr, was ' .
-                               ( $codestr === '' ? 'empty string' : gettype( $codestr ) )
-                       );
-               }
-       }
-
-       /**
-        * @return string
-        */
-       public function getCodeString() {
-               return $this->mCodestr;
-       }
-
-       /**
-        * @return array
-        */
-       public function getMessageArray() {
-               $result = [
-                       'code' => $this->mCodestr,
-                       'info' => $this->getMessage()
-               ];
-               if ( is_array( $this->mExtraData ) ) {
-                       $result = array_merge( $result, $this->mExtraData );
-               }
-
-               return $result;
-       }
-
-       /**
-        * @return string
-        */
-       public function __toString() {
-               return "{$this->getCodeString()}: {$this->getMessage()}";
-       }
-}
-
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 617db22..3299f73 100644 (file)
@@ -32,11 +32,9 @@ class ApiManageTags extends ApiBase {
                if ( $params['operation'] !== 'delete'
                        && !$this->getUser()->isAllowed( 'managechangetags' )
                ) {
-                       $this->dieUsage( "You don't have permission to manage change tags",
-                               'permissiondenied' );
+                       $this->dieWithError( 'tags-manage-no-permission', 'permissiondenied' );
                } elseif ( !$this->getUser()->isAllowed( 'deletechangetags' ) ) {
-                       $this->dieUsage( "You don't have permission to delete change tags",
-                               'permissiondenied' );
+                       $this->dieWithError( 'tags-delete-no-permission', 'permissiondenied' );
                }
 
                $result = $this->getResult();
index 276f1c0..357698e 100644 (file)
@@ -42,24 +42,24 @@ class ApiMergeHistory extends ApiBase {
                if ( isset( $params['from'] ) ) {
                        $fromTitle = Title::newFromText( $params['from'] );
                        if ( !$fromTitle || $fromTitle->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['from'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
                        }
                } elseif ( isset( $params['fromid'] ) ) {
                        $fromTitle = Title::newFromID( $params['fromid'] );
                        if ( !$fromTitle ) {
-                               $this->dieUsageMsg( [ 'nosuchpageid', $params['fromid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
                        }
                }
 
                if ( isset( $params['to'] ) ) {
                        $toTitle = Title::newFromText( $params['to'] );
                        if ( !$toTitle || $toTitle->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['to'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
                        }
                } elseif ( isset( $params['toid'] ) ) {
                        $toTitle = Title::newFromID( $params['toid'] );
                        if ( !$toTitle ) {
-                               $this->dieUsageMsg( [ 'nosuchpageid', $params['toid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchpageid', $params['toid'] ] );
                        }
                }
 
index ae66778..9d69a77 100644 (file)
@@ -36,9 +36,10 @@ interface IApiMessage extends MessageSpecifier {
        /**
         * Returns a machine-readable code for use by the API
         *
-        * The message key is often sufficient, but sometimes there are multiple
-        * messages used for what is really the same underlying condition (e.g.
-        * badaccess-groups and badaccess-group0)
+        * If no code was specifically set, the message key is used as the code
+        * after removing "apiwarn-" or "apierror-" prefixes and applying
+        * backwards-compatibility mappings.
+        *
         * @return string
         */
        public function getApiCode();
@@ -51,7 +52,7 @@ interface IApiMessage extends MessageSpecifier {
 
        /**
         * Sets the machine-readable code for use by the API
-        * @param string|null $code If null, the message key should be returned by self::getApiCode()
+        * @param string|null $code If null, uses the default (see self::getApiCode())
         * @param array|null $data If non-null, passed to self::setApiData()
         */
        public function setApiCode( $code, array $data = null );
@@ -69,14 +70,95 @@ interface IApiMessage extends MessageSpecifier {
  * @ingroup API
  */
 trait ApiMessageTrait {
+
+       /**
+        * Compatibility code mappings for various MW messages.
+        * @todo Ideally anything relying on this should be changed to use ApiMessage.
+        */
+       protected static $messageMap = [
+               'actionthrottledtext' => 'ratelimited',
+               'autoblockedtext' => 'autoblocked',
+               'badaccess-group0' => 'permissiondenied',
+               'badaccess-groups' => 'permissiondenied',
+               'badipaddress' => 'invalidip',
+               'blankpage' => 'emptypage',
+               'blockedtext' => 'blocked',
+               'cannotdelete' => 'cantdelete',
+               'cannotundelete' => 'cantundelete',
+               'cantmove-titleprotected' => 'protectedtitle',
+               'cantrollback' => 'onlyauthor',
+               'confirmedittext' => 'confirmemail',
+               'content-not-allowed-here' => 'contentnotallowedhere',
+               'deleteprotected' => 'cantedit',
+               'delete-toobig' => 'bigdelete',
+               'edit-conflict' => 'editconflict',
+               'imagenocrossnamespace' => 'nonfilenamespace',
+               'imagetypemismatch' => 'filetypemismatch',
+               'importbadinterwiki' => 'badinterwiki',
+               'importcantopen' => 'cantopenfile',
+               'import-noarticle' => 'badinterwiki',
+               'importnofile' => 'nofile',
+               'importuploaderrorpartial' => 'partialupload',
+               'importuploaderrorsize' => 'filetoobig',
+               'importuploaderrortemp' => 'notempdir',
+               'ipb_already_blocked' => 'alreadyblocked',
+               'ipb_blocked_as_range' => 'blockedasrange',
+               'ipb_cant_unblock' => 'cantunblock',
+               'ipb_expiry_invalid' => 'invalidexpiry',
+               'ip_range_invalid' => 'invalidrange',
+               'mailnologin' => 'cantsend',
+               'markedaspatrollederror-noautopatrol' => 'noautopatrol',
+               'movenologintext' => 'cantmove-anon',
+               'movenotallowed' => 'cantmove',
+               'movenotallowedfile' => 'cantmovefile',
+               'namespaceprotected' => 'protectednamespace',
+               'nocreate-loggedin' => 'cantcreate',
+               'nocreatetext' => 'cantcreate-anon',
+               'noname' => 'invaliduser',
+               'nosuchusershort' => 'nosuchuser',
+               'notanarticle' => 'missingtitle',
+               'nouserspecified' => 'invaliduser',
+               'ns-specialprotected' => 'unsupportednamespace',
+               'protect-cantedit' => 'cantedit',
+               'protectedinterface' => 'protectednamespace-interface',
+               'protectedpagetext' => 'protectedpage',
+               'range_block_disabled' => 'rangedisabled',
+               'rcpatroldisabled' => 'patroldisabled',
+               'readonlytext' => 'readonly',
+               'sessionfailure' => 'badtoken',
+               'titleprotected' => 'protectedtitle',
+               'undo-failure' => 'undofailure',
+               'userrights-nodatabase' => 'nosuchdatabase',
+               'userrights-no-interwiki' => 'nointerwikiuserrights',
+       ];
+
        protected $apiCode = null;
        protected $apiData = [];
 
        public function getApiCode() {
-               return $this->apiCode === null ? $this->getKey() : $this->apiCode;
+               if ( $this->apiCode === null ) {
+                       $key = $this->getKey();
+                       if ( isset( self::$messageMap[$key] ) ) {
+                               $this->apiCode = self::$messageMap[$key];
+                       } elseif ( $key === 'apierror-missingparam' ) {
+                               /// @todo: Kill this case along with ApiBase::$messageMap
+                               $this->apiCode = 'no' . $this->getParams()[0];
+                       } elseif ( substr( $key, 0, 8 ) === 'apiwarn-' ) {
+                               $this->apiCode = substr( $key, 8 );
+                       } elseif ( substr( $key, 0, 9 ) === 'apierror-' ) {
+                               $this->apiCode = substr( $key, 9 );
+                       } else {
+                               $this->apiCode = $key;
+                       }
+               }
+               return $this->apiCode;
        }
 
        public function setApiCode( $code, array $data = null ) {
+               if ( $code !== null && !( is_string( $code ) && $code !== '' ) ) {
+                       throw new InvalidArgumentException( "Invalid code \"$code\"" );
+               }
+
                $this->apiCode = $code;
                if ( $data !== null ) {
                        $this->setApiData( $data );
@@ -124,9 +206,25 @@ class ApiMessage extends Message implements IApiMessage {
         * @param Message|RawMessage|array|string $msg
         * @param string|null $code
         * @param array|null $data
-        * @return ApiMessage
+        * @return IApiMessage
         */
        public static function create( $msg, $code = null, array $data = null ) {
+               if ( is_array( $msg ) ) {
+                       // From StatusValue
+                       if ( isset( $msg['message'] ) ) {
+                               if ( isset( $msg['params'] ) ) {
+                                       $msg = array_merge( [ $msg['message'] ], $msg['params'] );
+                               } else {
+                                       $msg = [ $msg['message'] ];
+                               }
+                       }
+
+                       // Weirdness that comes in sometimes, including the above
+                       if ( $msg[0] instanceof MessageSpecifier ) {
+                               $msg = $msg[0];
+                       }
+               }
+
                if ( $msg instanceof IApiMessage ) {
                        return $msg;
                } elseif ( $msg instanceof RawMessage ) {
@@ -143,7 +241,6 @@ class ApiMessage extends Message implements IApiMessage {
         *  - string: passed to Message::__construct
         * @param string|null $code
         * @param array|null $data
-        * @return ApiMessage
         */
        public function __construct( $msg, $code = null, array $data = null ) {
                if ( $msg instanceof Message ) {
@@ -158,8 +255,7 @@ class ApiMessage extends Message implements IApiMessage {
                } else {
                        parent::__construct( $msg );
                }
-               $this->apiCode = $code;
-               $this->apiData = (array)$data;
+               $this->setApiCode( $code, $data );
        }
 }
 
@@ -192,7 +288,6 @@ class ApiRawMessage extends RawMessage implements IApiMessage {
                } else {
                        parent::__construct( $msg );
                }
-               $this->apiCode = $code;
-               $this->apiData = (array)$data;
+               $this->setApiCode( $code, $data );
        }
 }
index 29e67b0..7c8aa90 100644 (file)
@@ -41,23 +41,23 @@ class ApiMove extends ApiBase {
                if ( isset( $params['from'] ) ) {
                        $fromTitle = Title::newFromText( $params['from'] );
                        if ( !$fromTitle || $fromTitle->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['from'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
                        }
                } elseif ( isset( $params['fromid'] ) ) {
                        $fromTitle = Title::newFromID( $params['fromid'] );
                        if ( !$fromTitle ) {
-                               $this->dieUsageMsg( [ 'nosuchpageid', $params['fromid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
                        }
                }
 
                if ( !$fromTitle->exists() ) {
-                       $this->dieUsageMsg( 'notanarticle' );
+                       $this->dieWithError( 'apierror-missingtitle' );
                }
                $fromTalk = $fromTitle->getTalkPage();
 
                $toTitle = Title::newFromText( $params['to'] );
                if ( !$toTitle || $toTitle->isExternal() ) {
-                       $this->dieUsageMsg( [ 'invalidtitle', $params['to'] ] );
+                       $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
                }
                $toTalk = $toTitle->getTalkPage();
 
@@ -66,15 +66,15 @@ class ApiMove extends ApiBase {
                        && wfFindFile( $toTitle )
                ) {
                        if ( !$params['ignorewarnings'] && $user->isAllowed( 'reupload-shared' ) ) {
-                               $this->dieUsageMsg( 'sharedfile-exists' );
+                               $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
                        } elseif ( !$user->isAllowed( 'reupload-shared' ) ) {
-                               $this->dieUsageMsg( 'cantoverwrite-sharedfile' );
+                               $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
                        }
                }
 
                // Rate limit
                if ( $user->pingLimiter( 'move' ) ) {
-                       $this->dieUsageMsg( 'actionthrottledtext' );
+                       $this->dieWithError( 'apierror-ratelimited' );
                }
 
                // Move the page
@@ -108,10 +108,8 @@ class ApiMove extends ApiBase {
                                $r['talkto'] = $toTalk->getPrefixedText();
                                $r['talkmoveoverredirect'] = $toTalkExists;
                        } else {
-                               // We're not gonna dieUsage() on failure, since we already changed something
-                               $error = $this->getErrorFromStatus( $status );
-                               $r['talkmove-error-code'] = $error[0];
-                               $r['talkmove-error-info'] = $error[1];
+                               // We're not going to dieWithError() on failure, since we already changed something
+                               $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
                        }
                }
 
@@ -184,7 +182,8 @@ class ApiMove extends ApiBase {
                $retval = [];
                $success = $fromTitle->moveSubpages( $toTitle, true, $reason, !$noredirect );
                if ( isset( $success[0] ) ) {
-                       return [ 'error' => $this->parseMsg( $success ) ];
+                       $status = $this->errorArrayToStatus( $success );
+                       return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $status ) ];
                }
 
                // At least some pages could be moved
@@ -192,7 +191,8 @@ class ApiMove extends ApiBase {
                foreach ( $success as $oldTitle => $newTitle ) {
                        $r = [ 'from' => $oldTitle ];
                        if ( is_array( $newTitle ) ) {
-                               $r['error'] = $this->parseMsg( reset( $newTitle ) );
+                               $status = $this->errorArrayToStatus( $newTitle );
+                               $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
                        } else {
                                // Success
                                $r['to'] = $newTitle;
index ace776c..e6fe27c 100644 (file)
@@ -391,14 +391,14 @@ class ApiOpenSearchFormatJson extends ApiFormatJson {
        }
 
        public function execute() {
-               if ( !$this->getResult()->getResultData( 'error' ) ) {
-                       $result = $this->getResult();
-
+               $result = $this->getResult();
+               if ( !$result->getResultData( 'error' ) && !$result->getResultData( 'errors' ) ) {
                        // Ignore warnings or treat as errors, as requested
                        $warnings = $result->removeValue( 'warnings', null );
                        if ( $this->warningsAsError && $warnings ) {
-                               $this->dieUsage(
-                                       'Warnings cannot be represented in OpenSearch JSON format', 'warnings', 0,
+                               $this->dieWithError(
+                                       'apierror-opensearch-json-warnings',
+                                       'warnings',
                                        [ 'warnings' => $warnings ]
                                );
                        }
index 8bfe447..466d186 100644 (file)
@@ -36,22 +36,26 @@ class ApiOptions extends ApiBase {
         */
        public function execute() {
                if ( $this->getUser()->isAnon() ) {
-                       $this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' );
-               } elseif ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) {
-                       $this->dieUsage( "You don't have permission to edit your options", 'permissiondenied' );
+                       $this->dieWithError(
+                               [ 'apierror-mustbeloggedin', $this->msg( 'action-editmyoptions' ) ], 'notloggedin'
+                       );
                }
 
+               $this->checkUserRightsAny( 'editmyoptions' );
+
                $params = $this->extractRequestParams();
                $changed = false;
 
                if ( isset( $params['optionvalue'] ) && !isset( $params['optionname'] ) ) {
-                       $this->dieUsageMsg( [ 'missingparam', 'optionname' ] );
+                       $this->dieWithError( [ 'apierror-missingparam', 'optionname' ] );
                }
 
                // Load the user from the master to reduce CAS errors on double post (T95839)
                $user = $this->getUser()->getInstanceForUpdate();
                if ( !$user ) {
-                       $this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' );
+                       $this->dieWithError(
+                               [ 'apierror-mustbeloggedin', $this->msg( 'action-editmyoptions' ) ], 'notloggedin'
+                       );
                }
 
                if ( $params['reset'] ) {
@@ -71,7 +75,7 @@ class ApiOptions extends ApiBase {
                        $changes[$params['optionname']] = $newValue;
                }
                if ( !$changed && !count( $changes ) ) {
-                       $this->dieUsage( 'No changes were requested', 'nochanges' );
+                       $this->dieWithError( 'apierror-nochanges' );
                }
 
                $prefs = Preferences::getPreferences( $user, $this->getContext() );
@@ -98,26 +102,26 @@ class ApiOptions extends ApiBase {
                                case 'userjs':
                                        // Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
                                        if ( strlen( $key ) > 255 ) {
-                                               $validation = 'key too long (no more than 255 bytes allowed)';
+                                               $validation = $this->msg( 'apiwarn-validationfailed-keytoolong', Message::numParam( 255 ) );
                                        } elseif ( preg_match( '/[^a-zA-Z0-9_-]/', $key ) !== 0 ) {
-                                               $validation = 'invalid key (only a-z, A-Z, 0-9, _, - allowed)';
+                                               $validation = $this->msg( 'apiwarn-validationfailed-badchars' );
                                        } else {
                                                $validation = true;
                                        }
                                        break;
                                case 'special':
-                                       $validation = 'cannot be set by this module';
+                                       $validation = $this->msg( 'apiwarn-validationfailed-cannotset' );
                                        break;
                                case 'unused':
                                default:
-                                       $validation = 'not a valid preference';
+                                       $validation = $this->msg( 'apiwarn-validationfailed-badpref' );
                                        break;
                        }
                        if ( $validation === true ) {
                                $user->setOption( $key, $value );
                                $changed = true;
                        } else {
-                               $this->setWarning( "Validation error for '$key': $validation" );
+                               $this->addWarning( [ 'apiwarn-validationfailed', wfEscapeWikitext( $key ), $validation ] );
                        }
                }
 
index 1a509c5..4cf896f 100644 (file)
@@ -155,10 +155,10 @@ class ApiPageSet extends ApiBase {
                        }
                        $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
                        if ( $generator === null ) {
-                               $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' );
+                               $this->dieWithError( [ 'apierror-badgenerator-unknown', $generatorName ], 'badgenerator' );
                        }
                        if ( !$generator instanceof ApiQueryGeneratorBase ) {
-                               $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
+                               $this->dieWithError( [ 'apierror-badgenerator-notgenerator', $generatorName ], 'badgenerator' );
                        }
                        // Create a temporary pageset to store generator's output,
                        // add any additional fields generator may need, and execute pageset to populate titles/pageids
@@ -194,13 +194,27 @@ class ApiPageSet extends ApiBase {
                        }
                        if ( isset( $this->mParams['pageids'] ) ) {
                                if ( isset( $dataSource ) ) {
-                                       $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
+                                       $this->dieWithError(
+                                               [
+                                                       'apierror-invalidparammix-cannotusewith',
+                                                       $this->encodeParamName( 'pageids' ),
+                                                       $this->encodeParamName( $dataSource )
+                                               ],
+                                               'multisource'
+                                       );
                                }
                                $dataSource = 'pageids';
                        }
                        if ( isset( $this->mParams['revids'] ) ) {
                                if ( isset( $dataSource ) ) {
-                                       $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
+                                       $this->dieWithError(
+                                               [
+                                                       'apierror-invalidparammix-cannotusewith',
+                                                       $this->encodeParamName( 'revids' ),
+                                                       $this->encodeParamName( $dataSource )
+                                               ],
+                                               'multisource'
+                                       );
                                }
                                $dataSource = 'revids';
                        }
@@ -216,9 +230,7 @@ class ApiPageSet extends ApiBase {
                                                break;
                                        case 'revids':
                                                if ( $this->mResolveRedirects ) {
-                                                       $this->setWarning( 'Redirect resolution cannot be used ' .
-                                                               'together with the revids= parameter. Any redirects ' .
-                                                               'the revids= point to have not been resolved.' );
+                                                       $this->addWarning( 'apiwarn-redirectsandrevids' );
                                                }
                                                $this->mResolveRedirects = false;
                                                $this->initFromRevIDs( $this->mParams['revids'] );
@@ -1412,13 +1424,7 @@ class ApiPageSet extends ApiBase {
                                ApiBase::PARAM_DFLT => false,
                                ApiBase::PARAM_HELP_MSG => [
                                        'api-pageset-param-converttitles',
-                                       new DeferredStringifier(
-                                               function ( IContextSource $context ) {
-                                                       return $context->getLanguage()
-                                                               ->commaList( LanguageConverter::$languagesWithVariants );
-                                               },
-                                               $this
-                                       )
+                                       [ Message::listParam( LanguageConverter::$languagesWithVariants, 'text' ) ],
                                ],
                        ],
                ];
index ffc3fc2..a9b3dde 100644 (file)
@@ -66,14 +66,17 @@ class ApiParamInfo extends ApiBase {
                                if ( $submodules ) {
                                        try {
                                                $module = $this->getModuleFromPath( $path );
-                                       } catch ( UsageException $ex ) {
-                                               $this->setWarning( $ex->getMessage() );
+                                       } catch ( ApiUsageException $ex ) {
+                                               foreach ( $ex->getStatusValue()->getErrors() as $error ) {
+                                                       $this->addWarning( $error );
+                                               }
+                                               continue;
                                        }
                                        $submodules = $this->listAllSubmodules( $module, $recursive );
                                        if ( $submodules ) {
                                                $modules = array_merge( $modules, $submodules );
                                        } else {
-                                               $this->setWarning( "Module $path has no submodules" );
+                                               $this->addWarning( [ 'apierror-badmodule-nosubmodules', $path ], 'badmodule' );
                                        }
                                } else {
                                        $modules[] = $path;
@@ -108,8 +111,10 @@ class ApiParamInfo extends ApiBase {
                foreach ( $modules as $m ) {
                        try {
                                $module = $this->getModuleFromPath( $m );
-                       } catch ( UsageException $ex ) {
-                               $this->setWarning( $ex->getMessage() );
+                       } catch ( ApiUsageException $ex ) {
+                               foreach ( $ex->getStatusValue()->getErrors() as $error ) {
+                                       $this->addWarning( $error );
+                               }
                                continue;
                        }
                        $key = 'modules';
index 0cad5de..2263b8f 100644 (file)
@@ -36,18 +36,18 @@ class ApiParse extends ApiBase {
        /** @var Content $pstContent */
        private $pstContent = null;
 
-       private function checkReadPermissions( Title $title ) {
-               if ( !$title->userCan( 'read', $this->getUser() ) ) {
-                       $this->dieUsage( "You don't have permission to view this page", 'permissiondenied' );
-               }
-       }
-
        public function execute() {
                // The data is hot but user-dependent, like page views, so we set vary cookies
                $this->getMain()->setCacheMode( 'anon-public-user-private' );
 
                // Get parameters
                $params = $this->extractRequestParams();
+
+               // No easy way to say that text & title are allowed together while the
+               // rest aren't, so just do it in two calls.
+               $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
+               $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
+
                $text = $params['text'];
                $title = $params['title'];
                if ( $title === null ) {
@@ -65,21 +65,12 @@ class ApiParse extends ApiBase {
                $model = $params['contentmodel'];
                $format = $params['contentformat'];
 
-               if ( !is_null( $page ) && ( !is_null( $text ) || $titleProvided ) ) {
-                       $this->dieUsage(
-                               'The page parameter cannot be used together with the text and title parameters',
-                               'params'
-                       );
-               }
-
                $prop = array_flip( $params['prop'] );
 
                if ( isset( $params['section'] ) ) {
                        $this->section = $params['section'];
                        if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
-                               $this->dieUsage(
-                                       'The section parameter must be a valid section id or "new"', 'invalidsection'
-                               );
+                               $this->dieWithError( 'apierror-invalidsection' );
                        }
                } else {
                        $this->section = false;
@@ -97,21 +88,20 @@ class ApiParse extends ApiBase {
 
                if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
                        if ( $this->section === 'new' ) {
-                                       $this->dieUsage(
-                                               'section=new cannot be combined with oldid, pageid or page parameters. ' .
-                                               'Please use text', 'params'
-                                       );
+                               $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' );
                        }
                        if ( !is_null( $oldid ) ) {
                                // Don't use the parser cache
                                $rev = Revision::newFromId( $oldid );
                                if ( !$rev ) {
-                                       $this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
+                                       $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] );
                                }
 
-                               $this->checkReadPermissions( $rev->getTitle() );
+                               $this->checkTitleUserPermissions( $rev->getTitle(), 'read' );
                                if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
-                                       $this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
+                                       $this->dieWithError(
+                                               [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ]
+                                       );
                                }
 
                                $titleObj = $rev->getTitle();
@@ -131,7 +121,9 @@ class ApiParse extends ApiBase {
                                        $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
 
                                        if ( $this->section !== false ) {
-                                               $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
+                                               $this->content = $this->getSectionContent(
+                                                       $this->content, $this->msg( 'revid', $rev->getId() )
+                                               );
                                        }
 
                                        // Should we save old revision parses to the parser cache?
@@ -167,10 +159,10 @@ class ApiParse extends ApiBase {
                                $pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
                                $titleObj = $pageObj->getTitle();
                                if ( !$titleObj || !$titleObj->exists() ) {
-                                       $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
+                                       $this->dieWithError( 'apierror-missingtitle' );
                                }
 
-                               $this->checkReadPermissions( $titleObj );
+                               $this->checkTitleUserPermissions( $titleObj, 'read' );
                                $wgTitle = $titleObj;
 
                                if ( isset( $prop['revid'] ) ) {
@@ -201,7 +193,7 @@ class ApiParse extends ApiBase {
                } else { // Not $oldid, $pageid, $page. Hence based on $text
                        $titleObj = Title::newFromText( $title );
                        if ( !$titleObj || $titleObj->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $title ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
                        }
                        $wgTitle = $titleObj;
                        if ( $titleObj->canExist() ) {
@@ -217,10 +209,7 @@ class ApiParse extends ApiBase {
 
                        if ( !$textProvided ) {
                                if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
-                                       $this->setWarning(
-                                               "'title' used without 'text', and parsed page properties were requested " .
-                                               "(did you mean to use 'page' instead of 'title'?)"
-                                       );
+                                       $this->addWarning( 'apiwarn-parse-titlewithouttext' );
                                }
                                // Prevent warning from ContentHandler::makeContent()
                                $text = '';
@@ -230,13 +219,17 @@ class ApiParse extends ApiBase {
                        // API title, but default to wikitext to keep BC.
                        if ( $textProvided && !$titleProvided && is_null( $model ) ) {
                                $model = CONTENT_MODEL_WIKITEXT;
-                               $this->setWarning( "No 'title' or 'contentmodel' was given, assuming $model." );
+                               $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] );
                        }
 
                        try {
                                $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
                        } catch ( MWContentSerializationException $ex ) {
-                               $this->dieUsage( $ex->getMessage(), 'parseerror' );
+                               // @todo: Internationalize MWContentSerializationException
+                               $this->dieWithError(
+                                       [ 'apierror-contentserializationexception', wfEscapeWikiText( $ex->getMessage() ) ],
+                                       'parseerror'
+                               );
                        }
 
                        if ( $this->section !== false ) {
@@ -357,10 +350,7 @@ class ApiParse extends ApiBase {
 
                if ( isset( $prop['headitems'] ) ) {
                        $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
-                       $this->logFeatureUsage( 'action=parse&prop=headitems' );
-                       $this->setWarning( 'headitems is deprecated since MediaWiki 1.28. '
-                               . 'Use prop=headhtml when creating new HTML documents, or '
-                               . 'prop=modules|jsconfigvars when updating a document client-side.' );
+                       $this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
                }
 
                if ( isset( $prop['headhtml'] ) ) {
@@ -397,9 +387,7 @@ class ApiParse extends ApiBase {
 
                if ( isset( $prop['modules'] ) &&
                        !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
-                       $this->setWarning( 'Property "modules" was set but not "jsconfigvars" ' .
-                               'or "encodedjsconfigvars". Configuration variables are necessary ' .
-                               'for proper module usage.' );
+                       $this->addWarning( 'apiwarn-moduleswithoutvars' );
                }
 
                if ( isset( $prop['indicators'] ) ) {
@@ -435,7 +423,7 @@ class ApiParse extends ApiBase {
 
                if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
                        if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
-                               $this->dieUsage( 'parsetree is only supported for wikitext content', 'notwikitext' );
+                               $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
                        }
 
                        $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
@@ -516,7 +504,7 @@ class ApiParse extends ApiBase {
                // getParserOutput will save to Parser cache if able
                $pout = $page->getParserOutput( $popts );
                if ( !$pout ) {
-                       $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
+                       $this->dieWithError( [ 'apierror-nosuchrevid', $page->getLatest() ] );
                }
                if ( $getWikitext ) {
                        $this->content = $page->getContent( Revision::RAW );
@@ -538,7 +526,9 @@ class ApiParse extends ApiBase {
                if ( $this->section !== false && $content !== null ) {
                        $content = $this->getSectionContent(
                                $content,
-                               !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText()
+                               !is_null( $pageId )
+                                       ? $this->msg( 'pageid', $pageId )
+                                       : $page->getTitle()->getPrefixedText()
                        );
                }
                return $content;
@@ -548,17 +538,17 @@ class ApiParse extends ApiBase {
         * Extract the requested section from the given Content
         *
         * @param Content $content
-        * @param string $what Identifies the content in error messages, e.g. page title.
+        * @param string|Message $what Identifies the content in error messages, e.g. page title.
         * @return Content|bool
         */
        private function getSectionContent( Content $content, $what ) {
                // Not cached (save or load)
                $section = $content->getSection( $this->section );
                if ( $section === false ) {
-                       $this->dieUsage( "There is no section {$this->section} in $what", 'nosuchsection' );
+                       $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
                }
                if ( $section === null ) {
-                       $this->dieUsage( "Sections are not supported by $what", 'nosuchsection' );
+                       $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
                        $section = false;
                }
 
index 6252882..c33542f 100644 (file)
@@ -40,19 +40,16 @@ class ApiPatrol extends ApiBase {
                if ( isset( $params['rcid'] ) ) {
                        $rc = RecentChange::newFromId( $params['rcid'] );
                        if ( !$rc ) {
-                               $this->dieUsageMsg( [ 'nosuchrcid', $params['rcid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchrcid', $params['rcid'] ] );
                        }
                } else {
                        $rev = Revision::newFromId( $params['revid'] );
                        if ( !$rev ) {
-                               $this->dieUsageMsg( [ 'nosuchrevid', $params['revid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchrevid', $params['revid'] ] );
                        }
                        $rc = $rev->getRecentChange();
                        if ( !$rc ) {
-                               $this->dieUsage(
-                                       'The revision ' . $params['revid'] . " can't be patrolled as it's too old",
-                                       'notpatrollable'
-                               );
+                               $this->dieWithError( [ 'apierror-notpatrollable', $params['revid'] ] );
                        }
                }
 
@@ -70,7 +67,7 @@ class ApiPatrol extends ApiBase {
                $retval = $rc->doMarkPatrolled( $user, false, $tags );
 
                if ( $retval ) {
-                       $this->dieUsageMsg( reset( $retval ) );
+                       $this->dieStatus( $this->errorArrayToStatus( $retval, $user ) );
                }
 
                $result = [ 'rcid' => intval( $rc->getAttribute( 'rc_id' ) ) ];
index d289060..746dc9a 100644 (file)
@@ -36,11 +36,7 @@ class ApiProtect extends ApiBase {
                $pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
                $titleObj = $pageObj->getTitle();
 
-               $errors = $titleObj->getUserPermissionsErrors( 'protect', $this->getUser() );
-               if ( $errors ) {
-                       // We don't care about multiple errors, just report one of them
-                       $this->dieUsageMsg( reset( $errors ) );
-               }
+               $this->checkTitleUserPermissions( $titleObj, 'protect' );
 
                $user = $this->getUser();
                $tags = $params['tags'];
@@ -58,8 +54,8 @@ class ApiProtect extends ApiBase {
                        if ( count( $expiry ) == 1 ) {
                                $expiry = array_fill( 0, count( $params['protections'] ), $expiry[0] );
                        } else {
-                               $this->dieUsageMsg( [
-                                       'toofewexpiries',
+                               $this->dieWithError( [
+                                       'apierror-toofewexpiries',
                                        count( $expiry ),
                                        count( $params['protections'] )
                                ] );
@@ -76,17 +72,17 @@ class ApiProtect extends ApiBase {
                        $protections[$p[0]] = ( $p[1] == 'all' ? '' : $p[1] );
 
                        if ( $titleObj->exists() && $p[0] == 'create' ) {
-                               $this->dieUsageMsg( 'create-titleexists' );
+                               $this->dieWithError( 'apierror-create-titleexists' );
                        }
                        if ( !$titleObj->exists() && $p[0] != 'create' ) {
-                               $this->dieUsageMsg( 'missingtitle-createonly' );
+                               $this->dieWithError( 'apierror-missingtitle-createonly' );
                        }
 
                        if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) {
-                               $this->dieUsageMsg( [ 'protect-invalidaction', $p[0] ] );
+                               $this->dieWithError( [ 'apierror-protect-invalidaction', wfEscapeWikiText( $p[0] ) ] );
                        }
                        if ( !in_array( $p[1], $this->getConfig()->get( 'RestrictionLevels' ) ) && $p[1] != 'all' ) {
-                               $this->dieUsageMsg( [ 'protect-invalidlevel', $p[1] ] );
+                               $this->dieWithError( [ 'apierror-protect-invalidlevel', wfEscapeWikiText( $p[1] ) ] );
                        }
 
                        if ( wfIsInfinity( $expiry[$i] ) ) {
@@ -94,12 +90,12 @@ class ApiProtect extends ApiBase {
                        } else {
                                $exp = strtotime( $expiry[$i] );
                                if ( $exp < 0 || !$exp ) {
-                                       $this->dieUsageMsg( [ 'invalidexpiry', $expiry[$i] ] );
+                                       $this->dieWithError( [ 'apierror-invalidexpiry', wfEscapeWikiText( $expiry[$i] ) ] );
                                }
 
                                $exp = wfTimestamp( TS_MW, $exp );
                                if ( $exp < wfTimestampNow() ) {
-                                       $this->dieUsageMsg( [ 'pastexpiry', $expiry[$i] ] );
+                                       $this->dieWithError( [ 'apierror-pastexpiry', wfEscapeWikiText( $expiry[$i] ) ] );
                                }
                                $expiryarray[$p[0]] = $exp;
                        }
index 8bbd88d..324d030 100644 (file)
@@ -39,8 +39,7 @@ class ApiPurge extends ApiBase {
        public function execute() {
                $main = $this->getMain();
                if ( !$main->isInternalMode() && !$main->getRequest()->wasPosted() ) {
-                       $this->logFeatureUsage( 'purge-via-GET' );
-                       $this->setWarning( 'Use of action=purge via GET is deprecated. Use POST instead.' );
+                       $this->addDeprecation( 'apiwarn-deprecation-purge-get', 'purge-via-GET' );
                }
 
                $params = $this->extractRequestParams();
@@ -69,8 +68,7 @@ class ApiPurge extends ApiBase {
                                $page->doPurge( $flags );
                                $r['purged'] = true;
                        } else {
-                               $error = $this->parseMsg( [ 'actionthrottledtext' ] );
-                               $this->setWarning( $error['info'] );
+                               $this->addWarning( 'apierror-ratelimited' );
                        }
 
                        if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
@@ -114,8 +112,7 @@ class ApiPurge extends ApiBase {
                                                }
                                        }
                                } else {
-                                       $error = $this->parseMsg( [ 'actionthrottledtext' ] );
-                                       $this->setWarning( $error['info'] );
+                                       $this->addWarning( 'apierror-ratelimited' );
                                        $forceLinkUpdate = false;
                                }
                        }
index 16bd725..8196cfa 100644 (file)
@@ -310,7 +310,7 @@ class ApiQuery extends ApiBase {
                                        ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
                                }
                                if ( !$wasPosted && $instance->mustBePosted() ) {
-                                       $this->dieUsageMsgOrDebug( [ 'mustbeposted', $moduleName ] );
+                                       $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
                                }
                                // Ignore duplicates. TODO 2.0: die()?
                                if ( !array_key_exists( $moduleName, $modules ) ) {
@@ -415,11 +415,7 @@ class ApiQuery extends ApiBase {
                }
 
                if ( !$fit ) {
-                       $this->dieUsage(
-                               'The value of $wgAPIMaxResultSize on this wiki is ' .
-                                       'too small to hold basic result information',
-                               'badconfig'
-                       );
+                       $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
                }
 
                if ( $this->mParams['export'] ) {
index 3073a95..b09b977 100644 (file)
@@ -41,15 +41,10 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
         * @return void
         */
        protected function run( ApiPageSet $resultPageSet = null ) {
-               $user = $this->getUser();
                // Before doing anything at all, let's check permissions
-               if ( !$user->isAllowed( 'deletedhistory' ) ) {
-                       $this->dieUsage(
-                               'You don\'t have permission to view deleted revision information',
-                               'permissiondenied'
-                       );
-               }
+               $this->checkUserRightsAny( 'deletedhistory' );
 
+               $user = $this->getUser();
                $db = $this->getDB();
                $params = $this->extractRequestParams( false );
 
@@ -75,16 +70,20 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        foreach ( [ 'from', 'to', 'prefix', 'excludeuser' ] as $param ) {
                                if ( !is_null( $params[$param] ) ) {
                                        $p = $this->getModulePrefix();
-                                       $this->dieUsage( "The '{$p}{$param}' parameter cannot be used with '{$p}user'",
-                                               'badparams' );
+                                       $this->dieWithError(
+                                               [ 'apierror-invalidparammix-cannotusewith', $p.$param, "{$p}user" ],
+                                               'invalidparammix'
+                                       );
                                }
                        }
                } else {
                        foreach ( [ 'start', 'end' ] as $param ) {
                                if ( !is_null( $params[$param] ) ) {
                                        $p = $this->getModulePrefix();
-                                       $this->dieUsage( "The '{$p}{$param}' parameter may only be used with '{$p}user'",
-                                               'badparams' );
+                                       $this->dieWithError(
+                                               [ 'apierror-invalidparammix-mustusewith', $p.$param, "{$p}user" ],
+                                               'invalidparammix'
+                                       );
                                }
                        }
                }
@@ -100,7 +99,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                                $optimizeGenerateTitles = true;
                        } else {
                                $p = $this->getModulePrefix();
-                               $this->setWarning( "For better performance when generating titles, set {$p}dir=newer" );
+                               $this->addWarning( [ 'apiwarn-alldeletedrevisions-performance', $p ], 'performance' );
                        }
                }
 
@@ -148,12 +147,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        $this->addFields( [ 'ar_text', 'ar_flags', 'old_text', 'old_flags' ] );
 
                        // This also means stricter restrictions
-                       if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
-                               $this->dieUsage(
-                                       'You don\'t have permission to view deleted revision content',
-                                       'permissiondenied'
-                               );
-                       }
+                       $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
                }
 
                $miser_ns = null;
index 8734f38..e3e5ed6 100644 (file)
@@ -64,11 +64,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
         */
        public function executeGenerator( $resultPageSet ) {
                if ( $resultPageSet->isResolvingRedirects() ) {
-                       $this->dieUsage(
-                               'Use "gaifilterredir=nonredirects" option instead of "redirects" ' .
-                                       'when using allimages as a generator',
-                               'params'
-                       );
+                       $this->dieWithError( 'apierror-allimages-redirect', 'invalidparammix' );
                }
 
                $this->run( $resultPageSet );
@@ -81,10 +77,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
        private function run( $resultPageSet = null ) {
                $repo = $this->mRepo;
                if ( !$repo instanceof LocalRepo ) {
-                       $this->dieUsage(
-                               'Local file repository does not support querying all images',
-                               'unsupportedrepo'
-                       );
+                       $this->dieWithError( 'apierror-unsupportedrepo' );
                }
 
                $prefix = $this->getModulePrefix();
@@ -109,16 +102,24 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                        $disallowed = [ 'start', 'end', 'user' ];
                        foreach ( $disallowed as $pname ) {
                                if ( isset( $params[$pname] ) ) {
-                                       $this->dieUsage(
-                                               "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp",
-                                               'badparams'
+                                       $this->dieWithError(
+                                               [
+                                                       'apierror-invalidparammix-mustusewith',
+                                                       "{$prefix}{$pname}",
+                                                       "{$prefix}sort=timestamp"
+                                               ],
+                                               'invalidparammix'
                                        );
                                }
                        }
                        if ( $params['filterbots'] != 'all' ) {
-                               $this->dieUsage(
-                                       "Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp",
-                                       'badparams'
+                               $this->dieWithError(
+                                       [
+                                               'apierror-invalidparammix-mustusewith',
+                                               "{$prefix}filterbots",
+                                               "{$prefix}sort=timestamp"
+                                       ],
+                                       'invalidparammix'
                                );
                        }
 
@@ -146,18 +147,21 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                        $disallowed = [ 'from', 'to', 'prefix' ];
                        foreach ( $disallowed as $pname ) {
                                if ( isset( $params[$pname] ) ) {
-                                       $this->dieUsage(
-                                               "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name",
-                                               'badparams'
+                                       $this->dieWithError(
+                                               [
+                                                       'apierror-invalidparammix-mustusewith',
+                                                       "{$prefix}{$pname}",
+                                                       "{$prefix}sort=name"
+                                               ],
+                                               'invalidparammix'
                                        );
                                }
                        }
                        if ( !is_null( $params['user'] ) && $params['filterbots'] != 'all' ) {
                                // Since filterbots checks if each user has the bot right, it
                                // doesn't make sense to use it with user
-                               $this->dieUsage(
-                                       "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together",
-                                       'badparams'
+                               $this->dieWithError(
+                                       [ 'apierror-invalidparammix-cannotusewith', "{$prefix}user", "{$prefix}filterbots" ]
                                );
                        }
 
@@ -214,13 +218,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                if ( isset( $params['sha1'] ) ) {
                        $sha1 = strtolower( $params['sha1'] );
                        if ( !$this->validateSha1Hash( $sha1 ) ) {
-                               $this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
+                               $this->dieWithError( 'apierror-invalidsha1hash' );
                        }
                        $sha1 = Wikimedia\base_convert( $sha1, 16, 36, 31 );
                } elseif ( isset( $params['sha1base36'] ) ) {
                        $sha1 = strtolower( $params['sha1base36'] );
                        if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
-                               $this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
+                               $this->dieWithError( 'apierror-invalidsha1base36hash' );
                        }
                }
                if ( $sha1 ) {
@@ -229,7 +233,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
 
                if ( !is_null( $params['mime'] ) ) {
                        if ( $this->getConfig()->get( 'MiserMode' ) ) {
-                               $this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
+                               $this->dieWithError( 'apierror-mimesearchdisabled' );
                        }
 
                        $mimeConds = [];
index ac90605..c3636c6 100644 (file)
@@ -116,9 +116,13 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
                        $matches = array_intersect_key( $prop, $this->props + [ 'ids' => 1 ] );
                        if ( $matches ) {
                                $p = $this->getModulePrefix();
-                               $this->dieUsage(
-                                       "Cannot use {$p}prop=" . implode( '|', array_keys( $matches ) ) . " with {$p}unique",
-                                       'params'
+                               $this->dieWithError(
+                                       [
+                                               'apierror-invalidparammix-cannotusewith',
+                                               "{$p}prop=" . implode( '|', array_keys( $matches ) ),
+                                               "{$p}unique"
+                                       ],
+                                       'invalidparammix'
                                );
                        }
                        $this->addOption( 'DISTINCT' );
index e0ba4ea..244effc 100644 (file)
@@ -41,7 +41,9 @@ class ApiQueryAllMessages extends ApiQueryBase {
                if ( is_null( $params['lang'] ) ) {
                        $langObj = $this->getLanguage();
                } elseif ( !Language::isValidCode( $params['lang'] ) ) {
-                       $this->dieUsage( 'Invalid language code for parameter lang', 'invalidlang' );
+                       $this->dieWithError(
+                               [ 'apierror-invalidlang', $this->encodeParamName( 'lang' ) ], 'invalidlang'
+                       );
                } else {
                        $langObj = Language::factory( $params['lang'] );
                }
@@ -50,7 +52,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
                        if ( !is_null( $params['title'] ) ) {
                                $title = Title::newFromText( $params['title'] );
                                if ( !$title || $title->isExternal() ) {
-                                       $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
+                                       $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
                                }
                        } else {
                                $title = Title::newFromText( 'API' );
index 6a0f124..7460bd5 100644 (file)
@@ -50,11 +50,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
         */
        public function executeGenerator( $resultPageSet ) {
                if ( $resultPageSet->isResolvingRedirects() ) {
-                       $this->dieUsage(
-                               'Use "gapfilterredir=nonredirects" option instead of "redirects" ' .
-                                       'when using allpages as a generator',
-                               'params'
-                       );
+                       $this->dieWithError( 'apierror-allpages-generator-redirects', 'params' );
                }
 
                $this->run( $resultPageSet );
@@ -157,7 +153,9 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
 
                        $this->addOption( 'DISTINCT' );
                } elseif ( isset( $params['prlevel'] ) ) {
-                       $this->dieUsage( 'prlevel may not be used without prtype', 'params' );
+                       $this->dieWithError(
+                               [ 'apierror-invalidparammix-mustusewith', 'prlevel', 'prtype' ], 'invalidparammix'
+                       );
                }
 
                if ( $params['filterlanglinks'] == 'withoutlanglinks' ) {
index b7ed9dd..2e2ac32 100644 (file)
@@ -110,9 +110,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                        }
                }
 
-               if ( !is_null( $params['group'] ) && !is_null( $params['excludegroup'] ) ) {
-                       $this->dieUsage( 'group and excludegroup cannot be used together', 'group-excludegroup' );
-               }
+               $this->requireMaxOneParameter( $params, 'group', 'excludegroup' );
 
                if ( !is_null( $params['group'] ) && count( $params['group'] ) ) {
                        // Filter only users that belong to a given group. This might
index fb502e4..4c32320 100644 (file)
@@ -348,8 +348,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
 
                // only image titles are allowed for the root in imageinfo mode
                if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE ) {
-                       $this->dieUsage(
-                               "The title for {$this->getModuleName()} query must be a file",
+                       $this->dieWithError(
+                               [ 'apierror-imageusage-badtitle', $this->getModuleName() ],
                                'bad_image_title'
                        );
                }
index 8e89c32..ef7b9af 100644 (file)
@@ -238,7 +238,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                        if ( isset( $show['fragment'] ) && isset( $show['!fragment'] ) ||
                                isset( $show['redirect'] ) && isset( $show['!redirect'] )
                        ) {
-                               $this->dieUsageMsg( 'show' );
+                               $this->dieWithError( 'apierror-show' );
                        }
                        $this->addWhereIf( "rd_fragment != $emptyString", isset( $show['fragment'] ) );
                        $this->addWhereIf(
index bba5375..af2aed5 100644 (file)
@@ -421,7 +421,7 @@ abstract class ApiQueryBase extends ApiBase {
 
                        $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
                        if ( !$likeQuery ) {
-                               $this->dieUsage( 'Invalid query', 'bad_query' );
+                               $this->dieWithError( 'apierror-badquery' );
                        }
 
                        $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
@@ -547,7 +547,7 @@ abstract class ApiQueryBase extends ApiBase {
                $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' );
                if ( !$t || $t->hasFragment() ) {
                        // Invalid title (e.g. bad chars) or contained a '#'.
-                       $this->dieUsageMsg( [ 'invalidtitle', $titlePart ] );
+                       $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] );
                }
                if ( $namespace != $t->getNamespace() || $t->isExternal() ) {
                        // This can happen in two cases. First, if you call titlePartToKey with a title part
@@ -555,7 +555,7 @@ abstract class ApiQueryBase extends ApiBase {
                        // difficult to handle such a case. Such cases cannot exist and are therefore treated
                        // as invalid user input. The second case is when somebody specifies a title interwiki
                        // prefix.
-                       $this->dieUsageMsg( [ 'invalidtitle', $titlePart ] );
+                       $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] );
                }
 
                return substr( $t->getDBkey(), 0, -1 );
@@ -573,7 +573,7 @@ abstract class ApiQueryBase extends ApiBase {
                $t = Title::newFromText( $titlePart . 'x', $defaultNamespace );
                if ( !$t || $t->hasFragment() || $t->isExternal() ) {
                        // Invalid title (e.g. bad chars) or contained a '#'.
-                       $this->dieUsageMsg( [ 'invalidtitle', $titlePart ] );
+                       $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] );
                }
 
                return [ $t->getNamespace(), substr( $t->getDBkey(), 0, -1 ) ];
index 5d7c664..ef79efd 100644 (file)
@@ -114,16 +114,13 @@ class ApiQueryBlocks extends ApiQueryBase {
                                $cidrLimit = $blockCIDRLimit['IPv6'];
                                $prefixLen = 3; // IP::toHex output is prefixed with "v6-"
                        } else {
-                               $this->dieUsage( 'IP parameter is not valid', 'param_ip' );
+                               $this->dieWithError( 'apierror-badip', 'param_ip' );
                        }
 
                        # Check range validity, if it's a CIDR
                        list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
                        if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
-                               $this->dieUsage(
-                                       "$type CIDR ranges broader than /$cidrLimit are not accepted",
-                                       'cidrtoobroad'
-                               );
+                               $this->dieWithError( [ 'apierror-cidrtoobroad', $type, $cidrLimit ] );
                        }
 
                        # Let IP::parseRange handle calculating $upper, instead of duplicating the logic here.
@@ -154,7 +151,7 @@ class ApiQueryBlocks extends ApiQueryBase {
                                || ( isset( $show['range'] ) && isset( $show['!range'] ) )
                                || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
                        ) {
-                               $this->dieUsageMsg( 'show' );
+                               $this->dieWithError( 'apierror-show' );
                        }
 
                        $this->addWhereIf( 'ipb_user = 0', isset( $show['!account'] ) );
@@ -237,13 +234,19 @@ class ApiQueryBlocks extends ApiQueryBase {
 
        protected function prepareUsername( $user ) {
                if ( !$user ) {
-                       $this->dieUsage( 'User parameter may not be empty', 'param_user' );
+                       $encParamName = $this->encodeParamName( 'users' );
+                       $this->dieWithError( [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $user ) ],
+                               "baduser_{$encParamName}"
+                       );
                }
                $name = User::isIP( $user )
                        ? $user
                        : User::getCanonicalName( $user, 'valid' );
                if ( $name === false ) {
-                       $this->dieUsage( "User name {$user} is not valid", 'param_user' );
+                       $encParamName = $this->encodeParamName( 'users' );
+                       $this->dieWithError( [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $user ) ],
+                               "baduser_{$encParamName}"
+                       );
                }
                return $name;
        }
index 63d0f6d..f2498ca 100644 (file)
@@ -74,7 +74,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
                        foreach ( $params['categories'] as $cat ) {
                                $title = Title::newFromText( $cat );
                                if ( !$title || $title->getNamespace() != NS_CATEGORY ) {
-                                       $this->setWarning( "\"$cat\" is not a category" );
+                                       $this->addWarning( [ 'apiwarn-invalidcategory', wfEscapeWikiText( $cat ) ] );
                                } else {
                                        $cats[] = $title->getDBkey();
                                }
@@ -96,7 +96,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
                }
 
                if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) ) {
-                       $this->dieUsageMsg( 'show' );
+                       $this->dieWithError( 'apierror-show' );
                }
                if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) ) {
                        $this->addOption( 'STRAIGHT_JOIN' );
index 4865ad5..02961aa 100644 (file)
@@ -65,7 +65,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
 
                $categoryTitle = $this->getTitleOrPageId( $params )->getTitle();
                if ( $categoryTitle->getNamespace() != NS_CATEGORY ) {
-                       $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
+                       $this->dieWithError( 'apierror-invalidcategory' );
                }
 
                $prop = array_flip( $params['prop'] );
@@ -153,7 +153,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
                                        $startsortkey = Collation::singleton()->getSortKey( $params['startsortkeyprefix'] );
                                } elseif ( $params['starthexsortkey'] !== null ) {
                                        if ( !$this->validateHexSortkey( $params['starthexsortkey'] ) ) {
-                                               $this->dieUsage( 'The starthexsortkey provided is not valid', 'bad_starthexsortkey' );
+                                               $encParamName = $this->encodeParamName( 'starthexsortkey' );
+                                               $this->dieWithError( [ 'apierror-badparameter', $encParamName ], "badvalue_$encParamName" );
                                        }
                                        $startsortkey = hex2bin( $params['starthexsortkey'] );
                                } else {
@@ -163,7 +164,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
                                        $endsortkey = Collation::singleton()->getSortKey( $params['endsortkeyprefix'] );
                                } elseif ( $params['endhexsortkey'] !== null ) {
                                        if ( !$this->validateHexSortkey( $params['endhexsortkey'] ) ) {
-                                               $this->dieUsage( 'The endhexsortkey provided is not valid', 'bad_endhexsortkey' );
+                                               $encParamName = $this->encodeParamName( 'endhexsortkey' );
+                                               $this->dieWithError( [ 'apierror-badparameter', $encParamName ], "badvalue_$encParamName" );
                                        }
                                        $endsortkey = hex2bin( $params['endhexsortkey'] );
                                } else {
index cfd0653..d0b8214 100644 (file)
@@ -39,12 +39,7 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
        protected function run( ApiPageSet $resultPageSet = null ) {
                $user = $this->getUser();
                // Before doing anything at all, let's check permissions
-               if ( !$user->isAllowed( 'deletedhistory' ) ) {
-                       $this->dieUsage(
-                               'You don\'t have permission to view deleted revision information',
-                               'permissiondenied'
-                       );
-               }
+               $this->checkUserRightsAny( 'deletedhistory' );
 
                $pageSet = $this->getPageSet();
                $pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
@@ -63,9 +58,7 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
 
                $db = $this->getDB();
 
-               if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
-                       $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
-               }
+               $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
 
                $this->addTables( 'archive' );
                if ( $resultPageSet === null ) {
@@ -106,12 +99,7 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
                        $this->addFields( [ 'ar_text', 'ar_flags', 'old_text', 'old_flags' ] );
 
                        // This also means stricter restrictions
-                       if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
-                               $this->dieUsage(
-                                       'You don\'t have permission to view deleted revision content',
-                                       'permissiondenied'
-                               );
-                       }
+                       $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
                }
 
                $dir = $params['dir'];
index d58efa1..6a259cd 100644 (file)
@@ -37,21 +37,12 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
        }
 
        public function execute() {
-               $user = $this->getUser();
                // Before doing anything at all, let's check permissions
-               if ( !$user->isAllowed( 'deletedhistory' ) ) {
-                       $this->dieUsage(
-                               'You don\'t have permission to view deleted revision information',
-                               'permissiondenied'
-                       );
-               }
+               $this->checkUserRightsAny( 'deletedhistory' );
 
-               $this->setWarning(
-                       'list=deletedrevs has been deprecated. Please use prop=deletedrevisions or ' .
-                       'list=alldeletedrevisions instead.'
-               );
-               $this->logFeatureUsage( 'action=query&list=deletedrevs' );
+               $this->addDeprecation( 'apiwarn-deprecation-deletedrevs', 'action=query&list=deletedrevs' );
 
+               $user = $this->getUser();
                $db = $this->getDB();
                $params = $this->extractRequestParams( false );
                $prop = array_flip( $params['prop'] );
@@ -70,9 +61,6 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
 
                if ( isset( $prop['token'] ) ) {
                        $p = $this->getModulePrefix();
-                       $this->setWarning(
-                               "{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead."
-                       );
                }
 
                // If we're in a mode that breaks the same-origin policy, no tokens can
@@ -105,19 +93,19 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                        // Ignore namespace and unique due to inability to know whether they were purposely set
                        foreach ( [ 'from', 'to', 'prefix', /*'namespace', 'unique'*/ ] as $p ) {
                                if ( !is_null( $params[$p] ) ) {
-                                       $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams' );
+                                       $this->dieWithError( [ 'apierror-deletedrevs-param-not-1-2', $p ], 'badparams' );
                                }
                        }
                } else {
                        foreach ( [ 'start', 'end' ] as $p ) {
                                if ( !is_null( $params[$p] ) ) {
-                                       $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams' );
+                                       $this->dieWithError( [ 'apierror-deletedrevs-param-not-3', $p ], 'badparams' );
                                }
                        }
                }
 
                if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
-                       $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
+                       $this->dieWithError( 'user and excludeuser cannot be used together', 'badparams' );
                }
 
                $this->addTables( 'archive' );
@@ -162,12 +150,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                        $this->addFields( [ 'ar_text', 'ar_flags', 'ar_text_id', 'old_text', 'old_flags' ] );
 
                        // This also means stricter restrictions
-                       if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
-                               $this->dieUsage(
-                                       'You don\'t have permission to view deleted revision content',
-                                       'permissiondenied'
-                               );
-                       }
+                       $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
                }
                // Check limits
                $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
index e1c97e1..9476066 100644 (file)
@@ -37,7 +37,7 @@
 class ApiQueryDisabled extends ApiQueryBase {
 
        public function execute() {
-               $this->setWarning( "The \"{$this->getModuleName()}\" module has been disabled." );
+               $this->addWarning( [ 'apierror-moduledisabled', $this->getModuleName() ] );
        }
 
        public function getAllowedParams() {
index 03be491..116dbb3 100644 (file)
@@ -38,15 +38,10 @@ class ApiQueryFilearchive extends ApiQueryBase {
        }
 
        public function execute() {
-               $user = $this->getUser();
                // Before doing anything at all, let's check permissions
-               if ( !$user->isAllowed( 'deletedhistory' ) ) {
-                       $this->dieUsage(
-                               'You don\'t have permission to view deleted file information',
-                               'permissiondenied'
-                       );
-               }
+               $this->checkUserRightsAny( 'deletedhistory' );
 
+               $user = $this->getUser();
                $db = $this->getDB();
 
                $params = $this->extractRequestParams();
@@ -112,13 +107,13 @@ class ApiQueryFilearchive extends ApiQueryBase {
                        if ( $sha1Set ) {
                                $sha1 = strtolower( $params['sha1'] );
                                if ( !$this->validateSha1Hash( $sha1 ) ) {
-                                       $this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
+                                       $this->dieWithError( 'apierror-invalidsha1hash' );
                                }
                                $sha1 = Wikimedia\base_convert( $sha1, 16, 36, 31 );
                        } elseif ( $sha1base36Set ) {
                                $sha1 = strtolower( $params['sha1base36'] );
                                if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
-                                       $this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
+                                       $this->dieWithError( 'apierror-invalidsha1base36hash' );
                                }
                        }
                        if ( $sha1 ) {
index 7568107..6e2fb67 100644 (file)
@@ -51,7 +51,14 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
                $params = $this->extractRequestParams();
 
                if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) {
-                       $this->dieUsageMsg( [ 'missingparam', 'prefix' ] );
+                       $this->dieWithError(
+                               [
+                                       'apierror-invalidparammix-mustusewith',
+                                       $this->encodeParamName( 'title' ),
+                                       $this->encodeParamName( 'prefix' ),
+                               ],
+                               'invalidparammix'
+                       );
                }
 
                if ( !is_null( $params['continue'] ) ) {
index 6d9c2ca..cfd990b 100644 (file)
@@ -45,7 +45,14 @@ class ApiQueryIWLinks extends ApiQueryBase {
                $prop = array_flip( (array)$params['prop'] );
 
                if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) {
-                       $this->dieUsageMsg( [ 'missingparam', 'prefix' ] );
+                       $this->dieWithError(
+                               [
+                                       'apierror-invalidparammix-mustusewith',
+                                       $this->encodeParamName( 'title' ),
+                                       $this->encodeParamName( 'prefix' ),
+                               ],
+                               'invalidparammix'
+                       );
                }
 
                // Handle deprecated param
index d1fcfa3..c9dae8d 100644 (file)
@@ -58,6 +58,15 @@ class ApiQueryImageInfo extends ApiQueryBase {
                        'revdelUser' => $this->getUser(),
                ];
 
+               if ( isset( $params['badfilecontexttitle'] ) ) {
+                       $badFileContextTitle = Title::newFromText( $params['badfilecontexttitle'] );
+                       if ( !$badFileContextTitle ) {
+                               $this->dieUsage( 'Invalid title in badfilecontexttitle parameter', 'invalid-title' );
+                       }
+               } else {
+                       $badFileContextTitle = false;
+               }
+
                $pageIds = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
                if ( !empty( $pageIds[NS_FILE] ) ) {
                        $titles = array_keys( $pageIds[NS_FILE] );
@@ -95,13 +104,16 @@ class ApiQueryImageInfo extends ApiQueryBase {
 
                        $result = $this->getResult();
                        foreach ( $titles as $title ) {
+                               $info = [];
                                $pageId = $pageIds[NS_FILE][$title];
                                $start = $title === $fromTitle ? $fromTimestamp : $params['start'];
 
                                if ( !isset( $images[$title] ) ) {
-                                       if ( isset( $prop['uploadwarning'] ) ) {
-                                               // Uploadwarning needs info about non-existing files
+                                       if ( isset( $prop['uploadwarning'] ) || isset( $prop['badfile'] ) ) {
+                                               // uploadwarning and badfile need info about non-existing files
                                                $images[$title] = wfLocalFile( $title );
+                                               // Doesn't exist, so set an empty image repository
+                                               $info['imagerepository'] = '';
                                        } else {
                                                $result->addValue(
                                                        [ 'query', 'pages', intval( $pageId ) ],
@@ -128,10 +140,14 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                        break;
                                }
 
-                               $fit = $result->addValue(
-                                       [ 'query', 'pages', intval( $pageId ) ],
-                                       'imagerepository', $img->getRepoName()
-                               );
+                               if ( !isset( $info['imagerepository'] ) ) {
+                                       $info['imagerepository'] = $img->getRepoName();
+                               }
+                               if ( isset( $prop['badfile'] ) ) {
+                                       $info['badfile'] = (bool)wfIsBadImage( $title, $badFileContextTitle );
+                               }
+
+                               $fit = $result->addValue( [ 'query', 'pages' ], intval( $pageId ), $info );
                                if ( !$fit ) {
                                        if ( count( $pageIds[NS_FILE] ) == 1 ) {
                                                // The user is screwed. imageinfo can't be solely
@@ -280,8 +296,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
 
                $h = $image->getHandler();
                if ( !$h ) {
-                       $this->setWarning( 'Could not create thumbnail because ' .
-                               $image->getName() . ' does not have an associated image handler' );
+                       $this->addWarning( [ 'apiwarn-nothumb-noimagehandler', wfEscapeWikiText( $image->getName() ) ] );
 
                        return $thumbParams;
                }
@@ -292,23 +307,24 @@ class ApiQueryImageInfo extends ApiQueryBase {
                        // we could still render the image using width and height parameters,
                        // and this type of thing could happen between different versions of
                        // handlers.
-                       $this->setWarning( "Could not parse {$p}urlparam for " . $image->getName()
-                               . '. Using only width and height' );
+                       $this->addWarning( [ 'apiwarn-badurlparam', $p, wfEscapeWikiText( $image->getName() ) ] );
                        $this->checkParameterNormalise( $image, $thumbParams );
                        return $thumbParams;
                }
 
                if ( isset( $paramList['width'] ) && isset( $thumbParams['width'] ) ) {
                        if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) {
-                               $this->setWarning( "Ignoring width value set in {$p}urlparam ({$paramList['width']}) "
-                                       . "in favor of width value derived from {$p}urlwidth/{$p}urlheight "
-                                       . "({$thumbParams['width']})" );
+                               $this->addWarning(
+                                       [ 'apiwarn-urlparamwidth', $p, $paramList['width'], $thumbParams['width'] ]
+                               );
                        }
                }
 
                foreach ( $paramList as $name => $value ) {
                        if ( !$h->validateParam( $name, $value ) ) {
-                               $this->dieUsage( "Invalid value for {$p}urlparam ($name=$value)", 'urlparam' );
+                               $this->dieWithError(
+                                       [ 'apierror-invalidurlparam', $p, wfEscapeWikiText( $name ), wfEscapeWikiText( $value ) ]
+                               );
                        }
                }
 
@@ -337,8 +353,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                // in the actual normalised version, only if we can actually normalise them,
                // so we use the functions scope to throw away the normalisations.
                if ( !$h->normaliseParams( $image, $finalParams ) ) {
-                       $this->dieUsage( 'Could not normalise image parameters for ' .
-                               $image->getName(), 'urlparamnormal' );
+                       $this->dieWithError( [ 'apierror-urlparamnormal', wfEscapeWikiText( $image->getName() ) ] );
                }
        }
 
@@ -689,6 +704,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                ApiBase::PARAM_DFLT => '',
                                ApiBase::PARAM_TYPE => 'string',
                        ],
+                       'badfilecontexttitle' => [
+                               ApiBase::PARAM_TYPE => 'string',
+                       ],
                        'continue' => [
                                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
                        ],
@@ -734,6 +752,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                'archivename' => 'apihelp-query+imageinfo-paramvalue-prop-archivename',
                                'bitdepth' => 'apihelp-query+imageinfo-paramvalue-prop-bitdepth',
                                'uploadwarning' => 'apihelp-query+imageinfo-paramvalue-prop-uploadwarning',
+                               'badfile' => 'apihelp-query+imageinfo-paramvalue-prop-badfile',
                        ],
                        array_flip( $filter )
                );
index e04d8c8..ae6f5bf 100644 (file)
@@ -90,7 +90,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
                        foreach ( $params['images'] as $img ) {
                                $title = Title::newFromText( $img );
                                if ( !$title || $title->getNamespace() != NS_FILE ) {
-                                       $this->setWarning( "\"$img\" is not a file" );
+                                       $this->addWarning( [ 'apiwarn-notfile', wfEscapeWikiText( $img ) ] );
                                } else {
                                        $images[] = $title->getDBkey();
                                }
index d287020..fd65038 100644 (file)
@@ -427,7 +427,7 @@ class ApiQueryInfo extends ApiQueryBase {
                        foreach ( $this->params['token'] as $t ) {
                                $val = call_user_func( $tokenFunctions[$t], $pageid, $title );
                                if ( $val === false ) {
-                                       $this->setWarning( "Action '$t' is not allowed for the current user" );
+                                       $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
                                } else {
                                        $pageInfo[$t . 'token'] = $val;
                                }
index a6153de..8d5b5f3 100644 (file)
@@ -51,7 +51,14 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
                $params = $this->extractRequestParams();
 
                if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
-                       $this->dieUsageMsg( [ 'missingparam', 'lang' ] );
+                       $this->dieWithError(
+                               [
+                                       'apierror-invalidparammix-mustusewith',
+                                       $this->encodeParamName( 'title' ),
+                                       $this->encodeParamName( 'lang' )
+                               ],
+                               'nolang'
+                       );
                }
 
                if ( !is_null( $params['continue'] ) ) {
index 67f2c9e..55e3c85 100644 (file)
@@ -44,7 +44,14 @@ class ApiQueryLangLinks extends ApiQueryBase {
                $prop = array_flip( (array)$params['prop'] );
 
                if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
-                       $this->dieUsageMsg( [ 'missingparam', 'lang' ] );
+                       $this->dieWithError(
+                               [
+                                       'apierror-invalidparammix-mustusewith',
+                                       $this->encodeParamName( 'title' ),
+                                       $this->encodeParamName( 'lang' ),
+                               ],
+                               'invalidparammix'
+                       );
                }
 
                // Handle deprecated param
index 6e5239f..e9ae132 100644 (file)
@@ -94,7 +94,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
                        foreach ( $params[$this->titlesParam] as $t ) {
                                $title = Title::newFromText( $t );
                                if ( !$title ) {
-                                       $this->setWarning( "\"$t\" is not a valid title" );
+                                       $this->addWarning( [ 'apiwarn-invalidtitle', wfEscapeWikiText( $t ) ] );
                                } else {
                                        $lb->addObj( $title );
                                }
index 122594d..2dcd0b4 100644 (file)
@@ -121,10 +121,10 @@ class ApiQueryLogEvents extends ApiQueryBase {
                        }
 
                        if ( !$valid ) {
-                               $valueName = $this->encodeParamName( 'action' );
-                               $this->dieUsage(
-                                       "Unrecognized value for parameter '$valueName': {$logAction}",
-                                       "unknown_$valueName"
+                               $encParamName = $this->encodeParamName( 'action' );
+                               $this->dieWithError(
+                                       [ 'apierror-unrecognizedvalue', $encParamName, wfEscapeWikiText( $logAction ) ],
+                                       "unknown_$encParamName"
                                );
                        }
 
@@ -173,7 +173,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
                if ( !is_null( $title ) ) {
                        $titleObj = Title::newFromText( $title );
                        if ( is_null( $titleObj ) ) {
-                               $this->dieUsage( "Bad title value '$title'", 'param_title' );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
                        }
                        $this->addWhereFld( 'log_namespace', $titleObj->getNamespace() );
                        $this->addWhereFld( 'log_title', $titleObj->getDBkey() );
@@ -187,12 +187,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
 
                if ( !is_null( $prefix ) ) {
                        if ( $this->getConfig()->get( 'MiserMode' ) ) {
-                               $this->dieUsage( 'Prefix search disabled in Miser Mode', 'prefixsearchdisabled' );
+                               $this->dieWithError( 'apierror-prefixsearchdisabled' );
                        }
 
                        $title = Title::newFromText( $prefix );
                        if ( is_null( $title ) ) {
-                               $this->dieUsage( "Bad title value '$prefix'", 'param_prefix' );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $prefix ) ] );
                        }
                        $this->addWhereFld( 'log_namespace', $title->getNamespace() );
                        $this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) );
index 0c70a8a..1324f2f 100644 (file)
@@ -36,7 +36,7 @@ class ApiQueryMyStashedFiles extends ApiQueryBase {
                $user = $this->getUser();
 
                if ( $user->isAnon() ) {
-                       $this->dieUsage( 'The upload stash is only available to logged-in users.', 'stashnotloggedin' );
+                       $this->dieWithError( 'apierror-mustbeloggedin-uploadstash', 'stashnotloggedin' );
                }
 
                // Note: If user is logged in but cannot upload, they can still see
index 9ba757c..908cdee 100644 (file)
@@ -62,7 +62,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
                /** @var $qp QueryPage */
                $qp = new $this->qpMap[$params['page']]();
                if ( !$qp->userCanExecute( $this->getUser() ) ) {
-                       $this->dieUsageMsg( 'specialpage-cantexecute' );
+                       $this->dieWithError( 'apierror-specialpage-cantexecute' );
                }
 
                $r = [ 'name' => $params['page'] ];
index 8b11dc2..8d14927 100644 (file)
@@ -195,7 +195,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                                || ( isset( $show['patrolled'] ) && isset( $show['unpatrolled'] ) )
                                || ( isset( $show['!patrolled'] ) && isset( $show['unpatrolled'] ) )
                        ) {
-                               $this->dieUsageMsg( 'show' );
+                               $this->dieWithError( 'apierror-show' );
                        }
 
                        // Check permissions
@@ -204,10 +204,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                                || isset( $show['unpatrolled'] )
                        ) {
                                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
-                                       $this->dieUsage(
-                                               'You need patrol or patrolmarks permission to request the patrolled flag',
-                                               'permissiondenied'
-                                       );
+                                       $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
                                }
                        }
 
@@ -239,9 +236,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        );
                }
 
-               if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
-                       $this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
-               }
+               $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
 
                if ( !is_null( $params['user'] ) ) {
                        $this->addWhereFld( 'rc_user_text', $params['user'] );
@@ -274,10 +269,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        $this->initProperties( $prop );
 
                        if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
-                               $this->dieUsage(
-                                       'You need patrol or patrolmarks permission to request the patrolled flag',
-                                       'permissiondenied'
-                               );
+                               $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
                        }
 
                        /* Add fields to our query if they are specified as a needed parameter. */
@@ -571,7 +563,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                                $val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
                                        $title, RecentChange::newFromRow( $row ) );
                                if ( $val === false ) {
-                                       $this->setWarning( "Action '$t' is not allowed for the current user" );
+                                       $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
                                } else {
                                        $vals[$t . 'token'] = $val;
                                }
index 3259927..48f6046 100644 (file)
@@ -110,19 +110,14 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                }
 
                if ( $revCount > 0 && $enumRevMode ) {
-                       $this->dieUsage(
-                               'The revids= parameter may not be used with the list options ' .
-                                       '(limit, startid, endid, dirNewer, start, end).',
-                               'revids'
+                       $this->dieWithError(
+                               [ 'apierror-revisions-nolist', $this->getModulePrefix() ], 'invalidparammix'
                        );
                }
 
                if ( $pageCount > 1 && $enumRevMode ) {
-                       $this->dieUsage(
-                               'titles, pageids or a generator was used to supply multiple pages, ' .
-                                       'but the limit, startid, endid, dirNewer, user, excludeuser, start ' .
-                                       'and end parameters may only be used on a single page.',
-                               'multpages'
+                       $this->dieWithError(
+                               [ 'apierror-revisions-singlepage', $this->getModulePrefix() ], 'invalidparammix'
                        );
                }
 
@@ -170,14 +165,19 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                if ( $this->fetchContent ) {
                        // For each page we will request, the user must have read rights for that page
                        $user = $this->getUser();
+                       $status = Status::newGood();
                        /** @var $title Title */
                        foreach ( $pageSet->getGoodTitles() as $title ) {
                                if ( !$title->userCan( 'read', $user ) ) {
-                                       $this->dieUsage(
-                                               'The current user is not allowed to read ' . $title->getPrefixedText(),
-                                               'accessdenied' );
+                                       $status->fatal( ApiMessage::create(
+                                               [ 'apierror-cannotviewtitle', wfEscapeWikiText( $title->getPrefixedText() ) ],
+                                               'accessdenied'
+                                       ) );
                                }
                        }
+                       if ( !$status->isGood() ) {
+                               $this->dieStatus( $status );
+                       }
 
                        $this->addTables( 'text' );
                        $this->addJoinConds(
@@ -201,17 +201,9 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                        //  page_timestamp or usertext_timestamp if we have an IP rvuser
 
                        // This is mostly to prevent parameter errors (and optimize SQL?)
-                       if ( $params['startid'] !== null && $params['start'] !== null ) {
-                               $this->dieUsage( 'start and startid cannot be used together', 'badparams' );
-                       }
-
-                       if ( $params['endid'] !== null && $params['end'] !== null ) {
-                               $this->dieUsage( 'end and endid cannot be used together', 'badparams' );
-                       }
-
-                       if ( $params['user'] !== null && $params['excludeuser'] !== null ) {
-                               $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
-                       }
+                       $this->requireMaxOneParameter( $params, 'startid', 'start' );
+                       $this->requireMaxOneParameter( $params, 'endid', 'end' );
+                       $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
 
                        if ( $params['continue'] !== null ) {
                                $cont = explode( '|', $params['continue'] );
@@ -344,7 +336,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                                        foreach ( $this->token as $t ) {
                                                $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
                                                if ( $val === false ) {
-                                                       $this->setWarning( "Action '$t' is not allowed for the current user" );
+                                                       $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
                                                } else {
                                                        $rev[$t . 'token'] = $val;
                                                }
index 266d699..696ec87 100644 (file)
@@ -70,10 +70,7 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
                        ) {
                                $p = $this->getModulePrefix();
-                               $this->dieUsage(
-                                       "{$p}diffto must be set to a non-negative number, \"prev\", \"next\" or \"cur\"",
-                                       'diffto'
-                               );
+                               $this->dieWithError( [ 'apierror-baddiffto', $p ], 'diffto' );
                        }
                        // Check whether the revision exists and is readable,
                        // DifferenceEngine returns a rather ambiguous empty
@@ -81,10 +78,10 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                        if ( $params['diffto'] != 0 ) {
                                $difftoRev = Revision::newFromId( $params['diffto'] );
                                if ( !$difftoRev ) {
-                                       $this->dieUsageMsg( [ 'nosuchrevid', $params['diffto'] ] );
+                                       $this->dieWithError( [ 'apierror-nosuchrevid', $params['diffto'] ] );
                                }
                                if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
-                                       $this->setWarning( "Couldn't diff to r{$difftoRev->getId()}: content is hidden" );
+                                       $this->addWarning( [ 'apiwarn-difftohidden', $difftoRev->getId() ] );
                                        $params['diffto'] = null;
                                }
                        }
@@ -262,8 +259,12 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                        if ( $content && $this->section !== false ) {
                                $content = $content->getSection( $this->section, false );
                                if ( !$content ) {
-                                       $this->dieUsage(
-                                               "There is no section {$this->section} in r" . $revision->getId(),
+                                       $this->dieWithError(
+                                               [
+                                                       'apierror-nosuchsection-what',
+                                                       wfEscapeWikiText( $this->section ),
+                                                       $this->msg( 'revid', $revision->getId() )
+                                               ],
                                                'nosuchsection'
                                        );
                                }
@@ -294,9 +295,14 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                        $vals['parsetree'] = $xml;
                                } else {
                                        $vals['badcontentformatforparsetree'] = true;
-                                       $this->setWarning( 'Conversion to XML is supported for wikitext only, ' .
-                                               $title->getPrefixedDBkey() .
-                                               ' uses content model ' . $content->getModel() );
+                                       $this->addWarning(
+                                               [
+                                                       'apierror-parsetree-notwikitext-title',
+                                                       wfEscapeWikiText( $title->getPrefixedText() ),
+                                                       $content->getModel()
+                                               ],
+                                               'parsetree-notwikitext'
+                                       );
                                }
                        }
                }
@@ -315,9 +321,11 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                                ParserOptions::newFromContext( $this->getContext() )
                                        );
                                } else {
-                                       $this->setWarning( 'Template expansion is supported for wikitext only, ' .
-                                               $title->getPrefixedDBkey() .
-                                               ' uses content model ' . $content->getModel() );
+                                       $this->addWarning( [
+                                               'apierror-templateexpansion-notwikitext',
+                                               wfEscapeWikiText( $title->getPrefixedText() ),
+                                               $content->getModel()
+                                       ] );
                                        $vals['badcontentformat'] = true;
                                        $text = false;
                                }
@@ -336,9 +344,8 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                $model = $content->getModel();
 
                                if ( !$content->isSupportedFormat( $format ) ) {
-                                       $name = $title->getPrefixedDBkey();
-                                       $this->setWarning( "The requested format {$this->contentFormat} is not " .
-                                               "supported for content model $model used by $name" );
+                                       $name = wfEscapeWikiText( $title->getPrefixedText() );
+                                       $this->addWarning( [ 'apierror-badformat', $this->contentFormat, $model, $name ] );
                                        $vals['badcontentformat'] = true;
                                        $text = false;
                                } else {
@@ -370,9 +377,8 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                        if ( $this->contentFormat
                                                && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat )
                                        ) {
-                                               $name = $title->getPrefixedDBkey();
-                                               $this->setWarning( "The requested format {$this->contentFormat} is not " .
-                                                       "supported for content model $model used by $name" );
+                                               $name = wfEscapeWikiText( $title->getPrefixedText() );
+                                               $this->addWarning( [ 'apierror-badformat', $this->contentFormat, $model, $name ] );
                                                $vals['diff']['badcontentformat'] = true;
                                                $engine = null;
                                        } else {
index 6be5198..64bc43f 100644 (file)
@@ -64,12 +64,14 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
 
                // Deprecated parameters
                if ( isset( $prop['hasrelated'] ) ) {
-                       $this->logFeatureUsage( 'action=search&srprop=hasrelated' );
-                       $this->setWarning( 'srprop=hasrelated has been deprecated' );
+                       $this->addDeprecation(
+                               [ 'apiwarn-deprecation-parameter', 'srprop=hasrelated' ], 'action=search&srprop=hasrelated'
+                       );
                }
                if ( isset( $prop['score'] ) ) {
-                       $this->logFeatureUsage( 'action=search&srprop=score' );
-                       $this->setWarning( 'srprop=score has been deprecated' );
+                       $this->addDeprecation(
+                               [ 'apiwarn-deprecation-parameter', 'srprop=score' ], 'action=search&srprop=score'
+                       );
                }
 
                // Create search engine instance and set options
@@ -107,10 +109,25 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                                $matches = $search->searchText( $query );
                        }
                }
-               if ( is_null( $matches ) ) {
-                       $this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" );
-               } elseif ( $matches instanceof Status && !$matches->isGood() ) {
-                       $this->dieUsage( $matches->getWikiText( false, false, 'en' ), 'search-error' );
+
+               if ( $matches instanceof Status ) {
+                       $status = $matches;
+                       $matches = $status->getValue();
+               } else {
+                       $status = null;
+               }
+
+               if ( $status ) {
+                       if ( $status->isOK() ) {
+                               $this->getMain()->getErrorFormatter()->addMessagesFromStatus(
+                                       $this->getModuleName(),
+                                       $status
+                               );
+                       } else {
+                               $this->dieStatus( $status );
+                       }
+               } elseif ( is_null( $matches ) ) {
+                       $this->dieWithError( [ 'apierror-searchdisabled', $what ], "search-{$what}-disabled" );
                }
 
                if ( $resultPageSet === null ) {
index 19e0c93..6fc6aa3 100644 (file)
@@ -447,10 +447,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                $showHostnames = $this->getConfig()->get( 'ShowHostnames' );
                if ( $includeAll ) {
                        if ( !$showHostnames ) {
-                               $this->dieUsage(
-                                       'Cannot view all servers info unless $wgShowHostnames is true',
-                                       'includeAllDenied'
-                               );
+                               $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
                        }
 
                        $lags = $lb->getLagTimes();
index b039a1e..981cb09 100644 (file)
@@ -33,7 +33,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
 
        public function execute() {
                if ( !$this->getUser()->isLoggedIn() ) {
-                       $this->dieUsage( 'You must be logged-in to have an upload stash', 'notloggedin' );
+                       $this->dieWithError( 'apierror-mustbeloggedin-uploadstash', 'notloggedin' );
                }
 
                $params = $this->extractRequestParams();
@@ -45,9 +45,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
 
                $result = $this->getResult();
 
-               if ( !$params['filekey'] && !$params['sessionkey'] ) {
-                       $this->dieUsage( 'One of filekey or sessionkey must be supplied', 'nofilekey' );
-               }
+               $this->requireAtLeastOneParameter( $params, 'filekey', 'sessionkey' );
 
                // Alias sessionkey to filekey, but give an existing filekey precedence.
                if ( !$params['filekey'] && $params['sessionkey'] ) {
@@ -65,10 +63,11 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
                                $result->addIndexedTagName( [ 'query', $this->getModuleName() ], $modulePrefix );
                        }
                // @todo Update exception handling here to understand current getFile exceptions
+               // @todo Internationalize the exceptions
                } catch ( UploadStashFileNotFoundException $e ) {
-                       $this->dieUsage( 'File not found: ' . $e->getMessage(), 'invalidsessiondata' );
+                       $this->dieWithError( [ 'apierror-stashedfilenotfound', wfEscapeWikiText( $e->getMessage() ) ] );
                } catch ( UploadStashBadPathException $e ) {
-                       $this->dieUsage( 'Bad path: ' . $e->getMessage(), 'invalidsessiondata' );
+                       $this->dieWithError( [ 'apierror-stashpathinvalid', wfEscapeWikiText( $e->getMessage() ) ] );
                }
        }
 
index de5a377..5b700db 100644 (file)
@@ -40,7 +40,7 @@ class ApiQueryTokens extends ApiQueryBase {
                ];
 
                if ( $this->lacksSameOriginSecurity() ) {
-                       $this->setWarning( 'Tokens may not be obtained when the same-origin policy is not applied' );
+                       $this->addWarning( [ 'apiwarn-tokens-origin' ] );
                        return;
                }
 
index b85bec4..b6d871b 100644 (file)
@@ -78,11 +78,17 @@ class ApiQueryContributions extends ApiQueryBase {
                                $this->params['user'] = [ $this->params['user'] ];
                        }
                        if ( !count( $this->params['user'] ) ) {
-                               $this->dieUsage( 'User parameter may not be empty.', 'param_user' );
+                               $encParamName = $this->encodeParamName( 'user' );
+                               $this->dieWithError(
+                                       [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
+                               );
                        }
                        foreach ( $this->params['user'] as $u ) {
                                if ( is_null( $u ) || $u === '' ) {
-                                       $this->dieUsage( 'User parameter may not be empty', 'param_user' );
+                                       $encParamName = $this->encodeParamName( 'user' );
+                                       $this->dieWithError(
+                                               [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
+                                       );
                                }
 
                                if ( User::isIP( $u ) ) {
@@ -91,7 +97,10 @@ class ApiQueryContributions extends ApiQueryBase {
                                } else {
                                        $name = User::getCanonicalName( $u, 'valid' );
                                        if ( $name === false ) {
-                                               $this->dieUsage( "User name {$u} is not valid", 'param_user' );
+                                               $encParamName = $this->encodeParamName( 'user' );
+                                               $this->dieWithError(
+                                                       [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $u ) ], "baduser_$encParamName"
+                                               );
                                        }
                                        $this->usernames[] = $name;
                                }
@@ -254,7 +263,7 @@ class ApiQueryContributions extends ApiQueryBase {
                                || ( isset( $show['top'] ) && isset( $show['!top'] ) )
                                || ( isset( $show['new'] ) && isset( $show['!new'] ) )
                        ) {
-                               $this->dieUsageMsg( 'show' );
+                               $this->dieWithError( 'apierror-show' );
                        }
 
                        $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
@@ -285,10 +294,7 @@ class ApiQueryContributions extends ApiQueryBase {
                        $this->fld_patrolled
                ) {
                        if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
-                               $this->dieUsage(
-                                       'You need the patrol right to request the patrolled flag',
-                                       'permissiondenied'
-                               );
+                               $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
                        }
 
                        // Use a redundant join condition on both
index d3cd0c4..3b60478 100644 (file)
@@ -170,8 +170,13 @@ class ApiQueryUserInfo extends ApiQueryBase {
 
                if ( isset( $this->prop['preferencestoken'] ) ) {
                        $p = $this->getModulePrefix();
-                       $this->setWarning(
-                               "{$p}prop=preferencestoken has been deprecated. Please use action=query&meta=tokens instead."
+                       $this->addDeprecation(
+                               [
+                                       'apiwarn-deprecation-withreplacement',
+                                       "{$p}prop=preferencestoken",
+                                       'action=query&meta=tokens',
+                               ],
+                               "meta=userinfo&{$p}prop=preferencestoken"
                        );
                }
                if ( isset( $this->prop['preferencestoken'] ) &&
index 9b45b91..2d620a4 100644 (file)
@@ -98,14 +98,18 @@ class ApiQueryUsers extends ApiQueryBase {
 
        public function execute() {
                $params = $this->extractRequestParams();
+               $this->requireMaxOneParameter( $params, 'userids', 'users' );
 
                if ( !is_null( $params['prop'] ) ) {
                        $this->prop = array_flip( $params['prop'] );
                } else {
                        $this->prop = [];
                }
+               $useNames = !is_null( $params['users'] );
 
                $users = (array)$params['users'];
+               $userids = (array)$params['userids'];
+
                $goodNames = $done = [];
                $result = $this->getResult();
                // Canonicalize user names
@@ -127,12 +131,22 @@ class ApiQueryUsers extends ApiQueryBase {
                        }
                }
 
+               if ( $useNames ) {
+                       $parameters = &$goodNames;
+               } else {
+                       $parameters = &$userids;
+               }
+
                $result = $this->getResult();
 
-               if ( count( $goodNames ) ) {
+               if ( count( $parameters ) ) {
                        $this->addTables( 'user' );
                        $this->addFields( User::selectFields() );
-                       $this->addWhereFld( 'user_name', $goodNames );
+                       if ( $useNames ) {
+                               $this->addWhereFld( 'user_name', $goodNames );
+                       } else {
+                               $this->addWhereFld( 'user_id', $userids );
+                       }
 
                        $this->showHiddenUsersAddBlockInfo( isset( $this->prop['blockinfo'] ) );
 
@@ -145,7 +159,12 @@ class ApiQueryUsers extends ApiQueryBase {
                                $userGroups = [];
 
                                $this->addTables( 'user' );
-                               $this->addWhereFld( 'user_name', $goodNames );
+                               if ( $useNames ) {
+                                       $this->addWhereFld( 'user_name', $goodNames );
+                               } else {
+                                       $this->addWhereFld( 'user_id', $userids );
+                               }
+
                                $this->addTables( 'user_groups' );
                                $this->addJoinConds( [ 'user_groups' => [ 'INNER JOIN', 'ug_user=user_id' ] ] );
                                $this->addFields( [ 'user_name', 'ug_group' ] );
@@ -157,6 +176,7 @@ class ApiQueryUsers extends ApiQueryBase {
                        }
 
                        foreach ( $res as $row ) {
+
                                // create user object and pass along $userGroups if set
                                // that reduces the number of database queries needed in User dramatically
                                if ( !isset( $userGroups ) ) {
@@ -167,44 +187,47 @@ class ApiQueryUsers extends ApiQueryBase {
                                        }
                                        $user = User::newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
                                }
-                               $name = $user->getName();
-
-                               $data[$name]['userid'] = $user->getId();
-                               $data[$name]['name'] = $name;
+                               if ( $useNames ) {
+                                       $key = $user->getName();
+                               } else {
+                                       $key = $user->getId();
+                               }
+                               $data[$key]['userid'] = $user->getId();
+                               $data[$key]['name'] = $user->getName();
 
                                if ( isset( $this->prop['editcount'] ) ) {
-                                       $data[$name]['editcount'] = $user->getEditCount();
+                                       $data[$key]['editcount'] = $user->getEditCount();
                                }
 
                                if ( isset( $this->prop['registration'] ) ) {
-                                       $data[$name]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
+                                       $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
                                }
 
                                if ( isset( $this->prop['groups'] ) ) {
-                                       $data[$name]['groups'] = $user->getEffectiveGroups();
+                                       $data[$key]['groups'] = $user->getEffectiveGroups();
                                }
 
                                if ( isset( $this->prop['implicitgroups'] ) ) {
-                                       $data[$name]['implicitgroups'] = $user->getAutomaticGroups();
+                                       $data[$key]['implicitgroups'] = $user->getAutomaticGroups();
                                }
 
                                if ( isset( $this->prop['rights'] ) ) {
-                                       $data[$name]['rights'] = $user->getRights();
+                                       $data[$key]['rights'] = $user->getRights();
                                }
                                if ( $row->ipb_deleted ) {
-                                       $data[$name]['hidden'] = true;
+                                       $data[$key]['hidden'] = true;
                                }
                                if ( isset( $this->prop['blockinfo'] ) && !is_null( $row->ipb_by_text ) ) {
-                                       $data[$name]['blockid'] = (int)$row->ipb_id;
-                                       $data[$name]['blockedby'] = $row->ipb_by_text;
-                                       $data[$name]['blockedbyid'] = (int)$row->ipb_by;
-                                       $data[$name]['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
-                                       $data[$name]['blockreason'] = $row->ipb_reason;
-                                       $data[$name]['blockexpiry'] = $row->ipb_expiry;
+                                       $data[$key]['blockid'] = (int)$row->ipb_id;
+                                       $data[$key]['blockedby'] = $row->ipb_by_text;
+                                       $data[$key]['blockedbyid'] = (int)$row->ipb_by;
+                                       $data[$key]['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
+                                       $data[$key]['blockreason'] = $row->ipb_reason;
+                                       $data[$key]['blockexpiry'] = $row->ipb_expiry;
                                }
 
                                if ( isset( $this->prop['emailable'] ) ) {
-                                       $data[$name]['emailable'] = $user->canReceiveEmail();
+                                       $data[$key]['emailable'] = $user->canReceiveEmail();
                                }
 
                                if ( isset( $this->prop['gender'] ) ) {
@@ -212,11 +235,11 @@ class ApiQueryUsers extends ApiQueryBase {
                                        if ( strval( $gender ) === '' ) {
                                                $gender = 'unknown';
                                        }
-                                       $data[$name]['gender'] = $gender;
+                                       $data[$key]['gender'] = $gender;
                                }
 
                                if ( isset( $this->prop['centralids'] ) ) {
-                                       $data[$name] += ApiQueryUserInfo::getCentralUserInfo(
+                                       $data[$key] += ApiQueryUserInfo::getCentralUserInfo(
                                                $this->getConfig(), $user, $params['attachedwiki']
                                        );
                                }
@@ -226,9 +249,9 @@ class ApiQueryUsers extends ApiQueryBase {
                                        foreach ( $params['token'] as $t ) {
                                                $val = call_user_func( $tokenFunctions[$t], $user );
                                                if ( $val === false ) {
-                                                       $this->setWarning( "Action '$t' is not allowed for the current user" );
+                                                       $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
                                                } else {
-                                                       $data[$name][$t . 'token'] = $val;
+                                                       $data[$key][$t . 'token'] = $val;
                                                }
                                        }
                                }
@@ -237,38 +260,44 @@ class ApiQueryUsers extends ApiQueryBase {
 
                $context = $this->getContext();
                // Second pass: add result data to $retval
-               foreach ( $goodNames as $u ) {
+               foreach ( $parameters as $u ) {
                        if ( !isset( $data[$u] ) ) {
-                               $data[$u] = [ 'name' => $u ];
-                               $urPage = new UserrightsPage;
-                               $urPage->setContext( $context );
-                               $iwUser = $urPage->fetchUser( $u );
-
-                               if ( $iwUser instanceof UserRightsProxy ) {
-                                       $data[$u]['interwiki'] = true;
-
-                                       if ( !is_null( $params['token'] ) ) {
-                                               $tokenFunctions = $this->getTokenFunctions();
-
-                                               foreach ( $params['token'] as $t ) {
-                                                       $val = call_user_func( $tokenFunctions[$t], $iwUser );
-                                                       if ( $val === false ) {
-                                                               $this->setWarning( "Action '$t' is not allowed for the current user" );
-                                                       } else {
-                                                               $data[$u][$t . 'token'] = $val;
+                               if ( $useNames ) {
+                                       $data[$u] = [ 'name' => $u ];
+                                       $urPage = new UserrightsPage;
+                                       $urPage->setContext( $context );
+
+                                       $iwUser = $urPage->fetchUser( $u );
+
+                                       if ( $iwUser instanceof UserRightsProxy ) {
+                                               $data[$u]['interwiki'] = true;
+
+                                               if ( !is_null( $params['token'] ) ) {
+                                                       $tokenFunctions = $this->getTokenFunctions();
+
+                                                       foreach ( $params['token'] as $t ) {
+                                                               $val = call_user_func( $tokenFunctions[$t], $iwUser );
+                                                               if ( $val === false ) {
+                                                                       $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
+                                                               } else {
+                                                                       $data[$u][$t . 'token'] = $val;
+                                                               }
                                                        }
                                                }
-                                       }
-                               } else {
-                                       $data[$u]['missing'] = true;
-                                       if ( isset( $this->prop['cancreate'] ) ) {
-                                               $status = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount( $u );
-                                               $data[$u]['cancreate'] = $status->isGood();
-                                               if ( !$status->isGood() ) {
-                                                       $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
+                                       } else {
+                                               $data[$u]['missing'] = true;
+                                               if ( isset( $this->prop['cancreate'] ) ) {
+                                                       $status = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount( $u );
+                                                       $data[$u]['cancreate'] = $status->isGood();
+                                                       if ( !$status->isGood() ) {
+                                                               $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
+                                                       }
                                                }
                                        }
+                               } else {
+                                       $data[$u] = [ 'userid' => $u, 'missing' => true ];
                                }
+
                        } else {
                                if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
                                        ApiResult::setArrayType( $data[$u]['groups'], 'array' );
@@ -287,8 +316,13 @@ class ApiQueryUsers extends ApiQueryBase {
                        $fit = $result->addValue( [ 'query', $this->getModuleName() ],
                                null, $data[$u] );
                        if ( !$fit ) {
-                               $this->setContinueEnumParameter( 'users',
-                                       implode( '|', array_diff( $users, $done ) ) );
+                               if ( $useNames ) {
+                                       $this->setContinueEnumParameter( 'users',
+                                               implode( '|', array_diff( $users, $done ) ) );
+                               } else {
+                                       $this->setContinueEnumParameter( 'userids',
+                                               implode( '|', array_diff( $userids, $done ) ) );
+                               }
                                break;
                        }
                        $done[] = $u;
@@ -330,6 +364,10 @@ class ApiQueryUsers extends ApiQueryBase {
                        'users' => [
                                ApiBase::PARAM_ISMULTI => true
                        ],
+                       'userids' => [
+                               ApiBase::PARAM_ISMULTI => true,
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ],
                        'token' => [
                                ApiBase::PARAM_DEPRECATED => true,
                                ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
index 42ea55d..6b5ceb7 100644 (file)
@@ -82,7 +82,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
 
                        if ( $this->fld_patrol ) {
                                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
-                                       $this->dieUsage( 'patrol property is not available', 'patrol' );
+                                       $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'patrol' );
                                }
                        }
                }
@@ -134,7 +134,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
 
                        /* Check for conflicting parameters. */
                        if ( $this->showParamsConflicting( $show ) ) {
-                               $this->dieUsageMsg( 'show' );
+                               $this->dieWithError( 'apierror-show' );
                        }
 
                        // Check permissions.
@@ -142,10 +142,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                                || isset( $show[WatchedItemQueryService::FILTER_NOT_PATROLLED] )
                        ) {
                                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
-                                       $this->dieUsage(
-                                               'You need the patrol right to request the patrolled flag',
-                                               'permissiondenied'
-                                       );
+                                       $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
                                }
                        }
 
@@ -160,9 +157,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                        }
                }
 
-               if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
-                       $this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
-               }
+               $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
                if ( !is_null( $params['user'] ) ) {
                        $options['onlyByUser'] = $params['user'];
                }
index 806861e..a1078a5 100644 (file)
@@ -60,7 +60,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
                if ( isset( $show[WatchedItemQueryService::FILTER_CHANGED] )
                        && isset( $show[WatchedItemQueryService::FILTER_NOT_CHANGED] )
                ) {
-                       $this->dieUsageMsg( 'show' );
+                       $this->dieWithError( 'apierror-show' );
                }
 
                $options = [];
index d72c8a4..359d045 100644 (file)
@@ -45,7 +45,7 @@ class ApiRemoveAuthenticationData extends ApiBase {
 
        public function execute() {
                if ( !$this->getUser()->isLoggedIn() ) {
-                       $this->dieUsage( 'Must be logged in to remove authentication data', 'notloggedin' );
+                       $this->dieWithError( 'apierror-mustbeloggedin-removeauth', 'notloggedin' );
                }
 
                $params = $this->extractRequestParams();
@@ -67,7 +67,7 @@ class ApiRemoveAuthenticationData extends ApiBase {
                        }
                );
                if ( count( $reqs ) !== 1 ) {
-                       $this->dieUsage( 'Failed to create change request', 'badrequest' );
+                       $this->dieWithError( 'apierror-changeauth-norequest', 'badrequest' );
                }
                $req = reset( $reqs );
 
index 2d7f5df..b5fa8ed 100644 (file)
@@ -52,7 +52,7 @@ class ApiResetPassword extends ApiBase {
 
        public function execute() {
                if ( !$this->hasAnyRoutes() ) {
-                       $this->dieUsage( 'No password reset routes are available.', 'moduledisabled' );
+                       $this->dieWithError( 'apihelp-resetpassword-description-noroutes', 'moduledisabled' );
                }
 
                $params = $this->extractRequestParams() + [
index 6e27fc8..61a4394 100644 (file)
@@ -413,11 +413,9 @@ class ApiResult implements ApiSerializable {
 
                        $newsize = $this->size + self::size( $value );
                        if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
-                               /// @todo Add i18n message when replacing calls to ->setWarning()
-                               $msg = new ApiRawMessage( 'This result was truncated because it would otherwise ' .
-                                       'be larger than the limit of $1 bytes', 'truncatedresult' );
-                               $msg->numParams( $this->maxSize );
-                               $this->errorFormatter->addWarning( 'result', $msg );
+                               $this->errorFormatter->addWarning(
+                                       'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
+                               );
                                return false;
                        }
                        $this->size = $newsize;
index ed9fba2..763aef5 100644 (file)
@@ -36,24 +36,22 @@ class ApiRevisionDelete extends ApiBase {
 
                $params = $this->extractRequestParams();
                $user = $this->getUser();
-               if ( !$user->isAllowed( RevisionDeleter::getRestriction( $params['type'] ) ) ) {
-                       $this->dieUsageMsg( 'badaccess-group0' );
-               }
+               $this->checkUserRightsAny( RevisionDeleter::getRestriction( $params['type'] ) );
 
                if ( $user->isBlocked() ) {
                        $this->dieBlocked( $user->getBlock() );
                }
 
                if ( !$params['ids'] ) {
-                       $this->dieUsage( "At least one value is required for 'ids'", 'badparams' );
+                       $this->dieWithError( [ 'apierror-paramempty', 'ids' ], 'paramempty_ids' );
                }
 
                $hide = $params['hide'] ?: [];
                $show = $params['show'] ?: [];
                if ( array_intersect( $hide, $show ) ) {
-                       $this->dieUsage( "Mutually exclusive values for 'hide' and 'show'", 'badparams' );
+                       $this->dieWithError( 'apierror-revdel-mutuallyexclusive', 'badparams' );
                } elseif ( !$hide && !$show ) {
-                       $this->dieUsage( "At least one value is required for 'hide' or 'show'", 'badparams' );
+                       $this->dieWithError( 'apierror-revdel-paramneeded', 'badparams' );
                }
                $bits = [
                        'content' => RevisionDeleter::getRevdelConstant( $params['type'] ),
@@ -72,9 +70,7 @@ class ApiRevisionDelete extends ApiBase {
                }
 
                if ( $params['suppress'] === 'yes' ) {
-                       if ( !$user->isAllowed( 'suppressrevision' ) ) {
-                               $this->dieUsageMsg( 'badaccess-group0' );
-                       }
+                       $this->checkUserRightsAny( 'suppressrevision' );
                        $bitfield[Revision::DELETED_RESTRICTED] = 1;
                } elseif ( $params['suppress'] === 'no' ) {
                        $bitfield[Revision::DELETED_RESTRICTED] = 0;
@@ -88,7 +84,7 @@ class ApiRevisionDelete extends ApiBase {
                }
                $targetObj = RevisionDeleter::suggestTarget( $params['type'], $targetObj, $params['ids'] );
                if ( $targetObj === null ) {
-                       $this->dieUsage( 'A target title is required for this RevDel type', 'needtarget' );
+                       $this->dieWithError( [ 'apierror-revdel-needtarget' ], 'needtarget' );
                }
 
                $list = RevisionDeleter::createList(
@@ -124,49 +120,19 @@ class ApiRevisionDelete extends ApiBase {
                $ret = [
                        'status' => $status->isOK() ? 'Success' : 'Fail',
                ];
-               $errors = $this->formatStatusMessages( $status->getErrorsByType( 'error' ) );
+
+               $errors = $this->getErrorFormatter()->arrayFromStatus( $status, 'error' );
                if ( $errors ) {
-                       ApiResult::setIndexedTagName( $errors, 'e' );
                        $ret['errors'] = $errors;
                }
-               $warnings = $this->formatStatusMessages( $status->getErrorsByType( 'warning' ) );
+               $warnings = $this->getErrorFormatter()->arrayFromStatus( $status, 'warning' );
                if ( $warnings ) {
-                       ApiResult::setIndexedTagName( $warnings, 'w' );
                        $ret['warnings'] = $warnings;
                }
 
                return $ret;
        }
 
-       private function formatStatusMessages( $messages ) {
-               if ( !$messages ) {
-                       return [];
-               }
-               $ret = [];
-               foreach ( $messages as $m ) {
-                       if ( $m['message'] instanceof Message ) {
-                               $msg = $m['message'];
-                               $message = [ 'message' => $msg->getKey() ];
-                               if ( $msg->getParams() ) {
-                                       $message['params'] = $msg->getParams();
-                                       ApiResult::setIndexedTagName( $message['params'], 'p' );
-                               }
-                       } else {
-                               $message = [ 'message' => $m['message'] ];
-                               $msg = wfMessage( $m['message'] );
-                               if ( isset( $m['params'] ) ) {
-                                       $message['params'] = $m['params'];
-                                       ApiResult::setIndexedTagName( $message['params'], 'p' );
-                                       $msg->params( $m['params'] );
-                               }
-                       }
-                       $message['rendered'] = $msg->useDatabase( false )->inLanguage( 'en' )->plain();
-                       $ret[] = $message;
-               }
-
-               return $ret;
-       }
-
        public function mustBePosted() {
                return true;
        }
index b9911da..9584f09 100644 (file)
@@ -69,24 +69,8 @@ class ApiRollback extends ApiBase {
                        $params['tags']
                );
 
-               // We don't care about multiple errors, just report one of them
                if ( $retval ) {
-                       if ( isset( $retval[0][0] ) &&
-                               ( $retval[0][0] == 'alreadyrolled' || $retval[0][0] == 'cantrollback' )
-                       ) {
-                               $error = $retval[0];
-                               $userMessage = $this->msg( $error[0], array_slice( $error, 1 ) );
-                               // dieUsageMsg() doesn't support $extraData
-                               $errorCode = $error[0];
-                               $errorInfo = isset( ApiBase::$messageMap[$errorCode] ) ?
-                                       ApiBase::$messageMap[$errorCode]['info'] :
-                                       $errorCode;
-                               $this->dieUsage( $errorInfo, $errorCode, 0, [
-                                       'messageHtml' => $userMessage->parseAsBlock()
-                               ] );
-                       }
-
-                       $this->dieUsageMsg( reset( $retval ) );
+                       $this->dieStatus( $this->errorArrayToStatus( $retval, $user ) );
                }
 
                $watch = 'preferences';
@@ -108,17 +92,6 @@ class ApiRollback extends ApiBase {
                        'last_revid' => intval( $details['target']->getID() )
                ];
 
-               $oldUser = $details['current']->getUserText( Revision::FOR_THIS_USER );
-               $lastUser = $details['target']->getUserText( Revision::FOR_THIS_USER );
-               $diffUrl = $titleObj->getFullURL( [
-                       'diff' => $info['revid'],
-                       'oldid' => $info['old_revid'],
-                       'diffonly' => '1'
-               ] );
-               $info['messageHtml'] = $this->msg( 'rollback-success-notify' )
-                       ->params( $oldUser, $lastUser, $diffUrl )
-                       ->parseAsBlock();
-
                $this->getResult()->addValue( null, $this->getModuleName(), $info );
        }
 
@@ -181,7 +154,7 @@ class ApiRollback extends ApiBase {
                        ? $params['user']
                        : User::getCanonicalName( $params['user'] );
                if ( !$this->mUser ) {
-                       $this->dieUsageMsg( [ 'invaliduser', $params['user'] ] );
+                       $this->dieWithError( [ 'apierror-invaliduser', wfEscapeWikiText( $params['user'] ) ] );
                }
 
                return $this->mUser;
@@ -202,17 +175,17 @@ class ApiRollback extends ApiBase {
                if ( isset( $params['title'] ) ) {
                        $this->mTitleObj = Title::newFromText( $params['title'] );
                        if ( !$this->mTitleObj || $this->mTitleObj->isExternal() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
+                               $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
                        }
                } elseif ( isset( $params['pageid'] ) ) {
                        $this->mTitleObj = Title::newFromID( $params['pageid'] );
                        if ( !$this->mTitleObj ) {
-                               $this->dieUsageMsg( [ 'nosuchpageid', $params['pageid'] ] );
+                               $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
                        }
                }
 
                if ( !$this->mTitleObj->exists() ) {
-                       $this->dieUsageMsg( 'notanarticle' );
+                       $this->dieWithError( 'apierror-missingtitle' );
                }
 
                return $this->mTitleObj;
index 3412f38..5769ff6 100644 (file)
@@ -38,11 +38,9 @@ class ApiSetNotificationTimestamp extends ApiBase {
                $user = $this->getUser();
 
                if ( $user->isAnon() ) {
-                       $this->dieUsage( 'Anonymous users cannot use watchlist change notifications', 'notloggedin' );
-               }
-               if ( !$user->isAllowed( 'editmywatchlist' ) ) {
-                       $this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
+                       $this->dieWithError( 'watchlistanontext', 'notloggedin' );
                }
+               $this->checkUserRightsAny( 'editmywatchlist' );
 
                $params = $this->extractRequestParams();
                $this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
@@ -52,8 +50,12 @@ class ApiSetNotificationTimestamp extends ApiBase {
 
                $pageSet = $this->getPageSet();
                if ( $params['entirewatchlist'] && $pageSet->getDataSource() !== null ) {
-                       $this->dieUsage(
-                               "Cannot use 'entirewatchlist' at the same time as '{$pageSet->getDataSource()}'",
+                       $this->dieWithError(
+                               [
+                                       'apierror-invalidparammix-cannotusewith',
+                                       $this->encodeParamName( 'entirewatchlist' ),
+                                       $pageSet->encodeParamName( $pageSet->getDataSource() )
+                               ],
                                'multisource'
                        );
                }
@@ -71,7 +73,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
 
                if ( isset( $params['torevid'] ) ) {
                        if ( $params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1 ) {
-                               $this->dieUsage( 'torevid may only be used with a single page', 'multpages' );
+                               $this->dieWithError( [ 'apierror-multpages', $this->encodeParamName( 'torevid' ) ] );
                        }
                        $title = reset( $pageSet->getGoodTitles() );
                        if ( $title ) {
@@ -85,7 +87,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
                        }
                } elseif ( isset( $params['newerthanrevid'] ) ) {
                        if ( $params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1 ) {
-                               $this->dieUsage( 'newerthanrevid may only be used with a single page', 'multpages' );
+                               $this->dieWithError( [ 'apierror-multpages', $this->encodeParamName( 'newerthanrevid' ) ] );
                        }
                        $title = reset( $pageSet->getGoodTitles() );
                        if ( $title ) {
index 92cbe90..e29fda5 100644 (file)
@@ -51,7 +51,7 @@ class ApiStashEdit extends ApiBase {
                $params = $this->extractRequestParams();
 
                if ( $user->isBot() ) { // sanity
-                       $this->dieUsage( 'This interface is not supported for bots', 'botsnotsupported' );
+                       $this->dieWithError( 'apierror-botsnotsupported' );
                }
 
                $cache = ObjectCache::getLocalClusterInstance();
@@ -61,9 +61,14 @@ class ApiStashEdit extends ApiBase {
                if ( !ContentHandler::getForModelID( $params['contentmodel'] )
                        ->isSupportedFormat( $params['contentformat'] )
                ) {
-                       $this->dieUsage( 'Unsupported content model/format', 'badmodelformat' );
+                       $this->dieWithError(
+                               [ 'apierror-badformat-generic', $params['contentformat'], $params['contentmodel'] ],
+                               'badmodelformat'
+                       );
                }
 
+               $this->requireAtLeastOneParameter( $params, 'stashedtexthash', 'text' );
+
                $text = null;
                $textHash = null;
                if ( strlen( $params['stashedtexthash'] ) ) {
@@ -72,15 +77,18 @@ class ApiStashEdit extends ApiBase {
                        $textKey = $cache->makeKey( 'stashedit', 'text', $textHash );
                        $text = $cache->get( $textKey );
                        if ( !is_string( $text ) ) {
-                               $this->dieUsage( 'No stashed text found with the given hash', 'missingtext' );
+                               $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
                        }
                } elseif ( $params['text'] !== null ) {
                        // Trim and fix newlines so the key SHA1's match (see WebRequest::getText())
                        $text = rtrim( str_replace( "\r\n", "\n", $params['text'] ) );
                        $textHash = sha1( $text );
                } else {
-                       $this->dieUsage(
-                               'The text or stashedtexthash parameter must be given', 'missingtextparam' );
+                       $this->dieWithError( [
+                               'apierror-missingparam-at-least-one-of',
+                               Message::listParam( [ '<var>stashedtexthash</var>', '<var>text</var>' ] ),
+                               2,
+                       ], 'missingparam' );
                }
 
                $textContent = ContentHandler::makeContent(
@@ -91,11 +99,11 @@ class ApiStashEdit extends ApiBase {
                        // Page exists: get the merged content with the proposed change
                        $baseRev = Revision::newFromPageId( $page->getId(), $params['baserevid'] );
                        if ( !$baseRev ) {
-                               $this->dieUsage( "No revision ID {$params['baserevid']}", 'missingrev' );
+                               $this->dieWithError( [ 'apierror-nosuchrevid', $params['baserevid'] ] );
                        }
                        $currentRev = $page->getRevision();
                        if ( !$currentRev ) {
-                               $this->dieUsage( "No current revision of page ID {$page->getId()}", 'missingrev' );
+                               $this->dieWithError( [ 'apierror-missingrev-pageid', $page->getId() ], 'missingrev' );
                        }
                        // Merge in the new version of the section to get the proposed version
                        $editContent = $page->replaceSectionAtRev(
@@ -105,7 +113,7 @@ class ApiStashEdit extends ApiBase {
                                $baseRev->getId()
                        );
                        if ( !$editContent ) {
-                               $this->dieUsage( 'Could not merge updated section.', 'replacefailed' );
+                               $this->dieWithError( 'apierror-sectionreplacefailed', 'replacefailed' );
                        }
                        if ( $currentRev->getId() == $baseRev->getId() ) {
                                // Base revision was still the latest; nothing to merge
@@ -115,7 +123,7 @@ class ApiStashEdit extends ApiBase {
                                $baseContent = $baseRev->getContent();
                                $currentContent = $currentRev->getContent();
                                if ( !$baseContent || !$currentContent ) {
-                                       $this->dieUsage( "Missing content for page ID {$page->getId()}", 'missingrev' );
+                                       $this->dieWithError( [ 'apierror-missingcontent-pageid', $page->getId() ], 'missingrev' );
                                }
                                $handler = ContentHandler::getForModelID( $baseContent->getModel() );
                                $content = $handler->merge3( $baseContent, $editContent, $currentContent );
index f88c2db..f6c0584 100644 (file)
@@ -30,10 +30,7 @@ class ApiTag extends ApiBase {
                $user = $this->getUser();
 
                // make sure the user is allowed
-               if ( !$user->isAllowed( 'changetags' ) ) {
-                       $this->dieUsage( "You don't have permission to add or remove change tags from individual edits",
-                               'permissiondenied' );
-               }
+               $this->checkUserRightsAny( 'changetags' );
 
                if ( $user->isBlocked() ) {
                        $this->dieBlocked( $user->getBlock() );
@@ -88,7 +85,8 @@ class ApiTag extends ApiBase {
 
                if ( !$valid ) {
                        $idResult['status'] = 'error';
-                       $idResult += $this->parseMsg( [ "nosuch$type", $id ] );
+                       // Messages: apierror-nosuchrcid apierror-nosuchrevid apierror-nosuchlogid
+                       $idResult += $this->getErrorFormatter()->formatMessage( [ "apierror-nosuch$type", $id ] );
                        return $idResult;
                }
 
index 4940394..fc2951a 100644 (file)
 class ApiTokens extends ApiBase {
 
        public function execute() {
-               $this->setWarning(
-                       'action=tokens has been deprecated. Please use action=query&meta=tokens instead.'
+               $this->addDeprecation(
+                       [ 'apiwarn-deprecation-withreplacement', 'action=tokens', 'action=query&meta=tokens' ],
+                       'action=tokens'
                );
-               $this->logFeatureUsage( 'action=tokens' );
 
                $params = $this->extractRequestParams();
                $res = [
@@ -46,7 +46,7 @@ class ApiTokens extends ApiBase {
                        $val = call_user_func( $types[$type], null, null );
 
                        if ( $val === false ) {
-                               $this->setWarning( "Action '$type' is not allowed for the current user" );
+                               $this->addWarning( [ 'apiwarn-tokennotallowed', $type ] );
                        } else {
                                $res[$type . 'token'] = $val;
                        }
index ace41a4..3eeb7a4 100644 (file)
@@ -39,25 +39,18 @@ class ApiUnblock extends ApiBase {
                $user = $this->getUser();
                $params = $this->extractRequestParams();
 
-               if ( is_null( $params['id'] ) && is_null( $params['user'] ) ) {
-                       $this->dieUsageMsg( 'unblock-notarget' );
-               }
-               if ( !is_null( $params['id'] ) && !is_null( $params['user'] ) ) {
-                       $this->dieUsageMsg( 'unblock-idanduser' );
-               }
+               $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
 
                if ( !$user->isAllowed( 'block' ) ) {
-                       $this->dieUsageMsg( 'cantunblock' );
+                       $this->dieWithError( 'apierror-permissiondenied-unblock', 'permissiondenied' );
                }
                # bug 15810: blocked admins should have limited access here
                if ( $user->isBlocked() ) {
                        $status = SpecialBlock::checkUnblockSelf( $params['user'], $user );
                        if ( $status !== true ) {
-                               $msg = $this->parseMsg( $status );
-                               $this->dieUsage(
-                                       $msg['info'],
-                                       $msg['code'],
-                                       0,
+                               $this->dieWithError(
+                                       $status,
+                                       null,
                                        [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
                                );
                        }
@@ -71,6 +64,16 @@ class ApiUnblock extends ApiBase {
                        }
                }
 
+               if ( $params['userid'] !== null ) {
+                       $username = User::whoIs( $params['userid'] );
+
+                       if ( $username === false ) {
+                               $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
+                       } else {
+                               $params['user'] = $username;
+                       }
+               }
+
                $data = [
                        'Target' => is_null( $params['id'] ) ? $params['user'] : "#{$params['id']}",
                        'Reason' => $params['reason'],
@@ -79,7 +82,7 @@ class ApiUnblock extends ApiBase {
                $block = Block::newFromTarget( $data['Target'] );
                $retval = SpecialUnblock::processUnblock( $data, $this->getContext() );
                if ( $retval !== true ) {
-                       $this->dieUsageMsg( $retval[0] );
+                       $this->dieStatus( $this->errorArrayToStatus( $retval ) );
                }
 
                $res['id'] = $block->getId();
@@ -104,6 +107,9 @@ class ApiUnblock extends ApiBase {
                                ApiBase::PARAM_TYPE => 'integer',
                        ],
                        'user' => null,
+                       'userid' => [
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ],
                        'reason' => '',
                        'tags' => [
                                ApiBase::PARAM_TYPE => 'tags',
index e24f2ce..7fda1ea 100644 (file)
@@ -33,18 +33,16 @@ class ApiUndelete extends ApiBase {
                $this->useTransactionalTimeLimit();
 
                $params = $this->extractRequestParams();
-               $user = $this->getUser();
-               if ( !$user->isAllowed( 'undelete' ) ) {
-                       $this->dieUsageMsg( 'permdenied-undelete' );
-               }
+               $this->checkUserRightsAny( 'undelete' );
 
+               $user = $this->getUser();
                if ( $user->isBlocked() ) {
                        $this->dieBlocked( $user->getBlock() );
                }
 
                $titleObj = Title::newFromText( $params['title'] );
                if ( !$titleObj || $titleObj->isExternal() ) {
-                       $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
+                       $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
                }
 
                // Check if user can add tags
@@ -76,7 +74,7 @@ class ApiUndelete extends ApiBase {
                        $params['tags']
                );
                if ( !is_array( $retval ) ) {
-                       $this->dieUsageMsg( 'cannotundelete' );
+                       $this->dieWithError( 'apierror-cantundelete' );
                }
 
                if ( $retval[1] ) {
index 7b44f40..6bdd68f 100644 (file)
@@ -36,7 +36,7 @@ class ApiUpload extends ApiBase {
        public function execute() {
                // Check whether upload is enabled
                if ( !UploadBase::isEnabled() ) {
-                       $this->dieUsageMsg( 'uploaddisabled' );
+                       $this->dieWithError( 'uploaddisabled' );
                }
 
                $user = $this->getUser();
@@ -61,11 +61,10 @@ class ApiUpload extends ApiBase {
                        if ( !$this->selectUploadModule() ) {
                                return; // not a true upload, but a status request or similar
                        } elseif ( !isset( $this->mUpload ) ) {
-                               $this->dieUsage( 'No upload module set', 'nomodule' );
+                               $this->dieDebug( __METHOD__, 'No upload module set' );
                        }
                } catch ( UploadStashException $e ) { // XXX: don't spam exception log
-                       list( $msg, $code ) = $this->handleStashException( get_class( $e ), $e->getMessage() );
-                       $this->dieUsage( $msg, $code );
+                       $this->dieStatus( $this->handleStashException( $e ) );
                }
 
                // First check permission to upload
@@ -75,19 +74,17 @@ class ApiUpload extends ApiBase {
                /** @var $status Status */
                $status = $this->mUpload->fetchFile();
                if ( !$status->isGood() ) {
-                       $errors = $status->getErrorsArray();
-                       $error = array_shift( $errors[0] );
-                       $this->dieUsage( 'Error fetching file from remote source', $error, 0, $errors[0] );
+                       $this->dieStatus( $status );
                }
 
                // Check if the uploaded file is sane
                if ( $this->mParams['chunk'] ) {
                        $maxSize = UploadBase::getMaxUploadSize();
                        if ( $this->mParams['filesize'] > $maxSize ) {
-                               $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
+                               $this->dieWithError( 'file-too-large' );
                        }
                        if ( !$this->mUpload->getTitle() ) {
-                               $this->dieUsage( 'Invalid file title supplied', 'internal-error' );
+                               $this->dieWithError( 'illegal-filename' );
                        }
                } elseif ( $this->mParams['async'] && $this->mParams['filekey'] ) {
                        // defer verification to background process
@@ -102,7 +99,7 @@ class ApiUpload extends ApiBase {
                if ( !$this->mParams['stash'] ) {
                        $permErrors = $this->mUpload->verifyTitlePermissions( $user );
                        if ( $permErrors !== true ) {
-                               $this->dieRecoverableError( $permErrors[0], 'filename' );
+                               $this->dieRecoverableError( $permErrors, 'filename' );
                        }
                }
 
@@ -110,8 +107,7 @@ class ApiUpload extends ApiBase {
                try {
                        $result = $this->getContextResult();
                } catch ( UploadStashException $e ) { // XXX: don't spam exception log
-                       list( $msg, $code ) = $this->handleStashException( get_class( $e ), $e->getMessage() );
-                       $this->dieUsage( $msg, $code );
+                       $this->dieStatus( $this->handleStashException( $e ) );
                }
                $this->getResult()->addValue( null, $this->getModuleName(), $result );
 
@@ -146,7 +142,7 @@ class ApiUpload extends ApiBase {
                // Check throttle after we've handled warnings
                if ( UploadBase::isThrottled( $this->getUser() )
                ) {
-                       $this->dieUsageMsg( 'actionthrottledtext' );
+                       $this->dieWithError( 'apierror-ratelimited' );
                }
 
                // This is the most common case -- a normal upload with no warnings
@@ -208,16 +204,12 @@ class ApiUpload extends ApiBase {
 
                // Sanity check sizing
                if ( $totalSoFar > $this->mParams['filesize'] ) {
-                       $this->dieUsage(
-                               'Offset plus current chunk is greater than claimed file size', 'invalid-chunk'
-                       );
+                       $this->dieWithError( 'apierror-invalid-chunk' );
                }
 
                // Enforce minimum chunk size
                if ( $totalSoFar != $this->mParams['filesize'] && $chunkSize < $minChunkSize ) {
-                       $this->dieUsage(
-                               "Minimum chunk size is $minChunkSize bytes for non-final chunks", 'chunk-too-small'
-                       );
+                       $this->dieWithError( [ 'apierror-chunk-too-small', Message::numParam( $minChunkSize ) ] );
                }
 
                if ( $this->mParams['offset'] == 0 ) {
@@ -229,11 +221,9 @@ class ApiUpload extends ApiBase {
                        $progress = UploadBase::getSessionStatus( $this->getUser(), $filekey );
                        if ( !$progress ) {
                                // Probably can't get here, but check anyway just in case
-                               $this->dieUsage( 'No chunked upload session with this key', 'stashfailed' );
+                               $this->dieWithError( 'apierror-stashfailed-nosession', 'stashfailed' );
                        } elseif ( $progress['result'] !== 'Continue' || $progress['stage'] !== 'uploading' ) {
-                               $this->dieUsage(
-                                       'Chunked upload is already completed, check status for details', 'stashfailed'
-                               );
+                               $this->dieWithError( 'apierror-stashfailed-complete', 'stashfailed' );
                        }
 
                        $status = $this->mUpload->addChunk(
@@ -352,16 +342,13 @@ class ApiUpload extends ApiBase {
                        list( $exceptionType, $message ) = $status->getMessage()->getParams();
                        $debugMessage = 'Stashing temporary file failed: ' . $exceptionType . ' ' . $message;
                        wfDebug( __METHOD__ . ' ' . $debugMessage . "\n" );
-                       list( $msg, $code ) = $this->handleStashException( $exceptionType, $message );
-                       $status = Status::newFatal( new ApiRawMessage( $msg, $code ) );
                }
 
                // Bad status
                if ( $failureMode !== 'optional' ) {
                        $this->dieStatus( $status );
                } else {
-                       list( $code, $msg ) = $this->getErrorFromStatus( $status );
-                       $data['stashfailed'] = $msg;
+                       $data['stasherrors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
                        return null;
                }
        }
@@ -370,25 +357,25 @@ class ApiUpload extends ApiBase {
         * Throw an error that the user can recover from by providing a better
         * value for $parameter
         *
-        * @param array|string|MessageSpecifier $error Error suitable for passing to dieUsageMsg()
-        * @param string $parameter Parameter that needs revising
-        * @param array $data Optional extra data to pass to the user
-        * @param string $code Error code to use if the error is unknown
-        * @throws UsageException
+        * @param array $errors Array of Message objects, message keys, key+param
+        *  arrays, or StatusValue::getErrors()-style arrays
+        * @param string|null $parameter Parameter that needs revising
+        * @throws ApiUsageException
         */
-       private function dieRecoverableError( $error, $parameter, $data = [], $code = 'unknownerror' ) {
+       private function dieRecoverableError( $errors, $parameter = null ) {
                $this->performStash( 'optional', $data );
-               $data['invalidparameter'] = $parameter;
 
-               $parsed = $this->parseMsg( $error );
-               if ( isset( $parsed['data'] ) ) {
-                       $data = array_merge( $data, $parsed['data'] );
-               }
-               if ( $parsed['code'] === 'unknownerror' ) {
-                       $parsed['code'] = $code;
+               if ( $parameter ) {
+                       $data['invalidparameter'] = $parameter;
                }
 
-               $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
+               $sv = StatusValue::newGood();
+               foreach ( $errors as $error ) {
+                       $msg = ApiMessage::create( $error );
+                       $msg->setApiData( $msg->getApiData() + $data );
+                       $sv->fatal( $msg );
+               }
+               $this->dieStatus( $sv );
        }
 
        /**
@@ -398,20 +385,18 @@ class ApiUpload extends ApiBase {
         * @param Status $status
         * @param string $overrideCode Error code to use if there isn't one from IApiMessage
         * @param array|null $moreExtraData
-        * @throws UsageException
+        * @throws ApiUsageException
         */
        public function dieStatusWithCode( $status, $overrideCode, $moreExtraData = null ) {
-               $extraData = null;
-               list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
-               $errors = $status->getErrorsByType( 'error' ) ?: $status->getErrorsByType( 'warning' );
-               if ( !( $errors[0]['message'] instanceof IApiMessage ) ) {
-                       $code = $overrideCode;
-               }
-               if ( $moreExtraData ) {
-                       $extraData = $extraData ?: [];
-                       $extraData += $moreExtraData;
+               $sv = StatusValue::newGood();
+               foreach ( $status->getErrors() as $error ) {
+                       $msg = ApiMessage::create( $error, $overrideCode );
+                       if ( $moreExtraData ) {
+                               $msg->setApiData( $msg->getApiData() + $moreExtraData );
+                       }
+                       $sv->fatal( $msg );
                }
-               $this->dieUsage( $msg, $code, 0, $extraData );
+               $this->dieStatus( $sv );
        }
 
        /**
@@ -434,7 +419,7 @@ class ApiUpload extends ApiBase {
                if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
                        $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
                        if ( !$progress ) {
-                               $this->dieUsage( 'No result in status data', 'missingresult' );
+                               $this->dieWithError( 'api-upload-missingresult', 'missingresult' );
                        } elseif ( !$progress['status']->isGood() ) {
                                $this->dieStatusWithCode( $progress['status'], 'stashfailed' );
                        }
@@ -466,7 +451,7 @@ class ApiUpload extends ApiBase {
 
                // The following modules all require the filename parameter to be set
                if ( is_null( $this->mParams['filename'] ) ) {
-                       $this->dieUsageMsg( [ 'missingparam', 'filename' ] );
+                       $this->dieWithError( [ 'apierror-missingparam', 'filename' ] );
                }
 
                if ( $this->mParams['chunk'] ) {
@@ -474,7 +459,7 @@ class ApiUpload extends ApiBase {
                        $this->mUpload = new UploadFromChunks( $this->getUser() );
                        if ( isset( $this->mParams['filekey'] ) ) {
                                if ( $this->mParams['offset'] === 0 ) {
-                                       $this->dieUsage( 'Cannot supply a filekey when offset is 0', 'badparams' );
+                                       $this->dieWithError( 'apierror-upload-filekeynotallowed', 'filekeynotallowed' );
                                }
 
                                // handle new chunk
@@ -485,7 +470,7 @@ class ApiUpload extends ApiBase {
                                );
                        } else {
                                if ( $this->mParams['offset'] !== 0 ) {
-                                       $this->dieUsage( 'Must supply a filekey when offset is non-zero', 'badparams' );
+                                       $this->dieWithError( 'apierror-upload-filekeyneeded', 'filekeyneeded' );
                                }
 
                                // handle first chunk
@@ -497,7 +482,7 @@ class ApiUpload extends ApiBase {
                } elseif ( isset( $this->mParams['filekey'] ) ) {
                        // Upload stashed in a previous request
                        if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
-                               $this->dieUsageMsg( 'invalid-file-key' );
+                               $this->dieWithError( 'apierror-invalid-file-key' );
                        }
 
                        $this->mUpload = new UploadFromStash( $this->getUser() );
@@ -515,15 +500,15 @@ class ApiUpload extends ApiBase {
                } elseif ( isset( $this->mParams['url'] ) ) {
                        // Make sure upload by URL is enabled:
                        if ( !UploadFromUrl::isEnabled() ) {
-                               $this->dieUsageMsg( 'copyuploaddisabled' );
+                               $this->dieWithError( 'copyuploaddisabled' );
                        }
 
                        if ( !UploadFromUrl::isAllowedHost( $this->mParams['url'] ) ) {
-                               $this->dieUsageMsg( 'copyuploadbaddomain' );
+                               $this->dieWithError( 'apierror-copyuploadbaddomain' );
                        }
 
                        if ( !UploadFromUrl::isAllowedUrl( $this->mParams['url'] ) ) {
-                               $this->dieUsageMsg( 'copyuploadbadurl' );
+                               $this->dieWithError( 'apierror-copyuploadbadurl' );
                        }
 
                        $this->mUpload = new UploadFromUrl;
@@ -545,10 +530,10 @@ class ApiUpload extends ApiBase {
 
                if ( $permission !== true ) {
                        if ( !$user->isLoggedIn() ) {
-                               $this->dieUsageMsg( [ 'mustbeloggedin', 'upload' ] );
+                               $this->dieWithError( [ 'apierror-mustbeloggedin', $this->msg( 'action-upload' ) ] );
                        }
 
-                       $this->dieUsageMsg( 'badaccess-groups' );
+                       $this->dieStatus( User::newFatalPermissionDeniedStatus( $permission ) );
                }
 
                // Check blocks
@@ -583,28 +568,31 @@ class ApiUpload extends ApiBase {
                switch ( $verification['status'] ) {
                        // Recoverable errors
                        case UploadBase::MIN_LENGTH_PARTNAME:
-                               $this->dieRecoverableError( 'filename-tooshort', 'filename' );
+                               $this->dieRecoverableError( [ 'filename-tooshort' ], 'filename' );
                                break;
                        case UploadBase::ILLEGAL_FILENAME:
-                               $this->dieRecoverableError( 'illegal-filename', 'filename',
-                                       [ 'filename' => $verification['filtered'] ] );
+                               $this->dieRecoverableError(
+                                       [ ApiMessage::create(
+                                               'illegal-filename', null, [ 'filename' => $verification['filtered'] ]
+                                       ) ], 'filename'
+                               );
                                break;
                        case UploadBase::FILENAME_TOO_LONG:
-                               $this->dieRecoverableError( 'filename-toolong', 'filename' );
+                               $this->dieRecoverableError( [ 'filename-toolong' ], 'filename' );
                                break;
                        case UploadBase::FILETYPE_MISSING:
-                               $this->dieRecoverableError( 'filetype-missing', 'filename' );
+                               $this->dieRecoverableError( [ 'filetype-missing' ], 'filename' );
                                break;
                        case UploadBase::WINDOWS_NONASCII_FILENAME:
-                               $this->dieRecoverableError( 'windows-nonascii-filename', 'filename' );
+                               $this->dieRecoverableError( [ 'windows-nonascii-filename' ], 'filename' );
                                break;
 
                        // Unrecoverable errors
                        case UploadBase::EMPTY_FILE:
-                               $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
+                               $this->dieWithError( 'empty-file' );
                                break;
                        case UploadBase::FILE_TOO_LARGE:
-                               $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
+                               $this->dieWithError( 'file-too-large' );
                                break;
 
                        case UploadBase::FILETYPE_BADTYPE:
@@ -612,57 +600,47 @@ class ApiUpload extends ApiBase {
                                        'filetype' => $verification['finalExt'],
                                        'allowed' => array_values( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) )
                                ];
+                               $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
+                               $msg = [
+                                       'filetype-banned-type',
+                                       null, // filled in below
+                                       Message::listParam( $extensions, 'comma' ),
+                                       count( $extensions ),
+                                       null, // filled in below
+                               ];
                                ApiResult::setIndexedTagName( $extradata['allowed'], 'ext' );
 
-                               $msg = 'Filetype not permitted: ';
                                if ( isset( $verification['blacklistedExt'] ) ) {
-                                       $msg .= implode( ', ', $verification['blacklistedExt'] );
+                                       $msg[1] = Message::listParam( $verification['blacklistedExt'], 'comma' );
+                                       $msg[4] = count( $verification['blacklistedExt'] );
                                        $extradata['blacklisted'] = array_values( $verification['blacklistedExt'] );
                                        ApiResult::setIndexedTagName( $extradata['blacklisted'], 'ext' );
                                } else {
-                                       $msg .= $verification['finalExt'];
+                                       $msg[1] = $verification['finalExt'];
+                                       $msg[4] = 1;
                                }
-                               $this->dieUsage( $msg, 'filetype-banned', 0, $extradata );
+
+                               $this->dieWithError( $msg, 'filetype-banned', $extradata );
                                break;
+
                        case UploadBase::VERIFICATION_ERROR:
-                               $parsed = $this->parseMsg( $verification['details'] );
-                               $info = "This file did not pass file verification: {$parsed['info']}";
-                               if ( $verification['details'][0] instanceof IApiMessage ) {
-                                       $code = $parsed['code'];
-                               } else {
-                                       // For backwards-compatibility, all of the errors from UploadBase::verifyFile() are
-                                       // reported as 'verification-error', and the real error code is reported in 'details'.
-                                       $code = 'verification-error';
-                               }
-                               if ( $verification['details'][0] instanceof IApiMessage ) {
-                                       $msg = $verification['details'][0];
+                               $msg = ApiMessage::create( $verification['details'], 'verification-error' );
+                               if ( $verification['details'][0] instanceof MessageSpecifier ) {
                                        $details = array_merge( [ $msg->getKey() ], $msg->getParams() );
                                } else {
                                        $details = $verification['details'];
                                }
                                ApiResult::setIndexedTagName( $details, 'detail' );
-                               $data = [ 'details' => $details ];
-                               if ( isset( $parsed['data'] ) ) {
-                                       $data = array_merge( $data, $parsed['data'] );
-                               }
-
-                               $this->dieUsage( $info, $code, 0, $data );
+                               $msg->setApiData( $msg->getApiData() + [ 'details' => $details ] );
+                               $this->dieWithError( $msg );
                                break;
+
                        case UploadBase::HOOK_ABORTED:
-                               if ( is_array( $verification['error'] ) ) {
-                                       $params = $verification['error'];
-                               } elseif ( $verification['error'] !== '' ) {
-                                       $params = [ $verification['error'] ];
-                               } else {
-                                       $params = [ 'hookaborted' ];
-                               }
-                               $key = array_shift( $params );
-                               $msg = $this->msg( $key, $params )->inLanguage( 'en' )->useDatabase( false )->text();
-                               $this->dieUsage( $msg, 'hookaborted', 0, [ 'details' => $verification['error'] ] );
+                               $this->dieWithError( $params, 'hookaborted', [ 'details' => $verification['error'] ] );
                                break;
                        default:
-                               $this->dieUsage( 'An unknown error occurred', 'unknown-error',
-                                       0, [ 'details' => [ 'code' => $verification['status'] ] ] );
+                               $this->dieWithError( 'apierror-unknownerror-nocode', 'unknown-error',
+                                       [ 'details' => [ 'code' => $verification['status'] ] ] );
                                break;
                }
        }
@@ -735,41 +713,31 @@ class ApiUpload extends ApiBase {
 
        /**
         * Handles a stash exception, giving a useful error to the user.
-        * @param string $exceptionType Class name of the exception we encountered.
-        * @param string $message Message of the exception we encountered.
-        * @return array Array of message and code, suitable for passing to dieUsage()
+        * @todo Internationalize the exceptions
+        * @param Exception $e
+        * @return StatusValue
         */
-       protected function handleStashException( $exceptionType, $message ) {
-               switch ( $exceptionType ) {
+       protected function handleStashException( $e ) {
+               $err = wfEscapeWikiText( $e->getMessage() );
+               switch ( get_class( $exception ) ) {
                        case 'UploadStashFileNotFoundException':
-                               return [
-                                       'Could not find the file in the stash: ' . $message,
-                                       'stashedfilenotfound'
-                               ];
+                               return StatusValue::newFatal( 'apierror-stashedfilenotfound', $err );
                        case 'UploadStashBadPathException':
-                               return [
-                                       'File key of improper format or otherwise invalid: ' . $message,
-                                       'stashpathinvalid'
-                               ];
+                               return StatusValue::newFatal( 'apierror-stashpathinvalid', $err );
                        case 'UploadStashFileException':
-                               return [
-                                       'Could not store upload in the stash: ' . $message,
-                                       'stashfilestorage'
-                               ];
+                               return StatusValue::newFatal( 'apierror-stashfilestorage', $err );
                        case 'UploadStashZeroLengthFileException':
-                               return [
-                                       'File is of zero length, and could not be stored in the stash: ' .
-                                               $message,
-                                       'stashzerolength'
-                               ];
+                               return StatusValue::newFatal( 'apierror-stashzerolength', $err );
                        case 'UploadStashNotLoggedInException':
-                               return [ 'Not logged in: ' . $message, 'stashnotloggedin' ];
+                               return StatusValue::newFatal( ApiMessage::create(
+                                       [ 'apierror-mustbeloggedin', $this->msg( 'action-upload' ) ], 'stashnotloggedin'
+                               ) );
                        case 'UploadStashWrongOwnerException':
-                               return [ 'Wrong owner: ' . $message, 'stashwrongowner' ];
+                               return StatusValue::newFatal( 'apierror-stashwrongowner', $err );
                        case 'UploadStashNoSuchKeyException':
-                               return [ 'No such filekey: ' . $message, 'stashnosuchfilekey' ];
+                               return StatusValue::newFatal( 'apierror-stashnosuchfilekey', $err );
                        default:
-                               return [ $exceptionType . ': ' . $message, 'stasherror' ];
+                               return StatusValue::newFatal( 'uploadstash-exception', get_class( $e ), $err );
                }
        }
 
@@ -821,7 +789,7 @@ class ApiUpload extends ApiBase {
                if ( $this->mParams['async'] ) {
                        $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
                        if ( $progress && $progress['result'] === 'Poll' ) {
-                               $this->dieUsage( 'Upload from stash already in progress.', 'publishfailed' );
+                               $this->dieWithError( 'apierror-upload-inprogress', 'publishfailed' );
                        }
                        UploadBase::setSessionStatus(
                                $this->getUser(),
@@ -848,14 +816,7 @@ class ApiUpload extends ApiBase {
                                $this->mParams['text'], $watch, $this->getUser(), $this->mParams['tags'] );
 
                        if ( !$status->isGood() ) {
-                               // Is there really no better way to do this?
-                               $errors = $status->getErrorsByType( 'error' );
-                               $msg = array_merge( [ $errors[0]['message'] ], $errors[0]['params'] );
-                               $data = $status->getErrorsArray();
-                               ApiResult::setIndexedTagName( $data, 'error' );
-                               // For backwards-compatibility, we use the 'internal-error' fallback key and merge $data
-                               // into the root of the response (rather than something sane like [ 'details' => $data ]).
-                               $this->dieRecoverableError( $msg, null, $data, 'internal-error' );
+                               $this->dieRecoverableError( $status->getErrors() );
                        }
                        $result['result'] = 'Success';
                }
diff --git a/includes/api/ApiUsageException.php b/includes/api/ApiUsageException.php
new file mode 100644 (file)
index 0000000..7e21ab5
--- /dev/null
@@ -0,0 +1,217 @@
+<?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
+ * @defgroup API API
+ */
+
+/**
+ * This exception will be thrown when dieUsage is called to stop module execution.
+ *
+ * @ingroup API
+ * @deprecated since 1.29, use ApiUsageException instead
+ */
+class UsageException extends MWException {
+
+       private $mCodestr;
+
+       /**
+        * @var null|array
+        */
+       private $mExtraData;
+
+       /**
+        * @param string $message
+        * @param string $codestr
+        * @param int $code
+        * @param array|null $extradata
+        */
+       public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
+               parent::__construct( $message, $code );
+               $this->mCodestr = $codestr;
+               $this->mExtraData = $extradata;
+
+               // This should never happen, so throw an exception about it that will
+               // hopefully get logged with a backtrace (T138585)
+               if ( !is_string( $codestr ) || $codestr === '' ) {
+                       throw new InvalidArgumentException( 'Invalid $codestr, was ' .
+                               ( $codestr === '' ? 'empty string' : gettype( $codestr ) )
+                       );
+               }
+       }
+
+       /**
+        * @return string
+        */
+       public function getCodeString() {
+               return $this->mCodestr;
+       }
+
+       /**
+        * @return array
+        */
+       public function getMessageArray() {
+               $result = [
+                       'code' => $this->mCodestr,
+                       'info' => $this->getMessage()
+               ];
+               if ( is_array( $this->mExtraData ) ) {
+                       $result = array_merge( $result, $this->mExtraData );
+               }
+
+               return $result;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return "{$this->getCodeString()}: {$this->getMessage()}";
+       }
+}
+
+/**
+ * Exception used to abort API execution with an error
+ *
+ * If possible, use ApiBase::dieWithError() instead of throwing this directly.
+ *
+ * @ingroup API
+ * @note This currently extends UsageException for backwards compatibility, so
+ *  all the existing code that catches UsageException won't break when stuff
+ *  starts throwing ApiUsageException. Eventually UsageException will go away
+ *  and this will (probably) extend MWException directly.
+ */
+class ApiUsageException extends UsageException {
+
+       protected $modulePath;
+       protected $status;
+
+       /**
+        * @param ApiBase|null $module API module responsible for the error, if known
+        * @param StatusValue $status Status holding errors
+        * @param int $httpCode HTTP error code to use
+        */
+       public function __construct(
+               ApiBase $module = null, StatusValue $status, $httpCode = 0
+       ) {
+               if ( $status->isOK() ) {
+                       throw new InvalidArgumentException( __METHOD__ . ' requires a fatal Status' );
+               }
+
+               $this->modulePath = $module ? $module->getModulePath() : null;
+               $this->status = $status;
+
+               // Bug T46111: Messages in the log files should be in English and not
+               // customized by the local wiki.
+               $enMsg = clone $this->getApiMessage();
+               $enMsg->inLanguage( 'en' )->useDatabase( false );
+               parent::__construct(
+                       ApiErrorFormatter::stripMarkup( $enMsg->text() ),
+                       $enMsg->getApiCode(),
+                       $httpCode,
+                       $enMsg->getApiData()
+               );
+       }
+
+       /**
+        * @param ApiBase|null $module API module responsible for the error, if known
+        * @param string|array|Message $msg See ApiMessage::create()
+        * @param string|null $code See ApiMessage::create()
+        * @param array|null $data See ApiMessage::create()
+        * @param int $httpCode HTTP error code to use
+        * @return static
+        */
+       public static function newWithMessage(
+               ApiBase $module = null, $msg, $code = null, $data = null, $httpCode = 0
+       ) {
+               return new static(
+                       $module,
+                       StatusValue::newFatal( ApiMessage::create( $msg, $code, $data ) ),
+                       $httpCode
+               );
+       }
+
+       /**
+        * @returns ApiMessage
+        */
+       private function getApiMessage() {
+               $errors = $this->status->getErrorsByType( 'error' );
+               if ( !$errors ) {
+                       $errors = $this->status->getErrors();
+               }
+               if ( !$errors ) {
+                       $msg = new ApiMessage( 'apierror-unknownerror-nocode', 'unknownerror' );
+               } else {
+                       $msg = ApiMessage::create( $errors[0] );
+               }
+               return $msg;
+       }
+
+       /**
+        * Fetch the responsible module name
+        * @return string|null
+        */
+       public function getModulePath() {
+               return $this->modulePath;
+       }
+
+       /**
+        * Fetch the error status
+        * @return StatusValue
+        */
+       public function getStatusValue() {
+               return $this->status;
+       }
+
+       /**
+        * @deprecated Do not use. This only exists here because UsageException is in
+        *  the inheritance chain for backwards compatibility.
+        * @inheritdoc
+        */
+       public function getCodeString() {
+               return $this->getApiMessage()->getApiCode();
+       }
+
+       /**
+        * @deprecated Do not use. This only exists here because UsageException is in
+        *  the inheritance chain for backwards compatibility.
+        * @inheritdoc
+        */
+       public function getMessageArray() {
+               $enMsg = clone $this->getApiMessage();
+               $enMsg->inLanguage( 'en' )->useDatabase( false );
+
+               return [
+                       'code' => $enMsg->getApiCode(),
+                       'info' => ApiErrorFormatter::stripMarkup( $enMsg->text() ),
+               ] + $enMsg->getApiData();
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               $enMsg = clone $this->getApiMessage();
+               $enMsg->inLanguage( 'en' )->useDatabase( false );
+               $text = ApiErrorFormatter::stripMarkup( $enMsg->text() );
+
+               return get_class( $this ) . ": {$enMsg->getApiCode()}: {$text} "
+                       . "in {$this->getFile()}:{$this->getLine()}\n"
+                       . "Stack trace:\n{$this->getTraceAsString()}";
+       }
+
+}
index 3a7a082..88aff41 100644 (file)
@@ -35,12 +35,10 @@ class ApiWatch extends ApiBase {
        public function execute() {
                $user = $this->getUser();
                if ( !$user->isLoggedIn() ) {
-                       $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+                       $this->dieWithError( 'watchlistanontext', 'notloggedin' );
                }
 
-               if ( !$user->isAllowed( 'editmywatchlist' ) ) {
-                       $this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
-               }
+               $this->checkUserRightsAny( 'editmywatchlist' );
 
                $params = $this->extractRequestParams();
 
@@ -78,16 +76,19 @@ class ApiWatch extends ApiBase {
                        } ) );
 
                        if ( $extraParams ) {
-                               $p = $this->getModulePrefix();
-                               $this->dieUsage(
-                                       "The parameter {$p}title can not be used with " . implode( ', ', $extraParams ),
+                               $this->dieWithError(
+                                       [
+                                               'apierror-invalidparammix-cannotusewith',
+                                               $this->encodeParamName( 'title' ),
+                                               $pageSet->encodeParamName( $extraParams[0] )
+                                       ],
                                        'invalidparammix'
                                );
                        }
 
                        $title = Title::newFromText( $params['title'] );
                        if ( !$title || !$title->isWatchable() ) {
-                               $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
+                               $this->dieWithError( [ 'invalidtitle', $params['title'] ] );
                        }
                        $res = $this->watchTitle( $title, $user, $params, true );
                }
@@ -109,26 +110,20 @@ class ApiWatch extends ApiBase {
                if ( $params['unwatch'] ) {
                        $status = UnwatchAction::doUnwatch( $title, $user );
                        $res['unwatched'] = $status->isOK();
-                       if ( $status->isOK() ) {
-                               $msgKey = $title->isTalkPage() ? 'removedwatchtext-talk' : 'removedwatchtext';
-                               $res['message'] = $this->msg( $msgKey, $title->getPrefixedText() )
-                                       ->title( $title )->parseAsBlock();
-                       }
                } else {
                        $status = WatchAction::doWatch( $title, $user );
                        $res['watched'] = $status->isOK();
-                       if ( $status->isOK() ) {
-                               $msgKey = $title->isTalkPage() ? 'addedwatchtext-talk' : 'addedwatchtext';
-                               $res['message'] = $this->msg( $msgKey, $title->getPrefixedText() )
-                                       ->title( $title )->parseAsBlock();
-                       }
                }
 
                if ( !$status->isOK() ) {
                        if ( $compatibilityMode ) {
                                $this->dieStatus( $status );
                        }
-                       $res['error'] = $this->getErrorFromStatus( $status );
+                       $res['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'error' );
+                       $res['warnings'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'warning' );
+                       if ( !$res['warnings'] ) {
+                               unset( $res['warnings'] );
+                       }
                }
 
                return $res;
index 5072c66..03c9f52 100644 (file)
@@ -71,5 +71,6 @@
        "apihelp-query+allrevisions-description": "اعرض كل المراجعات.",
        "apihelp-query+blocks-example-simple": "قائمة المنع.",
        "apihelp-query+imageinfo-paramvalue-prop-userid": "إضافة هوية المستخدم الذي قام بتحميل كل إصدار ملف.",
-       "apihelp-query+prefixsearch-param-offset": "عدد النتائج المراد تخطيها."
+       "apihelp-query+prefixsearch-param-offset": "عدد النتائج المراد تخطيها.",
+       "api-feed-error-title": "خطأ ($1)"
 }
index 3818ce9..6634645 100644 (file)
@@ -18,7 +18,7 @@
        "apihelp-main-param-origin": "Пры звароце да API з дапамогай міждамэннага AJAX-запыту (CORS), выстаўце парамэтру значэньне зыходнага дамэну. Ён мусіць быць уключаны ў кожны папярэдні запыт і такім чынам мусіць быць часткай URI-запыту (ня цела POST).\n\nДля аўтэнтыфікаваных запытаў ён мусіць супадаць з адной з крыніц у загалоўку <code>Origin</code>, павінна быць зададзена нешта кшталту <kbd>https://en.wikipedia.org</kbd> або <kbd>https://meta.wikimedia.org</kbd>. Калі парамэтар не супадае з загалоўкам <code>Origin</code>, будзе вернуты адказ з кодам памылкі 403. Калі парамэтар супадае з загалоўкам <code>Origin</code> і крыніца знаходзіцца ў белым сьпісе, будуць выстаўленыя загалоўкі <code>Access-Control-Allow-Origin</code> і <code>Access-Control-Allow-Credentials</code>.\n\nДля неаўтэнтыфікаваных запытаў выстаўце значэньне <kbd>*</kbd>. Гэта прывядзе да выстаўленьня загалоўку <code>Access-Control-Allow-Origin</code>, але <code>Access-Control-Allow-Credentials</code> будзе мець значэньне <code>false</code> і ўсе зьвесткі пра карыстальніка будуць абмежаваныя.",
        "apihelp-main-param-uselang": "Мова для выкарыстаньня ў перакладах паведамленьняў. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> з <kbd>siprop=languages</kbd> вяртае сьпіс кодаў мовы, або трэба вызначыць <kbd>user</kbd>, каб ужываць налады мовы цяперашняга карыстальніка, або вызначыць <kbd>content</kbd>, каб ужываць мову зьместу гэтай вікі.",
        "apihelp-block-description": "Блякаваньне ўдзельніка.",
-       "apihelp-block-param-user": "Імя ўдзельніка, IP-адрас або IP-дыяпазон, якія вы хочаце заблякаваць.",
+       "apihelp-block-param-user": "Імя ўдзельніка, IP-адрас або IP-дыяпазон, якія вы хочаце заблякаваць. Ня можа быць ужыты разам з <var>$1userid</var>",
        "apihelp-block-param-expiry": "Час заканчэньня. Можа быць адносным (напрыклад, <kbd>5 months</kbd> або <kbd>2 weeks</kbd>) ці абсалютным (напрыклад, <kbd>2014-09-18T12:34:56Z</kbd>). Калі выстаўлены на <kbd>infinite</kbd>, <kbd>indefinite</kbd> ці <kbd>never</kbd>, блякаваньне будзе бестэрміновым.",
        "apihelp-block-param-reason": "Прычына блякаваньня.",
        "apihelp-block-param-anononly": "Заблякаваць толькі ананімных удзельнікаў (напрыклад, забараніць ананімныя праўкі з гэтага IP-адрасу).",
index 2396f69..cf50f90 100644 (file)
        "api-help-permissions-granted-to": "Uděleno {{PLURAL:$1|skupině|skupinám}}: $2",
        "api-help-right-apihighlimits": "Používání vyšších limitů v API dotazech (pomalé dotazy: $1, rychlé dotazy: $2). Limity pro pomalé dotazy se vztahují i na vícehodnotové parametry.",
        "api-help-open-in-apisandbox": "<small>[otevřít v pískovišti]</small>",
+       "apierror-nosuchsection-what": "$2 neobsahuje sekci $1.",
+       "apierror-sectionsnotsupported-what": "$1 nepodporuje sekce.",
        "api-credits-header": "Zásluhy",
        "api-credits": "Vývojáři API:\n* Roan Kattouw (hlavní vývojář září 2007–2009)\n* Viktor Vasiljev\n* Bryan Tong Minh\n* Sam Reed\n* Jurij Astrachan (tvůrce, hlavní vývojář září 2006–září 2007)\n* Brad Jorsch (hlavní vývojář od 2013)\n\nSvé komentáře, návrhy či dotazy posílejte na mediawiki-api@lists.wikimedia.org\nnebo založte chybové hlášení na https://phabricator.wikimedia.org/."
 }
index f1ce435..404c3b2 100644 (file)
        "apihelp-main-param-smaxage": "Den <code>s-maxage</code>-HTTP-Cache-Control-Header auf diese Anzahl Sekunden festlegen. Fehler werden niemals gepuffert.",
        "apihelp-main-param-maxage": "Den <code>max-age</code>-HTTP-Cache-Control-Header auf diese Anzahl Sekunden festlegen. Fehler werden niemals gecacht.",
        "apihelp-main-param-assert": "Sicherstellen, dass der Benutzer eingeloggt ist, wenn auf <kbd>user</kbd> gesetzt, oder Bot ist, wenn auf <kbd>bot</kbd> gesetzt.",
+       "apihelp-main-param-assertuser": "Überprüft, ob der aktuelle Benutzer der benannte Benutzer ist.",
        "apihelp-main-param-requestid": "Der angegebene Wert wird mit in die Antwort aufgenommen und kann zur Unterscheidung von Anfragen verwendet werden.",
        "apihelp-main-param-servedby": "Namen des bearbeitenden Hosts mit zurückgeben.",
        "apihelp-main-param-curtimestamp": "Aktuellen Zeitstempel mit zurückgeben.",
+       "apihelp-main-param-responselanginfo": "Bezieht die für <var>uselang</var> und <var>errorlang</var> verwendeten Sprachen im Ergebnis mit ein.",
        "apihelp-main-param-origin": "Beim Zugriff auf die API mit einer Kreuz-Domain-AJAX-Anfrage (CORS) muss dies als entstehende Domäne festgelegt werden. Dies muss in jeder Vorfluganfrage mit eingeschlossen werden und deshalb ein Teil der Anfragen-URI sein (nicht des POST-Körpers).\n\nFür authentifizierte Anfragen muss dies exakt einem der Ursprünge im Header <code>Origin</code> entsprechen, so dass es auf etwas wie <kbd>https://de.wikipedia.org</kbd> oder <kbd>https://meta.wikimedia.org</kbd> festgelegt werden muss. Falls dieser Parameter nicht mit dem Header <code>Origin</code> übereinstimmt, wird eine 403-Antwort zurückgegeben. Falls dieser Parameter mit dem Header <code>Origin</code> übereinstimmt und der Ursprung weißgelistet ist, werden die Header <code>Access-Control-Allow-Origin</code> und <code>Access-Control-Allow-Credentials</code> festgelegt.\n\nGib für nicht authentifizierte Anfragen den Wert <kbd>*</kbd> an. Dies verursacht, dass der Header <code>Access-Control-Allow-Origin</code> festgelegt wird, aber <code>Access-Control-Allow-Credentials</code> wird <code>false</code> sein und alle benutzerspezifischen Daten werden beschränkt.",
        "apihelp-main-param-uselang": "Zu verwendende Sprache für Nachrichtenübersetzungen. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> mit <kbd>siprop=languages</kbd> gibt eine Liste der Sprachcodes zurück. Gib <kbd>user</kbd> zum Verwenden der aktuellen Benutzerspracheinstellung oder <kbd>content</kbd> an, um die Inhaltssprache des Wikis zu verwenden.",
+       "apihelp-main-param-errorsuselocal": "Falls angegeben, verwenden Fehlertexte lokalisierte Nachrichten aus dem {{ns:MediaWiki}}-Namensraum.",
        "apihelp-block-description": "Einen Benutzer sperren.",
-       "apihelp-block-param-user": "Benutzername, IP-Adresse oder IP-Bereich, der gesperrt werden soll.",
+       "apihelp-block-param-user": "Benutzername, IP-Adresse oder IP-Adressbereich, der gesperrt werden soll. Kann nicht zusammen mit <var>$1userid</var> verwendet werden.",
        "apihelp-block-param-expiry": "Sperrdauer. Kann relativ (z.&nbsp;B. <kbd>5 months</kbd> oder <kbd>2 weeks</kbd>) oder absolut (z.&nbsp;B. <kbd>2014-09-18T12:34:56Z</kbd>) sein. Wenn auf <kbd>infinite</kbd>, <kbd>indefinite</kbd> oder <kbd>never</kbd> gesetzt, ist die Sperre unbegrenzt.",
        "apihelp-block-param-reason": "Sperrbegründung.",
        "apihelp-block-param-anononly": "Nur anonyme Benutzer sperren (z.&nbsp;B. anonyme Bearbeitungen für diese IP deaktivieren).",
        "apihelp-query+allmessages-param-prop": "Zurückzugebende Eigenschaften.",
        "apihelp-query+allmessages-param-enableparser": "Setzen, um den Parser zu aktivieren. Dies wird den Wikitext der Nachricht vorverarbeiten (magische Worte ersetzen, Vorlagen berücksichtigen, usw.).",
        "apihelp-query+allmessages-param-nocontent": "Wenn gesetzt, füge nicht den Inhalt der Nachricht der Ausgabe hinzu.",
-       "apihelp-query+allmessages-param-includelocal": "Schließt auch lokale Nachrichten ein. Zum Beispiel Nachrichten die es nicht in der Software gibt, die es aber als MediaWiki: - Seite gibt. Dies listet alle MediaWiki: - Seiten auf. Daher werden auch diejenigen aufgelistet, die eigentlich keine Nachrichten sind, wie [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "Schließt auch lokale Nachrichten ein, zum Beispiel Nachrichten, die nicht in der Software vorhanden sind, aber dafür im {{ns:MediaWiki}}-Namensraum.\nDies listet alle Seiten im {{ns:MediaWiki}}-Namensraum auf, auch solche, die nicht wirklich Nachrichten sind, wie [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Argumente die in der Nachricht ersetzt werden sollen.",
        "apihelp-query+allmessages-param-filter": "Gebe nur Nachrichten mit Namen, die diese Zeichenfolge enthalten, zurück.",
        "apihelp-query+allmessages-param-customised": "Gebe nur Nachrichten in diesem Anpassungszustand zurück.",
        "apihelp-query+allrevisions-param-generatetitles": "Wenn als Generator verwendet, werden eher Titel als Bearbeitungs-IDs erzeugt.",
        "apihelp-query+allrevisions-example-user": "Liste die letzten 50 Beiträge, sortiert nach Benutzer <kbd>Beispiel</kbd> auf.",
        "apihelp-query+allrevisions-example-ns-main": "Liste die ersten 50 Bearbeitungen im Hauptnamensraum auf.",
+       "apihelp-query+mystashedfiles-paramvalue-prop-size": "Ruft die Dateigröße und Bildabmessungen ab.",
        "apihelp-query+mystashedfiles-param-limit": "Wie viele Dateien zurückgegeben werden sollen.",
        "apihelp-query+alltransclusions-description": "Liste alle Transklusionen auf (eingebettete Seiten die &#123;&#123;x&#125;&#125; benutzen), einschließlich nicht vorhandener.",
        "apihelp-query+alltransclusions-param-from": "Der Titel der Transklusion bei dem die Auflistung beginnen soll.",
        "apihelp-query+users-paramvalue-prop-rights": "Listet alle Rechte auf, die jeder Benutzer hat.",
        "apihelp-query+users-paramvalue-prop-editcount": "Ergänzt den Bearbeitungszähler des Benutzers.",
        "apihelp-query+users-param-users": "Eine Liste der Benutzer, für die Informationen abgerufen werden sollen.",
+       "apihelp-query+users-param-userids": "Eine Liste der Benutzerkennungen, für die die Informationen abgerufen werden sollen.",
        "apihelp-query+users-example-simple": "Gibt Informationen für den Benutzer <kbd>Example</kbd> zurück.",
        "apihelp-query+watchlist-param-user": "Listet nur Änderungen von diesem Benutzer auf.",
        "apihelp-query+watchlist-param-excludeuser": "Listet keine Änderungen von diesem Benutzer auf.",
        "apihelp-tokens-example-edit": "Ruft einen Bearbeitungstoken ab (Standard).",
        "apihelp-tokens-example-emailmove": "Ruft einen E-Mail- und Verschiebungstoken ab.",
        "apihelp-unblock-description": "Einen Benutzer freigeben.",
-       "apihelp-unblock-param-id": "ID der Sperre zum Entsperren (über <kbd>list=blocks</kbd> erhalten). Darf nicht zusammen mit <var>$1user</var> verwendet werden.",
-       "apihelp-unblock-param-user": "Freizugebender Benutzername, IP-Adressbereich oder freizugebende IP-Adresse. Kann nicht zusammen mit <var>$1id</var> verwendet werden.",
+       "apihelp-unblock-param-id": "Kennung der Sperre zur Freigabe (abgerufen durch <kbd>list=blocks</kbd>). Kann nicht zusammen mit <var>$1user</var> oder <var>$1userid</var> verwendet werden.",
+       "apihelp-unblock-param-user": "Benutzername, IP-Adresse oder IP-Adressbereich, der freigegeben werden soll. Kann nicht zusammen mit <var>$1id</var> oder <var>$1userid</var> verwendet werden.",
        "apihelp-unblock-param-reason": "Grund für die Freigabe.",
        "apihelp-unblock-param-tags": "Auf den Benutzersperr-Logbuch-Eintrag anzuwendende Änderungsmarkierungen.",
        "apihelp-unblock-example-id": "Sperrkennung #<kbd>105</kbd> freigeben.",
        "apihelp-phpfm-description": "Daten im serialisierten PHP-Format ausgeben (schöngedruckt in HTML).",
        "apihelp-rawfm-description": "Daten, einschließlich Fehlerbehebungselementen, im JSON-Format ausgeben (schöngedruckt in HTML).",
        "apihelp-xml-description": "Daten im XML-Format ausgeben.",
-       "apihelp-xml-param-xslt": "Falls angegeben, fügt die benannte Seite als XSL-Stylesheet hinzu. Der Wert muss ein Titel im Namensraum „{{ns:mediawiki}}“ sein und mit <code>.xsl</code> enden.",
+       "apihelp-xml-param-xslt": "Falls angegeben, fügt die benannte Seite als XSL-Stylesheet hinzu. Der Wert muss ein Titel im Namensraum „{{ns:MediaWiki}}“ sein und mit <code>.xsl</code> enden.",
        "apihelp-xml-param-includexmlnamespace": "Falls angegeben, ergänzt einen XML-Namensraum.",
        "apihelp-xmlfm-description": "Daten im XML-Format ausgeben (schöngedruckt in HTML).",
        "api-format-title": "MediaWiki-API-Ergebnis",
        "api-help-right-apihighlimits": "Höhere Beschränkungen in API-Anfragen verwenden (langsame Anfragen: $1; schnelle Anfragen: $2). Die Beschränkungen für langsame Anfragen werden auch auf Mehrwertparameter angewandt.",
        "api-help-open-in-apisandbox": "<small>[in Spielwiese öffnen]</small>",
        "api-help-authmanagerhelper-messageformat": "Zu verwendendes Format zur Rückgabe von Nachrichten.",
+       "apierror-nosuchuserid": "Es gibt keinen Benutzer mit der Kennung $1.",
        "api-credits-header": "Danksagungen",
        "api-credits": "API-Entwickler:\n* Roan Kattouw (Hauptentwickler von September 2007 bis 2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (Autor, Hauptentwickler von September 2006 bis September 2007)\n* Brad Jorsch (Hauptentwickler seit 2013)\n\nBitte sende deine Kommentare, Vorschläge und Fragen an mediawiki-api@lists.wikimedia.org\noder reiche einen Fehlerbericht auf https://phabricator.wikimedia.org/ ein."
 }
index 28cd746..b2f9446 100644 (file)
        "apihelp-main-param-requestid": "Any value given here will be included in the response. May be used to distinguish requests.",
        "apihelp-main-param-servedby": "Include the hostname that served the request in the results.",
        "apihelp-main-param-curtimestamp": "Include the current timestamp in the result.",
+       "apihelp-main-param-responselanginfo": "Include the languages used for <var>uselang</var> and <var>errorlang</var> in the result.",
        "apihelp-main-param-origin": "When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain. This must be included in any pre-flight request, and therefore must be part of the request URI (not the POST body).\n\nFor authenticated requests, this must match one of the origins in the <code>Origin</code> header exactly, so it has to be set to something like <kbd>https://en.wikipedia.org</kbd> or <kbd>https://meta.wikimedia.org</kbd>. If this parameter does not match the <code>Origin</code> header, a 403 response will be returned. If this parameter matches the <code>Origin</code> header and the origin is whitelisted, the <code>Access-Control-Allow-Origin</code> and <code>Access-Control-Allow-Credentials</code> headers will be set.\n\nFor non-authenticated requests, specify the value <kbd>*</kbd>. This will cause the <code>Access-Control-Allow-Origin</code> header to be set, but <code>Access-Control-Allow-Credentials</code> will be <code>false</code> and all user-specific data will be restricted.",
        "apihelp-main-param-uselang": "Language to use for message translations. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> with <kbd>siprop=languages</kbd> returns a list of language codes, or specify <kbd>user</kbd> to use the current user's language preference, or specify <kbd>content</kbd> to use this wiki's content language.",
+       "apihelp-main-param-errorformat": "Format to use for warning and error text output.\n; plaintext: Wikitext with HTML tags removed and entities replaced.\n; wikitext: Unparsed wikitext.\n; html: HTML.\n; raw: Message key and parameters.\n; none: No text output, only the error codes.\n; bc: Format used prior to MediaWiki 1.29. <var>errorlang</var> and <var>errorsuselocal</var> are ignored.",
+       "apihelp-main-param-errorlang": "Language to use for warnings and errors. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> with <kbd>siprop=languages</kbd> returns a list of language codes, or specify <kbd>content</kbd> to use this wiki's content language, or specify <kbd>uselang</kbd> to use the same value as the <var>uselang</var> parameter.",
+       "apihelp-main-param-errorsuselocal": "If given, error texts will use locally-customized messages from the {{ns:MediaWiki}} namespace.",
 
        "apihelp-block-description": "Block a user.",
-       "apihelp-block-param-user": "Username, IP address, or IP address range to block.",
+       "apihelp-block-param-user": "Username, IP address, or IP address range to block. Cannot be used together with <var>$1userid</var>",
+       "apihelp-block-param-userid": "User ID to block. Cannot be used together with <var>$1user</var>.",
        "apihelp-block-param-expiry": "Expiry time. May be relative (e.g. <kbd>5 months</kbd> or <kbd>2 weeks</kbd>) or absolute (e.g. <kbd>2014-09-18T12:34:56Z</kbd>). If set to <kbd>infinite</kbd>, <kbd>indefinite</kbd>, or <kbd>never</kbd>, the block will never expire.",
        "apihelp-block-param-reason": "Reason for block.",
        "apihelp-block-param-anononly": "Block anonymous users only (i.e. disable anonymous edits for this IP address).",
        "apihelp-query+allmessages-param-prop": "Which properties to get.",
        "apihelp-query+allmessages-param-enableparser": "Set to enable parser, will preprocess the wikitext of message (substitute magic words, handle templates, etc.).",
        "apihelp-query+allmessages-param-nocontent": "If set, do not include the content of the messages in the output.",
-       "apihelp-query+allmessages-param-includelocal": "Also include local messages, i.e. messages that don't exist in the software but do exist as a MediaWiki: page.\nThis lists all MediaWiki: pages, so it will also list those that aren't really messages such as [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "Also include local messages, i.e. messages that don't exist in the software but do exist as in the {{ns:MediaWiki}} namespace.\nThis lists all {{ns:MediaWiki}}-namespace pages, so it will also list those that aren't really messages such as [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Arguments to be substituted into message.",
        "apihelp-query+allmessages-param-filter": "Return only messages with names that contain this string.",
        "apihelp-query+allmessages-param-customised": "Return only messages in this customisation state.",
        "apihelp-query+imageinfo-paramvalue-prop-archivename": "Adds the filename of the archive version for non-latest versions.",
        "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "Adds the bit depth of the version.",
        "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core.",
+       "apihelp-query+imageinfo-paramvalue-prop-badfile": "Adds whether the file is on the [[MediaWiki:Bad image list]]",
        "apihelp-query+imageinfo-param-limit": "How many file revisions to return per file.",
        "apihelp-query+imageinfo-param-start": "Timestamp to start listing from.",
        "apihelp-query+imageinfo-param-end": "Timestamp to stop listing at.",
        "apihelp-query+imageinfo-param-extmetadatamultilang": "If translations for extmetadata property are available, fetch all of them.",
        "apihelp-query+imageinfo-param-extmetadatafilter": "If specified and non-empty, only these keys will be returned for $1prop=extmetadata.",
        "apihelp-query+imageinfo-param-urlparam": "A handler specific parameter string. For example, PDFs might use <kbd>page15-100px</kbd>. <var>$1urlwidth</var> must be used and be consistent with <var>$1urlparam</var>.",
+       "apihelp-query+imageinfo-param-badfilecontexttitle": "If <kbd>$2prop=badfile</kbd> is set, this is the page title used when evaluting the [[MediaWiki:Bad image list]]",
        "apihelp-query+imageinfo-param-localonly": "Look only for files in the local repository.",
        "apihelp-query+imageinfo-example-simple": "Fetch information about the current version of [[:File:Albert Einstein Head.jpg]].",
        "apihelp-query+imageinfo-example-dated": "Fetch information about versions of [[:File:Test.jpg]] from 2008 and later.",
        "apihelp-query+users-paramvalue-prop-cancreate": "Indicates whether an account for valid but unregistered usernames can be created.",
        "apihelp-query+users-param-attachedwiki": "With <kbd>$1prop=centralids</kbd>, indicate whether the user is attached with the wiki identified by this ID.",
        "apihelp-query+users-param-users": "A list of users to obtain information for.",
+       "apihelp-query+users-param-userids": "A list of user IDs to obtain information for.",
        "apihelp-query+users-param-token": "Use <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> instead.",
        "apihelp-query+users-example-simple": "Return information for user <kbd>Example</kbd>.",
 
        "apihelp-tokens-example-emailmove": "Retrieve an email token and a move token.",
 
        "apihelp-unblock-description": "Unblock a user.",
-       "apihelp-unblock-param-id": "ID of the block to unblock (obtained through <kbd>list=blocks</kbd>). Cannot be used together with <var>$1user</var>.",
-       "apihelp-unblock-param-user": "Username, IP address or IP address range to unblock. Cannot be used together with <var>$1id</var>.",
+       "apihelp-unblock-param-id": "ID of the block to unblock (obtained through <kbd>list=blocks</kbd>). Cannot be used together with <var>$1user</var> or <var>$luserid</var>.",
+       "apihelp-unblock-param-user": "Username, IP address or IP address range to unblock. Cannot be used together with <var>$1id</var> or <var>$luserid</var>.",
+       "apihelp-unblock-param-userid": "User ID to unblock. Cannot be used together with <var>$1id</var> or <var>$1user</var>.",
        "apihelp-unblock-param-reason": "Reason for unblock.",
        "apihelp-unblock-param-tags": "Change tags to apply to the entry in the block log.",
        "apihelp-unblock-example-id": "Unblock block ID #<kbd>105</kbd>.",
        "apihelp-phpfm-description": "Output data in serialized PHP format (pretty-print in HTML).",
        "apihelp-rawfm-description": "Output data, including debugging elements, in JSON format (pretty-print in HTML).",
        "apihelp-xml-description": "Output data in XML format.",
-       "apihelp-xml-param-xslt": "If specified, adds the named page as an XSL stylesheet. The value must be a title in the {{ns:mediawiki}} namespace ending in <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "If specified, adds the named page as an XSL stylesheet. The value must be a title in the {{ns:MediaWiki}} namespace ending in <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "If specified, adds an XML namespace.",
        "apihelp-xmlfm-description": "Output data in XML format (pretty-print in HTML).",
 
        "api-help-authmanagerhelper-continue": "This request is a continuation after an earlier <samp>UI</samp> or <samp>REDIRECT</samp> response. Either this or <var>$1returnurl</var> is required.",
        "api-help-authmanagerhelper-additional-params": "This module accepts additional parameters depending on the available authentication requests. Use <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> with <kbd>amirequestsfor=$1</kbd> (or a previous response from this module, if applicable) to determine the requests available and the fields that they use.",
 
+       "apierror-allimages-redirect": "Use <kbd>gaifilterredir=nonredirects</kbd> instead of <var>redirects</var> when using <kbd>allimages</kbd> as a generator.",
+       "apierror-allpages-generator-redirects": "Use <kbd>gapfilterredir=nonredirects</kbd> instead of <var>redirects</var> when using <kbd>allpages</kbd> as a generator.",
+       "apierror-appendnotsupported": "Can't append to pages using content model $1.",
+       "apierror-articleexists": "The article you tried to create has been created already.",
+       "apierror-assertbotfailed": "Assertion that the user has the <code>bot</code> right failed.",
+       "apierror-assertnameduserfailed": "Assertion that the user is \"$1\" failed.",
+       "apierror-assertuserfailed": "Assertion that the user is logged in failed.",
+       "apierror-autoblocked": "Your IP address has been blocked automatically, because it was used by a blocked user.",
+       "apierror-badconfig-resulttoosmall": "The value of <code>$wgAPIMaxResultSize</code> on this wiki is too small to hold basic result information.",
+       "apierror-badcontinue": "Invalid continue param. You should pass the original value returned by the previous query.",
+       "apierror-baddiff": "The diff cannot be retrieved, one or both revisions do not exist or you do not have permission to view them.",
+       "apierror-baddiffto": "<var>$1diffto</var> must be set to a non-negative number, <kbd>prev</kbd>, <kbd>next</kbd> or <kbd>cur</kbd>.",
+       "apierror-badformat-generic": "The requested format $1 is not supported for content model $2.",
+       "apierror-badformat": "The requested format $1 is not supported for content model $2 used by $3.",
+       "apierror-badgenerator-notgenerator": "Module <kbd>$1</kbd> cannot be used as a generator.",
+       "apierror-badgenerator-unknown": "Unknown <kbd>generator=$1</kbd>.",
+       "apierror-badip": "IP parameter is not valid.",
+       "apierror-badmd5": "The supplied MD5 hash was incorrect.",
+       "apierror-badmodule-badsubmodule": "The module <kbd>$1</kbd> does not have a submodule \"$2\".",
+       "apierror-badmodule-nosubmodules": "The module <kbd>$1</kbd> has no submodules.",
+       "apierror-badparameter": "Invalid value for parameter <var>$1</var>.",
+       "apierror-badquery": "Invalid query.",
+       "apierror-badtimestamp": "Invalid value \"$2\" for timestamp parameter <var>$1</var>.",
+       "apierror-badtoken": "Invalid CSRF token.",
+       "apierror-badupload": "File upload parameter <var>$1</var> is not a file upload; be sure to use <code>multipart/form-data</code> for your POST and include a filename in the <code>Content-Disposition</code> header.",
+       "apierror-badurl": "Invalid value \"$2\" for URL parameter <var>$1</var>.",
+       "apierror-baduser": "Invalid value \"$2\" for user parameter <var>$1</var>.",
+       "apierror-badvalue-notmultivalue": "U+001F multi-value separation may only be used for multi-valued parameters.",
+       "apierror-bad-watchlist-token": "Incorrect watchlist token provided. Please set a correct token in [[Special:Preferences]].",
+       "apierror-blockedfrommail": "You have been blocked from sending email.",
+       "apierror-blocked": "You have been blocked from editing.",
+       "apierror-botsnotsupported": "This interface is not supported for bots.",
+       "apierror-cannotreauthenticate": "This action is not available as your identity cannot be verified.",
+       "apierror-cannotviewtitle": "You are not allowed to view $1.",
+       "apierror-cantblock-email": "You don't have permission to block users from sending email through the wiki.",
+       "apierror-cantblock": "You don't have permission to block users.",
+       "apierror-cantchangecontentmodel": "You don't have permission to change the content model of a page.",
+       "apierror-canthide": "You don't have permission to hide user names from the block log.",
+       "apierror-cantimport-upload": "You don't have permission to import uploaded pages.",
+       "apierror-cantimport": "You don't have permission to import pages.",
+       "apierror-cantoverwrite-sharedfile": "The target file exists on a shared repository and you do not have permission to override it.",
+       "apierror-cantsend": "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email.",
+       "apierror-cantundelete": "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already.",
+       "apierror-changeauth-norequest": "Failed to create change request.",
+       "apierror-chunk-too-small": "Minimum chunk size is $1 {{PLURAL:$1|byte|bytes}} for non-final chunks.",
+       "apierror-cidrtoobroad": "$1 CIDR ranges broader than /$2 are not accepted.",
+       "apierror-compare-inputneeded": "A title, a page ID, or a revision number is needed for both the <var>from</var> and the <var>to</var> parameters.",
+       "apierror-contentserializationexception": "Content serialization failed: $1",
+       "apierror-contenttoobig": "The content you supplied exceeds the article size limit of $1 {{PLURAL:$1|kilobyte|kilobytes}}.",
+       "apierror-copyuploadbaddomain": "Uploads by URL are not allowed from this domain.",
+       "apierror-copyuploadbadurl": "Upload not allowed from this URL.",
+       "apierror-create-titleexists": "Existing titles can't be protected with <kbd>create</kbd>.",
+       "apierror-csp-report": "Error processing CSP report: $1.",
+       "apierror-databaseerror": "[$1] Database query error.",
+       "apierror-deletedrevs-param-not-1-2": "The <var>$1</var> parameter cannot be used in modes 1 or 2.",
+       "apierror-deletedrevs-param-not-3": "The <var>$1</var> parameter cannot be used in mode 3.",
+       "apierror-emptynewsection": "Creating empty new sections is not possible.",
+       "apierror-emptypage": "Creating new, empty pages is not allowed.",
+       "apierror-exceptioncaught": "[$1] Exception caught: $2",
+       "apierror-filedoesnotexist": "File does not exist.",
+       "apierror-fileexists-sharedrepo-perm": "The target file exists on a shared repository. Use the <var>ignorewarnings</var> parameter to override it.",
+       "apierror-filenopath": "Cannot get local file path.",
+       "apierror-filetypecannotberotated": "File type cannot be rotated.",
+       "apierror-formatphp": "This response cannot be represented using <kbd>format=php</kbd>. See https://phabricator.wikimedia.org/T68776.",
+       "apierror-imageusage-badtitle": "The title for <kbd>$1</kbd> must be a file.",
+       "apierror-import-unknownerror": "Unknown error on import: $1.",
+       "apierror-integeroutofrange-abovebotmax": "<var>$1</var> may not be over $2 (set to $3) for bots or sysops.",
+       "apierror-integeroutofrange-abovemax": "<var>$1</var> may not be over $2 (set to $3) for users.",
+       "apierror-integeroutofrange-belowminimum": "<var>$1</var> may not be less than $2 (set to $3).",
+       "apierror-invalidcategory": "The category name you entered is not valid.",
+       "apierror-invalid-chunk": "Offset plus current chunk is greater than claimed file size.",
+       "apierror-invalidexpiry": "Invalid expiry time \"$1\".",
+       "apierror-invalid-file-key": "Not a valid file key.",
+       "apierror-invalidlang": "Invalid language code for parameter <var>$1</var>.",
+       "apierror-invalidoldimage": "The oldimage parameter has invalid format.",
+       "apierror-invalidparammix-cannotusewith": "The <kbd>$1</kbd> parameter cannot be used with <kbd>$2</kbd>.",
+       "apierror-invalidparammix-mustusewith": "The <kbd>$1</kbd> parameter may only be used with <kbd>$2</kbd>.",
+       "apierror-invalidparammix-parse-new-section": "<kbd>section=new</kbd> cannot be combined with the <var>oldid</var>, <var>pageid</var> or <var>page</var> parameters. Please use <var>title</var> and <var>text</var>.",
+       "apierror-invalidparammix": "The {{PLURAL:$2|parameters}} $1 can not be used together.",
+       "apierror-invalidsection": "The section parameter must be a valid section ID or <kbd>new</kbd>.",
+       "apierror-invalidsha1base36hash": "The SHA1Base36 hash provided is not valid.",
+       "apierror-invalidsha1hash": "The SHA1 hash provided is not valid.",
+       "apierror-invalidtitle": "Bad title \"$1\".",
+       "apierror-invalidurlparam": "Invalid value for <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
+       "apierror-invaliduser": "Invalid username \"$1\".",
+       "apierror-maxlag-generic": "Waiting for a database server: $1 {{PLURAL:$1|second|seconds}} lagged.",
+       "apierror-maxlag": "Waiting for $2: $1 {{PLURAL:$1|second|seconds}} lagged.",
+       "apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.",
+       "apierror-missingcontent-pageid": "Missing content for page ID $1.",
+       "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|The parameter|At least one of the parameters}} $1 is required.",
+       "apierror-missingparam-one-of": "{{PLURAL:$2|The parameter|One of the parameters}} $1 is required.",
+       "apierror-missingparam": "The <var>$1</var> parameter must be set.",
+       "apierror-missingrev-pageid": "No current revision of page ID $1.",
+       "apierror-missingtitle-createonly": "Missing titles can only be protected with <kbd>create</kbd>.",
+       "apierror-missingtitle": "The page you specified doesn't exist.",
+       "apierror-missingtitle-byname": "The page $1 doesn't exist.",
+       "apierror-moduledisabled": "The <kbd>$1</kbd> module has been disabled.",
+       "apierror-multival-only-one-of": "{{PLURAL:$3|Only|Only one of}} $2 is allowed for parameter <var>$1</var>.",
+       "apierror-multival-only-one": "Only one value is allowed for parameter <var>$1</var>.",
+       "apierror-multpages": "<var>$1</var> may only be used with a single page.",
+       "apierror-mustbeloggedin-changeauth": "You must be logged in to change authentication data.",
+       "apierror-mustbeloggedin-generic": "You must be logged in.",
+       "apierror-mustbeloggedin-linkaccounts": "You must be logged in to link accounts.",
+       "apierror-mustbeloggedin-removeauth": "You must be logged in to remove authentication data.",
+       "apierror-mustbeloggedin-uploadstash": "The upload stash is only available to logged-in users.",
+       "apierror-mustbeloggedin": "You must be logged in to $1.",
+       "apierror-mustbeposted": "The <kbd>$1</kbd> module requires a POST request.",
+       "apierror-mustpostparams": "The following {{PLURAL:$2|parameter was|parameters were}} found in the query string, but must be in the POST body: $1.",
+       "apierror-noapiwrite": "Editing of this wiki through the API is disabled. Make sure the <code>$wgEnableWriteAPI=true;</code> statement is included in the wiki's <code>LocalSettings.php</code> file.",
+       "apierror-nochanges": "No changes were requested.",
+       "apierror-nodeleteablefile": "No such old version of the file.",
+       "apierror-no-direct-editing": "Direct editing via API is not supported for content model $1 used by $2.",
+       "apierror-noedit-anon": "Anonymous users can't edit pages.",
+       "apierror-noedit": "You don't have permission to edit pages.",
+       "apierror-noimageredirect-anon": "Anonymous users can't create image redirects.",
+       "apierror-noimageredirect": "You don't have permission to create image redirects.",
+       "apierror-nosuchlogid": "There is no log entry with ID $1.",
+       "apierror-nosuchpageid": "There is no page with ID $1.",
+       "apierror-nosuchrcid": "There is no recent change with ID $1.",
+       "apierror-nosuchrevid": "There is no revision with ID $1.",
+       "apierror-nosuchsection": "There is no section $1.",
+       "apierror-nosuchsection-what": "There is no section $1 in $2.",
+       "apierror-nosuchuserid": "There is no user with ID $1.",
+       "apierror-notarget": "You have not specified a valid target for this action.",
+       "apierror-notpatrollable": "The revision r$1 can't be patrolled as it's too old.",
+       "apierror-nouploadmodule": "No upload module set.",
+       "apierror-opensearch-json-warnings": "Warnings cannot be represented in OpenSearch JSON format.",
+       "apierror-pagecannotexist": "Namespace doesn't allow actual pages.",
+       "apierror-pagedeleted": "The page has been deleted since you fetched its timestamp.",
+       "apierror-paramempty": "The parameter <var>$1</var> may not be empty.",
+       "apierror-parsetree-notwikitext": "<kbd>prop=parsetree</kbd> is only supported for wikitext content.",
+       "apierror-parsetree-notwikitext-title": "<kbd>prop=parsetree</kbd> is only supported for wikitext content. $1 uses content model $2.",
+       "apierror-pastexpiry": "Expiry time \"$1\" is in the past.",
+       "apierror-permissiondenied": "You don't have permission to $1.",
+       "apierror-permissiondenied-generic": "Permission denied.",
+       "apierror-permissiondenied-patrolflag": "You need the <code>patrol</code> or <code>patrolmarks</code> right to request the patrolled flag.",
+       "apierror-permissiondenied-unblock": "You don't have permission to unblock users.",
+       "apierror-prefixsearchdisabled": "Prefix search is disabled in Miser Mode.",
+       "apierror-promised-nonwrite-api": "The <code>Promise-Non-Write-API-Action</code> HTTP header cannot be sent to write-mode API modules.",
+       "apierror-protect-invalidaction": "Invalid protection type \"$1\".",
+       "apierror-protect-invalidlevel": "Invalid protection level \"$1\".",
+       "apierror-ratelimited": "You've exceeded your rate limit. Please wait some time and try again.",
+       "apierror-readapidenied": "You need read permission to use this module.",
+       "apierror-readonly": "The wiki is currently in read-only mode.",
+       "apierror-reauthenticate": "You have not authenticated recently in this session, please reauthenticate.",
+       "apierror-redirect-appendonly": "You have attempted to edit using the redirect-following mode, which must be used in conjuction with <kbd>section=new</kbd>, <var>prependtext</var>, or <var>appendtext</var>.",
+       "apierror-revdel-mutuallyexclusive": "The same field cannot be used in both <var>hide</var> and <var>show</var>.",
+       "apierror-revdel-needtarget": "A target title is required for this RevDel type.",
+       "apierror-revdel-paramneeded": "At least one value is required for <var>hide</var> and/or <var>show</var>.",
+       "apierror-revisions-norevids": "The <var>revids</var> parameter may not be used with the list options (<var>$1limit</var>, <var>$1startid</var>, <var>$1endid</var>, <kbd>$1dir=newer</kbd>, <var>$1user</var>, <var>$1excludeuser</var>, <var>$1start</var>, and <var>$1end</var>).",
+       "apierror-revisions-singlepage": "<var>titles</var>, <var>pageids</var> or a generator was used to supply multiple pages, but the <var>$1limit</var>, <var>$1startid</var>, <var>$1endid</var>, <kbd>$1dir=newer</kbd>, <var>$1user</var>, <var>$1excludeuser</var>, <var>$1start</var>, and <var>$1end</var> parameters may only be used on a single page.",
+       "apierror-revwrongpage": "r$1 is not a revision of $2.",
+       "apierror-searchdisabled": "<var>$1</var> search is disabled.",
+       "apierror-sectionreplacefailed": "Could not merge updated section.",
+       "apierror-sectionsnotsupported": "Sections are not supported for content model $1.",
+       "apierror-sectionsnotsupported-what": "Sections are not supported by $1.",
+       "apierror-show": "Incorrect parameter - mutually exclusive values may not be supplied.",
+       "apierror-siteinfo-includealldenied": "Cannot view all servers' info unless <var>$wgShowHostNames</var> is true.",
+       "apierror-sizediffdisabled": "Size difference is disabled in Miser Mode.",
+       "apierror-spamdetected": "Your edit was refused because it contained a spam fragment: <code>$1</code>.",
+       "apierror-specialpage-cantexecute": "You don't have permission to view the results of this special page.",
+       "apierror-stashedfilenotfound": "Could not find the file in the stash: $1.",
+       "apierror-stashedit-missingtext": "No stashed text found with the given hash.",
+       "apierror-stashfailed-complete": "Chunked upload is already completed, check status for details.",
+       "apierror-stashfailed-nosession": "No chunked upload session with this key.",
+       "apierror-stashfilestorage": "Could not store upload in the stash: $1",
+       "apierror-stashnosuchfilekey": "No such filekey: $1.",
+       "apierror-stashpathinvalid": "File key of improper format or otherwise invalid: $1.",
+       "apierror-stashwrongowner": "Wrong owner: $1",
+       "apierror-stashzerolength": "File is of zero length, and could not be stored in the stash: $1.",
+       "apierror-templateexpansion-notwikitext": "Template expansion is only supported for wikitext content. $1 uses content model $2.",
+       "apierror-toofewexpiries": "$1 expiry {{PLURAL:$1|timestamp was|timestamps were}} provided where $2 {{PLURAL:$2|was|were}} needed.",
+       "apierror-unknownaction": "The action specified, <kbd>$1</kbd>, is not recognized.",
+       "apierror-unknownerror-editpage": "Unknown EditPage error: $1.",
+       "apierror-unknownerror-nocode": "Unknown error.",
+       "apierror-unknownerror": "Unknown error: \"$1\".",
+       "apierror-unknownformat": "Unrecognized format \"$1\".",
+       "apierror-unrecognizedparams": "Unrecognized {{PLURAL:$2|parameter|parameters}}: $1.",
+       "apierror-unrecognizedvalue": "Unrecognized value for parameter <var>$1</var>: $2.",
+       "apierror-unsupportedrepo": "Local file repository does not support querying all images.",
+       "apierror-upload-filekeyneeded": "Must supply a <var>filekey</var> when <var>offset</var> is non-zero.",
+       "apierror-upload-filekeynotallowed": "Cannot supply a <var>filekey</var> when <var>offset</var> is 0.",
+       "apierror-upload-inprogress": "Upload from stash already in progress.",
+       "apierror-upload-missingresult": "No result in status data.",
+       "apierror-urlparamnormal": "Could not normalize image parameters for $1.",
+       "apierror-writeapidenied": "You're not allowed to edit this wiki through the API.",
+
+       "apiwarn-alldeletedrevisions-performance": "For better performance when generating titles, set <kbd>$1dir=newer</kbd>.",
+       "apiwarn-badurlparam": "Could not parse <var>$1urlparam</var> for $2. Using only width and height.",
+       "apiwarn-badutf8": "The value passed for <var>$1</var> contains invalid or non-normalized data. Textual data should be valid, NFC-normalized Unicode without C0 control characters other than HT (\\t), LF (\\n), and CR (\\r).",
+       "apiwarn-checktoken-percentencoding": "Check that symbols such as \"+\" in the token are properly percent-encoded in the URL.",
+       "apiwarn-deprecation-deletedrevs": "<kbd>list=deletedrevs</kbd> has been deprecated. Please use <kbd>prop=deletedrevisions</kbd> or <kbd>list=alldeletedrevisions</kbd> instead.",
+       "apiwarn-deprecation-expandtemplates-prop": "Because no values have been specified for the <var>prop</var> parameter, a legacy format has been used for the output. This format is deprecated, and in the future, a default value will be set for the <var>prop</var> parameter, causing the new format to always be used.",
+       "apiwarn-deprecation-httpsexpected": "HTTP used when HTTPS was expected.",
+       "apiwarn-deprecation-login-botpw": "Main-account login via <kbd>action=login</kbd> is deprecated and may stop working without warning. To continue login with <kbd>action=login</kbd>, see [[Special:BotPasswords]]. To safely continue using main-account login, see <kbd>action=clientlogin</kbd>.",
+       "apiwarn-deprecation-login-nobotpw": "Main-account login via <kbd>action=login</kbd> is deprecated and may stop working without warning. To safely log in, see <kbd>action=clientlogin</kbd>.",
+       "apiwarn-deprecation-login-token": "Fetching a token via <kbd>action=login</kbd> is deprecated. Use <kbd>action=query&meta=tokens&type=login</kbd> instead.",
+       "apiwarn-deprecation-parameter": "The parameter <var>$1</var> has been deprecated.",
+       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> is deprecated since MediaWiki 1.28. Use <kbd>prop=headhtml</kbd> when creating new HTML documents, or <kbd>prop=modules|jsconfigvars</kbd> when updating a document client-side.",
+       "apiwarn-deprecation-purge-get": "Use of <kbd>action=purge</kbd> via GET is deprecated. Use POST instead.",
+       "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> has been deprecated. Please use <kbd>$2</kbd> instead.",
+       "apiwarn-difftohidden": "Couldn't diff to r$1: content is hidden.",
+       "apiwarn-errorprinterfailed": "Error printer failed. Will retry without params.",
+       "apiwarn-errorprinterfailed-ex": "Error printer failed (will retry without params): $1",
+       "apiwarn-invalidcategory": "\"$1\" is not a category.",
+       "apiwarn-invalidtitle": "\"$1\" is not a valid title.",
+       "apiwarn-invalidxmlstylesheetext": "Stylesheet should have <code>.xsl</code> extension.",
+       "apiwarn-invalidxmlstylesheet": "Invalid or non-existent stylesheet specified.",
+       "apiwarn-invalidxmlstylesheetns": "Stylesheet should be in the {{ns:MediaWiki}} namespace.",
+       "apiwarn-moduleswithoutvars": "Property <kbd>modules</kbd> was set but not <kbd>jsconfigvars</kbd> or <kbd>encodedjsconfigvars</kbd>. Configuration variables are necessary for proper module usage.",
+       "apiwarn-notfile": "\"$1\" is not a file.",
+       "apiwarn-nothumb-noimagehandler": "Could not create thumbnail because $1 does not have an associated image handler.",
+       "apiwarn-parse-nocontentmodel": "No <var>title</var> or <var>contentmodel</var> was given, assuming $1.",
+       "apiwarn-parse-titlewithouttext": "<var>title</var> used without <var>text</var>, and parsed page properties were requested. Did you mean to use <var>page</var> instead of <var>title</var>?",
+       "apiwarn-redirectsandrevids": "Redirect resolution cannot be used together with the <var>revids</var> parameter. Any redirects the <var>revids</var> point to have not been resolved.",
+       "apiwarn-tokennotallowed": "Action \"$1\" is not allowed for the current user.",
+       "apiwarn-tokens-origin": "Tokens may not be obtained when the same-origin policy is not applied.",
+       "apiwarn-toomanyvalues": "Too many values supplied for parameter <var>$1</var>: the limit is $2.",
+       "apiwarn-truncatedresult": "This result was truncated because it would otherwise be larger than the limit of $1 bytes.",
+       "apiwarn-unclearnowtimestamp": "Passing \"$2\" for timestamp parameter <var>$1</var> has been deprecated. If for some reason you need to explicitly specify the current time without calculating it client-side, use <kbd>now<kbd>.",
+       "apiwarn-unrecognizedvalues": "Unrecognized {{PLURAL:$3|value|values}} for parameter <var>$1</var>: $2.",
+       "apiwarn-unsupportedarray": "Parameter <var>$1</var> uses unsupported PHP array syntax.",
+       "apiwarn-urlparamwidth": "Ignoring width value set in <var>$1urlparam</var> ($2) in favor of width value derived from <var>$1urlwidth</var>/<var>$1urlheight</var> ($3).",
+       "apiwarn-validationfailed-badchars": "invalid characters in key (only <code>a-z</code>, <code>A-Z</code>, <code>0-9</code>, <code>_</code>, and <code>-</code> are allowed).",
+       "apiwarn-validationfailed-badpref": "not a valid preference.",
+       "apiwarn-validationfailed-cannotset": "cannot be set by this module.",
+       "apiwarn-validationfailed-keytoolong": "key too long (no more than $1 bytes allowed).",
+       "apiwarn-validationfailed": "Validation error for <kbd>$1</kbd>: $2",
+       "apiwarn-wgDebugAPI": "<strong>Security Warning</strong>: <var>$wgDebugAPI</var> is enabled.",
+
+       "api-feed-error-title": "Error ($1)",
+       "api-usage-docref": "See $1 for API usage.",
+       "api-exception-trace": "$1 at $2($3)\n$4",
        "api-credits-header": "Credits",
        "api-credits": "API developers:\n* Yuri Astrakhan (creator, lead developer Sep 2006–Sep 2007)\n* Roan Kattouw (lead developer Sep 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Brad Jorsch (lead developer 2013–present)\n\nPlease send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org\nor file a bug report at https://phabricator.wikimedia.org/."
 }
index 1078a6d..0480a64 100644 (file)
        "apihelp-none-description": "No extraer nada.",
        "apihelp-php-description": "Extraer los datos de salida en formato serializado PHP.",
        "apihelp-rawfm-description": "Extraer los datos de salida, incluidos los elementos de depuración, en formato JSON (embellecido en HTML).",
-       "apihelp-xml-param-xslt": "Si se especifica, añade la página nombrada como una hoja de estilo XSL. El valor debe ser un título en el espacio de nombres {{ns:mediawiki}} que termine en <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "Si se especifica, añade la página nombrada como una hoja de estilo XSL. El valor debe ser un título en el espacio de nombres {{ns:MediaWiki}} que termine en <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Si se especifica, añade un espacio de nombres XML.",
        "api-help-main-header": "Módulo principal",
        "api-help-flag-deprecated": "Este módulo está en desuso.",
        "api-help-examples": "{{PLURAL:$1|Ejemplo|Ejemplos}}:",
        "api-help-permissions": "{{PLURAL:$1|Permiso|Permisos}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|Concedido a|Concedidos a}}: $2",
+       "apierror-badip": "El parámetro IP no es válido.",
+       "apierror-invalidtitle": "Título incorrecto \"$1\".",
+       "apierror-revwrongpage": "r$1 no es una revisión de $2.",
+       "apierror-unknownerror-nocode": "Error desconocido.",
+       "apiwarn-invalidcategory": "\"$1\" no es una categoría.",
+       "apiwarn-notfile": "\"$1\" no es un archivo.",
+       "api-feed-error-title": "Error ($1)",
+       "api-exception-trace": "$1 en $2($3)\n$4",
        "api-credits-header": "Créditos",
        "api-credits": "Desarrolladores de la API:\n* Roan Kattouw (desarrollador principal, sep. 2007-2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (creador y desarrollador principal, sep. 2006-sep. 2007)\n* Brad Jorsch (desarrollador principal, 2013-actualidad)\n\nEnvía comentarios, sugerencias y preguntas a mediawiki-api@lists.wikimedia.org\no informa de un error en https://phabricator.wikimedia.org/."
 }
index 9a923c7..acc409e 100644 (file)
        "apihelp-main-param-requestid": "Toute valeur fournie ici sera incluse dans la réponse. Peut être utilisé pour distinguer des demandes.",
        "apihelp-main-param-servedby": "Inclure le nom d’hôte qui a renvoyé la requête dans les résultats.",
        "apihelp-main-param-curtimestamp": "Inclure l’horodatage actuel dans le résultat.",
+       "apihelp-main-param-responselanginfo": "Inclure les langues utilisées pour <var>uselang</var> et <var>errorlang</var> dans le résultat.",
        "apihelp-main-param-origin": "En accédant à l’API en utilisant une requête AJAX inter-domaines (CORS), mettre le domaine d’origine dans ce paramètre. Il doit être inclus dans toute requête de pre-flight, et doit donc faire partie de l’URI de la requête (pas du corps du POST).\n\nPour les requêtes authentifiées, il doit correspondre exactement à une des origines dans l’entête <code>Origin</code> header, donc il doit être fixé avec quelque chose comme <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Si ce paramètre ne correspond pas à l’entête <code>Origin</code>, une réponse 403 sera renvoyée. Si ce paramètre correspond à l’entête <code>Origin</code> et que l’origine est en liste blanche, des entêtes <code>Access-Control-Allow-Origin</code> et <code>Access-Control-Allow-Credentials</code> seront positionnés.\n\nPour les requêtes non authentifiées, spécifiez la valeur <kbd>*</kbd>. Cela positionnera l’entête <code>Access-Control-Allow-Origin</code>, mais <code>Access-Control-Allow-Credentials</code> vaudra <code>false</code> et toutes les données spécifiques à l’utilisateur seront filtrées.",
        "apihelp-main-param-uselang": "Langue à utiliser pour les traductions de message. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd> renvoie une liste de codes de langue, ou en spécifiant <kbd>user</kbd> pour utiliser la préférence de langue de l’utilisateur actuel, ou en spécifiant <kbd>content</kbd> pour utiliser le langage du contenu de ce wiki.",
+       "apihelp-main-param-errorformat": "Format à utiliser pour la sortie du texte d’avertissement et d’erreur.\n; plaintext: Wikitexte avec balises HTML supprimées et les entités remplacées.\n; wikitext: wikitexte non analysé.\n; html: HTML.\n; raw: Clé de message et paramètres.\n; none: Aucune sortie de texte, uniquement les codes erreur.\n; bc: Format utilisé avant MédiaWiki 1.29. <var>errorlang</var> et <var>errorsuselocal</var> sont ignorés.",
+       "apihelp-main-param-errorlang": "Langue à utiliser pour les avertissements et les erreurs. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd> renvoyant une liste de codes de langue, ou spécifier <kbd>content</kbd> pour utiliser la langue du contenu de ce wiki, ou spécifier <kbd>uselang</kbd> pour utiliser la même valeur que le paramètre <var>uselang</var>.",
+       "apihelp-main-param-errorsuselocal": "S’il est fourni, les textes d’erreur utiliseront des messages adaptés à la langue dans l’espace de noms {{ns:MediaWiki}}.",
        "apihelp-block-description": "Bloquer un utilisateur.",
-       "apihelp-block-param-user": "Nom d’utilisateur, adresse IP ou plage d’adresses IP que vous voulez bloquer.",
+       "apihelp-block-param-user": "Nom d’utilisateur, adresse IP ou plage d’adresses IP que vous voulez bloquer. Ne peut pas être utilisé en même temps que <var>$1userid</var>",
+       "apihelp-block-param-userid": "ID d'utilisateur à bloquer. Ne peut pas être utilisé avec <var>$1user</var>.",
        "apihelp-block-param-expiry": "Durée d’expiration. Peut être relative (par ex. <kbd>5 months</kbd> ou <kbd>2 weeks</kbd>) ou absolue (par ex. <kbd>2014-09-18T12:34:56Z</kbd>). Si elle est mise à <kbd>infinite</kbd>, <kbd>indefinite</kbd> ou <kbd>never</kbd>, le blocage n’expirera jamais.",
        "apihelp-block-param-reason": "Motif du blocage.",
        "apihelp-block-param-anononly": "Bloquer uniquement les utilisateurs anonymes (c’est-à-dire désactiver les modifications anonymes pour cette adresse IP).",
        "apihelp-query+allmessages-param-prop": "Quelles propriétés obtenir.",
        "apihelp-query+allmessages-param-enableparser": "Si positionné pour activer l’analyseur, traitera en avance le wikitexte du message (substitution des mots magiques, gestion des modèles, etc.).",
        "apihelp-query+allmessages-param-nocontent": "Si positionné, ne pas inclure le contenu des messages dans la sortie.",
-       "apihelp-query+allmessages-param-includelocal": "Inclure aussi les messages locaux, c’est-à-dire les messages qui n’existent pas dans le logiciel mais sous forme d’une page MediaWiki:.\nCela liste toutes les pages MediaWiki:, donc aussi celles qui ne sont pas vraiment des messages, telles que [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "Inclure aussi les messages locaux, c’est-à-dire les messages qui n’existent pas dans le logiciel mais dans l’espace de noms {{ns:MediaWiki}}.\nCela liste toutes les pages de l’espace de noms {{ns:MediaWiki}}, donc aussi celles qui ne sont pas vraiment des messages, telles que [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Arguments à substituer dans le message.",
        "apihelp-query+allmessages-param-filter": "Renvoyer uniquement les messages avec des noms contenant cette chaîne.",
        "apihelp-query+allmessages-param-customised": "Renvoyer uniquement les messages dans cet état de personnalisation.",
        "apihelp-query+users-paramvalue-prop-cancreate": "Indique si un compte peut être créé pour les noms d’utilisateurs valides mais non enregistrés.",
        "apihelp-query+users-param-attachedwiki": "Avec <kbd>$1prop=centralids</kbd>, indiquer si l’utilisateur est attaché au wiki identifié par cet ID.",
        "apihelp-query+users-param-users": "Une liste des utilisateurs sur lesquels obtenir de l’information.",
+       "apihelp-query+users-param-userids": "Une liste d’ID utilisateur pour lesquels obtenir des informations.",
        "apihelp-query+users-param-token": "Utiliser plutôt <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-query+users-example-simple": "Renvoyer des informations pour l'utilisateur <kbd>Example</kbd>.",
        "apihelp-query+watchlist-description": "Obtenir les modifications récentes des pages dans la liste de suivi de l’utilisateur actuel.",
        "apihelp-tokens-example-edit": "Récupérer un jeton de modification (par défaut).",
        "apihelp-tokens-example-emailmove": "Récupérer un jeton de courriel et un jeton de déplacement.",
        "apihelp-unblock-description": "Débloquer un utilisateur.",
-       "apihelp-unblock-param-id": "ID du blocage à lever (obtenu via <kbd>list=blocks</kbd>). Impossible à utiliser avec <var>$1user</var>.",
-       "apihelp-unblock-param-user": "Nom d’utilisateur, adresse IP ou plage d’adresses IP à débloquer. Impossible à utiliser en même temps que <var>$1id</var>.",
+       "apihelp-unblock-param-id": "ID du blocage à lever (obtenu via <kbd>list=blocks</kbd>). Impossible à utiliser avec <var>$1user</var> ou <var>$luserid</var>.",
+       "apihelp-unblock-param-user": "Nom d’utilisateur, adresse IP ou plage d’adresses IP à débloquer. Impossible à utiliser en même temps que <var>$1id</var> ou <var>$luserid</var>.",
+       "apihelp-unblock-param-userid": "ID de l'utilisateur à débloquer. Ne peut être utilisé avec <var>$1id</var> ou <var>$1user</var>.",
        "apihelp-unblock-param-reason": "Motif de déblocage.",
        "apihelp-unblock-param-tags": "Modifier les balises à appliquer à l’entrée dans le journal de blocage.",
        "apihelp-unblock-example-id": "Lever le blocage d’ID #<kbd>105</kbd>.",
        "apihelp-phpfm-description": "Extraire les données au format sérialisé de PHP (affiché proprement en HTML).",
        "apihelp-rawfm-description": "Extraire les données, y compris les éléments de débogage, au format JSON (affiché proprement en HTML).",
        "apihelp-xml-description": "Extraire les données au format XML.",
-       "apihelp-xml-param-xslt": "Si spécifié, ajoute la page nommée comme une feuille de style XSL. La valeur doit être un titre dans l’espace de noms {{ns:mediawiki}} se terminant par <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "Si spécifié, ajoute la page nommée comme une feuille de style XSL. La valeur doit être un titre dans l’espace de noms {{ns:MediaWiki}} se terminant par <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Si spécifié, ajoute un espace de noms XML.",
        "apihelp-xmlfm-description": "Extraire les données au format XML (affiché proprement en HTML).",
        "api-format-title": "Résultat de l’API de MediaWiki",
        "api-help-authmanagerhelper-returnurl": "Renvoyer l’URL pour les flux d’authentification tiers, qui doit être absolue. Cela ou <var>$1continue</var> est obligatoire.\n\nDès réception d’une réponse <samp>REDIRECT</samp>, vous ouvrirez typiquement un navigateur ou un affichage web vers l’URL <samp>redirecttarget</samp> spécifiée pour un flux d’authentification tiers. Une fois ceci terminé, le tiers renverra le navigateur ou l’affichage web vers cette URL. Vous devez extraire toute requête ou paramètre POST de l’URL et les passer comme une requête <var>$1continue</var> à ce module de l’API.",
        "api-help-authmanagerhelper-continue": "Cette requête est une continuation après une précédente réponse <samp>UI</samp> ou <samp>REDIRECT</samp>. Cela ou <var>$1returnurl</var> est obligatoire.",
        "api-help-authmanagerhelper-additional-params": "Ce module accepte des paramètres supplémentaires selon les requêtes d’authentification disponibles. Utiliser <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> avec <kbd>amirequestsfor=$1</kbd> (ou une réponse précédente de ce module, le cas échéant) pour déterminer les requêtes disponibles et les champs qu’elles utilisent.",
+       "apierror-allimages-redirect": "Utiliser <kbd>gaifilterredir=nonredirects</kbd> au lieu de <var>redirects</var> quand <kbd>allimages</kbd> est utilisé comme générateur.",
+       "apierror-allpages-generator-redirects": "Utiliser <kbd>gapfilterredir=nonredirects</kbd> au lieu de <var>redirects</var> quand <kbd>allpages</kbd> est utilisé comme un générateur.",
+       "apierror-appendnotsupported": "Impossible d’ajouter aux pages utilisant le modèle de contenu $1.",
+       "apierror-articleexists": "L’article que vous essayez de créer l’a déjà été.",
+       "apierror-assertbotfailed": "La vérification que l’utilisateur a le droit <code>bot</code> a échoué.",
+       "apierror-assertnameduserfailed": "La vérification que l’utilisateur est « $1 » a échoué.",
+       "apierror-assertuserfailed": "La vérification que l’utilisateur est connecté a échoué.",
+       "apierror-autoblocked": "Votre adresse IP a été bloquée automatiquement, parce qu’elle a été utilisée par un utilisateur bloqué.",
+       "apierror-badconfig-resulttoosmall": "La valeur de <code>$wgAPIMaxResultSize</code> sur ce wiki est trop petite pour contenir des informations de résultat basiques.",
+       "apierror-badcontinue": "Paramètre de continuation non valide. Vous devez passer la valeur d’origine renvoyée par la requête précédente.",
+       "apierror-baddiff": "La différence ne peut être récupérée, une ou plusieurs révisions n’existent pas ou vous n’avez pas le droit de les voir.",
+       "apierror-baddiffto": "<var>$1diffto</var> doit être fixé à un nombre positif ou nul, <kbd>prev</kbd>, <kbd>next</kbd> ou <kbd>cur</kbd>.",
+       "apierror-badformat-generic": "Le format demandé $1 n’est pas supporté pour le modèle de contenu $2.",
+       "apierror-badformat": "Le format demandé $1 n’est pas supporté pour le modèle de contenu $2 utilisé par $3.",
+       "apierror-badgenerator-notgenerator": "Le module <kbd>$1</kbd> ne peut pas être utilisé comme générateur.",
+       "apierror-badgenerator-unknown": "<kbd>generator=$1</kbd> inconnu.",
+       "apierror-badip": "Paramètre IP non valide.",
+       "apierror-badmd5": "Le hachage MD5 fourni n’était pas correct.",
+       "apierror-badmodule-badsubmodule": "Le module <kbd>$1</kbd> n’a pas de sous-module « $2 ».",
+       "apierror-badmodule-nosubmodules": "Le module <kbd>$1</kbd> n’a pas de sous-modules.",
+       "apierror-badparameter": "Valeur non valide pour le paramètre <var>$1</var>.",
+       "apierror-badquery": "Requête invalide.",
+       "apierror-badtimestamp": "Valeur non valide « $2 » pour le paramètre de référence horaire  <var>$1</var>.",
+       "apierror-badtoken": "Jeton CSRF non valide.",
+       "apierror-badupload": "Le paramètre de téléchargement de fichier <var>$1</var> n’est pas un téléchargement de fichier ; assurez-vous d’utiliser <code>multipart/form-data</code> pour votre POST et d’inclure un nom de fichier dans l’entête <code>Content-Disposition</code>.",
+       "apierror-badurl": "Valeur « $2 » non valide pour le paramètre d’URL <var>$1</var>.",
+       "apierror-baduser": "Valeur « $2 » non valide pour le paramètre utilisateur <var>$1</var>.",
+       "apierror-badvalue-notmultivalue": "La séparation multi-valeur U+001F ne peut être utilisée que pour des paramètres multi-valeurs.",
+       "apierror-bad-watchlist-token": "Jeton de liste de suivi fourni non valide. Veuillez mettre un jeton valide dans [[Special:Preferences]].",
+       "apierror-blockedfrommail": "Vous avez été bloqué pour l’envoi de courriel.",
+       "apierror-blocked": "Vous avez été bloqué pour modifier.",
+       "apierror-botsnotsupported": "Cette interface n’est pas supportée pour les robots.",
+       "apierror-cannotreauthenticate": "Cette action n’est pas disponible car votre identité ne peut pas être vérifiée.",
+       "apierror-cannotviewtitle": "Vous n’êtes pas autorisé à voir $1.",
+       "apierror-cantblock-email": "Vous n’avez pas le droit de bloquer des utilisateurs pour envoyer des courriels via ce wiki.",
+       "apierror-cantblock": "Vous n’avez pas le droit de bloquer des utilisateurs.",
+       "apierror-cantchangecontentmodel": "Vous n’avez pas le droit de modifier le modèle de contenu d’une page.",
+       "apierror-canthide": "Vous n’avez pas le droit de masquer les noms d’utilisateur du journal de blog.",
+       "apierror-cantimport-upload": "Vous n’avez pas le droit d’importer des pages téléchargées.",
+       "apierror-cantimport": "Vous n’avez pas le droit d’importer des pages.",
+       "apierror-cantoverwrite-sharedfile": "Le fichier cible existe dans un dépôt partagé et vous n’avez pas le droit de l’écraser.",
+       "apierror-cantsend": "Vous n’êtes pas connecté, vous n’avez pas d’adresse de courriel confirmée, ou vous n’êtes pas autorisé à envoyer des courriels aux autres utilisateurs, donc vous ne pouvez envoyer de courriel.",
+       "apierror-cantundelete": "Impossible d’annuler : les révisions demandées peuvent ne plus exister, ou avoir déjà été annulées.",
+       "apierror-changeauth-norequest": "Échec à la création de la requête de modification.",
+       "apierror-chunk-too-small": "La taille minimale d’un segment est de $1 {{PLURAL:$1|octet|octets}} pour les segments hors le dernier.",
+       "apierror-cidrtoobroad": "Les plages CIDR $1 plus large que /$2 ne sont pas acceptées.",
+       "apierror-compare-inputneeded": "Un titre, un ID de page ou un numéro de révision est nécessaire pour les paramètres <var>from</var> et <var>to</var>.",
+       "apierror-contentserializationexception": "Échec de sérialisation du contenu : $1",
+       "apierror-contenttoobig": "Le contenu que vous avez fourni dépasse la limite de taille d’un article, qui est de $1 {{PLURAL:$1|kilooctet|kilooctets}}.",
+       "apierror-copyuploadbaddomain": "Les téléchargements par URL ne sont pas autorisés pour ce domaine.",
+       "apierror-copyuploadbadurl": "Les téléchargements ne sont pas autorisés depuis cette URL.",
+       "apierror-create-titleexists": "Les titres existants ne peuvent pas être protégés avec <kbd>create</kbd>.",
+       "apierror-csp-report": "Erreur lors du traitement du rapport CSP: $1.",
+       "apierror-databaseerror": "[$1] erreur de requête de base de données.",
+       "apierror-deletedrevs-param-not-1-2": "Le paramètre <var>$1</var> ne peut pas être utilisé dans les modes 1 ou 2.",
+       "apierror-deletedrevs-param-not-3": "Le paramètre <var>$1</var> ne peut pas être utilisé dans le mode 3.",
+       "apierror-emptynewsection": "Il n'est pas possible de créer de nouvelles sections vides.",
+       "apierror-emptypage": "Il n'est pas possible de créer de nouvelles pages vides.",
+       "apierror-exceptioncaught": "[$1] Exception interceptée: $2",
+       "apierror-filedoesnotexist": "Le fichier n’existe pas.",
+       "apierror-fileexists-sharedrepo-perm": "Le fichier cible existe dans un dépôt partagé. Utilisr le paramètre <var>ignorewarnings</var> pour l’écraser.",
+       "apierror-filenopath": "Il n'est pas possible de récupérer le chemin du fichier local.",
+       "apierror-filetypecannotberotated": "Le type du fichier ne peut pas être tourné.",
+       "apierror-formatphp": "Cette réponse ne peut pas être représentée en utilisant <kbd>format=php</kbd>. Voir https://phabricator.wikimedia.org/T68776.",
+       "apierror-imageusage-badtitle": "Le titre pour <kbd>$1</kbd> doit être un fichier.",
+       "apierror-import-unknownerror": "Erreur inconnue lors de l'importation: $1.",
+       "apierror-integeroutofrange-abovebotmax": "<var>$1</var> ne peut pas dépasser $2 (fixé à $3) pour les robots ou les opérateurs système.",
+       "apierror-integeroutofrange-abovemax": "<var>$1</var> ne peut pas dépasser $2 (fixé à $3) pour les utilisateurs.",
+       "apierror-integeroutofrange-belowminimum": "<var>$1</var> ne peut pas être inférieur à $2 (fixé à $3).",
+       "apierror-invalidcategory": "Le nom de la catégorie que vous avez entré n'est pas valide.",
+       "apierror-invalid-chunk": "Le déplacement plus le segment actuel dépassent la taille demandée du fichier.",
+       "apierror-invalidexpiry": "Heure d'expiration invalide \"$1\".",
+       "apierror-invalid-file-key": "Ne correspond pas à une clé valide de fichier.",
+       "apierror-invalidlang": "Code de langue non valide pour le paramètre <var>$1</var>.",
+       "apierror-invalidoldimage": "Le paramètre oldimage a un format non valide.",
+       "apierror-invalidparammix-cannotusewith": "Le paramètre <kbd>$1</kbd> ne peut pas être utilisé avec <kbd>$2</kbd>.",
+       "apierror-invalidparammix-mustusewith": "Le paramètre <kbd>$1</kbd> ne peut être utilisé qu’avec <kbd>$2</kbd>.",
+       "apierror-invalidparammix-parse-new-section": "<kbd>section=new</kbd> ne peut pas être combiné avec le paramètre <var>oldid</var>, <var>pageid</var> ou <var>page</var>. Veuillez utiliser <var>title</var> et <var>text</var>.",
+       "apierror-invalidparammix": "{{PLURAL:$2|Les paramètres}} $1 ne peuvent pas être utilisés ensemble.",
+       "apierror-invalidsection": "Le paramètre section doit être un ID de section valide ou <kbd>new</kbd>.",
+       "apierror-invalidsha1base36hash": "Le hachage SHA1Base36 fourni n’est pas valide.",
+       "apierror-invalidsha1hash": "Le hachage SHA1 fourni n’est pas valide.",
+       "apierror-invalidtitle": "Mauvais titre « $1 ».",
+       "apierror-invalidurlparam": "Valeur non valide pour <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
+       "apierror-invaliduser": "Nom d'utilisateur invalide \"$1\".",
+       "apierror-maxlag-generic": "Attente d’un serveur de base de données : $1 {{PLURAL:$1|seconde|secondes}} de délai.",
+       "apierror-maxlag": "Attente de $2 : $1 {{PLURAL:$1|seconed|secondes}} de délai.",
+       "apierror-mimesearchdisabled": "La recherche MIME est désactivée en mode Misère.",
+       "apierror-missingcontent-pageid": "Contenu manquant pour la page d’ID $1.",
+       "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|Le paramètre|Au moins un des paramètres}} $1 est obligatoire.",
+       "apierror-missingparam-one-of": "{{PLURAL:$2|Le paramètre|Un des paramètres}} $1 est obligatoire.",
+       "apierror-missingparam": "Le paramètre <var>$1</var> doit être défini.",
+       "apierror-missingrev-pageid": "Aucune révision actuelle de la page d’ID $1.",
+       "apierror-missingtitle-createonly": "Les titres manquants ne peuvent être protégés qu’avec <kbd>create</kbd>.",
+       "apierror-missingtitle": "La page que vous avez spécifié n’existe pas.",
+       "apierror-missingtitle-byname": "La page $1 n’existe pas.",
+       "apierror-moduledisabled": "Le module <kbd>$1</kbd> a été désactivé.",
+       "apierror-multival-only-one-of": "{{PLURAL:$3|Seul|Seul un des}} $2 est autorisé pour le paramètre <var>$1</var>.",
+       "apierror-multival-only-one": "Une seule valeur est autorisée pour le paramètre <var>$1</var>.",
+       "apierror-multpages": "<var>$1</var> ne peut être utilisé qu’avec une seule page.",
+       "apierror-mustbeloggedin-changeauth": "Vous devez être connecté pour modifier les données d’authentification.",
+       "apierror-mustbeloggedin-generic": "Vous devez être connecté.",
+       "apierror-mustbeloggedin-linkaccounts": "Vous devez être connecté pour lier des comptes.",
+       "apierror-mustbeloggedin-removeauth": "Vous devez être connecté pour supprimer les données d’authentification.",
+       "apierror-mustbeloggedin-uploadstash": "La réserve de téléchargement n’est disponible que pour les utilisateurs connectés.",
+       "apierror-mustbeloggedin": "Vous devez être connecté pour $1.",
+       "apierror-mustbeposted": "Le module <kbd>$1</kbd> nécessite une requête POST.",
+       "apierror-mustpostparams": "{{PLURAL:$2|Le paramètre suivant a été trouvé|Les paramètres suivants ont été trouvés}} dans la chaîne de requête, mais doit être dans le corps du POST : $1.",
+       "apierror-noapiwrite": "La modification de ce wiki via l’API est désactivée. Assurez-vous que la déclaration <code>$wgEnableWriteAPI=true;</code> st inclue dans le fichier <code>LocalSettings.php</code> du wiki.",
+       "apierror-nochanges": "Aucun changement n’a été demandé.",
+       "apierror-nodeleteablefile": "Pas de telle ancienne version du fichier.",
+       "apierror-no-direct-editing": "La modification directe via l’API n’est pas supportée pour le modèle de contenu $1 utilisée par $2.",
+       "apierror-noedit-anon": "Les utilisateurs anonymes ne peuvent pas modifier les pages.",
+       "apierror-noedit": "Vous n’avez pas le droit de modifier les pages.",
+       "apierror-noimageredirect-anon": "Les utilisateurs anonymes ne peut pas créer des redirections d’image.",
+       "apierror-noimageredirect": "Vous n’avez pas le droit de créer des redirections d’image.",
+       "apierror-nosuchlogid": "Il n’y a pas d’entrée du journal avec l’ID $1.",
+       "apierror-nosuchpageid": "Il n’y a pas de page avec l’ID $1.",
+       "apierror-nosuchrcid": "Il n’y a pas de modification récente avec l’ID $1.",
+       "apierror-nosuchrevid": "Il n’y a pas de révision d’ID $1.",
+       "apierror-nosuchsection": "Il n’y a pas de section $1.",
+       "apierror-nosuchsection-what": "Il ’y a pas de section $1 dans $2.",
+       "apierror-nosuchuserid": "Il n'y a pas d'utilisateur ayant l'ID $1.",
+       "apierror-notarget": "Vous n’avez pas spécifié une cible valide pour cette action.",
+       "apierror-notpatrollable": "La révision r$1 ne peut pas être patrouillée car elle est trop ancienne.",
+       "apierror-nouploadmodule": "Aucun module de téléchargement défini.",
+       "apierror-opensearch-json-warnings": "Les avertissements ne peuvent pas être représentés dans le format JSON OpenSearch.",
+       "apierror-pagecannotexist": "L’espace de noms ne permet pas de pages réelles.",
+       "apierror-pagedeleted": "La page a été supprimée depuis que vous avez récupéré son horodatage.",
+       "apierror-paramempty": "Le paramètre <var>$1</var> ne peut pas être vide.",
+       "apierror-parsetree-notwikitext": "<kbd>prop=parsetree</kbd> n’est supporté que pour le contenu wikitexte.",
+       "apierror-parsetree-notwikitext-title": "<kbd>prop=parsetree</kbd> n’est supporté que pour le contenu wikitexte. $1 utilise le modèle de contenu $2.",
+       "apierror-pastexpiry": "Le temps d’expiration « $1 » est dans le passé.",
+       "apierror-permissiondenied": "Vous n’avez pas le droit de $1.",
+       "apierror-permissiondenied-generic": "Autorisation refusée.",
+       "apierror-permissiondenied-patrolflag": "Vous avez besoin du droit <code>patrol</code> ou <code>patrolmarks</code> pour demander le drapeau patrouillé.",
+       "apierror-permissiondenied-unblock": "Vous n’avez pas le droit de débloquer les utilisateurs.",
+       "apierror-prefixsearchdisabled": "La recherche de préfixe est désactivée en mode misérable.",
+       "apierror-promised-nonwrite-api": "L’entête HTTP <code>Promise-Non-Write-API-Action</code> ne peut pas être envoyé aux modules de l’API en mode écriture.",
+       "apierror-protect-invalidaction": "Type de protection non valide « $1 ».",
+       "apierror-protect-invalidlevel": "Niveau de protection non valide « $1 ».",
+       "apierror-ratelimited": "Vous avez dépassé votre limite de débit. Veuillez attendre un peu et réessayer.",
+       "apierror-readapidenied": "Vous avez besoin du droit de lecture pour utiliser ce module.",
+       "apierror-readonly": "Ce wiki est actuellement en mode lecture seule.",
+       "apierror-reauthenticate": "Vous n’avez pas authentifié récemment cette session ; veuillez vous authentifier de nouveau.",
+       "apierror-redirect-appendonly": "Vous avez essayé de modifier en utilisant le mode de suivi de redirection, qui doit être utilisé en lien avec <kbd>section=new</kbd>, <var>prependtext</var>, ou <var>appendtext</var>.",
+       "apierror-revdel-mutuallyexclusive": "Le même champ ne peut pas être utilisé à la fois en <var>hide</var> et <var>show</var>.",
+       "apierror-revdel-needtarget": "Un titre cible est nécessaire pour ce type RevDel.",
+       "apierror-revdel-paramneeded": "Au moins une valeur est nécessaire pour <var>hide</var> ou <var>show</var>.",
+       "apierror-revisions-norevids": "Le paramètre <var>revids</var> ne peut pas être utilisé avec les options de liste (<var>$1limit</var>, <var>$1startid</var>, <var>$1endid</var>, <kbd>$1dir=newer</kbd>, <var>$1user</var>, <var>$1excludeuser</var>, <var>$1start</var>, et <var>$1end</var>).",
+       "apierror-revisions-singlepage": "<var>titles</var>, <var>pageids</var> ou un générateur a été utilisé pour fournir plusieurs pages, mais les paramètres <var>$1limit</var>, <var>$1startid</var>, <var>$1endid</var>, <kbd>$1dir=newer</kbd>, <var>$1user</var>, <var>$1excludeuser</var>, <var>$1start</var> et <var>$1end</var> ne peuvent être utilisés que sur une seule page.",
+       "apierror-revwrongpage": "r$1 n'est pas une révision de $2.",
+       "apierror-searchdisabled": "La recherche <var>$1</var> est désactivée.",
+       "apierror-sectionreplacefailed": "Impossible de fusionner la section mise à jour.",
+       "apierror-sectionsnotsupported": "Les sections ne sont pas prises en charge pour le modèle de contenu $1.",
+       "apierror-sectionsnotsupported-what": "Les sections ne sont pas prises en charge par $1.",
+       "apierror-show": "Paramètre incorrect - des valeurs mutuellement exclusives ne peuvent pas être fournies.",
+       "apierror-siteinfo-includealldenied": "Impossible d’afficher toutes les informatiosn du serveur, sauf si <var>$wgShowHostNames</var> vaut vrai.",
+       "apierror-sizediffdisabled": "La différence de taille est désactivée dans le mode Miser.",
+       "apierror-spamdetected": "Votre modification a été refusée parce qu'elle contenait un fragment de spam: <code>$1</code>.",
+       "apierror-specialpage-cantexecute": "Vous n'avez pas l'autorisation d'afficher les résultats de cette page spéciale.",
+       "apierror-stashedfilenotfound": "Impossible de trouver le fichier dans la réserve: $1.",
+       "apierror-stashedit-missingtext": "Pas de texte en réserve associé à la donnée de hachage.",
+       "apierror-stashfailed-complete": "Un téléchargement par morceaux est déjà achevé, vérifiez l’état pour plus de détails.",
+       "apierror-stashfailed-nosession": "Aucune session de téléchargement par morceaux avec cette clé.",
+       "apierror-stashfilestorage": "Impossible de mettre le téléchargement en réserve: $1",
+       "apierror-stashnosuchfilekey": "Filekey inconnue: $1.",
+       "apierror-stashpathinvalid": "La clé du fichier n'a pas le bon format ou est invalide: $1 .",
+       "apierror-stashwrongowner": "Erreur de propriétaire: $1",
+       "apierror-stashzerolength": "Fichier est de longueur nulle, et n'a pas pu être mis dans la réserve: $1.",
+       "apierror-templateexpansion-notwikitext": "Le développement du modèle n'est effectif que sur un contenu wikitext. $1 utilise le modèle de contenu $2.",
+       "apierror-toofewexpiries": "$1 {{PLURAL:$1|horodatage d’expiration a été fourni|horodatages d’expiration ont été fournis}} alors que $2 {{PLURAL:$2|était attendu|étaient attendus}}.",
+       "apierror-unknownaction": "L'action spécifiée, <kbd>$1</kbd>, n'est pas reconnue.",
+       "apierror-unknownerror-editpage": "Erreur inconnue EditPage: $1.",
+       "apierror-unknownerror-nocode": "Erreur inconnue.",
+       "apierror-unknownerror": "Erreur inconnue : « $1 ».",
+       "apierror-unknownformat": "Format inconnu \"$1\".",
+       "apierror-unrecognizedparams": "Paramètre{{PLURAL:$2||s}} non reconnu{{PLURAL:$2||s}} : $1.",
+       "apierror-unrecognizedvalue": "Valeur non reconnue du paramètre <var>$1</var>: $2.",
+       "apierror-unsupportedrepo": "Le dépôt local des fichiers ne prend pas en charge la recherche de toutes les images.",
+       "apierror-upload-filekeyneeded": "Un <var>filekey</var> est nécessaire si le <var>décalage</var> est non nul.",
+       "apierror-upload-filekeynotallowed": "Pas possible de fournir une <var>filekey</var> si <var>offset</var> vaut 0.",
+       "apierror-upload-inprogress": "Le téléversement à partir de la réserve est déjà en cours.",
+       "apierror-upload-missingresult": "Pas de résultat dans les données d'état.",
+       "apierror-urlparamnormal": "Impossible de normaliser les paramètres de l'image pour $1.",
+       "apierror-writeapidenied": "Vous n'êtes pas autorisé à modifier ce wiki au travers de l'API.",
+       "apiwarn-alldeletedrevisions-performance": "Pour de meilleures performances lors de la génération des titres, mettre <kbd>$1dir=newer</kbd>.",
+       "apiwarn-badurlparam": "Impossible d'analyser <var>$1urlparam</var> pour $2. En utilisant seulement la largeur et la hauteur.",
+       "apiwarn-badutf8": "La valeur passée pour <var>$1</var> contient des données non valides ou non normalisées. Les données textuelles doivent être de l’Unicode valide normalisé en NFC sans caractères de contrôle c0 autres que HT (\\t), LF (\\n) et CR (\\r).",
+       "apiwarn-checktoken-percentencoding": "Vérifier que les symboles tels que \"+\" dans le jeton sont correctement  codés avec des pourcents dans l'URL.",
+       "apiwarn-deprecation-deletedrevs": "<kbd>list=deletedrevs</kbd> est devenu obsolète. Veuillez utiliser <kbd>prop=deletedrevisions</kbd> ou <kbd>list=alldeletedrevisions</kbd> à la place.",
+       "apiwarn-deprecation-expandtemplates-prop": "Comme aucune valeur n’a été spécifiée pour le paramètre <var>prop</var>, un format d’héritage a été utilisé pour la sortie. Ce format est obsolète et, dans le futur, une valeur par défaut sera fixée pour le paramètre <var>prop</var>, provoquant ainsi l’utilisation systématique du nouveau format.",
+       "apiwarn-deprecation-httpsexpected": "HTTP est utilisé alors que HTTPS est attendu.",
+       "apiwarn-deprecation-login-botpw": "La connexion au compte principal via <kbd>action=login</kbd> est obsolète et peut ne plus fonctionner sans avertissement. Pour continuer à vous connecter avec <kbd>action=login</kbd>, voyez [[Special:BotPasswords]]. Pour continuer à utiliser la connexion au compte principal en toute sécurité, voyez <kbd>action=clientlogin</kbd>.",
+       "apiwarn-deprecation-login-nobotpw": "La connexion au compte principal via <kbd>action=login</kbd> est obsolète et peut ne plus fonctionner sans avertissement. Pour vous connecter en toute sécurité, voyez <kbd>action=clientlogin</kbd>.",
+       "apiwarn-deprecation-login-token": "La récupération d’un jeton via <kbd>action=login</kbd> est obsolète. Utilisez <kbd>action=query&meta=tokens&type=login</kbd> à la place.",
+       "apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est obsolète.",
+       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est obsolète depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+       "apiwarn-deprecation-purge-get": "L'utilisation de <kbd>action=purge</kbd> via un GET est obsolète. Utiliser POST à la place.",
+       "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est obsolète. Veuillez utiliser <kbd>$2</kbd> à la place.",
+       "apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
+       "apiwarn-errorprinterfailed": "Erreur échec imprimante. Nouvel essai sans paramètres.",
+       "apiwarn-errorprinterfailed-ex": "Erreur d’échec de l’impression (réessayera sans paramètres) : $1",
+       "apiwarn-invalidcategory": "« $1 » n'est pas une catégorie.",
+       "apiwarn-invalidtitle": "« $1 » n’est pas un titre valide.",
+       "apiwarn-invalidxmlstylesheetext": "Une feuille de style doit avoir une extension <code>.xsl</code>.",
+       "apiwarn-invalidxmlstylesheet": "Feuille de style spécifiée non valide ou inexistante.",
+       "apiwarn-invalidxmlstylesheetns": "La feuille de style devrait être dans l’espace de noms {{ns:MediaWiki}}.",
+       "apiwarn-moduleswithoutvars": "La propriété <kbd>modules</kbd> a été définie mais pas <kbd>jsconfigvars</kbd> ni <kbd>encodedjsconfigvars</kbd>. Les variables de configuration sont nécessaires pour une utilisation correcte du module.",
+       "apiwarn-notfile": "« $1 » n'est pas un fichier.",
+       "apiwarn-nothumb-noimagehandler": "Impossible de créer la vignette car $1 n’a pas de gestionnaire d’image associé.",
+       "apiwarn-parse-nocontentmodel": "Ni <var>title</var> ni <var>contentmodel</var> n’ont été fournis, $1 est supposé.",
+       "apiwarn-parse-titlewithouttext": "<var>title</var> utilisé sans <var>text</var>, et les propriétés de page analysées sont nécessaires. Voulez-vous dire que vous voulez utiliser <var>page</var> à la place de <var>title</var> ?",
+       "apiwarn-redirectsandrevids": "La résolution de la redirection ne peut pas être utilisée avec le paramètre <var>revids</var>. Toutes les redirections vers lesquelles pointent <var>revids</var> n’ont pas été résolues.",
+       "apiwarn-tokennotallowed": "L'action « $1 » n'est pas autorisée pour l'utilisateur actuel.",
+       "apiwarn-tokens-origin": "Les jetons ne peuvent pas être obtenus quand la politique de même origine n’est pas appliquée.",
+       "apiwarn-toomanyvalues": "Trop de valeurs fournies pour le paramètre <var>$1</var>: la limite est $2.",
+       "apiwarn-truncatedresult": "Ce résultat a été tronqué parce que sinon, il dépasserait la limite de $1 octets.",
+       "apiwarn-unclearnowtimestamp": "Passer « $2 » comme paramètre d’horodatage <var>$1</var> a été rendu obsolète. Si, pour une raison quelconque, vous avez besoin de spécifier explicitement l’heure courante sans calculer son côté client, utilisez <kbd>now<kbd>.",
+       "apiwarn-unrecognizedvalues": "{{PLURAL:$3|Valeur non reconnue|Valeurs non reconnues}} pour le paramètre <var>$1</var> : $2.",
+       "apiwarn-unsupportedarray": "Le paramètre <var>$1</var> utilise une syntaxe PHP de tableau non prise en charge.",
+       "apiwarn-urlparamwidth": "Valeur de la largeur définie dans <var>$1urlparam</var> ($2) ignorée en faveur de la largeur calculée à partir de <var>$1urlwidth</var>/<var>$1urlheight</var> ($3).",
+       "apiwarn-validationfailed-badchars": "caractères non valides dans la clé (<code>a-z</code>, <code>A-Z</code>, <code>0-9</code>, <code>_</code>et <code>-</code> sont les seuls autorisés).",
+       "apiwarn-validationfailed-badpref": "pas une préférence valide.",
+       "apiwarn-validationfailed-cannotset": "ne peut pas être initialisé par ce module.",
+       "apiwarn-validationfailed-keytoolong": "clé trop longue (au plus $1 octets).",
+       "apiwarn-validationfailed": "Erreur de validation pour <kbd>$1</kbd>: $2",
+       "apiwarn-wgDebugAPI": "<strong>Avertissement de sécurité</strong>: <var>$wgDebugAPI</var> est activé.",
+       "api-feed-error-title": "Erreur ($1)",
+       "api-usage-docref": "Voir $1 concernant l'utilisation de l'API.",
+       "api-exception-trace": "$1 à $2($3)\n$4",
        "api-credits-header": "Remerciements",
        "api-credits": "Développeurs de l’API :\n* Roan Kattouw (développeur en chef Sept. 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (créateur, développeur en chef Sept. 2006–Sept. 2007)\n* Brad Jorsch (développeur en chef depuis 2013)\n\nVeuillez envoyer vos commentaires, suggestions et questions à mediawiki-api@lists.wikimedia.org\nou remplir un rapport de bogue sur https://phabricator.wikimedia.org/."
 }
index 69fdf56..2a14cc2 100644 (file)
        "apihelp-main-param-requestid": "Calquera valor dado aquí será incluído na resposta. Pode usarse para distingir peticións.",
        "apihelp-main-param-servedby": "Inclúa o nome do servidor que servía a solicitude nos resultados.",
        "apihelp-main-param-curtimestamp": "Incluir a marca de tempo actual no resultado.",
+       "apihelp-main-param-responselanginfo": "Incluír no resultado as linguas usada para <var>uselang</var> e <var>errorlang</var>.",
        "apihelp-main-param-origin": "Cando se accede á API usando unha petición AJAX entre-dominios (CORS), inicializar o parámetro co dominio orixe. Isto debe incluírse en calquera petición pre-flight, e polo tanto debe ser parte da petición URI (non do corpo POST). Para peticións autenticadas, isto debe coincidir exactamente cunha das orixes na cabeceira <code>Origin</code>, polo que ten que ser fixado a algo como <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Se este parámetro non coincide coa cabeceira <code>Origin</code>, devolverase unha resposta 403. Se este parámetro coincide coa cabeceira <code>Origin</code> e a orixe está na lista branca, as cabeceiras <code>Access-Control-Allow-Origin</code> e <code>Access-Control-Allow-Credentials</code> serán fixadas.\n\nPara peticións non autenticadas, especifique o valor <kbd>*</kbd>. Isto fará que se fixe a cabeceira <code>Access-Control-Allow-Origin</code>, pero <code>Access-Control-Allow-Credentials</code> será <code>false</code> e todos os datos específicos do usuario serán ocultados.",
        "apihelp-main-param-uselang": "Linga a usar para a tradución de mensaxes. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd> devolve unha lista de códigos de lingua, ou especificando <kbd>user</kbd> coa preferencia de lingua do usuario actual, ou especificando <kbd>content</kbd> para usar a lingua do contido desta wiki.",
+       "apihelp-main-param-errorformat": "Formato a usar para a saída do texto de aviso e de erroː\n; plaintext:  texto wiki sen as etiquetas HTML e coas entidades substituídas.\n; wikitext: texto wiki sen analizar.\n; html: HTML.\n; raw: Clave de mensaxe e parámetros.\n; none: Sen saída de texto, só os códigos de erro.\n; bc: Formato utilizado antes de MediaWiki 1.29. <var>errorlang</var> e <var>errorsuselocal</var> non se teñen en conta.",
        "apihelp-block-description": "Bloquear un usuario.",
        "apihelp-block-param-user": "Nome de usuario, dirección ou rango de IPs que quere bloquear.",
        "apihelp-block-param-expiry": "Tempo de caducidade. Pode ser relativo (p. ex.<kbd>5 meses</kbd> ou <kbd>2 semanas</kbd>) ou absoluto (p. ex. 2014-09-18T12:34:56Z</kbd>). Se se pon kbd>infinite</kbd>, <kbd>indefinite</kbd>, ou <kbd>never</kbd>, o bloqueo nunca caducará.",
        "apihelp-query+allmessages-param-prop": "Que propiedades obter.",
        "apihelp-query+allmessages-param-enableparser": "Marcar para activar o analizador, isto preprocesará o texto wiki da mensaxe (substituir palabras máxicas, xestionar modelo, etc.)",
        "apihelp-query+allmessages-param-nocontent": "Se se marca, non inclúe o contido das mensaxes na saída.",
-       "apihelp-query+allmessages-param-includelocal": "Tamén inclúe mensaxes locais, p.ex. mensaxes que non existen no software pero existen como unha páxina MediaWiki:. \nIsto lista todas as páxinas MediaWiki:, polo que tamén listará as que non son realmente mensaxes como [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "Tamén inclúe mensaxes locais, p.ex. mensaxes que non existen no software pero existen como no espazo de nomes {{ns:MediaWiki}}. \nIsto lista todas as páxinas do espazo de nomes {{ns:MediaWiki}}, polo que tamén listará as que non son realmente mensaxes como [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Argumentos a substituír na mensaxe.",
        "apihelp-query+allmessages-param-filter": "Retornar só mensaxes con nomes que conteñan esta cadea.",
        "apihelp-query+allmessages-param-customised": "Devolver só mensaxes neste estado de personalización.",
        "apihelp-phpfm-description": "Datos de saída en formato serializado de PHP(impresión en HTML).",
        "apihelp-rawfm-description": "Datos de saída, incluíndo os elementos de depuración, en formato JSON (impresión en HTML).",
        "apihelp-xml-description": "Datos de saída en formato XML.",
-       "apihelp-xml-param-xslt": "Se está indicado, engade o nome da páxina como unha folla de estilo XSL. O valor debe ser un título no espazo de nomes {{ns:mediawiki}} rematando con <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "Se está indicado, engade o nome da páxina como unha folla de estilo XSL. O valor debe ser un título no espazo de nomes {{ns:MediaWiki}} rematando con <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Se está indicado, engade un espazo de nomes XML.",
        "apihelp-xmlfm-description": "Datos de saída en formato XML(impresión en HTML).",
        "api-format-title": "Resultado de API de MediaWiki",
        "api-help-authmanagerhelper-returnurl": "Devolve o URL para os fluxos de autenticación de terceiros, que debe ser absoluto. Este ou <var>$1continue</var> é obrigatorio.\n\nLogo da recepción dunha resposta <samp>REDIRECT</samp>, vostede normalmente abrirá un navegador web ou un visor web para ver a URL <samp>redirecttarget</samp> especificada para un fluxo de autenticación de terceiros. Cando isto se complete, a aplicación de terceiros enviará ó navegador web ou visor web a esta URL. Vostede debe eliminar calquera consulta ou parámetros POST da URL e pasalos como unha consulta <var>$1continue</var> a este módulo API.",
        "api-help-authmanagerhelper-continue": "Esta petición é unha continucación despois dun resposta precedente <samp>UI</samp> ou <samp>REDIRECT</samp>. Esta ou <var>$1returnurl</var> é requirida.",
        "api-help-authmanagerhelper-additional-params": "Este módulo acepta parámetros adicionais dependendo das consultas de autenticación dispoñibles. Use <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> con <kbd>amirequestsfor=$1</kbd> (ou unha resposta previa deste módulo, se aplicable) para determinar as consultas dispoñibles e os campos que usan.",
+       "apierror-articleexists": "O artigo que intentou crear xa existe.",
+       "apierror-badip": "O parámetro IP non é válido.",
+       "apierror-badmd5": "O código hash MD5 non era incorrecto.",
+       "apierror-badmodule-badsubmodule": "O módulo <kbd>$1</kbd> non ten un submódulo \"$2\".",
+       "apierror-badmodule-nosubmodules": "O módulo <kbd>$1</kbd> non ten submódulos.",
+       "apierror-badparameter": "Valor non válido para o parámetro <var>$1</var>.",
+       "apierror-badquery": "A consulta non é válida.",
+       "apierror-badtimestamp": "Valor \"$2\" non válido para o parámetro de data e hora <var>$1</var>.",
+       "apierror-badtoken": "Identificador CSRF non válido.",
+       "apierror-badurl": "Valor \"$2\" non válido para o parámetro de URL <var>$1</var>.",
+       "apierror-baduser": "Valor \"$2\" non válido para o parámetro de usuario <var>$1</var>.",
+       "apierror-blocked": "Foi bloqueado fronte á edición.",
+       "apierror-botsnotsupported": "Esta interface non está dispoñible para bots.",
+       "apierror-cannotviewtitle": "Non está autorizado para ver $1.",
+       "apierror-cantblock": "Non ten permisos para bloquear usuarios.",
+       "apierror-cantimport": "Non ten permisos para importar páxinas.",
+       "apierror-exceptioncaught": "[$1] Excepción capturada: $2",
+       "apierror-filedoesnotexist": "O ficheiro non existe.",
+       "apierror-filenopath": "Non é posible obter o camiño do ficheiro local.",
+       "apierror-filetypecannotberotated": "O tipo de ficheiro non permite que sexa rotado.",
+       "apierror-imageusage-badtitle": "O título para <kbd>$1</kbd> debe ser un ficheiro.",
+       "apierror-import-unknownerror": "Erro descoñecido ó importarː $1.",
+       "apierror-invalidexpiry": "Hora de caducidade incorrecta \"$1\".",
+       "apierror-invalid-file-key": "Non se corresponde cunha clave válida de ficheiro.",
+       "apierror-invalidlang": "Código de lingua incorrecto para o parámetro <var>$1</var>.",
+       "apierror-invalidoldimage": "O parámetro oldimage ten un formato incorrecto.",
+       "apierror-invalidparammix-cannotusewith": "O parámetro <kbd>$1</kbd> non pode usarse xunto con <kbd>$2</kbd>.",
+       "apierror-invalidparammix-mustusewith": "O parámetro <kbd>$1</kbd> só pode usarse xunto con <kbd>$2</kbd>.",
+       "apierror-invalidparammix": "{{PLURAL:$2|Os parámetros}} $1 non poden usarse xuntos.",
+       "apierror-invalidsection": "O parámetro sección debe ser un ID de sección válido ou <kbd>new</kbd>.",
+       "apierror-invalidsha1base36hash": "O código hash SHA1Base36 proporcionado non é correcto.",
+       "apierror-invalidsha1hash": "O código hash SHA1 proporcionado non é correcto.",
+       "apierror-invalidtitle": "Título incorrecto \"$1\".",
+       "apierror-invalidurlparam": "Valor non válido para <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
+       "apierror-invaliduser": "Nome de usuario incorrecto \"$1\".",
+       "apierror-maxlag-generic": "Esparando por un servidor de base de datosː $1 {{PLURAL:$1|segundo|segundos}} de atraso.",
+       "apierror-noedit-anon": "Os usuarios anónimos non poden editar páxinas.",
+       "apierror-noedit": "Non ten permisos para editar páxinas.",
+       "apierror-permissiondenied-generic": "Permisos rexeitados.",
+       "apierror-unknownerror-nocode": "Erro descoñecido.",
+       "apierror-unknownerror": "Erro descoñecido: \"$1\".",
+       "apierror-unknownformat": "Formato descoñecido \"$1\".",
+       "apiwarn-invalidcategory": "\"$1\" non é unha categoría.",
+       "apiwarn-invalidtitle": "\"$1\" non é un título válido.",
+       "apiwarn-notfile": "\"$1\" non é un ficheiro.",
+       "api-feed-error-title": "Erro ($1)",
+       "api-usage-docref": "Consulte $1 para ver o uso da API.",
+       "api-exception-trace": "$1 en $2($3)\n$4",
        "api-credits-header": "Créditos",
        "api-credits": "Desenvolvedores da API:\n* Roan Kattouw (desenvolvedor principal, set. 2007-2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (creador e desenvolvedor principal, set. 2006-sep. 2007)\n* Brad Jorsch (desenvolvedor principal, 2013-actualidade)\n\nEnvía comentarios, suxerencias e preguntas a mediawiki-api@lists.wikimedia.org\nou informa dun erro en https://phabricator.wikimedia.org/."
 }
index 52b28d6..6f6a84f 100644 (file)
@@ -25,6 +25,7 @@
        "apihelp-main-param-requestid": "כל ערך שיינתן כאן ייכלל בתשובה. אפשר להשתמש בזה כדי להבדיל בין בקשות.",
        "apihelp-main-param-servedby": "לכלול את שם המארח ששירת את הבקשה בתוצאות.",
        "apihelp-main-param-curtimestamp": "הכללת חותם־הזמן הנוכחי בתוצאה.",
+       "apihelp-main-param-responselanginfo": "לכלול את השפות שמשמשות ל־<var>uselang</var> ול־<var>errorlang</var> בתוצאה.",
        "apihelp-main-param-origin": "בעת גישה ל־API עם בקשת AJAX חוצה מתחמים (CORS), יש להציב כאן את המתחם שהבקשה יוצאת ממנו. זה היה להיות כלול בכל בקשה מקדימה, ולכן הוא חייב להיות חלק מה־URI של הבקשה (לא גוף ה־POST).\n\nעבור בקשות מאומתות, זה חייב להיות תואם במדויק לאחד המקורות בכותרת <code>Origin</code>, כך שזה צריך להיות מוגדר למשהו כמו <kbd>https://en.wikipedia.org</kbd> או <kbd>https://meta.wikimedia.org</kbd>. אם הפרמטר הזה אינו תואם לכותרת <code>Origin</code>, תוחזר תשובת 403. אם הפרמטר הזה תורם לכותרת <code>Origin</code> והמקור נמצא ברשימה הלבנה, תוגדרנה הכותרות <code>Access-Control-Allow-Origin</code> ו־<code>Access-Control-Allow-Credentials</code>.\n\nעבור בקשות בלתי־מאומתות, יש לציין את הערך <kbd>*</kbd>. זה יגרום לכותרת להיות <code>Access-Control-Allow-Origin</code>, אבל <code>Access-Control-Allow-Credentials</code> תהיה <code>false</code> וכל הנתונים הייחודיים למשתמש יהיו מוגבלים.",
        "apihelp-main-param-uselang": "באיזו שפה להשתמש לתרגומי הודעות. הקריאה <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> עם <kbd>siprop=languages</kbd> מחזירה רשימת קודים. ציון <kbd>user</kbd> כדי להשתמש בהעדפת השפה של המשתמש הנוכחי, וציון <kbd>content</kbd> להשתמש בקוד השפה של הוויקי הזה.",
        "apihelp-block-description": "חסימת משתמש.",
        "apihelp-query+allmessages-param-prop": "אלו מאפיינים לקבל.",
        "apihelp-query+allmessages-param-enableparser": "יש להגדיר כדי להפעיל את המפענח, יעשה קדם־עיבוד לקוד ויקי של ההודעה (יחליף מילות קסם, יטפל בתבניות, וכו').",
        "apihelp-query+allmessages-param-nocontent": "אם זה מוגדר, לא לכלול את תוכן ההודעות בפלט.",
-       "apihelp-query+allmessages-param-includelocal": "×\9c×\9b×\9c×\95×\9c ×\92×\9d ×\94×\95×\93×¢×\95ת ×\9eק×\95×\9e×\99×\95ת, ×\9b×\9c×\95×\9eר ×\94×\95×\93×¢×\95ת ×©×\90×\99× ×\9f ×§×\99×\99×\9e×\95ת ×\91ת×\9b× ×\94, ×\90×\91×\9c ×\9b×\9f ×§×\99×\99×\9e×\95ת ×\91ת×\95ר ×\93×£ ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99.\n×\96×\94 ×¨×\95ש×\9d ×\90ת ×\9b×\9c ×\93פ×\99 MediaWiki: כך שזה ירשום גם דפים שאינם באמת הודעות, כגון [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "×\9c×\9b×\9c×\95×\9c ×\92×\9d ×\94×\95×\93×¢×\95ת ×\9eק×\95×\9e×\99×\95ת, ×\9b×\9c×\95×\9eר ×\94×\95×\93×¢×\95ת ×©×\90×\99× ×\9f ×§×\99×\99×\9e×\95ת ×\91ת×\9b× ×\94, ×\90×\91×\9c ×\9b×\9f ×§×\99×\99×\9e×\95ת ×\91×\9eר×\97×\91 {{ns:MediaWiki}}.\n×\96×\94 ×¨×\95ש×\9d ×\90ת ×\9b×\9c ×\94×\93פ×\99×\9d ×\91×\9eר×\97×\91 {{ns:MediaWiki}}, כך שזה ירשום גם דפים שאינם באמת הודעות, כגון [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "ארגומנטים שיוחלפו לתוך ההודעה.",
        "apihelp-query+allmessages-param-filter": "החזרה רק של הודעות עם שמות שמכילים את המחרוזת הזאת.",
        "apihelp-query+allmessages-param-customised": "להחזיר רק הודעות במצב ההתאמה הזה.",
        "apihelp-phpfm-description": "לפלוט נתונים בתסדיר PHP מוסדר (עם הדפסה יפה ב־HTML).",
        "apihelp-rawfm-description": "לפלוט את הנתונים, כולל אלמנטים לניפוי שגיאות, בתסדיר JSON (עם הדפסה יפה ב־HTML).",
        "apihelp-xml-description": "לפלוט נתונים בתסדיר XML.",
-       "apihelp-xml-param-xslt": "אם צוין, יש להוסיף את שם הדף כגיליון עיצוב XSL. על הערך להיות כותרת ב {{ns:mediawiki}} במרחב שם המשתמש, המסתיים ב-  <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "אם צוין, יש להוסיף את שם הדף כגיליון עיצוב XSL. על הערך להיות כותרת ב {{ns:MediaWiki}} במרחב שם המשתמש, המסתיים ב-  <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "אם זה צוין, מוסיף מרחב שם של XML.",
        "apihelp-xmlfm-description": "לפלוט נתונים בתסדיר XML (עם הדפסה יפה ב־HTML).",
        "api-format-title": "תוצאה של API של מדיה־ויקי",
        "api-help-authmanagerhelper-returnurl": "כתובת URL לחזרה עם זרימות אימות צד־שלישי, חייב להיות מוחלט. נדרש או זה או <var>$1continue</var>.\n\nעם קבלת תשובת <samp>REDIRECT</samp>, בדרך־כלל תפתח דפדפן או תצוגת וב בכתובת ה־<samp>redirecttarget</samp> שצוינה בשביל זרימת אימות צד־שלישי. כשזה יושלם, הצד השלישי ישלח את הדפדפן או את תצוגת הווב לכתובת הזאת. יש לחלץ את כל הפרמטרים של שאילתה או בקשת POST מה־URL ולהעביר אותם בתור בקשת <var>$1continue</var> למודול ה־API הזה.",
        "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> (או תגובה קודמת מהמודול הזה, אם זה זמין) כדי להבין מה הבקשות הזמינות ובאילו שדות הן משתמשות.",
+       "apierror-appendnotsupported": "אי־אפשר להוסיף את זה לדפים שמשתמשים בדגם תוכן $1.",
+       "apierror-stashnosuchfilekey": "אין מפתח קובץ כזה: $1.",
+       "apierror-unknownerror-nocode": "שגיאה בלתי־ידועה.",
+       "apierror-unknownerror": "שגיאה בלתי ידועה: \"$1\".",
+       "apierror-unknownformat": "תסדיר בלתי־ידוע \"$1\".",
+       "apierror-upload-filekeyneeded": "חובה לספק <var>filekey</var> כאשר <var>offset</var> אינו אפס.",
+       "apierror-upload-filekeynotallowed": "לא ניתן לספק <var>filekey</var> כאשר <var>offset</var> הוא 0.",
+       "apierror-upload-missingresult": "אין תוצאות בנתוני מצב.",
+       "apiwarn-invalidcategory": "\"$1\" אינה קטגוריה.",
+       "apiwarn-invalidtitle": "\"$1\" אינה כותרת תקינה.",
+       "apiwarn-notfile": "\"$1\" אינו קובץ.",
+       "apiwarn-validationfailed-badpref": "לא העדפה תקינה.",
+       "apiwarn-validationfailed-cannotset": "לא יכולה להיות מוגדרת על־ידי המודול הזה.",
+       "apiwarn-validationfailed-keytoolong": "המפתח ארוך מדי (מותר לכתוב לא יותר מ־$1 בתים).",
+       "apiwarn-validationfailed": "שגיאה בבדיקת תקינות עבור <kbd>$1</kbd>: $2",
+       "apiwarn-wgDebugAPI": "<strong>אזהרת אבטחה</strong>: <var dir=\"ltr\">$wgDebugAPI</var> מופעל.",
+       "api-feed-error-title": "שגיאה ($1)",
+       "api-usage-docref": "ר' $1 לשימוש ב־API.",
+       "api-exception-trace": "$1 בקובץ $2 (שורה $3)\n$4",
        "api-credits-header": "קרדיטים",
        "api-credits": "מפתחי ה־API:\n* רואן קטאו (מפתח מוביל 2007–2009)\n* ויקטור וסילייב\n* בריאן טונג מין\n* סאם ריד\n* יורי אסטרחן (יוצר, מפתח מוביל מספטמבר 2006 עד ספטמבר 2007)\n* בראד יורש (מפתח מוביל מאז 2013)\n\nאנא שלחו הערות, הצעות ושאלות לכתובת mediawiki-api@lists.wikimedia.org או כתבו דיווח באג באתר https://phabricator.wikimedia.org."
 }
index e0920f6..232386e 100644 (file)
@@ -92,5 +92,6 @@
        "api-help-param-limit2": "Nem engedélyezett több mint $1 (botoknak $2).",
        "api-help-param-integer-min": "Az {{PLURAL:$1|1=érték nem lehet kisebb|2=értékek nem lehetnek kisebbek}} mint $2.",
        "api-help-param-integer-max": "Az {{PLURAL:$1|1=érték nem lehet nagyobb|2=értékek nem lehetnek nagyobbak}} mint $3.",
-       "api-help-param-integer-minmax": "{{PLURAL:$1|1=Az értéknek $2 és $3 között kell lennie.|2=Az értékeknek $2 és $3 között kell lenniük.}}"
+       "api-help-param-integer-minmax": "{{PLURAL:$1|1=Az értéknek $2 és $3 között kell lennie.|2=Az értékeknek $2 és $3 között kell lenniük.}}",
+       "api-help-param-default": "Alapértelmezett: $1"
 }
index b585885..5ae3547 100644 (file)
@@ -61,7 +61,7 @@
        "apihelp-edit-param-tags": "Ganti tag untuk menerapkan ke revisi.",
        "apihelp-edit-param-minor": "Suntingan kecil.",
        "apihelp-edit-param-notminor": "Bukan suntingan kecil.",
-       "apihelp-edit-param-bot": "Tandai suntingan ini sebagai bot.",
+       "apihelp-edit-param-bot": "Tandai suntingan ini sebagai suntingan bot.",
        "apihelp-edit-param-basetimestamp": "Stempel waktu dari revisi asal, digunakan untuk mendeteksi konflik penyuntingan. Dapat ditemukan di [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
        "apihelp-edit-param-starttimestamp": "Stempel waktu ketika proses penyuntingan dimulai, digunakan untuk mendeteksi konflik penyuntingan. Nilai yang cocok dapat ditemukan dengan menggunakan <var>[[Special:ApiHelp/main|curtimestamp]]</var> ketika memulai proses penyuntingan (seperti ketika memuat isi konten yang akan disunting).",
        "apihelp-edit-param-recreate": "Batalkan galat yang terjadi tentang halaman yang sudah dihapus pada saat itu.",
        "apihelp-emailuser-param-subject": "Tajuk subjek.",
        "apihelp-emailuser-param-text": "Badan pesan.",
        "apihelp-emailuser-param-ccme": "Kirimkan salinan pesan ini kepada saya.",
-       "apihelp-expandtemplates-description": "Tambahkan semua templat dalam teks wiki.",
+       "apihelp-expandtemplates-description": "Longgarkan semua templat dalam teks wiki.",
        "apihelp-expandtemplates-param-title": "Judul halaman.",
        "apihelp-expandtemplates-param-text": "Teks wiki yang akan diubah.",
        "apihelp-expandtemplates-param-revid": "ID revisi, untuk <nowiki>{{REVISIONID}}</nowiki> dan variabel serupa.",
        "apihelp-expandtemplates-param-prop": "Bagian informasi manakah yang ingin didapatkan.\n\nPerhatikan bahwa jika tidak ada nilai yang dipilih, hasilnya akan mengandung teks wiki, namun keluaran akan berupa format usang.",
+       "apihelp-feedcontributions-param-deletedonly": "Tampilkan hanya kontribusi terhapus.",
        "apihelp-login-example-login": "Masuk log.",
+       "apihelp-move-param-noredirect": "Jangan buat pengalihan.",
+       "apihelp-move-param-unwatch": "Hapus halaman dan pengalihan dari daftar pantauan pengguna ini.",
+       "apihelp-move-example-move": "Pindahkan <kbd>Judul buruk</kbd> ke <kbd>Judul benar</kbd> tanpa membuat pengalihan.",
+       "apihelp-opensearch-param-redirects": "Bagaimana menangani pengalihan:\n;return:Kembali ke pengalihan itu.\n;resolve:Kembali ke halaman tujuan. Mungkin hasil kembali kurang dari $1limit.\nUntuk alasan riwayat, nilai baku adalah \"kembali\" untuk $1format=json dan \"resolve\" untuk format lain.",
        "apihelp-query+prefixsearch-param-profile": "Cari profil untuk digunakan.",
        "apihelp-query+search-param-qiprofile": "Meminta profil independen untuk digunakan (berefek pada algoritma peringkat).",
        "apihelp-revisiondelete-param-ids": "Penanda untuk perubahan yang akan dihapus",
index ea3f415..ab0233a 100644 (file)
@@ -26,7 +26,7 @@
        "apihelp-main-param-servedby": "Includi nel risultato il nome dell'host che ha servito la richiesta.",
        "apihelp-main-param-curtimestamp": "Includi nel risultato il timestamp attuale.",
        "apihelp-block-description": "Blocca  un utente.",
-       "apihelp-block-param-user": "Nome utente, indirizzo IP o range di IP da bloccare.",
+       "apihelp-block-param-user": "Nome utente, indirizzo IP o range di IP da bloccare. Non può essere usato insieme a <var>$1userid</var>",
        "apihelp-block-param-expiry": "Tempo di scadenza. Può essere relativo (ad esempio, <kbd>5 months</kbd> o <kbd>2 weeks</kbd>) o assoluto (ad esempio <kbd>2014-09-18T12:34:56Z</kbd>). Se impostato a <kbd>infinite</kbd>, <kbd>indefinite</kbd> o <kbd>never</kbd>, il blocco non scadrà mai.",
        "apihelp-block-param-reason": "Motivo del blocco.",
        "apihelp-block-param-anononly": "Blocca solo gli utenti non registrati (cioè disattiva i contributi anonimi da questo indirizzo IP).",
index d9d079b..63865ed 100644 (file)
        "apihelp-phpfm-description": "データを PHP のシリアル化した形式 (HTML に埋め込んだ形式) で出力します。",
        "apihelp-rawfm-description": "データをデバッグ要素付きで JSON 形式 (HTML に埋め込んだ形式) で出力します。",
        "apihelp-xml-description": "データを XML 形式で出力します。",
-       "apihelp-xml-param-xslt": "指定すると、XSLスタイルシートとして名付けられたページを追加します。値は、必ず、{{ns:mediawiki}} 名前空間の、ページ名の末尾が <code>.xsl</code> でのタイトルである必要があります。",
+       "apihelp-xml-param-xslt": "指定すると、XSLスタイルシートとして名付けられたページを追加します。値は、必ず、{{ns:MediaWiki}} 名前空間の、ページ名の末尾が <code>.xsl</code> でのタイトルである必要があります。",
        "apihelp-xml-param-includexmlnamespace": "指定すると、XML 名前空間を追加します。",
        "apihelp-xmlfm-description": "データを XML 形式 (HTML に埋め込んだ形式) で出力します。",
        "api-format-title": "MediaWiki API の結果",
index 012ca8e..e5baedc 100644 (file)
@@ -28,6 +28,7 @@
        "apihelp-main-param-requestid": "주어진 요청 값은 응답에 포함됩니다. 요청을 구분하기 위해 사용될 수 있습니다.",
        "apihelp-main-param-servedby": "결과에 요청을 처리한 호스트네임을 포함합니다.",
        "apihelp-main-param-curtimestamp": "결과의 타임스탬프를 포함합니다.",
+       "apihelp-main-param-uselang": "메시지 번역을 위한 언어입니다. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>에 <kbd>siprop=languages</kbd>를 함께 사용하면 언어 코드의 목록을 반환하고, <kbd>user</kbd>를 지정하면 현재 사용자의 언어 환경 설정을 사용하며, <kbd>content</kbd>를 지정하면 이 위키의 콘텐츠 언어를 사용합니다.",
        "apihelp-block-description": "사용자를 차단합니다.",
        "apihelp-block-param-user": "차단하고자 하는 계정 이름, IP 주소 또는 대역",
        "apihelp-block-param-expiry": "기한. 상대값(예시: <kbd>5 months</kbd> 또는 </kbd>2 weeks</kbd>) 또는 절대값(예시: <kbd>2014-09-18T12:34:56Z</kbd>)이 될 수 있습니다. <kbd>infinite</kbd>, <kbd>indefinite</kbd> 또는 <kbd>never</kbd>로 설정하면 무기한으로 설정됩니다.",
        "apihelp-feedrecentchanges-param-invert": "선택한 항목을 제외한 모든 이름공간.",
        "apihelp-feedrecentchanges-param-associated": "관련 (토론 또는 일반) 이름공간을 포함합니다.",
        "apihelp-feedrecentchanges-param-limit": "반환할 결과의 최대 수.",
-       "apihelp-feedrecentchanges-param-from": "이후의 변경 사항을 보여줍니다.",
+       "apihelp-feedrecentchanges-param-from": "이후의 변경사항을 보여줍니다.",
        "apihelp-feedrecentchanges-param-hideminor": "사소한 편집을 숨깁니다.",
        "apihelp-feedrecentchanges-param-hidebots": "봇의 편집을 숨깁니다.",
        "apihelp-feedrecentchanges-param-hideanons": "익명 사용자의 편집을 숨깁니다.",
        "apihelp-query+users-param-prop": "포함할 정보:",
        "apihelp-query+users-paramvalue-prop-editcount": "사용자의 편집 수를 추가합니다.",
        "apihelp-query+users-paramvalue-prop-registration": "사용자의 등록 타임스탬프를 추가합니다.",
+       "apihelp-query+users-param-userids": "정보를 가져올 사용자 ID의 목록입니다.",
        "apihelp-query+users-param-token": "<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>을 대신 사용하십시오.",
        "apihelp-query+users-example-simple": "사용자 <kbd>Example</kbd>의 정보를 반환합니다.",
        "apihelp-query+watchlist-description": "현재 사용자의 주시목록의 문서의 최근 바뀜을 가져옵니다.",
        "apihelp-query+watchlist-param-user": "이 사용자의 변경 사항만 나열합니다.",
-       "apihelp-query+watchlist-param-excludeuser": "이 사용자의 변경 사항을 나열하지 않습니다.",
+       "apihelp-query+watchlist-param-excludeuser": "이 사용자의 변경사항을 나열하지 않습니다.",
        "apihelp-query+watchlist-paramvalue-prop-ids": "판 ID와 페이지 ID를 추가합니다.",
        "apihelp-query+watchlist-paramvalue-prop-title": "문서의 제목을 추가합니다.",
        "apihelp-query+watchlist-paramvalue-prop-flags": "편집에 대한 플래그를 추가합니다.",
        "api-help-right-apihighlimits": "API 쿼리에서 더 높은 제한 사용 (느린 쿼리: $1, 빠른 쿼리: $2) 느린 쿼리에 대한 제한은 다중값 매개변수에도 적용됩니다.",
        "api-help-open-in-apisandbox": "<small>[연습장에서 열기]</small>",
        "api-help-authmanagerhelper-messageformat": "반환 메시지에 사용할 형식.",
+       "apierror-articleexists": "작성하려는 문서가 이미 만들어져 있습니다.",
+       "apierror-autoblocked": "사용자의 IP 주소는 차단된 사용자에 의해 사용되었으므로 자동으로 차단된 상태입니다.",
+       "apierror-badgenerator-unknown": "알 수 없는 <kbd>generator=$1</kbd>.",
+       "apierror-badip": "IP 변수가 유효하지 않습니다.",
+       "apierror-badmodule-badsubmodule": "<kbd>$1</kbd> 모듈에 \"$2\" 하위 모듈이 없습니다.",
+       "apierror-badmodule-nosubmodules": "<kbd>$1</kbd> 모듈은 하위 모듈이 없습니다.",
+       "apierror-badquery": "유효하지 않은 쿼리입니다.",
+       "apierror-badtoken": "잘못된 CSRF 토큰.",
+       "apierror-blocked": "편집에서 차단되어 있습니다.",
+       "apierror-botsnotsupported": "이 인터페이스는 봇을 위해 지원되지 않습니다.",
+       "apierror-cannotviewtitle": "$1을(를) 볼 권한이 없습니다.",
+       "apierror-cantblock": "사용자를 차단할 권한이 없습니다.",
+       "apierror-cantimport-upload": "업로드된 페이지를 가져올 권한이 없습니다.",
+       "apierror-databaseerror": "[$1] 데이터베이스 쿼리 오류.",
+       "apierror-emptynewsection": "비어있는 새 문단을 만들 수 없습니다.",
+       "apierror-emptypage": "새 문서로 빈 문서를 만들 수 없습니다.",
+       "apierror-exceptioncaught": "[$1] 예외가 발생했습니다: $2",
+       "apierror-filedoesnotexist": "파일이 존재하지 않습니다.",
+       "apierror-filenopath": "로컬 파일 경로를 가져올 수 없습니다.",
+       "apierror-import-unknownerror": "알 수 없는 가져오기 오류: $1.",
+       "apierror-invalidcategory": "입력한 분류 이름이 올바르지 않습니다.",
+       "apierror-invalid-file-key": "유효한 파일 키가 아닙니다.",
+       "apierror-invalidoldimage": "oldimage 변수에 유효하지 않은 형식이 있습니다.",
+       "apierror-invalidsha1base36hash": "제공된 SHA1Base36 해시가 유효하지 않습니다.",
+       "apierror-invalidsha1hash": "제공된 SHA1 해시가 유효하지 않습니다.",
+       "apierror-invalidtitle": "잘못된 제목 \"$1\".",
+       "apierror-invaliduser": "잘못된 사용자 이름 \"$1\".",
+       "apierror-missingparam": "<var>$1</var> 변수는 설정해야 합니다.",
+       "apierror-missingtitle": "지정한 페이지가 존재하지 않습니다.",
+       "apierror-missingtitle-byname": "$1 문서가 존재하지 않습니다.",
+       "apierror-moduledisabled": "<kbd>$1</kbd> 모듈은 비활성화되었습니다.",
+       "apierror-mustbeloggedin-generic": "로그인해야 합니다.",
+       "apierror-mustbeloggedin-linkaccounts": "계정을 연결하려면 로그인해야 합니다.",
+       "apierror-mustbeloggedin": "$1에 로그인해야 합니다.",
+       "apierror-noedit-anon": "익명 사용자는 문서를 편집할 수 없습니다.",
+       "apierror-noedit": "문서를 편집할 권한이 없습니다.",
+       "apierror-noimageredirect": "그림 넘겨주기를 만들 권한이 없습니다.",
+       "apierror-nosuchrevid": "ID $1에 해당하는 판이 없습니다.",
+       "apierror-pagecannotexist": "이름공간은 실제 페이지를 허용하지 않습니다.",
+       "apierror-permissiondenied": "$1에 대한 권한이 없습니다.",
+       "apierror-permissiondenied-generic": "권한이 없습니다.",
+       "apierror-permissiondenied-unblock": "사용자의 차단을 해제할 권한이 없습니다.",
+       "apierror-protect-invalidaction": "잘못된 보호 유형 \"$1\".",
+       "apierror-protect-invalidlevel": "잘못된 보호 수준 \"$1\".",
+       "apierror-readapidenied": "이 모듈을 사용하려면 읽기 권한이 필요합니다.",
+       "apierror-readonly": "위키는 현재 읽기 전용 모드입니다.",
+       "apierror-revwrongpage": "r$1은(는) $2의 판이 아닙니다.",
+       "apierror-specialpage-cantexecute": "특수 문서의 결과를 볼 권한이 없습니다.",
+       "apierror-stashwrongowner": "잘못된 소유자: $1",
+       "apierror-unknownerror-editpage": "알 수 없는 EditPage 오류: $1.",
+       "apierror-unknownerror-nocode": "알 수 없는 오류.",
+       "apierror-unknownerror": "알 수 없는 오류: \"$1\"",
+       "apierror-unknownformat": "인식되지 않는 형식 \"$1\".",
+       "apierror-unsupportedrepo": "로컬 파일 저장소는 모든 그림의 조회를 지원하지 않습니다.",
+       "apierror-writeapidenied": "API를 통해 이 위키를 편집할 권한이 없습니다.",
+       "apiwarn-deprecation-httpsexpected": "HTTPS를 예측하였으나 HTTP가 사용되었습니다.",
+       "apiwarn-difftohidden": "r$1에 대한 차이를 만들 수 없습니다: 내용이 숨겨져 있습니다.",
+       "apiwarn-invalidcategory": "\"$1\"은(는) 분류가 아닙니다.",
+       "apiwarn-invalidtitle": "\"$1\"은(는) 올바른 제목이 아닙니다.",
+       "apiwarn-notfile": "\"$1\"은(는) 파일이 아닙니다.",
+       "apiwarn-unsupportedarray": "<var>$1</var> 변수는 지원되지 않는 PHP 배열 문법을 사용합니다.",
+       "apiwarn-validationfailed-keytoolong": "키가 너무 깁니다. ($1 바이트 이상 허용하지 않습니다)",
+       "api-feed-error-title": "오류 ($1)",
+       "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 로 보내주시고,\n버그 보고는 https://phabricator.wikimedia.org/ 에 해주시기 바랍니다."
 }
index 47b79ab..836af02 100644 (file)
@@ -8,7 +8,7 @@
        "apihelp-main-param-assertuser": "Iwwerpréifen ob den aktuelle Benotzer de Benotzer mat deem Numm ass.",
        "apihelp-main-param-curtimestamp": "Den aktuellen Zäitstempel an d'Resultat integréieren.",
        "apihelp-block-description": "E Benotzer spären.",
-       "apihelp-block-param-user": "Benotzernumm, IP-Adress oder IP-Beräich deen Dir späre wëllt.",
+       "apihelp-block-param-user": "Benotzernumm, IP-Adress oder IP-Beräich fir ze spären. Kann net zesumme mat <var>$1userid</var> benotzt ginn.",
        "apihelp-block-param-reason": "Grond fir ze spären.",
        "apihelp-block-param-anononly": "Nëmmen anonym Benotzer spären (z. Bsp. anonym Ännerunge vun dëser IP-Adress ausschalten)",
        "apihelp-block-param-nocreate": "Opmaache vun engem Benotzerkont verhënneren.",
        "api-help-param-type-user": "Typ: {{PLURAL:$1|1=Benotzernumm|2=Lëscht vu Benotzernimm}}",
        "api-help-examples": "{{PLURAL:$1|Beispill|Beispiler}}:",
        "api-help-permissions": "{{PLURAL:$1|Autorisatioun|Autorisatiounen}}:",
-       "api-help-open-in-apisandbox": "<small>[an der Sandkëscht opmaachen]</small>"
+       "api-help-open-in-apisandbox": "<small>[an der Sandkëscht opmaachen]</small>",
+       "apierror-articleexists": "Den Artikel deen dir probéiert hutt unzeleeë gouf schonn ugeluecht.",
+       "apierror-autoblocked": "Är IP-Adress gouf automatesch gespaart well se vun engem gespaarte Benotzer benotzt gouf.",
+       "apierror-badip": "IP-Parameter ass net valabel.",
+       "apierror-cantblock": "Dir hutt net d'Recht fir Benotzer ze spären.",
+       "apierror-cantimport": "Dir hutt net déi néideg Rechter fir Säiten z'importéieren.",
+       "apierror-copyuploadbadurl": "D'Eroplueden ass vun dëser URL net erlaabt.",
+       "apierror-filetypecannotberotated": "Den Typ vu Fichier kann net rotéiert ginn.",
+       "apierror-import-unknownerror": "Onbekannte Feeler beim Import: $1\nf",
+       "apierror-invalidcategory": "Den Numm vun der Kategorie deen Dir aginn hutt ass net valabel.",
+       "apierror-invalidtitle": "Schlechten Titel \"$1\".",
+       "apierror-missingtitle": "D'Säit déi Dir spezifizéiert hutt gëtt et net.",
+       "apierror-missingtitle-byname": "D'Säit $1 gëtt et net.",
+       "apierror-moduledisabled": "De(n) <kbd>$1</kbd> Modul gouf ausgeschalt.",
+       "apierror-mustbeloggedin-generic": "Dir musst ageloggt sinn.",
+       "apierror-nochanges": "Et goufe keng Ännerungen ugefrot.",
+       "apierror-nodeleteablefile": "Et gëtt keng esou al Versioun vum Fichier.",
+       "apierror-noedit": "Dir hutt net déi néideg Rechter fir Säiten z'änneren.",
+       "apierror-nosuchuserid": "Et gëtt kee Benotzer mat der ID $1.",
+       "apierror-notarget": "Dir hutt kee valabelt Zil fir dës Aktioun spezifizéiert.",
+       "apierror-notpatrollable": "D'Versioun r$1 kann net nogekuckt gi well se ze al ass.",
+       "apierror-pagecannotexist": "Nummraum erlaabt keng aktuell Säiten.",
+       "apierror-permissiondenied-generic": "Autorisatioun refuséiert.",
+       "apierror-permissiondenied-unblock": "Dir hutt net d'Recht fir d'Spär vu Benotzer opzehiewen.",
+       "apierror-readonly": "D'Wiki kann elo just geliest ginn.",
+       "apierror-revwrongpage": "r$1 ass keng Versioun vu(n) $2.",
+       "apierror-stashwrongowner": "Falsche Besëtzer: $1",
+       "apierror-unknownerror-editpage": "Onbekannten EditPage-Feeler: $1",
+       "apierror-unknownerror-nocode": "Onbekannte Feeler.",
+       "apierror-unknownerror": "Onbekannte Feeler: \"$1\".",
+       "apierror-unknownformat": "Net-erkannte Format \"$1\".",
+       "apierror-unrecognizedparams": "Net {{PLURAL:$2|erkannte Parameter|erkannt Parameteren}}: $1",
+       "apierror-writeapidenied": "Dir däerft dës Wiki net iwwer den API ännneren.",
+       "apiwarn-invalidcategory": "\"$1\" ass keng Kategorie.",
+       "apiwarn-invalidtitle": "\"$1\" ass kee valabelen Titel",
+       "apiwarn-notfile": "\"$1\" ass kee Fichier.",
+       "apiwarn-tokennotallowed": "Aktioun \"$1\" ass net erlaabt fir den aktuelle Benotzer.",
+       "apiwarn-validationfailed-badpref": "keng valabel Astellung",
+       "api-feed-error-title": "Feeler ($1)"
 }
index 25012ba..b38e6a5 100644 (file)
        "apihelp-stashedit-param-title": "Puslapio pavadinimas buvo redaguotas.",
        "apihelp-stashedit-param-sectiontitle": "Naujo skyriaus pavadinimas.",
        "apihelp-stashedit-param-text": "Puslapio turinys.",
-       "api-format-prettyprint-status": "Šis atsakymas būtų gražintas su HTTP statusu $1 $2."
+       "api-format-prettyprint-status": "Šis atsakymas būtų gražintas su HTTP statusu $1 $2.",
+       "apierror-articleexists": "Straipsnis, kurį bandėte sukurti, jau yra sukurtas.",
+       "apierror-autoblocked": "Jūsų IP adresas buvo automatiškai užblokuotas, nes jis buvo naudojamas užblokuoto vartotojo.",
+       "apierror-badgenerator-unknown": "Nežinomas <kbd>generator=$1</kbd>.",
+       "apierror-badip": "IP parametras negalimas.",
+       "apierror-badurl": "Negalima reikšmė „$2“ URL parametrui <var>$1</var>.",
+       "apierror-blockedfrommail": "Jus buvote užblokuotas nuo el. laiško siuntimo.",
+       "apierror-botsnotsupported": "Ši sąsaja negali būti palaikoma robotams.",
+       "apierror-cannotreauthenticate": "Veiksmas negalimas, nes jūsų tapatybė negali būti patvirtinta.",
+       "apierror-cantimport": "Neturite teisės importuoti puslapių.",
+       "apierror-copyuploadbadurl": "Įkėlimas neleidžiamas iš šio URL.",
+       "apierror-databaseerror": "[$1] Duomenų bazės užklausos klaida.",
+       "apierror-emptynewsection": "Neįmanoma kurti naujų tuščių skyrių.",
+       "apierror-filetypecannotberotated": "Failo tipas negali būti pasuktas.",
+       "apierror-import-unknownerror": "Nežinoma klaida importuojant: $1.",
+       "apierror-invalidcategory": "Kategorijos pavadinimas, kurį įvedėte, yra negalimas.",
+       "apierror-invalidparammix": "{{PLURAL:$2|parametrai}} $1 negali būti naudojami kartu.",
+       "apierror-invalidtitle": "Blogas pavadinimas „$1“.",
+       "apierror-invaliduser": "Negalimas vartotojo vardas „$1“.",
+       "apierror-missingtitle": "Puslapis, kurį nurodėte, neegzistuoja.",
+       "apierror-missingtitle-byname": "Puslapis $1 neegzistuoja",
+       "apierror-multpages": "<var>$1</var> gali būti naudojamas tik su vienu puslapiu.",
+       "apierror-mustbeloggedin-generic": "Turite būti prisijungęs.",
+       "apierror-mustbeloggedin-linkaccounts": "Turite būti prisijungęs, kad galėtumėte susieti paskyras.",
+       "apierror-nochanges": "Neprašyta jokių keitimų.",
+       "apierror-noedit": "Neturite teisės redaguoti puslapius.",
+       "apierror-nosuchsection-what": "$2 nėra sekcijos $1.",
+       "apierror-paramempty": "Parametras <var>$1</var> negali būti tusčiau.",
+       "apierror-permissiondenied": "Neturite leidimo $1.",
+       "apierror-permissiondenied-generic": "Teisė nesuteikta.",
+       "apierror-protect-invalidaction": "Negalimas apsaugos tipas „$1“.",
+       "apierror-protect-invalidlevel": "Negalimas apsaugos lygis „$1“.",
+       "apierror-readonly": "Viki šiuo metu yra skaitymo režime.",
+       "apierror-sectionreplacefailed": "Nepavyko sujungti atnaujinto skyriaus.",
+       "apierror-specialpage-cantexecute": "Neturite teisės peržiūrėti šio specialaus puslapio rezultatus.",
+       "apierror-stashwrongowner": "Neteisingas savininkas: $1",
+       "apierror-unknownerror-nocode": "Nežinoma klaida.",
+       "apierror-unknownerror": "Nežinoma klaida: „$1“.",
+       "apierror-unknownformat": "Neatpažintas formatas „$1“.",
+       "apierror-unrecognizedparams": "{{PLURAL:$2|Neatpažintas parametras|Neatpažinti parametrai}}: $1.",
+       "apierror-writeapidenied": "Negalite redaguoti šios viki per API.",
+       "apiwarn-deprecation-httpsexpected": "panaudotas HTTP, kai buvo tikėtasi HTTPS.",
+       "apiwarn-invalidcategory": "„$1“ nėra kategorija.",
+       "apiwarn-invalidtitle": "„$1“ nėra galimas pavadinimas.",
+       "apiwarn-notfile": "„$1“ nėra failas.",
+       "apiwarn-tokennotallowed": "Veiksmas „$1“ nėra leidžiamas dabartiniam vartotojui.",
+       "apiwarn-validationfailed-badpref": "negalimas nustatymas.",
+       "apiwarn-validationfailed": "Patvirtinimo klaida skirta <kbd>$1</kbd>: $2",
+       "api-feed-error-title": "Klaida ($1)"
 }
index 8240e08..b1a2280 100644 (file)
        "apihelp-phpfm-description": "Давај го изводот во серијализиран PHP-формат (подобрен испис во HTML).",
        "apihelp-rawfm-description": "Давај го изводот со елементи за отстранување грешки во JSON-формат (подобрен испис во HTML).",
        "apihelp-xml-description": "Давај го изводот во XML-формат.",
-       "apihelp-xml-param-xslt": "Ако е укажано, ја додава именуваната страница како XSL-стилска страница. Вредноста мора да биде наслов во именскиот простор „{{ns:mediawiki}}“ што ќе завршува со <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "Ако е укажано, ја додава именуваната страница како XSL-стилска страница. Вредноста мора да биде наслов во именскиот простор „{{ns:MediaWiki}}“ што ќе завршува со <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Ако е укажано, додава именски простор XML.",
        "apihelp-xmlfm-description": "Давај го изводот во XML-формат (подобрен испис во HTML).",
        "api-format-title": "Резултат од Извршникот на МедијаВики",
index 3df95e7..8ebf223 100644 (file)
@@ -4,9 +4,12 @@
                        "Jeblad",
                        "Chameleon222",
                        "Macofe",
-                       "Jon Harald Søby"
+                       "Jon Harald Søby",
+                       "Event",
+                       "Kingu"
                ]
        },
+       "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Dokumentasjon]]\n* [[mw:API:FAQ|Ofte stilte spørsmål]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-post-liste]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-kunngjøringer]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Feil & forespørsler]\n</div>\n<strong>Status:</strong> Alle funksjonene som vises på denne siden skal virke, men API-en er fortsatt i aktiv utvikling, og kan bli endret når som helst. Abonner på [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ MediaWiki sin API-kunnkjøringsepostliste] for nyheter om oppdateringer.\n\n<strong>Feile kall:</strong> Hvis det blir sendt feile kall til API-et, blir det sendt en HTTP-header med nøkkelen \"MediaWiki-API-Error\" og da blir både header-verdien og feilkoden sendt tilbake med samme verdi. For mer informasjon se [[mw:API:Errors_and_warnings|API: Feil og advarsler]].\n\n<strong>Testing:</strong> For enkelt å teste API-kall, se [[Special:ApiSandbox]].",
        "apihelp-main-param-action": "Hvilken handling skal utføres",
        "apihelp-main-param-format": "Resultatets format.",
        "apihelp-main-param-assert": "Verifiser at brukeren er logget inn om satt til <kbd>user</kbd>, eller har botrettighet om satt til <kbd>bot</kbd>.",
        "api-help-flag-generator": "Denne modulen kan brukes som en generator.",
        "api-help-parameters": "{{PLURAL:$1|Parameter|Parametre}}:",
        "api-help-param-deprecated": "Utgått.",
-       "api-help-param-required": "Denne parameteren er påkrevd."
+       "api-help-param-required": "Denne parameteren er påkrevd.",
+       "apierror-multival-only-one": "Bare én verdi er tillatt for parameteret <var>$1</var>.",
+       "apierror-permissiondenied-generic": "Tilgang nektet.",
+       "apiwarn-validationfailed": "Bekreftelsesfeil <kbd>$1</kbd>: $2"
 }
index 7ba3854..573c3f8 100644 (file)
        "api-help-permissions-granted-to": "{{PLURAL:$1|Przydzielone dla}}: $2",
        "api-help-right-apihighlimits": "Użyj wyższych limitów w zapytaniach API (dla zapytań powolnych: $1; dla zapytań szbkich: $2). Limity zapytań powolnych są także stosowane dla parametrów z podanymi wieloma wartościami.",
        "api-help-open-in-apisandbox": "<small>[otwórz w brudnopisie]</small>",
+       "apierror-badgenerator-unknown": "Nieznany <kbd>generator=$1</kbd>.",
+       "apierror-badip": "Parametr IP nie jest prawidłowy.",
+       "apierror-badquery": "Nieprawidłowe zapytanie.",
+       "apierror-blockedfrommail": "Została Ci zablokowana możliwość wysyłania e-maili.",
+       "apierror-blocked": "Została Ci zablokowana możliwość edycji.",
+       "apierror-cantblock": "Nie masz uprawnień do blokowania użytkowników.",
+       "apierror-cantimport": "Nie masz uprawnień do importowania stron.",
+       "apierror-cantsend": "Nie jesteś zalogowany, nie masz potwierdzonego adresu e-mail, albo nie masz prawa wysyłać e-maili do innych użytkowników, więc nie możesz wysłać wiadomości e-mail.",
+       "apierror-filedoesnotexist": "Plik nie istnieje.",
+       "apierror-integeroutofrange-abovebotmax": "Wartość <var>$1</var> dla botów i administratorów nie może przekraczać $2 (ustawiono $3).",
+       "apierror-integeroutofrange-abovemax": "Wartość <var>$1</var> dla użytkowników nie może przekraczać $2 (ustawiono $3).",
+       "apierror-integeroutofrange-belowminimum": "Wartość <var>$1</var> nie może być mniejsza niż $2 (ustawiono $3).",
+       "apierror-invalidlang": "Nieprawidłowy kod języka dla parametru <var>$1</var>.",
+       "apierror-invalidparammix": "{{PLURAL:$2|Parametry}} $1 nie mogą być używane razem.",
+       "apierror-invalidtitle": "Zły tytuł „$1”.",
+       "apierror-invalidurlparam": "Nieprawidłowa wartość <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
+       "apierror-missingparam": "Parametr <var>$1</var> musi być podany.",
+       "apierror-missingtitle": "Wybrana przez ciebie strona nie istnieje.",
+       "apierror-missingtitle-byname": "Strona $1 nie istnieje.",
+       "apierror-moduledisabled": "Moduł <kbd>$1</kbd> został wyłączony.",
+       "apierror-mustbeloggedin-generic": "Musisz być zalogowany.",
+       "apierror-noedit-anon": "Niezarejestrowani użytkownicy nie mogą edytować stron.",
+       "apierror-noedit": "Nie masz uprawnień do edytowania stron.",
+       "apierror-permissiondenied": "Nie masz uprawnień do $1.",
+       "apierror-permissiondenied-generic": "Brak dostępu.",
+       "apierror-permissiondenied-unblock": "Nie masz uprawnień do odblokowania użytkowników.",
+       "apierror-protect-invalidaction": "Nieprawidłowy rodzaj zabezpieczenia „$1”.",
+       "apierror-protect-invalidlevel": "Nieprawidłowy poziom zabezpieczeń „$1”.",
+       "apierror-specialpage-cantexecute": "Nie masz uprawnień, aby zobaczyć wyniki tej strony specjalnej.",
+       "apierror-stashwrongowner": "Nieprawidłowy właściciel: $1",
+       "apierror-unknownerror-nocode": "Nieznany błąd.",
+       "apierror-unknownerror": "Nieznany błąd: „$1”.",
+       "apierror-unknownformat": "Nierozpoznany format „$1”.",
+       "apierror-unrecognizedvalue": "Nierozpoznana wartość parametru <var>$1</var>: $2.",
+       "apiwarn-invalidcategory": "„$1” nie jest kategorią.",
+       "apiwarn-invalidtitle": "„$1” nie jest poprawnym tytułem.",
+       "apiwarn-notfile": "„$1” nie jest plikiem.",
+       "api-feed-error-title": "Błąd ($1)",
+       "api-exception-trace": "$1 w $2($3)\n$4",
        "api-credits-header": "Twórcy",
        "api-credits": "Deweloperzy API:\n* Roan Kattouw (główny programista wrzesień 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (twórca, główny programista wrzesień 2006–wrzesień 2007)\n* Brad Jorsch (główny programista 2013–obecnie)\n\nProsimy wysyłać komentarze, sugestie i pytania do mediawiki-api@lists.wikimedia.org\nlub zgłoś błąd na https://phabricator.wikimedia.org/."
 }
index 20a2eda..0c5cf8e 100644 (file)
        "apihelp-main-param-requestid": "Qualquer valor fornecido aqui será incluído na resposta. Pode ser usado para distinguir pedidos.",
        "apihelp-main-param-servedby": "Incluir nos resultados o nome do servidor que serviu o pedido.",
        "apihelp-main-param-curtimestamp": "Incluir a data e hora atuais no resultado.",
+       "apihelp-main-param-responselanginfo": "Incluir as línguas usadas para <var>uselang</var> e <var>errorlang</var> no resultado.",
        "apihelp-main-param-origin": "Ao aceder à API usando um pedido AJAX entre domínios (CORS), coloque aqui o domínio de origem. Isto tem de ser incluído em todas as verificações prévias e, portanto, tem de fazer parte do URI do pedido (e não do conteúdo do POST).\n\nPara pedidos autenticados, este valor tem de corresponder de forma exata a um dos cabeçalhos <code>Origin</code>, portanto, tem de ser algo como <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Se este parâmetro não for igual ao cabeçalho <code>Origin</code>, será devolvida a resposta 403. Se este parâmetro for igual ao cabeçalho <code>Origin</code> e a origem for permitida (''white-listed'') os cabeçalhos <code>Access-Control-Allow-Origin</code> e <code>Access-Control-Allow-Credentials</code> serão preenchidos.\n\nPara pedidos não autenticados, especifique o valor <kbd>*</kbd>. Isto fará com que o cabeçalho <code>Access-Control-Allow-Origin</code>\nseja preenchido, mas <code>Access-Control-Allow-Credentials</code> terá o valor <code>false</code> e o acesso a todos os dados específicos do utilizador está restringido.",
-       "apihelp-main-param-uselang": "Língua a usar nas traduções de mensagens. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> com <kbd>siprop=languages</kbd> devolve uma lista de códigos de língua, ou especifique <kbd>user</kbd> para usar a língua nas preferências do utilizador atual, ou especifique <kbd>content</kbd> para usar a língua de conteúdo desta wiki.",
+       "apihelp-main-param-uselang": "A língua a ser usada nas traduções de mensagens. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> com <kbd>siprop=languages</kbd> devolve uma lista de códigos de língua, ou especifique <kbd>user</kbd> para usar a língua nas preferências do utilizador atual, ou especifique <kbd>content</kbd> para usar a língua de conteúdo desta wiki.",
+       "apihelp-main-param-errorformat": "O formato a ser usado no texto de avisos e erros.\n; plaintext: Texto wiki com os elementos HTML removidos e as entidades substituídas.\n; wikitext: Texto wiki sem análise sintática.\n; html: HTML.\n; raw: Chave e parâmetros da mensagem.\n; none: Sem saída de texto, só os códigos de erro.\n; bc: Formato usado antes do MediaWiki 1.29. <var>errorlang</var> e <var>errorsuselocal</var> são ignorados.",
+       "apihelp-main-param-errorlang": "A língua a ser usada para avisos e erros. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> com <kbd>siprop=languages</kbd> devolve uma lista de códigos de língua, ou especifique <kbd>content</kbd> para usar a língua de conteúdo desta wiki, ou especifique <kbd>uselang</kbd> para usar o mesmo valor que o parâmetro <var>uselang</var>.",
+       "apihelp-main-param-errorsuselocal": "Se fornecido, os textos de erro utilizarão mensagens personalizadas localmente do espaço nominal {{ns:MediaWiki}}.",
        "apihelp-block-description": "Bloquear um utilizador.",
        "apihelp-block-param-user": "O nome de utilizador, endereço IP ou gama de endereços IP a serem bloqueados.",
        "apihelp-block-param-expiry": "O período de expiração. Pode ser relativo (p. ex. <kbd>5 meses</kbd> ou <kbd>2 semanas</kbd>) ou absoluto (p. ex. <kbd>2014-09-18T12:34:56Z</kbd>). Se definido como <kbd>infinite</kbd>, <kbd>indefinite</kbd> ou <kbd>never</kbd>, o bloqueio nunca expirará.",
        "apihelp-query+allmessages-param-prop": "As propriedades a serem obtidas:",
        "apihelp-query+allmessages-param-enableparser": "Definir, para ativar o analisador sintático e pré-processar o texto da mensagem com notação wiki (substituir palavras mágicas, processar predefinições, etc.).",
        "apihelp-query+allmessages-param-nocontent": "Se definido, não incluir o conteúdo das mensagens no resultado de saída.",
-       "apihelp-query+allmessages-param-includelocal": "Incluir também as mensagens locais, isto é, mensagens que não existem no software mas existem como uma página no espaço nominal MediaWiki:.\nIsto lista todas as páginas do espaço nominal MediaWiki:, portanto, também irá listar aquelas que não são verdadeiramente mensagens, como [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "Incluir também as mensagens locais, isto é, mensagens que não existem no software mas existem como uma página no espaço nominal {{ns:MediaWiki}}.\nIsto lista todas as páginas do espaço nominal {{ns:MediaWiki}}, portanto, também irá listar aquelas que não são verdadeiramente mensagens, como [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Os argumentos a serem substituídos na mensagem.",
        "apihelp-query+allmessages-param-filter": "Devolver só as mensagens cujos nomes contêm este texto.",
        "apihelp-query+allmessages-param-customised": "Devolver só as mensagens neste estado de personalização.",
        "apihelp-phpfm-description": "Produzir os dados de saída em formato PHP seriado (realce sintático em HTML).",
        "apihelp-rawfm-description": "Produzir os dados de saída, incluindo elementos para despiste de erros, em formato JSON (realce sintático em HTML).",
        "apihelp-xml-description": "Produzir os dados de saída em formato XML.",
-       "apihelp-xml-param-xslt": "Se especificado, adiciona a página nomeada como uma folha de estilo XSL. O valor tem de ser um título no espaço nominal {{ns:mediawiki}} e acabar em <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "Se especificado, adiciona a página nomeada como uma folha de estilo XSL. O valor tem de ser um título no espaço nominal {{ns:MediaWiki}} e acabar em <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Se especificado, adiciona um espaço nominal XML.",
        "apihelp-xmlfm-description": "Produzir os dados de saída em formato XML (realce sintático em HTML).",
        "api-format-title": "Resultado da API do MediaWiki.",
index fd6a4dd..57cd43f 100644 (file)
        "apihelp-main-param-requestid": "{{doc-apihelp-param|main|requestid}}",
        "apihelp-main-param-servedby": "{{doc-apihelp-param|main|servedby}}",
        "apihelp-main-param-curtimestamp": "{{doc-apihelp-param|main|curtimestamp}}",
+       "apihelp-main-param-responselanginfo": "{{doc-apihelp-param|main|responselanginfo}}",
        "apihelp-main-param-origin": "{{doc-apihelp-param|main|origin}}",
        "apihelp-main-param-uselang": "{{doc-apihelp-param|main|uselang}}",
+       "apihelp-main-param-errorformat": "{{doc-apihelp-param|main|errorformat}}",
+       "apihelp-main-param-errorlang": "{{doc-apihelp-param|main|errorlang}}",
+       "apihelp-main-param-errorsuselocal": "{{doc-apihelp-param|main|errorsuselocal}}",
        "apihelp-block-description": "{{doc-apihelp-description|block}}",
        "apihelp-block-param-user": "{{doc-apihelp-param|block|user}}",
+       "apihelp-block-param-userid": "{{doc-apihelp-param|block|userid}}",
        "apihelp-block-param-expiry": "{{doc-apihelp-param|block|expiry}}\n{{doc-important|Do not translate \"5 months\", \"2 weeks\", \"infinite\", \"indefinite\" or \"never\"!}}",
        "apihelp-block-param-reason": "{{doc-apihelp-param|block|reason}}",
        "apihelp-block-param-anononly": "{{doc-apihelp-param|block|anononly}}\n* See also {{msg-mw|ipb-hardblock}}",
        "apihelp-query+imageinfo-paramvalue-prop-archivename": "{{doc-apihelp-paramvalue|query+imageinfo|prop|archivename}}",
        "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "{{doc-apihelp-paramvalue|query+imageinfo|prop|bitdepth}}",
        "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "{{doc-apihelp-paramvalue|query+imageinfo|prop|uploadwarning}}",
+       "apihelp-query+imageinfo-paramvalue-prop-badfile": "{{doc-apihelp-paramvalue|query+imageinfo|prop|badfile}}",
        "apihelp-query+imageinfo-param-limit": "{{doc-apihelp-param|query+imageinfo|limit}}",
        "apihelp-query+imageinfo-param-start": "{{doc-apihelp-param|query+imageinfo|start}}",
        "apihelp-query+imageinfo-param-end": "{{doc-apihelp-param|query+imageinfo|end}}",
        "apihelp-query+imageinfo-param-extmetadatamultilang": "{{doc-apihelp-param|query+imageinfo|extmetadatamultilang}}",
        "apihelp-query+imageinfo-param-extmetadatafilter": "{{doc-apihelp-param|query+imageinfo|extmetadatafilter}}",
        "apihelp-query+imageinfo-param-urlparam": "{{doc-apihelp-param|query+imageinfo|urlparam}}",
+       "apihelp-query+imageinfo-param-badfilecontexttitle": "{{doc-apihelp-param|query+imageinfo|badfilecontexttitle}}",
        "apihelp-query+imageinfo-param-localonly": "{{doc-apihelp-param|query+imageinfo|localonly}}",
        "apihelp-query+imageinfo-example-simple": "{{doc-apihelp-example|query+imageinfo}}",
        "apihelp-query+imageinfo-example-dated": "{{doc-apihelp-example|query+imageinfo}}",
        "apihelp-query+users-paramvalue-prop-cancreate": "{{doc-apihelp-paramvalue|query+users|prop|cancreate}}",
        "apihelp-query+users-param-attachedwiki": "{{doc-apihelp-param|query+users|attachedwiki}}",
        "apihelp-query+users-param-users": "{{doc-apihelp-param|query+users|users}}",
+       "apihelp-query+users-param-userids": "{{doc-apihelp-param|query+users|userids}}",
        "apihelp-query+users-param-token": "{{doc-apihelp-param|query+users|token}}",
        "apihelp-query+users-example-simple": "{{doc-apihelp-example|query+users}}",
        "apihelp-query+watchlist-description": "{{doc-apihelp-description|query+watchlist}}",
        "apihelp-unblock-description": "{{doc-apihelp-description|unblock}}",
        "apihelp-unblock-param-id": "{{doc-apihelp-param|unblock|id}}",
        "apihelp-unblock-param-user": "{{doc-apihelp-param|unblock|user}}",
+       "apihelp-unblock-param-userid": "{{doc-apihelp-param|unblock|userid}}",
        "apihelp-unblock-param-reason": "{{doc-apihelp-param|unblock|reason}}",
        "apihelp-unblock-param-tags": "{{doc-apihelp-param|unblock|tags}}",
        "apihelp-unblock-example-id": "{{doc-apihelp-example|unblock}}",
        "api-help-authmanagerhelper-returnurl": "{{doc-apihelp-param|description=the \"returnurl\" parameter for AuthManager-using API modules|noseealso=1}}",
        "api-help-authmanagerhelper-continue": "{{doc-apihelp-param|description=the \"continue\" parameter for AuthManager-using API modules|noseealso=1}}",
        "api-help-authmanagerhelper-additional-params": "Message to display for AuthManager modules that take additional parameters to populate AuthenticationRequests. Parameters:\n* $1 - AuthManager action used by this module\n* $2 - Module parameter prefix, e.g. \"login\"\n* $3 - Module name, e.g. \"clientlogin\"\n* $4 - Module path, e.g. \"clientlogin\"",
+       "apierror-allimages-redirect": "{{doc-apierror}}",
+       "apierror-allpages-generator-redirects": "{{doc-apierror}}",
+       "apierror-appendnotsupported": "{{doc-apierror}}\n\nParameters:\n* $1 - Content model",
+       "apierror-articleexists": "{{doc-apierror}}",
+       "apierror-assertbotfailed": "{{doc-apierror}}",
+       "apierror-assertnameduserfailed": "{{doc-apierror}}\n\nParameters:\n* $1 - User name passed in.",
+       "apierror-assertuserfailed": "{{doc-apierror}}",
+       "apierror-autoblocked": "{{doc-apierror}}",
+       "apierror-badconfig-resulttoosmall": "{{doc-apierror}}",
+       "apierror-badcontinue": "{{doc-apierror}}",
+       "apierror-baddiff": "{{doc-apierror}}",
+       "apierror-baddiffto": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".",
+       "apierror-badformat-generic": "{{doc-apierror}}\n\nParameters:\n* $1 - Content format.\n* $2 - Content model.",
+       "apierror-badformat": "{{doc-apierror}}\n\nParameters:\n* $1 - Content format.\n* $2 - Content model.\n* $3 - Title using the model.",
+       "apierror-badgenerator-notgenerator": "{{doc-apierror}}\n\nParameters:\n* $1 - Generator module name.",
+       "apierror-badgenerator-unknown": "{{doc-apierror}}\n\nParameters:\n* $1 - Generator module name.",
+       "apierror-badip": "{{doc-apierror}}",
+       "apierror-badmd5": "{{doc-apierror}}",
+       "apierror-badmodule-badsubmodule": "{{doc-apierror}}\n\nParameters:\n* $1 - Module path.\n* $2 - Submodule name.",
+       "apierror-badmodule-nosubmodules": "{{doc-apierror}}\n\nParameters:\n* $1 - Module path.",
+       "apierror-badparameter": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apierror-badquery": "{{doc-apierror}}",
+       "apierror-badtimestamp": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Value of the parameter.",
+       "apierror-badtoken": "{{doc-apierror}}",
+       "apierror-badupload": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apierror-badurl": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Value of the parameter.",
+       "apierror-baduser": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Value of the parameter.",
+       "apierror-badvalue-notmultivalue": "{{doc-apierror}}",
+       "apierror-bad-watchlist-token": "{{doc-apierror}}",
+       "apierror-blockedfrommail": "{{doc-apierror}}",
+       "apierror-blocked": "{{doc-apierror}}",
+       "apierror-botsnotsupported": "{{doc-apierror}}",
+       "apierror-cannotreauthenticate": "{{doc-apierror}}",
+       "apierror-cannotviewtitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Title.",
+       "apierror-cantblock-email": "{{doc-apierror}}",
+       "apierror-cantblock": "{{doc-apierror}}",
+       "apierror-cantchangecontentmodel": "{{doc-apierror}}",
+       "apierror-canthide": "{{doc-apierror}}",
+       "apierror-cantimport-upload": "{{doc-apierror}}",
+       "apierror-cantimport": "{{doc-apierror}}",
+       "apierror-cantoverwrite-sharedfile": "{{doc-apierror}}",
+       "apierror-cantsend": "{{doc-apierror}}",
+       "apierror-cantundelete": "{{doc-apierror}}",
+       "apierror-changeauth-norequest": "{{doc-apierror}}",
+       "apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.",
+       "apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.",
+       "apierror-compare-inputneeded": "{{doc-apierror}}",
+       "apierror-contentserializationexception": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, may end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-contenttoobig": "{{doc-apierror}}\n\nParameters:\n* $1 - Maximum article size in kilobytes.",
+       "apierror-copyuploadbaddomain": "{{doc-apierror}}",
+       "apierror-copyuploadbadurl": "{{doc-apierror}}",
+       "apierror-create-titleexists": "{{doc-apierror}}",
+       "apierror-csp-report": "{{doc-apierror}}\n\nParameters:\n* $1 - Error code, e.g. \"toobig\".",
+       "apierror-databaseerror": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception log ID code. This is meaningless to the end user, but can be used by people with access to the logs to easily find the logged error.",
+       "apierror-deletedrevs-param-not-1-2": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n\nSee also:\n* {{msg-mw|apihelp-query+deletedrevs-description}}",
+       "apierror-deletedrevs-param-not-3": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n\nSee also:\n* {{msg-mw|apihelp-query+deletedrevs-description}}",
+       "apierror-emptynewsection": "{{doc-apierror}}",
+       "apierror-emptypage": "{{doc-apierror}}",
+       "apierror-exceptioncaught": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception log ID code. This is meaningless to the end user, but can be used by people with access to the logs to easily find the logged error.\n* $2 - Exception message, which may end with punctuation. Probably in English.",
+       "apierror-filedoesnotexist": "{{doc-apierror}}",
+       "apierror-fileexists-sharedrepo-perm": "{{doc-apierror}}",
+       "apierror-filenopath": "{{doc-apierror}}",
+       "apierror-filetypecannotberotated": "{{doc-apierror}}",
+       "apierror-formatphp": "{{doc-apierror}}",
+       "apierror-imageusage-badtitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Module name.",
+       "apierror-import-unknownerror": "{{doc-apierror}}\n\nParameters:\n* $1 - Error message returned by the import, probably in English.",
+       "apierror-integeroutofrange-abovebotmax": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name\n* $2 - Maximum allowed value\n* $3 - Supplied value",
+       "apierror-integeroutofrange-abovemax": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name\n* $2 - Maximum allowed value\n* $3 - Supplied value",
+       "apierror-integeroutofrange-belowminimum": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name\n* $2 - Minimum allowed value\n* $3 - Supplied value",
+       "apierror-invalidcategory": "{{doc-apierror}}",
+       "apierror-invalid-chunk": "{{doc-apierror}}",
+       "apierror-invalidexpiry": "{{doc-apierror}}\n\nParameters:\n* $1 - Value provided.",
+       "apierror-invalid-file-key": "{{doc-apierror}}",
+       "apierror-invalidlang": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apierror-invalidoldimage": "{{doc-apierror}}",
+       "apierror-invalidparammix-cannotusewith": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name or \"parameter=value\" text.\n* $2 - Parameter name or \"parameter=value\" text.",
+       "apierror-invalidparammix-mustusewith": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name or \"parameter=value\" text.\n* $2 - Parameter name or \"parameter=value\" text.",
+       "apierror-invalidparammix-parse-new-section": "{{doc-apierror}}",
+       "apierror-invalidparammix": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names or \"parameter=value\" text.\n* $2 - Number of parameters.",
+       "apierror-invalidsection": "{{doc-apierror}}",
+       "apierror-invalidsha1base36hash": "{{doc-apierror}}",
+       "apierror-invalidsha1hash": "{{doc-apierror}}",
+       "apierror-invalidtitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Title that is invalid",
+       "apierror-invalidurlparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".\n* $2 - Key\n* $3 - Value.",
+       "apierror-invaliduser": "{{doc-apierror}}\n\nParameters:\n* $1 - User name that is invalid.",
+       "apierror-maxlag-generic": "{{doc-apierror}}\n\nParameters:\n* $1 - Database is lag in seconds.",
+       "apierror-maxlag": "{{doc-apierror}}\n\nParameters:\n* $1 - Database lag in seconds.\n* $2 - Database server that is lagged.",
+       "apierror-mimesearchdisabled": "{{doc-apierror}}",
+       "apierror-missingcontent-pageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.",
+       "apierror-missingparam-at-least-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
+       "apierror-missingparam-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
+       "apierror-missingparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apierror-missingrev-pageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.",
+       "apierror-missingtitle-createonly": "{{doc-apierror}}",
+       "apierror-missingtitle": "{{doc-apierror}}",
+       "apierror-missingtitle-byname": "{{doc-apierror}}",
+       "apierror-moduledisabled": "{{doc-apierror}}\n\nParameters:\n* $1 - Name of the module.",
+       "apierror-multival-only-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Possible values for the parameter.\n* $3 - Number of values.",
+       "apierror-multival-only-one": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apierror-multpages": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name",
+       "apierror-mustbeloggedin-changeauth": "{{doc-apierror}}",
+       "apierror-mustbeloggedin-generic": "{{doc-apierror}}",
+       "apierror-mustbeloggedin-linkaccounts": "{{doc-apierror}}",
+       "apierror-mustbeloggedin-removeauth": "{{doc-apierror}}",
+       "apierror-mustbeloggedin-uploadstash": "{{doc-apierror}}",
+       "apierror-mustbeloggedin": "{{doc-apierror}}\n\nParameters:\n* $1 - One of the action-* messages (for example {{msg-mw|action-edit}}) or other such messages tagged with {{tl|doc-action}} in their documentation\n\nPlease report at [[Support]] if you are unable to properly translate this message. Also see [[phab:T16246]] (now closed) for background.\n\nSee also:\n* {{msg-mw|apierror-permissiondenied}}\n* {{msg-mw|permissionserrorstext-withaction}}",
+       "apierror-mustbeposted": "{{doc-apierror}}\n\nParameters:\n* $1 - Module name.",
+       "apierror-mustpostparams": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter names.\n* $2 - Number of parameters.",
+       "apierror-noapiwrite": "{{doc-apierror}}",
+       "apierror-nochanges": "{{doc-apierror}}",
+       "apierror-nodeleteablefile": "{{doc-apierror}}",
+       "apierror-no-direct-editing": "{{doc-apierror}}\n\nParameters:\n* $1 - Content model.\n* $2 - Title using the model.",
+       "apierror-noedit-anon": "{{doc-apierror}}",
+       "apierror-noedit": "{{doc-apierror}}",
+       "apierror-noimageredirect-anon": "{{doc-apierror}}",
+       "apierror-noimageredirect": "{{doc-apierror}}",
+       "apierror-nosuchlogid": "{{doc-apierror}}\n\nParameters:\n* $1 - Log ID number.",
+       "apierror-nosuchpageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.",
+       "apierror-nosuchrcid": "{{doc-apierror}}\n\nParameters:\n* $1 - RecentChanges ID number.",
+       "apierror-nosuchrevid": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.",
+       "apierror-nosuchsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
+       "apierror-nosuchsection-what": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.\n* $2 - Page title, revision ID formatted with {{msg-mw|revid}}, or page ID formatted with {{msg-mw|pageid}}.",
+       "apierror-nosuchuserid": "{{doc-apierror}}",
+       "apierror-notarget": "{{doc-apierror}}",
+       "apierror-notpatrollable": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.",
+       "apierror-nouploadmodule": "{{doc-apierror}}",
+       "apierror-opensearch-json-warnings": "{{doc-apierror}}",
+       "apierror-pagecannotexist": "{{doc-apierror}}",
+       "apierror-pagedeleted": "{{doc-apierror}}",
+       "apierror-paramempty": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apierror-parsetree-notwikitext": "{{doc-apierror}}",
+       "apierror-parsetree-notwikitext-title": "{{doc-apierror}}\n\nParameters:\n* $1 - Page title.\n* $2 - Content model.",
+       "apierror-pastexpiry": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied expiry time.",
+       "apierror-permissiondenied": "{{doc-apierror}}\n\nParameters:\n* $1 - One of the action-* messages (for example {{msg-mw|action-edit}}) or other such messages tagged with {{tl|doc-action}} in their documentation\n\nPlease report at [[Support]] if you are unable to properly translate this message. Also see [[phab:T16246]] (now closed) for background.\n\nSee also:\n* {{msg-mw|permissionserrorstext-withaction}}",
+       "apierror-permissiondenied-generic": "{{doc-apierror}}",
+       "apierror-permissiondenied-patrolflag": "{{doc-apierror}}\n\nSee also:\n* {{msg-mw|apierror-permissiondenied}}",
+       "apierror-permissiondenied-unblock": "{{doc-apierror}}\n\nSee also:\n* {{msg-mw|apierror-permissiondenied}}",
+       "apierror-prefixsearchdisabled": "{{doc-apierror}}",
+       "apierror-promised-nonwrite-api": "{{doc-apierror}}",
+       "apierror-protect-invalidaction": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied protection type.",
+       "apierror-protect-invalidlevel": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied protection level.",
+       "apierror-ratelimited": "{{doc-apierror}}",
+       "apierror-readapidenied": "{{doc-apierror}}",
+       "apierror-readonly": "{{doc-apierror}}",
+       "apierror-reauthenticate": "{{doc-apierror}}",
+       "apierror-redirect-appendonly": "{{doc-apierror}}",
+       "apierror-revdel-mutuallyexclusive": "{{doc-apierror}}",
+       "apierror-revdel-needtarget": "{{doc-apierror}}",
+       "apierror-revdel-paramneeded": "{{doc-apierror}}",
+       "apierror-revisions-norevids": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".",
+       "apierror-revisions-singlepage": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".",
+       "apierror-revwrongpage": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.\n* $2 - Page title.",
+       "apierror-searchdisabled": "{{doc-apierror}}\n\nParameters:\n* $1 - Search parameter that is disabled.",
+       "apierror-sectionreplacefailed": "{{doc-apierror}}",
+       "apierror-sectionsnotsupported": "{{doc-apierror}}\n\nParameters:\n* $1 - Content model that doesn't support sections.",
+       "apierror-sectionsnotsupported-what": "{{doc-apierror}}\n\nParameters:\n* $1 - Page title, revision ID formatted with {{msg-mw|revid}}, or page ID formatted with {{msg-mw|pageid}}.",
+       "apierror-show": "{{doc-apierror}}",
+       "apierror-siteinfo-includealldenied": "{{doc-apierror}}",
+       "apierror-sizediffdisabled": "{{doc-apierror}}",
+       "apierror-spamdetected": "{{doc-apierror}}\n\nParameters:\n* $1 - Matching \"spam filter\".\n\nSee also:\n* {{msg-mw|spamprotectionmatch}}",
+       "apierror-specialpage-cantexecute": "{{doc-apierror}}",
+       "apierror-stashedfilenotfound": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-stashedit-missingtext": "{{doc-apierror}}",
+       "apierror-stashfailed-complete": "{{doc-apierror}}",
+       "apierror-stashfailed-nosession": "{{doc-apierror}}",
+       "apierror-stashfilestorage": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, which may already end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-stashnosuchfilekey": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-stashpathinvalid": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-stashwrongowner": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, which should already end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-stashzerolength": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text. Currently this is probably English, hopefully we'll fix that in the future.",
+       "apierror-templateexpansion-notwikitext": "{{doc-apierror}}\n\nParameters:\n* $1 - Page title.\n* $2 - Content model.",
+       "apierror-toofewexpiries": "{{doc-apierror}}\n\nParameters:\n* $1 - Number provided.\n* $2 - Number needed.",
+       "apierror-unknownaction": "{{doc-apierror}}\n\nParameters:\n* $1 - Action provided.",
+       "apierror-unknownerror-editpage": "{{doc-apierror}}\n\nParameters:\n* $1 - Error code (an integer).",
+       "apierror-unknownerror-nocode": "{{doc-apierror}}",
+       "apierror-unknownerror": "{{doc-apierror}}\n\nParameters:\n* $1 - Error code (possibly a message key) not handled by ApiBase::parseMsg().",
+       "apierror-unknownformat": "{{doc-apierror}}\n\nParameters:\n* $1 - Format provided.",
+       "apierror-unrecognizedparams": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameters.\n* $2 - Number of parameters.",
+       "apierror-unrecognizedvalue": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Parameter value.",
+       "apierror-unsupportedrepo": "{{doc-apierror}}",
+       "apierror-upload-filekeyneeded": "{{doc-apierror}}",
+       "apierror-upload-filekeynotallowed": "{{doc-apierror}}",
+       "apierror-upload-inprogress": "{{doc-apierror}}",
+       "apierror-upload-missingresult": "{{doc-apierror}}",
+       "apierror-urlparamnormal": "{{doc-apierror}}\n\nParameters:\n* $1 - Image title.",
+       "apierror-writeapidenied": "{{doc-apierror}}",
+       "apiwarn-alldeletedrevisions-performance": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".",
+       "apiwarn-badurlparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".\n* $2 - Image title.",
+       "apiwarn-badutf8": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apiwarn-checktoken-percentencoding": "{{doc-apierror}}",
+       "apiwarn-deprecation-deletedrevs": "{{doc-apierror}}",
+       "apiwarn-deprecation-expandtemplates-prop": "{{doc-apierror}}",
+       "apiwarn-deprecation-httpsexpected": "{{doc-apierror}}",
+       "apiwarn-deprecation-login-botpw": "{{doc-apierror}}",
+       "apiwarn-deprecation-login-nobotpw": "{{doc-apierror}}",
+       "apiwarn-deprecation-login-token": "{{doc-apierror}}",
+       "apiwarn-deprecation-parameter": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apiwarn-deprecation-parse-headitems": "{{doc-apierror}}",
+       "apiwarn-deprecation-purge-get": "{{doc-apierror}}",
+       "apiwarn-deprecation-withreplacement": "{{doc-apierror}}\n\nParameters:\n* $1 - Query string fragment that is deprecated, e.g. \"action=tokens\".\n* $2 - Query string fragment to use instead, e.g. \"action=tokens\".",
+       "apiwarn-difftohidden": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.",
+       "apiwarn-errorprinterfailed": "{{doc-apierror}}",
+       "apiwarn-errorprinterfailed-ex": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception message, which may already end in punctuation. Probably in English.",
+       "apiwarn-invalidcategory": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied category name.",
+       "apiwarn-invalidtitle": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied title.",
+       "apiwarn-invalidxmlstylesheetext": "{{doc-apierror}}",
+       "apiwarn-invalidxmlstylesheet": "{{doc-apierror}}",
+       "apiwarn-invalidxmlstylesheetns": "{{doc-apierror}}",
+       "apiwarn-moduleswithoutvars": "{{doc-apierror}}",
+       "apiwarn-notfile": "{{doc-apierror}}\n\nParameters:\n* $1 - Supplied file name.",
+       "apiwarn-nothumb-noimagehandler": "{{doc-apierror}}\n\nParameters:\n* $1 - File name.",
+       "apiwarn-parse-nocontentmodel": "{{doc-apierror}}\n\nParameters:\n* $1 - Content model being assumed.",
+       "apiwarn-parse-titlewithouttext": "{{doc-apierror}}",
+       "apiwarn-redirectsandrevids": "{{doc-apierror}}",
+       "apiwarn-tokennotallowed": "{{doc-apierror}}\n\nParameters:\n* $1 - Token type being requested, typically named after the action requiring the token.",
+       "apiwarn-tokens-origin": "{{doc-apierror}}",
+       "apiwarn-toomanyvalues": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Maximum number of values allowed.",
+       "apiwarn-truncatedresult": "{{doc-apierror}}\n\nParameters:\n* $1 - Size limit in bytes.",
+       "apiwarn-unclearnowtimestamp": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Supplied value.",
+       "apiwarn-unrecognizedvalues": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - List of unknown values supplied.\n* $3 - Number of unknown values.",
+       "apiwarn-unsupportedarray": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
+       "apiwarn-urlparamwidth": "{{doc-apierror}}\n\nParameters:\n* $1 - Module parameter prefix, e.g. \"bl\".\n* $2 - Width being ignored.\n* $3 - Width being used.",
+       "apiwarn-validationfailed-badchars": "{{doc-apierror}}\n\nUsed with {{msg-mw|apiwarn-validationfailed}}.",
+       "apiwarn-validationfailed-badpref": "{{doc-apierror}}\n\nUsed with {{msg-mw|apiwarn-validationfailed}}.",
+       "apiwarn-validationfailed-cannotset": "{{doc-apierror}}\n\nUsed with {{msg-mw|apiwarn-validationfailed}}.",
+       "apiwarn-validationfailed-keytoolong": "{{doc-apierror}}\n\nUsed with {{msg-mw|apiwarn-validationfailed}}.\n\nParameters:\n* $1 - Maximum allowed key length in bytes.",
+       "apiwarn-validationfailed": "{{doc-apierror}}\n\nParameters:\n* $1 - User preference name.\n* $2 - Failure message, such as {{msg-mw|apiwarn-validationfailed-badpref}}. Probably already ends with punctuation",
+       "apiwarn-wgDebugAPI": "{{doc-apierror}}",
+       "api-feed-error-title": "Used as a feed item title when an error occurs in <kbd>action=feedwatchlist</kbd>.\n\nParameters:\n* $1 - API error code\n{{Identical|Error}}",
+       "api-usage-docref": "\n\nParameters:\n* $1 - URL of the API auto-generated documentation.",
+       "api-exception-trace": "\n\nParameters:\n* $1 - Exception class.\n* $2 - File from which the exception was thrown.\n* $3 - Line number from which the exception was thrown.\n* $4 - Exception backtrace.",
        "api-credits-header": "Header for the API credits section in the API help output\n{{Identical|Credit}}",
        "api-credits": "API credits text, displayed in the API help output"
 }
index 2879020..6a747d0 100644 (file)
@@ -37,7 +37,7 @@
        "apihelp-main-param-origin": "При обращении к API, используя кросс-доменный AJAX-запрос (CORS), задайте параметру значение исходного домена. Он должен быть включён в любой предварительный запрос и таким образом должен быть частью URI-запроса (не тела POST).\n\nДля аутентифицированных запросов он должен точно соответствовать одному из источников в заголовке <code>Origin</code>, так что он должен быть задан наподобие <kbd>https://ru.wikipedia.org</kbd> или <kbd>https://meta.wikimedia.org</kbd>. Если параметр не соответствует заголовку <code>Origin</code>, будет возвращён ответ с кодом ошибки 403. Если параметр соответствует заголовку <code>Origin</code>, и источник находится в белом списке, будут установлены заголовки <code>Access-Control-Allow-Origin</code> и <code>Access-Control-Allow-Credentials</code>.\n\nДля неаутентифицированных запросов укажите значение <kbd>*</kbd>. Это приведёт к установке заголовка <code>Access-Control-Allow-Origin</code> заголовка должен быть установлен, но <code>Access-Control-Allow-Credentials</code> примет значение <code>false</code> и все пользовательские данные будут ограничены.",
        "apihelp-main-param-uselang": "Язык, используемый для перевода Сообщений. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> с <kbd>siprop=языки</kbd> возвращает список кодов языков, или указать <kbd>пользователей</kbd> , чтобы использовать текущий язык пользователя предпочтения, или указать <kbd>контента</kbd> для использования этой Вики содержание язык.",
        "apihelp-block-description": "Блокировка участника.",
-       "apihelp-block-param-user": "Имя участника, IP-адрес или диапазон IP-адресов, которые вы хотите заблокировать.",
+       "apihelp-block-param-user": "Имя участника, IP-адрес или диапазон IP-адресов, которые вы хотите заблокировать. Нельзя использовать вместе с <var>$1userid</var>",
        "apihelp-block-param-expiry": "Время истечения срока действия. Могут быть относительными (например, <kbd>5 месяцев</kbd> или <kbd>2 недели</kbd>) или абсолютными (например, <kbd>2014-09-18T12:34:56Z</kbd>). Если задано <kbd>бессрочно</kbd>, <kbd>бессрочно</kbd>, или <kbd>не</kbd>, блок никогда не истекает.",
        "apihelp-block-param-reason": "Причина блокировки.",
        "apihelp-block-param-anononly": "Блокировать только анонимных пользователей (т. е. запретить анонимные правки для этого IP-адреса).",
        "api-help-examples": "Пример{{PLURAL:$1||ы}}:",
        "api-help-permissions": "{{PLURAL:$1|Permission|Permissions}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|Granted to}}: $2",
+       "apierror-integeroutofrange-abovemax": "<var>$1</var> не может быть более $2 (на $3) для пользователей.",
+       "apierror-nosuchuserid": "Нет пользователя с ID $1.",
+       "apierror-protect-invalidaction": "Недопустимый тип защиты \"$1\".",
+       "apierror-unknownformat": "Неизвестный Формат \"$1\".",
+       "apierror-urlparamnormal": "Не могли нормализовать параметры изображения для $1.",
        "api-credits-header": "Создатели"
 }
index e409591..b3253a5 100644 (file)
        "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Måste vara tom|Kan vara tom, eller $2}}",
        "api-help-param-limit": "Inte mer än $1 tillåts.",
        "api-help-param-limit2": "Inte mer än $1 ($2 för robotar) tillåts.",
-       "api-help-param-multi-separate": "Separera värden med <kbd>|</kbd> eller [[Special:ApiHelp/main#main/datatypes|alternativ]]."
+       "api-help-param-multi-separate": "Separera värden med <kbd>|</kbd> eller [[Special:ApiHelp/main#main/datatypes|alternativ]].",
+       "apierror-articleexists": "Artikeln du försökte skapa har redan skapats.",
+       "apierror-protect-invalidaction": "Ogiltig skyddstyp \"$1\".",
+       "apierror-unknownformat": "Okänt format \"$1\".",
+       "api-feed-error-title": "Fel ($1)"
 }
index 96a47fc..b7491b5 100644 (file)
@@ -3,7 +3,8 @@
                "authors": [
                        "Veeven",
                        "HAUSANRIK",
-                       "Ravichandra"
+                       "Ravichandra",
+                       "Jedimaster26"
                ]
        },
        "apihelp-block-description": "ఓ వాడుకరిని నిరోధించండి.",
@@ -15,5 +16,6 @@
        "apihelp-edit-example-edit": "ఓ పేజీని మార్చు.",
        "apihelp-emailuser-description": "వాడుకరికి ఈమెయిలు పంపించండి.",
        "apihelp-feedrecentchanges-example-simple": "ఇటీవలి మార్పులను చూడండి",
+       "apihelp-query+users-param-userids": "వివరములు సేకరించవలసిన ఉపయోగదారుల పేర్లు",
        "apihelp-rawfm-description": "బయటకు వచ్చిన సమాచారo, డీబగ్గింగ్ అంశముతో కలిపి, JSON పద్ధతిలో (HTMLలో అందంగా-ముద్రించు)"
 }
diff --git a/includes/api/i18n/th.json b/includes/api/i18n/th.json
new file mode 100644 (file)
index 0000000..d2ea18f
--- /dev/null
@@ -0,0 +1,9 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Aefgh39622"
+               ]
+       },
+       "apihelp-imagerotate-description": "หมุนรูปภาพอย่างน้อยหนึ่งรูป",
+       "api-help-param-continue": "เมื่อมีผลลัพธ์เพิ่มเติมพร้อมใช้งาน ใช้ตัวเลือกนี้เพื่อดำเนินการต่อ"
+}
index fe9d085..9aaba31 100644 (file)
@@ -1,9 +1,11 @@
 {
        "@metadata": {
                "authors": [
-                       "Kaganer"
+                       "Kaganer",
+                       "Mouse21"
                ]
        },
+       "apihelp-block-description": "Блокировка пыриськисьёс.",
        "apihelp-edit-example-edit": "Бамез тупатъяно.",
        "apihelp-login-example-login": "Пырыны."
 }
index d5b4c62..a823a00 100644 (file)
@@ -11,7 +11,8 @@
                        "Mix Gerder",
                        "Piramidion",
                        "Andriykopanytsia",
-                       "Максим Підліснюк"
+                       "Максим Підліснюк",
+                       "AS"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Документація]]\n* [[mw:API:FAQ|ЧаПи]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Список розсилки]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Оголошення API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Баґи і запити]\n</div>\n<strong>Статус:</strong> Усі функції, вказані на цій сторінці, мають працювати, але API далі перебуває в активній розробці і може змінитися у будь-який момент. Підпишіться на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ список розсилки mediawiki-api-announce], щоб помічати оновлення.\n\n<strong>Хибні запити:</strong> Коли до API надсилаються хибні запити, буде відіслано HTTP-шапку з ключем «MediaWiki-API-Error», а тоді і значення шапки, і код помилки, надіслані назад, будуть встановлені з тим же значенням. Більше інформації див. на [[mw:API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Тестування:</strong> Для зручності тестування запитів API, див. [[Special:ApiSandbox]].",
        "apihelp-main-param-requestid": "Будь-яке значення, вказане тут, буде включене у відповідь. Може використовуватися, щоб відрізняти запити.",
        "apihelp-main-param-servedby": "Включити в результати ім'я хоста, який обробив запит.",
        "apihelp-main-param-curtimestamp": "Включити в результат поточну мітку часу.",
+       "apihelp-main-param-responselanginfo": "Включати мови, які були використані для <var>uselang</var> і <var>errorlang</var>, у результат.",
        "apihelp-main-param-origin": "При доступі до API з використанням крос-доменного AJAX-запиту (CORS), задайте параметру значення вихідного домена. Він має бути включений у будь-який попередній запит і таким чином мусить бути частиною запиту URI (не тіла POST). \n\nДля автентифікованих запитів він повинен точно співпадати з одним з виходів у заголовку <code>Origin</code>, тобто бути заданим чимось на зразок <kbd>https://uk.wikipedia.org</kbd> або <kbd>https://meta.wikimedia.org</kbd>. Якщо цей параметр не співпадає з заголовком <code>Origin</code>, повернеться помилка 403. Якщо цей параметр співпадає з заголовком <code>Origin</code> і вихід знаходиться у білому списку, буде встановлено заголовки <code>Access-Control-Allow-Origin</code> і <code>Access-Control-Allow-Credentials</code>.\n\nДля неавтентифікованих запитів укажіть значення <kbd>*</kbd>. Це дасть встановлення заголовка <code>Access-Control-Allow-Origin</code>, але <code>Access-Control-Allow-Credentials</code> буде <code>false</code> і всі дані про користувача будуть заборонені.",
        "apihelp-main-param-uselang": "Мова, що використовується для перекладу повідомлень. Список кодів можна видати на <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> з <kbd>siprop=languages</kbd> або вказати <kbd>user</kbd> на використання поточного налаштування мови користувача, або вказати <kbd>content</kbd> на використання мови вмісту цієї вікі.",
+       "apihelp-main-param-errorformat": "Формат попереджень і тексту помилок.\n; plaintext: вікітекст із прираними HTML-тегами і заміненими HTML-мнемоніками.\n; wikitext: неопрацьований вікітекст.\n; html: HTML.\n; raw: лише ключ і параметри повідомлення.\n; none: без тексту, тільки коди помилок.\n; bc: формат, який використовувався до MediaWiki 1.29. <var>errorlang</var> і <var>errorsuselocal</var> ігноруються.",
        "apihelp-block-description": "Заблокувати користувача.",
        "apihelp-block-param-user": "Ім'я користувача, IP-адреса або діапазон IP-адрес для блокування.",
        "apihelp-block-param-expiry": "Закінчення часу. Може бути відносним (напр., <kbd>5 місяців</kbd> або <kbd>2 тижні</kbd>) чи абсолютним (напр., <kbd>2014-09-18T12:34:56Z</kbd>). Якщо вказано <kbd>infinite</kbd>, <kbd>indefinite</kbd> або <kbd>never</kbd>, блокування не закінчиться ніколи.",
        "apihelp-query+allmessages-param-prop": "Які властивості отримати.",
        "apihelp-query+allmessages-param-enableparser": "Встановити увімкнення парсеру, це попередньо обробить вікітекст повідомлення (підставити магічні слова, розкрити шаблони тощо).",
        "apihelp-query+allmessages-param-nocontent": "Якщо вказано, не включати повідомлення вміст повідомлення у результат.",
-       "apihelp-query+allmessages-param-includelocal": "Також включити локальні повідомлення, тобто повідомлення, що не існують у програмному забезпеченні, але існують як сторінка MediaWiki:.\nЦе видає список усіх сторінок MediaWiki:, так що у ньому будуть сторінки, які насправді не є повідомленнями, як-то [[MediaWiki:Common.js|Common.js]].",
+       "apihelp-query+allmessages-param-includelocal": "Також включити локальні повідомлення, тобто повідомлення, що не існують у програмному забезпеченні, але існують як сторінка в просторі назв {{ns:MediaWiki}}.\nЦе видає список усіх сторінок простору {{ns:MediaWiki}}, так що у ньому також будуть сторінки, які насправді не є повідомленнями, як-то [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Аргументи будуть підставлятися в повідомлення.",
        "apihelp-query+allmessages-param-filter": "Видати лише повідомлення з назвами, що місять цей рядок.",
        "apihelp-query+allmessages-param-customised": "Видати лише повідомлення у цьому стані налаштувань.",
        "apihelp-phpfm-description": "Виводити дані у форматі серіалізованого PHP (вивід відформатованого коду за допомогою HTML).",
        "apihelp-rawfm-description": "Виводити дані, включно з елементами налагодження, у форматі JSON (вивід відформатованого коду за допомогою HTML).",
        "apihelp-xml-description": "Виводити дані у форматі XML.",
-       "apihelp-xml-param-xslt": "Якщо вказано, додає названу сторінку як таблицю стилів XSL. Це значення повинне бути назвою у просторі назв {{ns:mediawiki}}, що закінчується на <code>.xsl</code>.",
+       "apihelp-xml-param-xslt": "Якщо вказано, додає названу сторінку як таблицю стилів XSL. Це значення повинне бути назвою у просторі назв {{ns:MediaWiki}}, що закінчується на <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Якщо вказано, додає простір назв XML.",
        "apihelp-xmlfm-description": "Вивести дані у форматі XML (вивід відформатованого коду за допомогою HTML).",
        "api-format-title": "Результат запиту до API MediaWiki",
index 60295aa..36b9d1e 100644 (file)
        "apihelp-main-param-requestid": "任何在此提供的值将包含在响应中。可以用以区别请求。",
        "apihelp-main-param-servedby": "包含保存结果请求的主机名。",
        "apihelp-main-param-curtimestamp": "在结果中包括当前时间戳。",
+       "apihelp-main-param-responselanginfo": "包含在结果中用于<var>uselang</var>和<var>errorlang</var>的语言。",
        "apihelp-main-param-origin": "当通过跨域名AJAX请求(CORS)访问API时,设置此作为起始域名。这必须包括在任何pre-flight请求中,并因此必须是请求的URI的一部分(而不是POST正文)。\n\n对于已验证的请求,这必须正确匹配<code>Origin</code>标头中的原点之一,因此它已经设置为像<kbd>https://zh.wikipedia.org</kbd>或<kbd>https://meta.wikimedia.org</kbd>的东西。如果此参数不匹配<code>Origin</code>页顶,就返回403错误响应。如果此参数匹配<code>Origin</code>页顶并且起点被白名单,将设置<code>Access-Control-Allow-Origin</code>和<code>Access-Control-Allow-Credentials</code>开头。\n\n对于未验证的请求,会指定值<kbd>*</kbd>。这将导致<code>Access-Control-Allow-Origin</code>标头被设置,但<code>Access-Control-Allow-Credentials</code>将为<code>false</code>,且所有用户特定数据将受限制。",
        "apihelp-main-param-uselang": "用于消息翻译的语言。<kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>与<kbd>siprop=languages</kbd>可返回语言代码列表,或指定<kbd>user</kbd>以使用当前用户的语言设置,或指定<kbd>content</kbd>以使用此wiki的内容语言。",
+       "apihelp-main-param-errorformat": "用于警告和错误文本输出的格式。\n; plaintext:已移除HTML标签,并被替换实体的Wiki文本。\n; wikitext:未解析的wiki文本。\n; html:HTML。\n; raw:消息关键词和参数。\n; none:无文本输出,仅包含错误代码。\n; bc:在MediaWiki 1.29以前版本使用的格式。<var>errorlang</var>和<var>errorsuselocal</var>会被忽略。",
+       "apihelp-main-param-errorlang": "用于警告和错误的语言。<kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>带<kbd>siprop=languages</kbd>返回语言代码的列表,或指定<kbd>content</kbd>以使用此wiki的内容语言,或指定<kbd>uselang</kbd>以使用与<var>uselang</var>参数相同的值。",
+       "apihelp-main-param-errorsuselocal": "如果指定,错误文本将使用来自{{ns:MediaWiki}}名字空间的本地自定义消息。",
        "apihelp-block-description": "封禁一位用户。",
-       "apihelp-block-param-user": "您要封禁的用户、IP地址或IP地址段。",
+       "apihelp-block-param-user": "要封禁的用户、IP地址或IP地址段。不能与<var>$1userid</var>一起使用",
+       "apihelp-block-param-userid": "要封禁的用户ID。不能与<var>$1user</var>一起使用。",
        "apihelp-block-param-expiry": "到期时间。可以是相对时间(例如<kbd>5 months</kbd>或<kbd>2 weeks</kbd>)或绝对时间(例如<kbd>2014-09-18T12:34:56Z</kbd>)。如果设置为<kbd>infinite</kbd>、<kbd>indefinite</kbd>或<kbd>never</kbd>,封禁将无限期。",
        "apihelp-block-param-reason": "封禁的原因。",
        "apihelp-block-param-anononly": "只封禁匿名用户(也就是说禁止此 IP 地址的匿名编辑)。",
        "apihelp-query+allmessages-param-prop": "要获取的属性。",
        "apihelp-query+allmessages-param-enableparser": "设置以启用解析器,将处理消息的wiki文本(替代魔术字、处理模板等)。",
        "apihelp-query+allmessages-param-nocontent": "如果设置,不要在输出中包含消息内容。",
-       "apihelp-query+allmessages-param-includelocal": "也包括本地消息,也就是不存在于软件但作为MediaWiki:页面存在的消息。\n这会列举所有MediaWiki:页面,因此它也将列举那些不是真消息的消息,例如[[MediaWiki:Common.js|Common.js]]。",
+       "apihelp-query+allmessages-param-includelocal": "也包括本地消息,也就是不存在于软件但存在于{{ns:MediaWiki}}名字空间的消息。\n这会列举所有{{ns:MediaWiki}}名字空间页面,因此它也将列举那些不是真消息的消息,例如[[MediaWiki:Common.js|Common.js]]。",
        "apihelp-query+allmessages-param-args": "要替代进消息的参数。",
        "apihelp-query+allmessages-param-filter": "只返回名称包含此字符串的消息。",
        "apihelp-query+allmessages-param-customised": "只返回在此定制情形下的消息。",
        "apihelp-query+users-paramvalue-prop-cancreate": "表明是否可以为有效但尚未注册的用户名创建一个账户。",
        "apihelp-query+users-param-attachedwiki": "与<kbd>$1prop=centralids</kbd>一起使用,表明用户是否附加于此ID定义的wiki。",
        "apihelp-query+users-param-users": "要获取信息的用户列表。",
+       "apihelp-query+users-param-userids": "要获得信息的用户ID列表。",
        "apihelp-query+users-param-token": "请改用<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>。",
        "apihelp-query+users-example-simple": "返回用户<kbd>Example</kbd>的信息。",
        "apihelp-query+watchlist-description": "在当前用户的监视列表中获取对页面的最近更改。",
        "apihelp-tokens-example-edit": "检索一个编辑令牌(默认)。",
        "apihelp-tokens-example-emailmove": "检索一个电子邮件令牌和一个移动令牌。",
        "apihelp-unblock-description": "解封一位用户。",
-       "apihelp-unblock-param-id": "解封时需要的封禁ID(通过<kbd>list=blocks</kbd>获得)。不能与<var>$1user</var>一起使用。",
-       "apihelp-unblock-param-user": "要解封的用户名、IP地址或IP段。不能与<var>$1id</var>一起使用。",
+       "apihelp-unblock-param-id": "解封时需要的封禁ID(通过<kbd>list=blocks</kbd>获得)。不能与<var>$1user</var>或<var>$luserid</var>一起使用。",
+       "apihelp-unblock-param-user": "要解封的用户名、IP地址或IP地址段。不能与<var>$1id</var>或<var>$luserid</var>一起使用。",
+       "apihelp-unblock-param-userid": "要封禁的用户ID。不能与<var>$1id</var>或<var>$1user</var>一起使用。",
        "apihelp-unblock-param-reason": "解封的原因。",
        "apihelp-unblock-param-tags": "要在封禁日志中应用到实体的更改标签。",
        "apihelp-unblock-example-id": "解封封禁ID #<kbd>105</kbd>。",
        "apihelp-phpfm-description": "输出数据为序列化PHP格式(HTML优质打印效果)。",
        "apihelp-rawfm-description": "输出数据为JSON格式,包含调试元素(HTML优质打印效果)。",
        "apihelp-xml-description": "输出数据为XML格式。",
-       "apihelp-xml-param-xslt": "如果指定,加入已命名的页面作为一个XSL样式表。值必须是在{{ns:mediawiki}}名字空间以<code>.xsl</code>为结尾的标题。",
+       "apihelp-xml-param-xslt": "如果指定,加入已命名的页面作为一个XSL样式表。值必须是在{{ns:MediaWiki}}名字空间以<code>.xsl</code>为结尾的标题。",
        "apihelp-xml-param-includexmlnamespace": "如果指定,添加一个XML名字空间。",
        "apihelp-xmlfm-description": "输出数据为XML格式(HTML优质打印效果)。",
        "api-format-title": "MediaWiki API 结果",
        "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# 在响应中检查<samp>status</samp>。\n#* 如果您收到了<samp>PASS</samp>或<samp>FAIL</samp>,您已经完成。操作要么成功,要么不成功。\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-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>,您已经完成。操作要么成功,要么不成功。\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>,这意味着身份验证可以工作,但我们没有链接的用户账户。您可以将此看做<samp>UI</samp>或<samp>FAIL</samp>。",
        "api-help-authmanagerhelper-requests": "只使用这些身份验证请求,通过返回自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的<samp>id</samp>与<kbd>amirequestsfor=$1</kbd>,或来自此模块之前的响应。",
        "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-returnurl": "为第三方身份验证流返回URL,必须为绝对值。需要此值或<var>$1continue</var>两者之一。\n\nUpon receiving a <samp>REDIRECT</samp> response, you will typically open a browser or web view to the specified <samp>redirecttarget</samp> URL for a third-party authentication flow. When that completes, the third party will send the browser or web view to this URL. You should extract any query or POST parameters from the URL and pass them as a <var>$1continue</var> request to this API module.",
        "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>(或之前来自此模块的相应,如果可以)以决定可用请求及其使用的字段。",
+       "apierror-allimages-redirect": "当使用<kbd>allimages</kbd>作为发生器时,请使用<kbd>gaifilterredir=nonredirects</kbd>而不是<var>redirects</var>。",
+       "apierror-allpages-generator-redirects": "当使用<kbd>allpages</kbd>作为发生器时,请使用<kbd>gapfilterredir=nonredirects</kbd>而不是<var>redirects</var>。",
+       "apierror-appendnotsupported": "不能使用内容模型$1附加在页面上。",
+       "apierror-articleexists": "您尝试创建的条目已刚刚被创建。",
+       "apierror-assertbotfailed": "主张用户有<code>bot</code>权限失败。",
+       "apierror-assertnameduserfailed": "主张用户为“$1”失败。",
+       "apierror-assertuserfailed": "主张用户已登录失败。",
+       "apierror-autoblocked": "您的IP地址已被自动封禁,因为它曾被一位已封禁用户使用。",
+       "apierror-badconfig-resulttoosmall": "此wiki上<code>$wgAPIMaxResultSize</code>的值太小,不能获得基础结果信息。",
+       "apierror-badcontinue": "无效继续参数。您应该传递由之前查询返回的原始值。",
+       "apierror-baddiff": "不能取得差异,一个或多个修订版本不存在,或您没有权限查看它们。",
+       "apierror-baddiffto": "<var>$1diffto</var>必须设置为非负数、<kbd>prev</kbd>、<kbd>next</kbd>或<kbd>cur</kbd>。",
+       "apierror-badformat-generic": "内容模型$2尚不支持请求的内容格式$1。",
+       "apierror-badformat": "由$3使用的内容模型$2尚不支持请求的内容格式$1。",
+       "apierror-badgenerator-notgenerator": "模块<kbd>$1</kbd>不能用作发生器。",
+       "apierror-badgenerator-unknown": "未知<kbd>generator=$1</kbd>。",
+       "apierror-badip": "IP参数无效。",
+       "apierror-badmd5": "提供的MD5哈希不正确。",
+       "apierror-badmodule-badsubmodule": "模块<kbd>$1</kbd>不包含子模块“$2”。",
+       "apierror-badmodule-nosubmodules": "模块<kbd>$1</kbd>没有子模块。",
+       "apierror-badparameter": "用于参数<var>$1</var>的值无效。",
+       "apierror-badquery": "无效的查询。",
+       "apierror-badtimestamp": "用于时间戳参数<var>$1</var>的值“$2”无效。",
+       "apierror-badtoken": "无效的CSRF令牌。",
+       "apierror-badupload": "文件上传参数<var>$1</var>不是文件上传;确保为您的POST使用<code>multipart/form-data</code>,并在<code>Content-Disposition</code>标头中包含文件名。",
+       "apierror-badurl": "用于URL参数<var>$1</var>的值“$2”无效。",
+       "apierror-baduser": "用于用户参数<var>$1</var>的值“$2”无效。",
+       "apierror-badvalue-notmultivalue": "U+001F多值分隔符只可以用于多值参数。",
+       "apierror-bad-watchlist-token": "提供了不正确的监视列表令牌。请在[[Special:Preferences]]设置正确的令牌。",
+       "apierror-blockedfrommail": "您已被封禁,不能发送电子邮件。",
+       "apierror-blocked": "您已被封禁,不能编辑。",
+       "apierror-botsnotsupported": "此界面不支持机器人。",
+       "apierror-cannotviewtitle": "您不被允许查看$1。",
+       "apierror-cantblock-email": "您没有权限封禁用户通过wiki发送电子邮件。",
+       "apierror-cantblock": "您没有权限封禁用户。",
+       "apierror-cantchangecontentmodel": "您没有权限更改页面的内容模型。",
+       "apierror-canthide": "您没有权限从封禁日志中隐藏用户名。",
+       "apierror-cantimport-upload": "您没有权限导入上传的页面。",
+       "apierror-cantimport": "您没有权限导入页面。",
+       "apierror-cantsend": "您没有登录,您没有已确认的电子邮件地址,或者您未被允许向其他用户发送电子邮件,所以您不能发送电子邮件。",
+       "apierror-changeauth-norequest": "创建更改请求失败。",
+       "apierror-cidrtoobroad": "比/$2更宽的$1 CIDR地址段不被接受。",
+       "apierror-contentserializationexception": "内容序列化失败:$1",
+       "apierror-contenttoobig": "您提供的内容超过了$1{{PLURAL:$1|千字节}}的条目大小限制。",
+       "apierror-copyuploadbaddomain": "不允许从此域名通过URL上传。",
+       "apierror-copyuploadbadurl": "不允许从此URL上传。",
+       "apierror-create-titleexists": "现有标题不能通过<kbd>create</kbd>保护。",
+       "apierror-csp-report": "处理CSP报告时出错:$1。",
+       "apierror-databaseerror": "[$1]数据库查询错误。",
+       "apierror-deletedrevs-param-not-1-2": "<var>$1</var>参数不能用于模式1或2。",
+       "apierror-deletedrevs-param-not-3": "<var>$1</var>参数不能用于模式3。",
+       "apierror-emptynewsection": "无法创建空白新章节。",
+       "apierror-emptypage": "不允许创建新的,空白的页面。",
+       "apierror-exceptioncaught": "[$1]捕获异常:$2",
+       "apierror-filedoesnotexist": "文件不存在。",
+       "apierror-filenopath": "不能获取本地文件路径。",
+       "apierror-filetypecannotberotated": "文件类型不能旋转。",
+       "apierror-formatphp": "此响应不能使用<kbd>format=php</kbd>代表。请参见https://phabricator.wikimedia.org/T68776。",
+       "apierror-imageusage-badtitle": "<kbd>$1</kbd>的标题必须是文件。",
+       "apierror-import-unknownerror": "导入时的未知错误:$1。",
+       "apierror-integeroutofrange-abovebotmax": "对于机器人和管理员,<var>$1</var>不能超过$2(设置为$3)。",
+       "apierror-integeroutofrange-abovemax": "对于用户,<var>$1</var>不能超过$2(设置为$3)。",
+       "apierror-integeroutofrange-belowminimum": "<var>$1</var>不能小于$2(设置为$3)。",
+       "apierror-invalidcategory": "您输入的分类名称无效。",
+       "apierror-invalid-chunk": "偏移值与当前数据块之和大于声称的文件大小。",
+       "apierror-invalidexpiry": "无效的过期时间“$1”。",
+       "apierror-invalid-file-key": "不是有效的文件关键词。",
+       "apierror-invalidlang": "用于参数<var>$1</var>的语言值无效。",
+       "apierror-invalidoldimage": "旧图片参数有无效格式。",
+       "apierror-invalidparammix-cannotusewith": "<kbd>$1</kbd>参数不能与<kbd>$2</kbd>一起使用。",
+       "apierror-invalidparammix-mustusewith": "<kbd>$1</kbd>参数只能与<kbd>$2</kbd>一起使用。",
+       "apierror-invalidparammix-parse-new-section": "<kbd>section=new</kbd>不能连同<var>oldid</var>、<var>pageid</var>或<var>page</var>参数使用。请使用<var>title</var>和<var>text</var>。",
+       "apierror-invalidparammix": "{{PLURAL:$2|参数}}$1不能一起使用。",
+       "apierror-invalidsection": "章节参数必须为有效的章节ID或<kbd>new</kbd>。",
+       "apierror-invalidsha1base36hash": "提供的SHA1Base36哈希无效。",
+       "apierror-invalidsha1hash": "提供的SHA1哈希无效。",
+       "apierror-invalidtitle": "错误标题“$1”。",
+       "apierror-invalidurlparam": "<var>$1urlparam</var>的值无效(<kbd>$2=$3</kbd>)。",
+       "apierror-invaliduser": "无效用户名“$1”。",
+       "apierror-maxlag-generic": "正在等待数据库服务器:已延迟$1{{PLURAL:$1|秒}}。",
+       "apierror-maxlag": "正在等待$2:已延迟$1{{PLURAL:$1|秒}}。",
+       "apierror-mimesearchdisabled": "MIME搜索在Miser模式中被禁用。",
+       "apierror-missingcontent-pageid": "丢失ID为$1的页面的内容。",
+       "apierror-missingparam-at-least-one-of": "需要{{PLURAL:$2|参数$1|$1中的至少一个参数}}。",
+       "apierror-missingparam-one-of": "需要{{PLURAL:$2|参数$1|$1中的一个参数}}。",
+       "apierror-missingparam": "<var>$1</var>参数必须被设置。",
+       "apierror-missingrev-pageid": "没有ID为$1的页面的当前修订版本。",
+       "apierror-missingtitle-createonly": "丢失标题只可以通过<kbd>create</kbd>保护。",
+       "apierror-missingtitle": "您指定的页面不存在。",
+       "apierror-missingtitle-byname": "页面$1不存在。",
+       "apierror-moduledisabled": "<kbd>$1</kbd>模块已被禁用。",
+       "apierror-multival-only-one-of": "参数<var>$1</var>只允许$2{{PLURAL:$3||之一}}。",
+       "apierror-multival-only-one": "参数<var>$1</var>只允许一个值。",
+       "apierror-multpages": "<var>$1</var>只可以在单一页面使用。",
+       "apierror-mustbeloggedin-changeauth": "您必须登录以更改身份验证数据。",
+       "apierror-mustbeloggedin-generic": "您必须登录。",
+       "apierror-mustbeloggedin-linkaccounts": "您必须登录以链接账户。",
+       "apierror-mustbeloggedin-removeauth": "您必须登录以移除身份验证数据。",
+       "apierror-mustbeloggedin-uploadstash": "上传暂存功能只对已登录用户可用。",
+       "apierror-mustbeloggedin": "您必须登录至$1。",
+       "apierror-mustbeposted": "<kbd>$1</kbd>模块需要POST请求。",
+       "apierror-mustpostparams": "以下{{PLURAL:$2|参数}}在查询字符串中被找到,但必须在POST正文中:$1。",
+       "apierror-noapiwrite": "通过API编辑此wiki已禁用。请确保<code>$wgEnableWriteAPI=true;</code>声明包含在wiki的<code>LocalSettings.php</code>文件中。",
+       "apierror-nochanges": "没有请求的更改。",
+       "apierror-nodeleteablefile": "没有该文件的旧版本。",
+       "apierror-no-direct-editing": "$2使用的内容模型$1不支持通过API直接编辑。",
+       "apierror-noedit-anon": "匿名用户不能编辑页面。",
+       "apierror-noedit": "您没有权限编辑页面。",
+       "apierror-noimageredirect-anon": "匿名用户不能创建图片重定向。",
+       "apierror-noimageredirect": "您没有权限创建图片重定向。",
+       "apierror-nosuchlogid": "没有ID为$1的日志记录。",
+       "apierror-nosuchpageid": "没有ID为$1的页面。",
+       "apierror-nosuchrcid": "没有ID为$1的最近更改。",
+       "apierror-nosuchrevid": "没有ID为$1的修订版本。",
+       "apierror-nosuchsection": "没有章节$1。",
+       "apierror-nosuchsection-what": "在$2中没有章节$1。",
+       "apierror-nosuchuserid": "没有ID为$1的用户。",
+       "apierror-notarget": "您没有为此章节指定有效目标。",
+       "apierror-notpatrollable": "修订版本r$1不能巡查,因为它太旧了。",
+       "apierror-nouploadmodule": "未设置上传模块。",
+       "apierror-opensearch-json-warnings": "警告不能以OpenSearch JSON格式表示。",
+       "apierror-pagecannotexist": "名字空间不允许实际页面。",
+       "apierror-pagedeleted": "在您取得页面时间戳以来,页面已被删除。",
+       "apierror-paramempty": "参数<var>$1</var>不能为空。",
+       "apierror-parsetree-notwikitext": "<kbd>prop=parsetree</kbd>只支持wiki文本内容。",
+       "apierror-parsetree-notwikitext-title": "<kbd>prop=parsetree</kbd>只支持wiki文本内容。$1使用内容模型$2。",
+       "apierror-pastexpiry": "终止时间“$1”已过去。",
+       "apierror-permissiondenied": "您没有权限$1。",
+       "apierror-permissiondenied-generic": "权限被拒绝。",
+       "apierror-permissiondenied-patrolflag": "您需要<code>patrol</code>或<code>patrolmarks</code>权限来请求巡查标记。",
+       "apierror-permissiondenied-unblock": "您没有权限解封用户。",
+       "apierror-prefixsearchdisabled": "前缀搜索在Miser模式中被禁用。",
+       "apierror-promised-nonwrite-api": "<code>Promise-Non-Write-API-Action</code> HTTP标头不能发送至写模式API模块。",
+       "apierror-protect-invalidaction": "无效的保护类型“$1”。",
+       "apierror-protect-invalidlevel": "无效的保护级别“$1”。",
+       "apierror-ratelimited": "您已超过您的速率限制。请等待一段时间再试。",
+       "apierror-readapidenied": "您需要读取权限以使用此模块。",
+       "apierror-readonly": "此wiki目前为只读模式。",
+       "apierror-revdel-mutuallyexclusive": "同一字段不能同时用于<var>hide</var>和<var>show</var>。",
+       "apierror-revdel-needtarget": "此修订版本删除类型需要目标标题。",
+       "apierror-revdel-paramneeded": "需要<var>hide</var>和/或<var>show</var>的至少一个值。",
+       "apierror-revisions-norevids": "<var>revids</var>参数不能与列表选项(<var>$1limit</var>、<var>$1startid</var>、<var>$1endid</var>、<kbd>$1dir=newer</kbd>、<var>$1user</var>、<var>$1excludeuser</var>、<var>$1start</var>和<var>$1end</var>)一起使用",
+       "apierror-revisions-singlepage": "<var>titles</var>、<var>pageids</var>或发生器用于提供多个页面,但<var>$1limit</var>、<var>$1startid</var>、<var>$1endid</var>、<kbd>$1dir=newer</kbd>、<var>$1user</var>、<var>$1excludeuser</var>、<var>$1start</var>和<var>$1end</var>参数只能在一个页面上使用。",
+       "apierror-revwrongpage": "r$1不是$2的修订版本。",
+       "apierror-searchdisabled": "<var>$1</var>搜索已禁用。",
+       "apierror-sectionreplacefailed": "不能合并更新的章节。",
+       "apierror-sectionsnotsupported": "内容模型$1不支持章节。",
+       "apierror-sectionsnotsupported-what": "章节不被$1所支持。",
+       "apierror-sizediffdisabled": "大小差异在Miser模式中被禁用。",
+       "apierror-spamdetected": "您的编辑被拒绝,因为它包含破坏性碎片:<code>$1</code>。",
+       "apierror-specialpage-cantexecute": "您没有权限查看此特殊页面的结果。",
+       "apierror-stashfilestorage": "不能在暂存处存储上传:$1",
+       "apierror-stashnosuchfilekey": "没有这个filekey:$1。",
+       "apierror-stashwrongowner": "错误所有者:$1",
+       "apierror-stashzerolength": "文件长度为0,并且不能在暂存库中储存:$1。",
+       "apierror-templateexpansion-notwikitext": "模板展开只支持wiki文本内容。$1使用内容模型$2。",
+       "apierror-unknownaction": "指定的操作<kbd>$1</kbd>不被承认。",
+       "apierror-unknownerror-editpage": "未知的编辑页面错误:$1。",
+       "apierror-unknownerror-nocode": "未知错误。",
+       "apierror-unknownerror": "未知错误:“$1”。",
+       "apierror-unknownformat": "无法识别的格式“$1”。",
+       "apierror-unrecognizedparams": "无法识别的{{PLURAL:$2|参数}}:$1。",
+       "apierror-unrecognizedvalue": "无法识别的参数<var>$1</var>的值:$2。",
+       "apierror-unsupportedrepo": "本地文件存储库不支持查询所有图片。",
+       "apierror-upload-filekeyneeded": "当<var>offset</var>不为0时,必须提供<var>filekey</var>。",
+       "apierror-upload-filekeynotallowed": "当<var>offset</var>为0时,不能提供<var>filekey</var>。",
+       "apierror-upload-inprogress": "从暂存处上传已在进行中。",
+       "apierror-upload-missingresult": "状态数据中没有结果。",
+       "apierror-urlparamnormal": "不能为$1标准化图片参数。",
+       "apierror-writeapidenied": "您不被允许通过API编辑此wiki。",
+       "apiwarn-alldeletedrevisions-performance": "当生成标题时,为获得更好性能,请设置<kbd>$1dir=newer</kbd>。",
+       "apiwarn-badurlparam": "不能为$2解析<var>$1urlparam</var>。请只使用宽和高。",
+       "apiwarn-deprecation-deletedrevs": "<kbd>list=deletedrevs</kbd>已被弃用。请改用<kbd>prop=deletedrevisions</kbd>或<kbd>list=alldeletedrevisions</kbd>。",
+       "apiwarn-deprecation-httpsexpected": "当应为HTTPS时,HTTP被使用。",
+       "apiwarn-deprecation-login-botpw": "通过<kbd>action=login</kbd>的主账户登录已被弃用,并可能在未事先警告的情况下停止工作。要继续通过<kbd>action=login</kbd>登录,请参见[[Special:BotPasswords]]。要安全继续使用主账户登录,请参见<kbd>action=clientlogin</kbd>。",
+       "apiwarn-deprecation-login-nobotpw": "通过<kbd>action=login</kbd>的主账户登录已被弃用,并可能在未事先警告的情况下停止工作。要安全登录,请参见<kbd>action=clientlogin</kbd>。",
+       "apiwarn-deprecation-login-token": "通过<kbd>action=login</kbd>取得令牌已弃用。请改用<kbd>action=query&meta=tokens&type=login</kbd>。",
+       "apiwarn-deprecation-parameter": "参数<var>$1</var>已被弃用。",
+       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd>从MediaWiki 1.28版开始已弃用。在创建新HTML文档时请使用<kbd>prop=headhtml</kbd>,或当更新文档客户端时请使用<kbd>prop=modules|jsconfigvars</kbd>。",
+       "apiwarn-deprecation-purge-get": "通过GET使用<kbd>action=purge</kbd>已被弃用。请改用POST。",
+       "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd>已弃用。请改用<kbd>$2</kbd>。",
+       "apiwarn-difftohidden": "不能与r$1做差异比较:内容被隐藏。",
+       "apiwarn-errorprinterfailed": "错误打印失败。将在没有参数的前提下重试。",
+       "apiwarn-errorprinterfailed-ex": "错误打印失败(将在没有参数的前提下重试):$1",
+       "apiwarn-invalidcategory": "“$1”不是一个分类。",
+       "apiwarn-invalidtitle": "“$1”不是一个有效的标题。",
+       "apiwarn-invalidxmlstylesheetext": "样式表应拥有<code>.xsl</code>扩展名。",
+       "apiwarn-invalidxmlstylesheet": "指定了无效或不存在的样式表。",
+       "apiwarn-invalidxmlstylesheetns": "样式表应位于{{ns:MediaWiki}}名字空间。",
+       "apiwarn-moduleswithoutvars": "属性<kbd>modules</kbd>被设置,但不是<kbd>jsconfigvars</kbd>或<kbd>encodedjsconfigvars</kbd>。需要配置变量以获得适当的模块使用。",
+       "apiwarn-notfile": "“$1”不是文件。",
+       "apiwarn-parse-nocontentmodel": "<var>title</var>或<var>contentmodel</var>未提供,假设$1。",
+       "apiwarn-parse-titlewithouttext": "<var>title</var>在没有<var>text</var>的情况下被使用,并且请求了已解析页面的属性。您是想用<var>page</var>而不是<var>title</var>么?",
+       "apiwarn-tokennotallowed": "操作“$1”不允许当前用户使用。",
+       "apiwarn-toomanyvalues": "参数<var>$1</var>指定了太多的值:上限为$2。",
+       "apiwarn-unclearnowtimestamp": "为时间戳参数<var>$1</var>传递“$2”已被弃用。如因某些原因您需要明确指定当前时间而不计算客户端,请使用<kbd>now<kbd>。",
+       "apiwarn-unrecognizedvalues": "参数<var>$1</var>有无法识别的{{PLURAL:$3|值}}:$2。",
+       "apiwarn-unsupportedarray": "参数<var>$1</var>使用未受支持的PHP数组语法。",
+       "apiwarn-validationfailed-badchars": "关键词中的字符无效(只允许<code>a-z</code>、<code>A-Z</code>、<code>0-9</code>、<code>_</code>和<code>-</code>)。",
+       "apiwarn-validationfailed-badpref": "不是有效的偏好。",
+       "apiwarn-validationfailed-cannotset": "不能通过此模块设置。",
+       "apiwarn-validationfailed-keytoolong": "关键词太长(不允许超过$1字节)。",
+       "apiwarn-validationfailed": "<kbd>$1</kbd>的合法性错误:$2",
+       "apiwarn-wgDebugAPI": "<strong>安全警告</strong>:<var>$wgDebugAPI</var>已启用。",
+       "api-feed-error-title": "错误($1)",
+       "api-usage-docref": "参见$1以获取API用法。",
+       "api-exception-trace": "$1在$2($3)\n$4",
        "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 859fd0c..fd36887 100644 (file)
@@ -242,14 +242,14 @@ class LocalPasswordPrimaryAuthenticationProvider
 
                $pwhash = null;
 
-               if ( $this->loginOnly ) {
-                       $pwhash = $this->getPasswordFactory()->newFromCiphertext( null );
-                       $expiry = null;
-                       // @codeCoverageIgnoreStart
-               } elseif ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
-                       // @codeCoverageIgnoreEnd
-                       $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
-                       $expiry = $this->getNewPasswordExpiry( $username );
+               if ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
+                       if ( $this->loginOnly ) {
+                               $pwhash = $this->getPasswordFactory()->newFromCiphertext( null );
+                               $expiry = null;
+                       } else {
+                               $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
+                               $expiry = $this->getNewPasswordExpiry( $username );
+                       }
                }
 
                if ( $pwhash ) {
index fb461b7..8c70be2 100644 (file)
@@ -82,6 +82,8 @@ interface ICacheHelper {
        function setExpiry( $cacheExpiry );
 }
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Helper class for caching various elements in a single cache entry.
  *
@@ -217,9 +219,9 @@ class CacheHelper implements ICacheHelper {
                        $subPage = explode( '/', $subPage, 2 );
                        $subPage = count( $subPage ) > 1 ? $subPage[1] : false;
 
-                       $message .= ' ' . Linker::link(
+                       $message .= ' ' . MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
                                $context->getTitle( $subPage ),
-                               $context->msg( 'cachedspecial-refresh-now' )->escaped(),
+                               $context->msg( 'cachedspecial-refresh-now' )->text(),
                                [],
                                $refreshArgs
                        );
index 3f78d9a..4e6b2fd 100644 (file)
@@ -22,7 +22,6 @@
  */
 use MediaWiki\MediaWikiServices;
 use Wikimedia\ScopedCallback;
-use MediaWiki\Logger\LoggerFactory;
 
 /**
  * MediaWiki message cache structure version.
@@ -53,11 +52,6 @@ class MessageCache {
         */
        protected $mCache;
 
-       /**
-        * @var bool[] Map of (language code => boolean)
-        */
-       protected $mCacheVolatile = [];
-
        /**
         * Should mean that database cannot be used, but check
         * @var bool $mDisable
@@ -71,12 +65,10 @@ class MessageCache {
        protected $mExpiry;
 
        /**
-        * Message cache has its own parser which it uses to transform messages
-        * @var ParserOptions
+        * Message cache has its own parser which it uses to transform
+        * messages.
         */
-       protected $mParserOptions;
-       /** @var Parser */
-       protected $mParser;
+       protected $mParserOptions, $mParser;
 
        /**
         * Variable for tracking which variables are already loaded
@@ -137,7 +129,6 @@ class MessageCache {
         */
        public static function normalizeKey( $key ) {
                global $wgContLang;
-
                $lckey = strtr( $key, ' ', '_' );
                if ( ord( $lckey ) < 128 ) {
                        $lckey[0] = strtolower( $lckey[0] );
@@ -267,7 +258,6 @@ class MessageCache {
                # Hash of the contents is stored in memcache, to detect if data-center cache
                # or local cache goes out of date (e.g. due to replace() on some other server)
                list( $hash, $hashVolatile ) = $this->getValidationHash( $code );
-               $this->mCacheVolatile[$code] = $hashVolatile;
 
                # Try the local cache and check against the cluster hash key...
                $cache = $this->getLocalCache( $code );
@@ -483,16 +473,9 @@ class MessageCache {
                $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
 
                # Load titles for all oversized pages in the MediaWiki namespace
-               $res = $dbr->select(
-                       'page',
-                       [ 'page_title', 'page_latest' ],
-                       $bigConds,
-                       __METHOD__ . "($code)-big"
-               );
+               $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
                foreach ( $res as $row ) {
                        $cache[$row->page_title] = '!TOO BIG';
-                       // At least include revision ID so page changes are reflected in the hash
-                       $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                }
 
                # Conditions to load the remaining pages with their contents
@@ -513,6 +496,7 @@ class MessageCache {
                        if ( $text === false ) {
                                // Failed to fetch data; possible ES errors?
                                // Store a marker to fetch on-demand as a workaround...
+                               // TODO Use a differnt marker
                                $entry = '!TOO BIG';
                                wfDebugLog(
                                        'MessageCache',
@@ -527,6 +511,10 @@ class MessageCache {
 
                $cache['VERSION'] = MSG_CACHE_VERSION;
                ksort( $cache );
+
+               # Hash for validating local cache (APC). No need to take into account
+               # messages larger than $wgMaxMsgCacheEntrySize, since those are only
+               # stored and fetched from memcache.
                $cache['HASH'] = md5( serialize( $cache ) );
                $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
 
@@ -537,7 +525,7 @@ class MessageCache {
         * Updates cache as necessary when message page is changed
         *
         * @param string|bool $title Name of the page changed (false if deleted)
-        * @param string|bool $text New contents of the page (false if deleted)
+        * @param mixed $text New contents of the page.
         */
        public function replace( $title, $text ) {
                global $wgMaxMsgCacheEntrySize, $wgContLang, $wgLanguageCode;
@@ -557,36 +545,36 @@ class MessageCache {
                // a self-deadlock. This is safe as no reads happen *directly* in this
                // method between getReentrantScopedLock() and load() below. There is
                // no risk of data "changing under our feet" for replace().
-               $scopedLock = $this->getReentrantScopedLock( wfMemcKey( 'messages', $code ) );
-               // Load the messages from the master DB to avoid race conditions
+               $cacheKey = wfMemcKey( 'messages', $code );
+               $scopedLock = $this->getReentrantScopedLock( $cacheKey );
                $this->load( $code, self::FOR_UPDATE );
 
-               // Load the new value into the process cache...
+               $titleKey = wfMemcKey( 'messages', 'individual', $title );
                if ( $text === false ) {
+                       // Article was deleted
                        $this->mCache[$code][$title] = '!NONEXISTENT';
+                       $this->wanCache->delete( $titleKey );
                } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
+                       // Check for size
                        $this->mCache[$code][$title] = '!TOO BIG';
-                       // Pre-fill the individual key cache with the known latest message text
-                       $key = $this->wanCache->makeKey( 'messages-big', $this->mCache[$code]['HASH'], $title );
-                       $this->wanCache->set( $key, " $text", $this->mExpiry );
+                       $this->wanCache->set( $titleKey, ' ' . $text, $this->mExpiry );
                } else {
                        $this->mCache[$code][$title] = ' ' . $text;
+                       $this->wanCache->delete( $titleKey );
                }
-               // Mark this cache as definitely being "latest" (non-volatile) so
-               // load() calls do not try to refresh the cache with replica DB data
+
+               // Mark this cache as definitely "latest" (non-volatile) so
+               // load() calls do try to refresh the cache with replica DB data
                $this->mCache[$code]['LATEST'] = time();
 
                // Update caches if the lock was acquired
                if ( $scopedLock ) {
                        $this->saveToCaches( $this->mCache[$code], 'all', $code );
-               } else {
-                       LoggerFactory::getInstance( 'MessageCache' )->error(
-                               __METHOD__ . ': could not acquire lock to update {title} ({code})',
-                               [ 'title' => $title, 'code' => $code ] );
                }
 
                ScopedCallback::consume( $scopedLock );
-               // Relay the purge to APC and other DCs
+               // Relay the purge. Touching this check key expires cache contents
+               // and local cache (APC) validation hash across all datacenters.
                $this->wanCache->touchCheckKey( wfMemcKey( 'messages', $code ) );
 
                // Also delete cached sidebar... just in case it is affected
@@ -662,26 +650,24 @@ class MessageCache {
        protected function getValidationHash( $code ) {
                $curTTL = null;
                $value = $this->wanCache->get(
-                       $this->wanCache->makeKey( 'messages', $code, 'hash', 'v1' ),
+                       wfMemcKey( 'messages', $code, 'hash', 'v1' ),
                        $curTTL,
                        [ wfMemcKey( 'messages', $code ) ]
                );
 
-               if ( $value ) {
+               if ( !$value ) {
+                       // No hash found at all; cache must regenerate to be safe
+                       $hash = false;
+                       $expired = true;
+               } else {
                        $hash = $value['hash'];
-                       if ( ( time() - $value['latest'] ) < WANObjectCache::TTL_MINUTE ) {
-                               // Cache was recently updated via replace() and should be up-to-date.
-                               // That method is only called in the primary datacenter and uses FOR_UPDATE.
-                               // Also, it is unlikely that the current datacenter is *now* secondary one.
+                       if ( ( time() - $value['latest'] ) < WANObjectCache::HOLDOFF_TTL ) {
+                               // Cache was recently updated via replace() and should be up-to-date
                                $expired = false;
                        } else {
                                // See if the "check" key was bumped after the hash was generated
                                $expired = ( $curTTL < 0 );
                        }
-               } else {
-                       // No hash found at all; cache must regenerate to be safe
-                       $hash = false;
-                       $expired = true;
                }
 
                return [ $hash, $expired ];
@@ -691,15 +677,14 @@ class MessageCache {
         * Set the md5 used to validate the local disk cache
         *
         * If $cache has a 'LATEST' UNIX timestamp key, then the hash will not
-        * be treated as "volatile" by getValidationHash() for the next few seconds.
-        * This is triggered when $cache is generated using FOR_UPDATE mode.
+        * be treated as "volatile" by getValidationHash() for the next few seconds
         *
         * @param string $code
         * @param array $cache Cached messages with a version
         */
        protected function setValidationHash( $code, array $cache ) {
                $this->wanCache->set(
-                       $this->wanCache->makeKey( 'messages', $code, 'hash', 'v1' ),
+                       wfMemcKey( 'messages', $code, 'hash', 'v1' ),
                        [
                                'hash' => $cache['HASH'],
                                'latest' => isset( $cache['LATEST'] ) ? $cache['LATEST'] : 0
@@ -862,7 +847,6 @@ class MessageCache {
         */
        private function getMessageForLang( $lang, $lckey, $useDB, &$alreadyTried ) {
                global $wgContLang;
-
                $langcode = $lang->getCode();
 
                // Try checking the database for the requested language
@@ -921,7 +905,6 @@ class MessageCache {
         */
        private function getMessagePageName( $langcode, $uckey ) {
                global $wgLanguageCode;
-
                if ( $langcode === $wgLanguageCode ) {
                        // Messages created in the content language will not have the /lang extension
                        return $uckey;
@@ -947,7 +930,8 @@ class MessageCache {
                if ( isset( $this->mCache[$code][$title] ) ) {
                        $entry = $this->mCache[$code][$title];
                        if ( substr( $entry, 0, 1 ) === ' ' ) {
-                               // The message exists, so make sure a string is returned.
+                               // The message exists, so make sure a string
+                               // is returned.
                                return (string)substr( $entry, 1 );
                        } elseif ( $entry === '!NONEXISTENT' ) {
                                return false;
@@ -966,19 +950,17 @@ class MessageCache {
                }
 
                // Try the individual message cache
-               $titleKey = $this->wanCache->makeKey( 'messages-big', $this->mCache[$code]['HASH'], $title );
-
-               if ( $this->mCacheVolatile[$code] ) {
-                       $entry = false;
-                       // Make sure that individual keys respect the WAN cache holdoff period too
-                       LoggerFactory::getInstance( 'MessageCache' )->debug(
-                               __METHOD__ . ': loading volatile key \'{titleKey}\'',
-                               [ 'titleKey' => $titleKey, 'code' => $code ] );
-               } else {
-                       $entry = $this->wanCache->get( $titleKey );
-               }
+               $titleKey = wfMemcKey( 'messages', 'individual', $title );
 
-               if ( $entry !== false ) {
+               $curTTL = null;
+               $entry = $this->wanCache->get(
+                       $titleKey,
+                       $curTTL,
+                       [ wfMemcKey( 'messages', $code ) ]
+               );
+               $entry = ( $curTTL >= 0 ) ? $entry : false;
+
+               if ( $entry ) {
                        if ( substr( $entry, 0, 1 ) === ' ' ) {
                                $this->mCache[$code][$title] = $entry;
                                // The message exists, so make sure a string is returned
@@ -993,7 +975,7 @@ class MessageCache {
                        }
                }
 
-               // Try loading the message from the database
+               // Try loading it from the database
                $dbr = wfGetDB( DB_REPLICA );
                $cacheOpts = Database::getCacheSetOptions( $dbr );
                // Use newKnownCurrent() to avoid querying revision/user tables
@@ -1010,18 +992,32 @@ class MessageCache {
 
                if ( $revision ) {
                        $content = $revision->getContent();
-                       if ( $content ) {
-                               $message = $this->getMessageTextFromContent( $content );
-                               if ( is_string( $message ) ) {
+                       if ( !$content ) {
+                               // A possibly temporary loading failure.
+                               wfDebugLog(
+                                       'MessageCache',
+                                       __METHOD__ . ": failed to load message page text for {$title} ($code)"
+                               );
+                               $message = null; // no negative caching
+                       } else {
+                               // XXX: Is this the right way to turn a Content object into a message?
+                               // NOTE: $content is typically either WikitextContent, JavaScriptContent or
+                               //       CssContent. MessageContent is *not* used for storing messages, it's
+                               //       only used for wrapping them when needed.
+                               $message = $content->getWikitextForTransclusion();
+
+                               if ( $message === false || $message === null ) {
+                                       wfDebugLog(
+                                               'MessageCache',
+                                               __METHOD__ . ": message content doesn't provide wikitext "
+                                                       . "(content model: " . $content->getModel() . ")"
+                                       );
+
+                                       $message = false; // negative caching
+                               } else {
                                        $this->mCache[$code][$title] = ' ' . $message;
                                        $this->wanCache->set( $titleKey, ' ' . $message, $this->mExpiry, $cacheOpts );
                                }
-                       } else {
-                               // A possibly temporary loading failure
-                               LoggerFactory::getInstance( 'MessageCache' )->warning(
-                                       __METHOD__ . ': failed to load message page text for \'{titleKey}\'',
-                                       [ 'titleKey' => $titleKey, 'code' => $code ] );
-                               $message = null; // no negative caching
                        }
                } else {
                        $message = false; // negative caching
@@ -1073,7 +1069,6 @@ class MessageCache {
         */
        function getParser() {
                global $wgParser, $wgParserConf;
-
                if ( !$this->mParser && isset( $wgParser ) ) {
                        # Do some initialisation so that we don't have to do it twice
                        $wgParser->firstCallInit();
@@ -1101,8 +1096,6 @@ class MessageCache {
        public function parse( $text, $title = null, $linestart = true,
                $interface = false, $language = null
        ) {
-               global $wgTitle;
-
                if ( $this->mInParser ) {
                        return htmlspecialchars( $text );
                }
@@ -1117,6 +1110,7 @@ class MessageCache {
                $popts->setTargetLanguage( $language );
 
                if ( !$title || !$title instanceof Title ) {
+                       global $wgTitle;
                        wfDebugLog( 'GlobalTitleFail', __METHOD__ . ' called by ' .
                                wfGetAllCallers( 6 ) . ' with no title set.' );
                        $title = $wgTitle;
@@ -1204,7 +1198,6 @@ class MessageCache {
         */
        public function getAllMessageKeys( $code ) {
                global $wgContLang;
-
                $this->load( $code );
                if ( !isset( $this->mCache[$code] ) ) {
                        // Apparently load() failed
@@ -1214,61 +1207,10 @@ class MessageCache {
                $cache = $this->mCache[$code];
                unset( $cache['VERSION'] );
                unset( $cache['EXPIRY'] );
-               unset( $cache['EXCESSIVE'] );
                // Remove any !NONEXISTENT keys
                $cache = array_diff( $cache, [ '!NONEXISTENT' ] );
 
                // Keys may appear with a capital first letter. lcfirst them.
                return array_map( [ $wgContLang, 'lcfirst' ], array_keys( $cache ) );
        }
-
-       /**
-        * Purge message caches when a MediaWiki: page is created, updated, or deleted
-        *
-        * @param Title $title Message page title
-        * @param Content|null $content New content for edit/create, null on deletion
-        * @since 1.29
-        */
-       public function updateMessageOverride( Title $title, Content $content = null ) {
-               global $wgContLang;
-
-               $msgText = $this->getMessageTextFromContent( $content );
-               if ( $msgText === null ) {
-                       $msgText = false; // treat as not existing
-               }
-
-               $this->replace( $title->getDBkey(), $msgText );
-
-               if ( $wgContLang->hasVariants() ) {
-                       $wgContLang->updateConversionTable( $title );
-               }
-       }
-
-       /**
-        * @param Content|null $content Content or null if the message page does not exist
-        * @return string|bool|null Returns false if $content is null and null on error
-        */
-       private function getMessageTextFromContent( Content $content = null ) {
-               // @TODO: could skip pseudo-messages like js/css here, based on content model
-               if ( $content ) {
-                       // Message page exists...
-                       // XXX: Is this the right way to turn a Content object into a message?
-                       // NOTE: $content is typically either WikitextContent, JavaScriptContent or
-                       //       CssContent. MessageContent is *not* used for storing messages, it's
-                       //       only used for wrapping them when needed.
-                       $msgText = $content->getWikitextForTransclusion();
-                       if ( $msgText === false || $msgText === null ) {
-                               // This might be due to some kind of misconfiguration...
-                               $msgText = null;
-                               LoggerFactory::getInstance( 'MessageCache' )->warning(
-                                       __METHOD__ . ": message content doesn't provide wikitext "
-                                       . "(content model: " . $content->getModel() . ")" );
-                       }
-               } else {
-                       // Message page does not exist...
-                       $msgText = false;
-               }
-
-               return $msgText;
-       }
 }
index 2dc953c..b78efaf 100644 (file)
@@ -19,6 +19,8 @@
  * @ingroup Change tagging
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Item class for a logging table row with its associated change tags.
  * @todo Abstract out a base class for this and RevDelLogItem, similar to the
@@ -70,9 +72,9 @@ class ChangeTagsLogItem extends RevisionItemBase {
                $formatter->setAudience( LogFormatter::FOR_THIS_USER );
 
                // Log link for this page
-               $loglink = Linker::link(
+               $loglink = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
                        SpecialPage::getTitleFor( 'Log' ),
-                       $this->list->msg( 'log' )->escaped(),
+                       $this->list->msg( 'log' )->text(),
                        [],
                        [ 'page' => $title->getPrefixedText() ]
                );
index 931128f..6a0a63b 100644 (file)
@@ -381,7 +381,7 @@ interface Content {
         *
         * @since 1.21
         *
-        * @param string|number $sectionId Section identifier as a number or string
+        * @param string|int $sectionId Section identifier as a number or string
         * (e.g. 0, 1 or 'T-1'). The ID "0" retrieves the section before the first heading, "1" the
         * text between the first heading (included) and the second heading (excluded), etc.
         *
@@ -396,7 +396,7 @@ interface Content {
         *
         * @since 1.21
         *
-        * @param string|number|null|bool $sectionId Section identifier as a number or string
+        * @param string|int|null|bool $sectionId Section identifier as a number or string
         * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
         * or 'new' for a new section.
         * @param Content $with New content of the section
index 9296728..d649baf 100644 (file)
@@ -38,7 +38,7 @@ class WikitextContent extends TextContent {
        }
 
        /**
-        * @param string|number $sectionId
+        * @param string|int $sectionId
         *
         * @return Content|bool|null
         *
@@ -58,7 +58,7 @@ class WikitextContent extends TextContent {
        }
 
        /**
-        * @param string|number|null|bool $sectionId
+        * @param string|int|null|bool $sectionId
         * @param Content $with
         * @param string $sectionTitle
         *
index 7208b7e..2f9f809 100644 (file)
@@ -28,7 +28,7 @@
 /**
  * @ingroup Database
  */
-class DatabaseMssql extends DatabaseBase {
+class DatabaseMssql extends Database {
        protected $mInsertId = null;
        protected $mLastResult = null;
        protected $mAffectedRows = null;
index 561dadb..9258506 100644 (file)
@@ -131,7 +131,7 @@ class ORAResult {
 /**
  * @ingroup Database
  */
-class DatabaseOracle extends DatabaseBase {
+class DatabaseOracle extends Database {
        /** @var resource */
        protected $mLastResult = null;
 
diff --git a/includes/debug/logger/monolog/LogstashFormatter.php b/includes/debug/logger/monolog/LogstashFormatter.php
new file mode 100644 (file)
index 0000000..553cbf6
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+namespace MediaWiki\Logger\Monolog;
+
+/**
+ * LogstashFormatter squashes the base message array and the context and extras subarrays into one.
+ * This can result in unfortunately named context fields overwriting other data (T145133).
+ * This class modifies the standard LogstashFormatter to rename such fields and flag the message.
+ *
+ * Compatible with Monolog 1.x only.
+ *
+ * @since 1.29
+ */
+class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
+       /** @var array Keys which should not be used in log context */
+       protected $reservedKeys = [
+               // from LogstashFormatter
+               'message', 'channel', 'level', 'type',
+               // from WebProcessor
+               'url', 'ip', 'http_method', 'server', 'referrer',
+               // from WikiProcessor
+               'host', 'wiki', 'reqId', 'mwversion',
+               // from config magic
+               'normalized_message',
+       ];
+
+       /**
+        * Prevent key conflicts
+        * @param array $record
+        * @return array
+        */
+       protected function formatV0( array $record ) {
+               if ( $this->contextPrefix ) {
+                       return parent::formatV0( $record );
+               }
+
+               $context = !empty( $record['context'] ) ? $record['context'] : [];
+               $record['context'] = [];
+               $formatted = parent::formatV0( $record );
+
+               $formatted['@fields'] = $this->fixKeyConflicts( $formatted['@fields'], $context );
+               return $formatted;
+       }
+
+       /**
+        * Prevent key conflicts
+        * @param array $record
+        * @return array
+        */
+       protected function formatV1( array $record ) {
+               if ( $this->contextPrefix ) {
+                       return parent::formatV1( $record );
+               }
+
+               $context = !empty( $record['context'] ) ? $record['context'] : [];
+               $record['context'] = [];
+               $formatted = parent::formatV1( $record );
+
+               $formatted = $this->fixKeyConflicts( $formatted, $context );
+               return $formatted;
+       }
+
+       /**
+        * Check whether some context field would overwrite another message key. If so, rename
+        * and flag.
+        * @param array $fields Fields to be sent to logstash
+        * @param array $context Copy of the original $record['context']
+        * @return array Updated version of $fields
+        */
+       protected function fixKeyConflicts( array $fields, array $context ) {
+               foreach ( $context as $key => $val ) {
+                       if (
+                               in_array( $key, $this->reservedKeys, true ) &&
+                               isset( $fields[$key] ) && $fields[$key] !== $val
+                       ) {
+                               $fields['logstash_formatter_key_conflict'][] = $key;
+                               $key = 'c_' . $key;
+                       }
+                       $fields[$key] = $val;
+               }
+               return $fields;
+       }
+}
index 81e1e14..ad939a0 100644 (file)
@@ -29,17 +29,6 @@ namespace MediaWiki\Logger\Monolog;
  * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
  */
 class WikiProcessor {
-       /** @var array Keys which should not be used in log context */
-       protected $reservedKeys = [
-               // from monolog:src/Monolog/Formatter/LogstashFormatter.php#L71-L88
-               'message', 'channel', 'level', 'type',
-               // from WebProcessor
-               'url', 'ip', 'http_method', 'server', 'referrer',
-               // from WikiProcessor
-               'host', 'wiki', 'reqId', 'mwversion',
-               // from config magic
-               'normalized_message',
-       ];
 
        /**
         * @param array $record
@@ -47,15 +36,6 @@ class WikiProcessor {
         */
        public function __invoke( array $record ) {
                global $wgVersion;
-
-               // some log aggregators such as Logstash will merge the log context into the main
-               // metadata and end up overwriting the data coming from processors
-               foreach ( $this->reservedKeys as $key ) {
-                       if ( isset( $record['context'][$key] ) ) {
-                               wfLogWarning( __METHOD__ . ": '$key' key overwritten in log context." );
-                       }
-               }
-
                $record['extra'] = array_merge(
                        $record['extra'],
                        [
@@ -67,4 +47,5 @@ class WikiProcessor {
                );
                return $record;
        }
+
 }
index 6aa3831..7215696 100644 (file)
@@ -24,7 +24,7 @@ use Wikimedia\ScopedCallback;
 
 /**
  * Update object handling the cleanup of links tables after a page was deleted.
- **/
+ */
 class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate {
        /** @var WikiPage */
        protected $page;
index a5a8676..c27c89c 100644 (file)
@@ -247,8 +247,9 @@ class DifferenceEngine extends ContextSource {
                Hooks::run( 'DifferenceEngineShowDiffPage', [ $out ] );
 
                if ( !$this->loadRevisionData() ) {
-                       $this->showMissingRevision();
-
+                       if ( Hooks::run( 'DifferenceEngineShowDiffPageMaybeShowMissingRevision', [ $this ] ) ) {
+                               $this->showMissingRevision();
+                       }
                        return;
                }
 
@@ -481,7 +482,7 @@ class DifferenceEngine extends ContextSource {
         *
         * @return string HTML or empty string
         */
-       protected function markPatrolledLink() {
+       public function markPatrolledLink() {
                if ( $this->mMarkPatrolledLink === null ) {
                        $linkInfo = $this->getMarkPatrolledLinkInfo();
                        // If false, there is no patrol link needed/allowed
@@ -601,28 +602,7 @@ class DifferenceEngine extends ContextSource {
                        $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
                        $out->setArticleFlag( true );
 
-                       // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
-                       if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
-                               // This needs to be synchronised with Article::showCssOrJsPage(), which sucks
-                               // Give hooks a chance to customise the output
-                               // @todo standardize this crap into one function
-                               if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', [ $this->mNewContent, $this->mNewPage, $out ], '1.24' ) ) {
-                                       // NOTE: deprecated hook, B/C only
-                                       // use the content object's own rendering
-                                       $cnt = $this->mNewRev->getContent();
-                                       $po = $cnt ? $cnt->getParserOutput( $this->mNewRev->getTitle(), $this->mNewRev->getId() ) : null;
-                                       if ( $po ) {
-                                               $out->addParserOutputContent( $po );
-                                       }
-                               }
-                       } elseif ( !Hooks::run( 'ArticleContentViewCustom', [ $this->mNewContent, $this->mNewPage, $out ] ) ) {
-                               // Handled by extension
-                       } elseif ( !ContentHandler::runLegacyHooks(
-                               'ArticleViewCustom',
-                               [ $this->mNewContent, $this->mNewPage, $out ],
-                               '1.21'
-                       ) ) {
-                               // NOTE: deprecated hook, B/C only
+                       if ( !Hooks::run( 'ArticleContentViewCustom', [ $this->mNewContent, $this->mNewPage, $out ] ) ) {
                                // Handled by extension
                        } else {
                                // Normal page
@@ -1085,7 +1065,7 @@ class DifferenceEngine extends ContextSource {
         *
         * @return string HTML fragment
         */
-       protected function getRevisionHeader( Revision $rev, $complete = '' ) {
+       public function getRevisionHeader( Revision $rev, $complete = '' ) {
                $lang = $this->getLanguage();
                $user = $this->getUser();
                $revtimestamp = $rev->getTimestamp();
@@ -1390,6 +1370,7 @@ class DifferenceEngine extends ContextSource {
 
                if ( $this->mNewRev ) {
                        $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+                       Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
                        if ( $this->mNewContent === null ) {
                                return false;
                        }
@@ -1416,6 +1397,8 @@ class DifferenceEngine extends ContextSource {
 
                $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
 
+               Hooks::run( 'DifferenceEngineAfterLoadNewText', [ $this ] );
+
                return true;
        }
 
index b600f42..a569bcd 100644 (file)
@@ -207,14 +207,14 @@ class MWExceptionRenderer {
         */
        public static function getHTML( $e ) {
                if ( self::showBackTrace( $e ) ) {
-                       $html = "<div class=\"errorbox\"><p>" .
+                       $html = "<div class=\"errorbox mw-content-ltr\"><p>" .
                                nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
                                '</p><p>Backtrace:</p><p>' .
                                nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
                                "</p></div>\n";
                } else {
                        $logId = WebRequest::getRequestId();
-                       $html = "<div class=\"errorbox\">" .
+                       $html = "<div class=\"errorbox mw-content-ltr\">" .
                                '[' . $logId . '] ' .
                                gmdate( 'Y-m-d H:i:s' ) . ": " .
                                self::msg( "internalerror-fatal-exception",
index ab26803..5be166b 100644 (file)
@@ -38,7 +38,7 @@ class XmlDumpWriter {
         * @return string
         */
        function openStream() {
-               global $wgLanguageCode;
+               global $wgContLang;
                $ver = WikiExporter::schemaVersion();
                return Xml::element( 'mediawiki', [
                        'xmlns'              => "http://www.mediawiki.org/xml/export-$ver/",
@@ -56,7 +56,7 @@ class XmlDumpWriter {
                        'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
                                "http://www.mediawiki.org/xml/export-$ver.xsd",
                        'version'            => $ver,
-                       'xml:lang'           => $wgLanguageCode ],
+                       'xml:lang'           => $wgContLang->getHtmlCode() ],
                        null ) .
                        "\n" .
                        $this->siteInfo();
index c1d5573..9188cd9 100644 (file)
@@ -1018,7 +1018,7 @@ abstract class File implements IDBAccessObject {
                        return $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
                } else {
                        return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
+                               $params['width'], 0, wfMessage( 'thumbnail-dest-create' ) );
                }
        }
 
index e24541c..f58acbe 100644 (file)
@@ -6,7 +6,8 @@ class HTMLTagFilter extends HTMLFormField {
        protected $tagFilter;
 
        public function getTableRow( $value ) {
-               $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
+               $this->tagFilter = ChangeTags::buildTagFilterSelector(
+                       $value, false, $this->mParent->getContext() );
                if ( $this->tagFilter ) {
                        return parent::getTableRow( $value );
                }
@@ -14,7 +15,8 @@ class HTMLTagFilter extends HTMLFormField {
        }
 
        public function getDiv( $value ) {
-               $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
+               $this->tagFilter = ChangeTags::buildTagFilterSelector(
+                       $value, false, $this->mParent->getContext() );
                if ( $this->tagFilter ) {
                        return parent::getDiv( $value );
                }
index f58c3a9..f958ff6 100644 (file)
@@ -37,12 +37,17 @@ class CurlHttpRequest extends MWHttpRequest {
                return strlen( $content );
        }
 
+       /**
+        * @see MWHttpRequest::execute
+        *
+        * @throws MWException
+        * @return Status
+        */
        public function execute() {
-
-               parent::execute();
+               $this->prepare();
 
                if ( !$this->status->isOK() ) {
-                       return $this->status;
+                       return Status::wrap( $this->status ); // TODO B/C; move this to callers
                }
 
                $this->curlOptions[CURLOPT_PROXY] = $this->proxy;
@@ -102,7 +107,7 @@ class CurlHttpRequest extends MWHttpRequest {
                $curlHandle = curl_init( $this->url );
 
                if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
-                       throw new MWException( "Error setting curl options." );
+                       throw new InvalidArgumentException( "Error setting curl options." );
                }
 
                if ( $this->followRedirects && $this->canFollowRedirects() ) {
@@ -140,7 +145,7 @@ class CurlHttpRequest extends MWHttpRequest {
                $this->parseHeader();
                $this->setStatus();
 
-               return $this->status;
+               return Status::wrap( $this->status );  // TODO B/C; move this to callers
        }
 
        /**
index 43ae2d0..8255bb3 100644 (file)
@@ -51,6 +51,8 @@ class Http {
         *    - userAgent           A user agent, if you want to override the default
         *                          MediaWiki/$wgVersion
         *    - logger              A \Psr\Logger\LoggerInterface instance for debug logging
+        *    - username            Username for HTTP Basic Authentication
+        *    - password            Password for HTTP Basic Authentication
         * @param string $caller The method making this request, for profiling
         * @return string|bool (bool)false on failure or a string on success
         */
@@ -74,7 +76,7 @@ class Http {
                } else {
                        $errors = $status->getErrorsByType( 'error' );
                        $logger = LoggerFactory::getInstance( 'http' );
-                       $logger->warning( $status->getWikiText( false, false, 'en' ),
+                       $logger->warning( Status::wrap( $status )->getWikiText( false, false, 'en' ),
                                [ 'error' => $errors, 'caller' => $caller, 'content' => $req->getContent() ] );
                        return false;
                }
index 08883ae..fac052f 100644 (file)
@@ -46,9 +46,11 @@ class MWHttpRequest implements LoggerAwareInterface {
        protected $reqHeaders = [];
        protected $url;
        protected $parsedUrl;
+       /** @var callable  */
        protected $callback;
        protected $maxRedirects = 5;
        protected $followRedirects = false;
+       protected $connectTimeout;
 
        /**
         * @var CookieJar
@@ -60,7 +62,8 @@ class MWHttpRequest implements LoggerAwareInterface {
        protected $respStatus = "200 Ok";
        protected $respHeaders = [];
 
-       public $status;
+       /** @var StatusValue */
+       protected $status;
 
        /**
         * @var Profiler
@@ -98,9 +101,9 @@ class MWHttpRequest implements LoggerAwareInterface {
                }
 
                if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
-                       $this->status = Status::newFatal( 'http-invalid-url', $url );
+                       $this->status = StatusValue::newFatal( 'http-invalid-url', $url );
                } else {
-                       $this->status = Status::newGood( 100 ); // continue
+                       $this->status = StatusValue::newGood( 100 ); // continue
                }
 
                if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
@@ -116,6 +119,12 @@ class MWHttpRequest implements LoggerAwareInterface {
                if ( isset( $options['userAgent'] ) ) {
                        $this->setUserAgent( $options['userAgent'] );
                }
+               if ( isset( $options['username'] ) && isset( $options['password'] ) ) {
+                       $this->setHeader(
+                               'Authorization',
+                               'Basic ' . base64_encode( $options['username'] . ':' . $options['password'] )
+                       );
+               }
 
                $members = [ "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
                                "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" ];
@@ -161,7 +170,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         * @param string $url Url to use
         * @param array $options (optional) extra params to pass (see Http::request())
         * @param string $caller The method making this request, for profiling
-        * @throws MWException
+        * @throws DomainException
         * @return CurlHttpRequest|PhpHttpRequest
         * @see MWHttpRequest::__construct
         */
@@ -169,7 +178,7 @@ class MWHttpRequest implements LoggerAwareInterface {
                if ( !Http::$httpEngine ) {
                        Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
                } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
+                       throw new DomainException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
                                ' Http::$httpEngine is set to "curl"' );
                }
 
@@ -186,7 +195,7 @@ class MWHttpRequest implements LoggerAwareInterface {
                                return new CurlHttpRequest( $url, $options, $caller, Profiler::instance() );
                        case 'php':
                                if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
-                                       throw new MWException( __METHOD__ . ': allow_url_fopen ' .
+                                       throw new DomainException( __METHOD__ . ': allow_url_fopen ' .
                                                'needs to be enabled for pure PHP http requests to ' .
                                                'work. If possible, curl should be used instead. See ' .
                                                'http://php.net/curl.'
@@ -194,7 +203,7 @@ class MWHttpRequest implements LoggerAwareInterface {
                                }
                                return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() );
                        default:
-                               throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
+                               throw new DomainException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
                }
        }
 
@@ -222,7 +231,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         *
         * @return void
         */
-       public function proxySetup() {
+       protected function proxySetup() {
                // If there is an explicit proxy set and proxies are not disabled, then use it
                if ( $this->proxy && !$this->noProxy ) {
                        return;
@@ -300,7 +309,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         * Get an array of the headers
         * @return array
         */
-       public function getHeaderList() {
+       protected function getHeaderList() {
                $list = [];
 
                if ( $this->cookieJar ) {
@@ -333,12 +342,14 @@ class MWHttpRequest implements LoggerAwareInterface {
         * bytes are reported handled than were passed to you, the HTTP fetch
         * will be aborted.
         *
-        * @param callable $callback
-        * @throws MWException
+        * @param callable|null $callback
+        * @throws InvalidArgumentException
         */
        public function setCallback( $callback ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new MWException( 'Invalid MwHttpRequest callback' );
+               if ( is_null( $callback ) ) {
+                       $callback = [ $this, 'read' ];
+               } elseif ( !is_callable( $callback ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ': invalid callback' );
                }
                $this->callback = $callback;
        }
@@ -350,6 +361,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         * @param resource $fh
         * @param string $content
         * @return int
+        * @internal
         */
        public function read( $fh, $content ) {
                $this->content .= $content;
@@ -359,9 +371,14 @@ class MWHttpRequest implements LoggerAwareInterface {
        /**
         * Take care of whatever is necessary to perform the URI request.
         *
-        * @return Status
+        * @return StatusValue
+        * @note currently returns Status for B/C
         */
        public function execute() {
+               throw new LogicException( 'children must override this' );
+       }
+
+       protected function prepare() {
                $this->content = "";
 
                if ( strtoupper( $this->method ) == "HEAD" ) {
@@ -371,7 +388,7 @@ class MWHttpRequest implements LoggerAwareInterface {
                $this->proxySetup(); // set up any proxy as needed
 
                if ( !$this->callback ) {
-                       $this->setCallback( [ $this, 'read' ] );
+                       $this->setCallback( null );
                }
 
                if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
@@ -494,6 +511,8 @@ class MWHttpRequest implements LoggerAwareInterface {
        /**
         * Tells the MWHttpRequest object to use this pre-loaded CookieJar.
         *
+        * To read response cookies from the jar, getCookieJar must be called first.
+        *
         * @param CookieJar $jar
         */
        public function setCookieJar( $jar ) {
@@ -519,14 +538,18 @@ class MWHttpRequest implements LoggerAwareInterface {
         * Set-Cookie headers.
         * @see Cookie::set
         * @param string $name
-        * @param mixed $value
+        * @param string $value
         * @param array $attr
         */
-       public function setCookie( $name, $value = null, $attr = null ) {
+       public function setCookie( $name, $value, $attr = [] ) {
                if ( !$this->cookieJar ) {
                        $this->cookieJar = new CookieJar;
                }
 
+               if ( $this->parsedUrl && !isset( $attr['domain'] ) ) {
+                       $attr['domain'] = $this->parsedUrl['host'];
+               }
+
                $this->cookieJar->setCookie( $name, $value, $attr );
        }
 
index 2af000f..3f3803b 100644 (file)
@@ -87,15 +87,20 @@ class PhpHttpRequest extends MWHttpRequest {
         * is completely useless (something like "fopen: failed to open stream")
         * so normal methods of handling errors programmatically
         * like get_last_error() don't work.
+        * @internal
         */
        public function errorHandler( $errno, $errstr ) {
                $n = count( $this->fopenErrors ) + 1;
                $this->fopenErrors += [ "errno$n" => $errno, "errstr$n" => $errstr ];
        }
 
+       /**
+        * @see MWHttpRequest::execute
+        *
+        * @return Status
+        */
        public function execute() {
-
-               parent::execute();
+               $this->prepare();
 
                if ( is_array( $this->postData ) ) {
                        $this->postData = wfArrayToCgi( $this->postData );
@@ -227,12 +232,12 @@ class PhpHttpRequest extends MWHttpRequest {
                                        . ': error opening connection: {errstr1}', $this->fopenErrors );
                        }
                        $this->status->fatal( 'http-request-error' );
-                       return $this->status;
+                       return Status::wrap( $this->status ); // TODO B/C; move this to callers
                }
 
                if ( $result['timed_out'] ) {
                        $this->status->fatal( 'http-timed-out', $this->url );
-                       return $this->status;
+                       return Status::wrap( $this->status ); // TODO B/C; move this to callers
                }
 
                // If everything went OK, or we received some error code
@@ -253,6 +258,6 @@ class PhpHttpRequest extends MWHttpRequest {
                }
                fclose( $fh );
 
-               return $this->status;
+               return Status::wrap( $this->status ); // TODO B/C; move this to callers
        }
 }
index 6a702e9..6a8a99f 100644 (file)
@@ -32,8 +32,6 @@ require_once __DIR__ . '/../../maintenance/Maintenance.php';
  * @since 1.17
  */
 abstract class DatabaseUpdater {
-       protected static $updateCounter = 0;
-
        /**
         * Array of updates to perform on the database
         *
@@ -423,8 +421,6 @@ abstract class DatabaseUpdater {
         * @param array $what What updates to perform
         */
        public function doUpdates( $what = [ 'core', 'extensions', 'stats' ] ) {
-               global $wgVersion;
-
                $this->db->setSchemaVars( $this->getSchemaVars() );
 
                $what = array_flip( $what );
@@ -441,12 +437,9 @@ abstract class DatabaseUpdater {
                        $this->checkStats();
                }
 
-               $this->setAppliedUpdates( $wgVersion, $this->updates );
-
                if ( $this->fileHandle ) {
                        $this->skipSchema = false;
                        $this->writeSchemaUpdateFile();
-                       $this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped );
                }
        }
 
@@ -482,23 +475,6 @@ abstract class DatabaseUpdater {
                $this->updates = array_merge( $this->updates, $updatesDone );
        }
 
-       /**
-        * @param string $version
-        * @param array $updates
-        */
-       protected function setAppliedUpdates( $version, $updates = [] ) {
-               $this->db->clearFlag( DBO_DDLMODE );
-               if ( !$this->canUseNewUpdatelog() ) {
-                       return;
-               }
-               $key = "updatelist-$version-" . time() . self::$updateCounter;
-               self::$updateCounter++;
-               $this->db->insert( 'updatelog',
-                       [ 'ul_key' => $key, 'ul_value' => serialize( $updates ) ],
-                       __METHOD__ );
-               $this->db->setFlag( DBO_DDLMODE );
-       }
-
        /**
         * Helper function: check if the given key is present in the updatelog table.
         * Obviously, only use this for updates that occur after the updatelog table was
index 1175e9e..968ee15 100644 (file)
@@ -94,6 +94,9 @@ class MssqlUpdater extends DatabaseUpdater {
                                'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
                        [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
                        [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
+
+                       // 1.29
+                       [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
                ];
        }
 
index a637ce0..d95222c 100644 (file)
@@ -291,6 +291,9 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
                        [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
                        [ 'modifyField', 'recentchanges', 'rc_ip', 'patch-rc_ip_modify.sql' ],
+
+                       // 1.29
+                       [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
                ];
        }
 
index e1e0d0f..1f0e411 100644 (file)
@@ -119,6 +119,9 @@ class OracleUpdater extends DatabaseUpdater {
                        [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
                        [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
 
+                       // 1.29
+                       [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
+
                        // KEEP THIS AT THE BOTTOM!!
                        [ 'doRebuildDuplicateFunction' ],
 
index 790fbe7..1eb3f41 100644 (file)
@@ -443,6 +443,11 @@ class PostgresUpdater extends DatabaseUpdater {
                                "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('change_tag_ct_id_seq')" ],
                        [ 'addPgField', 'tag_summary', 'ts_id',
                                "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('tag_summary_ts_id_seq')" ],
+
+                       // 1.29
+                       [ 'addPgField', 'externallinks', 'el_index_60', "BYTEA NOT NULL DEFAULT ''" ],
+                       [ 'addPgIndex', 'externallinks', 'el_index_60', '( el_index_60, el_id )' ],
+                       [ 'addPgIndex', 'externallinks', 'el_from_index_60', '( el_from, el_index_60, el_id )' ],
                ];
        }
 
index 388c034..32068e6 100644 (file)
@@ -158,6 +158,9 @@ class SqliteUpdater extends DatabaseUpdater {
                                'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
                        [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
                        [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
+
+                       // 1.29
+                       [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
                ];
        }
 
index 8e1442a..a455879 100644 (file)
@@ -17,5 +17,5 @@
        "config-page-options": "Настройкі",
        "config-upload-settings": "Загрузка выяў і файлаў",
        "mainpagetext": "<strong>MediaWiki паспяхова ўсталяваная.</strong>",
-       "mainpagedocfooter": "Гл. [https://meta.wikimedia.org/wiki/Help:Contents Дапаможнік карыстальніка (англ.)] па далейшыя звесткі аб карыстанні вікі-праграмамі.\n\n== З чаго пачаць ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Пералік параметраў канфігурацыі (англ.)]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧАПЫ MediaWiki (англ.)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ліставанне аб выпусках MediaWiki (англ.)]"
+       "mainpagedocfooter": "Гл. [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Дапаможнік карыстальніка (англ.)] для атрымання інфармацыі аб карыстанні вікі-праграмамі.\n\n== З чаго пачаць ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Пералік параметраў канфігурацыі (англ.)]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧАПЫ MediaWiki (англ.)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ліставанне аб выпусках MediaWiki (англ.)]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Пераклад MediaWiki на Вашу мову]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Даведайцеся, як змагацца са спамам у Вашай вікі]"
 }
index f28e2c5..f9a8da8 100644 (file)
        "config-help": "সাহায্য",
        "config-help-tooltip": "প্রসারিত করতে ক্লিক করুন",
        "mainpagetext": "<strong>মিডিয়াউইকি ইনস্টল করা হয়েছে।</strong>",
-       "mainpagedocfooter": "কীভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।\n\n== কোথা থেকে শুরু করবেন ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings কনফিগারেশন সেটিং তালিকা]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি মুক্তির মেইলিং লিস্ট]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources আপনার ভাষার জন্য মিডিয়াউইকি স্থানীয়করণ করুন]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam আপনার উইকিতে স্প্যামের সাথে লড়াই করার উপায় সম্পর্কে জানুন]"
+       "mainpagedocfooter": "কীভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।\n\n== কোথা থেকে শুরু করবেন ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings কনফিগারেশন সেটিং তালিকা]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি মুক্তির মেইলিং লিস্ট]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources আপনার ভাষার জন্য মিডিয়াউইকি স্থানীয়করণ করুন]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam আপনার উইকিতে স্প্যামের সাথে লড়াই করার উপায় সম্পর্কে জানুন]"
 }
index 2fee258..316ed3f 100644 (file)
        "config-nofile": "Soubor „$1“ nelze nalézt. Byl smazán?",
        "config-extension-link": "Věděli jste, že vaše wiki podporuje [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions rozšíření]?\n\nMůžete si prohlédnout [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category seznam rozšíření po kategoriích].",
        "mainpagetext": "<strong>MediaWiki byla úspěšně nainstalována.</strong>",
-       "mainpagedocfooter": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Uživatelská příručka] vám napoví, jak používat MediaWiki.\n\n== Začínáme ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Nastavení konfigurace]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Často kladené otázky o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Překlad MediaWiki do vašeho jazyka]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Naučte se bojovat se spamem na vaší wiki]"
+       "mainpagedocfooter": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Uživatelská příručka] vám napoví, jak používat MediaWiki.\n\n== Začínáme ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Nastavení konfigurace]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Často kladené otázky o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Překlad MediaWiki do vašeho jazyka]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Naučte se bojovat se spamem na vaší wiki]"
 }
index b25ff2c..95d2ba3 100644 (file)
        "config-nofile": "File \"$1\" could not be found. Has it been deleted?",
        "config-extension-link": "Did you know that your wiki supports [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions]?\n\nYou can browse [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category] or the [https://www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] to see the full list of extensions.",
        "mainpagetext": "<strong>MediaWiki has been installed.</strong>",
-       "mainpagedocfooter": "Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents] for information on using the wiki software.\n\n== Getting started ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
+       "mainpagedocfooter": "Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
 }
index 46e8d90..c9d2886 100644 (file)
@@ -79,5 +79,5 @@
        "config-install-tables": "Tabelite loomine",
        "config-help": "abi",
        "mainpagetext": "<strong>MediaWiki tarkvara on paigaldatud.</strong>",
-       "mainpagedocfooter": "Vikitarkvara kasutamise kohta leiad lisateavet [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents siit].\n\n== Alustamine ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Häälestussätete loend]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki KKK]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki versiooniuuenduste postiloend]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki lokaliseerimine]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Loe, kuidas vikis rämpspostitusi tõrjuda]"
+       "mainpagedocfooter": "Vikitarkvara kasutamise kohta leiad lisateavet [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents kasutaja teatmikust].\n\n== Alustamine ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Häälestussätete loend]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki KKK]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki versiooniuuenduste postiloend]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki lokaliseerimine]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Loe, kuidas vikis rämpspostitusi tõrjuda]"
 }
index dae9f2a..d1ba15a 100644 (file)
@@ -17,8 +17,8 @@
                        "Alifakoor"
                ]
        },
-       "config-desc": "Ù\86صب Ú©Ù\86Ù\86دÙ\87Ù\94 Ù\88Û\8cÚ©Û\8câ\80\8cÙ\85دÛ\8cا",
-       "config-title": "Ù\86صب Ù\88Û\8cÚ©Û\8câ\80\8cÙ\85دÛ\8cا $1",
+       "config-desc": "Ù\86صب Ú©Ù\86Ù\86دÙ\87Ù\94 Ù\85دÛ\8cاÙ\88Û\8cÚ©Û\8c",
+       "config-title": "Ù\86صب Ù\85دÛ\8cاÙ\88Û\8cÚ©Û\8c $1",
        "config-information": "اطلاعات",
        "config-localsettings-upgrade": "یک پرونده <code>LocalSettings.php</code> شناسایی شده‌است.\nبرای ارتقاء این نصب لطفاً مقدار <code>$wgUpgradeKey</code> در جعبه زیر وارد کنید.\nشما می‌توانید آن را در <code>LocalSettings.php</code> پیدا کنید.",
        "config-localsettings-cli-upgrade": "یک پرونده <code>LocalSettings.php</code> شناسایی شده است.\nبرای ارتقاء این نصب، لطفاً <code>update.php</code> را اجرا کنید.",
        "config-page-options": "گزینه‌ها",
        "config-page-install": "نصب",
        "config-page-complete": "کامل!",
-       "config-page-restart": "راÙ\87â\80\8cاÙ\86دازÛ\8c Ø¯Ù\88بارÙ\87 نصب",
+       "config-page-restart": "شرÙ\88ع Ø¯Ù\88بارÙ\87Ù\94 نصب",
        "config-page-readme": "مرا بخوان",
        "config-page-releasenotes": "یادداشت‌های انتشار",
        "config-page-copying": "تکثیر",
        "config-page-upgradedoc": "ارتقاء",
        "config-page-existingwiki": "ویکی موجود",
        "config-help-restart": "آیا می‌خواهید همهٔ اطلاعات ذخیره شده‌ای که وارد کرده‌اید را پاک کنید و دوباره روند نصب را شروع کنید؟",
-       "config-restart": "بÙ\84Ù\87Ø\8c Ø¯Ù\88بارÙ\87 Ø±Ø§Ù\87â\80\8cاÙ\86دازÛ\8c کن",
+       "config-restart": "بÙ\84Ù\87Ø\8c Ø¯Ù\88بارÙ\87 Ø´Ø±Ù\88ع کن",
        "config-welcome": "===بررسی‌های محیطی===\nبرای فهمیدن اینکه این محیط برای نصب مدیاویکی مناسب است، اکنون بررسی‌های اساسی انجام خواهد‌شد.\nاگر به دنبال پشتیبانی در چگونگی تکمیل نصب هستید،به یاد داشته باشید این اطلاعات را بگنجانید.",
-       "config-copyright": "===حق چاپ و شرایط===\n$1\nاین برنامه، یک نرم‌افزاری آزاد است. شما می‌توانید آن را بازتوزیع کرده و/یا با شرایط نگارش ۲ یا (با نظر خودتان) هر نگارش جدیدتری از پروانه جامع همگانی گنو که توسط بنیاد نرم‌افزار آزاد منتشر شده، تغییر دهید.\n\nاین برنامه با امید این که مفید واقع‌ شود توزیع شده‌است،اما '''بدون هیچ ضمانتی'''; حتی بدون اشارهٔ ضمانتی از '''قابلیت عرضه''' یا ''' صلاحیت برای یک هدف خاص'''.\nبرای جزئیات بیش‌تر پروانه جامع همگانی گنو را مشاهده کنید.\n\nشما باید <doclink href=Copying> یک نگارش ازمجوز عمومی کلی </doclink> همراه این برنامه دریافت کرده باشید. در غیر این صورت با بنیاد نرم‌افزار آزاد، ایالات متحده امریکا، بوستون، خیابان فرانکلین، پلاک ۵۱، طبقه پنجم، صندوق پستی MA۰۲۱۱۰-۱۳۰ مکاتبه کنید، یا [http://www.gnu.org/copyleft/gpl.html در این‌جا به صورت برخط بخوانید].",
+       "config-copyright": "=== حق رونوشت و شرایط ===\n\n$1\n\nاین برنامه، نرم‌افزاری آزاد است. می‌توانید تحت شرایط نگارش ۲ یا (بنا به نظر خود) هر نگارش جدیدتری از پروانهٔ جامع همگانی گنو که توسط بنیاد نرم‌افزار آزاد منتشر شده، بازنشرش کرده و/یا تغییرش دهید.\n\n\nاین برنامه با این امید توزیع شده که مفید باشد، ولی <strong>بدون هیچ ضمانتی</strong>، حتا ضمانت ضمنی <strong>معامله‌پذیری</strong> یا <strong>تناسب برای کاربردی خاص </strong>.\n\nبرای جزئیات بیش‌تر، پروانهٔ جامع همگانی گنو را ببینید.\n\n\nباید همراه این برنامه، <doclink href=Copying>نگارشی از پروانهٔ جامع همگانی گنو</doclink> را گرفته باشید. اگر چنین نیست، با بنیاد نرم‌افزار آزاد به نشانی 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA مکاتبه کرده یا [http://www.gnu.org/copyleft/gpl.html پروانه را برخط بخوانید].",
        "config-sidebar": "* [https://www.mediawiki.org صفحهٔ اصلی مدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents راهنمای کاربر]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents راهنمای مدیر]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسش‌های رایج]\n----\n* <doclink href=Readme>مرا بخوان</doclink>\n* <doclink href=ReleaseNotes>یادداشت‌های انتشار</doclink>\n* <doclink href=Copying>نسخه برداری</doclink>\n* <doclink href=UpgradeDoc>ارتقا</doclink>",
        "config-env-good": "محیط بررسی شده‌است.\nشما می‌توانید مدیاویکی را نصب کنید.",
        "config-env-bad": "محیط بررسی شده‌است.\nشما نمی‌توانید مدیاویکی را نصب کنید.",
        "config-env-php": "پی‌اچ‌پی $1 نصب شده‌است.",
-       "config-env-hhvm": "HHVM $1 نصب شده‌است.",
-       "config-unicode-using-intl": "برای یونیکد عادی از [http://pecl.php.net/intl intl PECL extension] استفاده کنید.",
+       "config-env-hhvm": "اچ‌اچ‌وی‌ام $1 نصب شده‌است.",
+       "config-unicode-using-intl": "برای یونیکد عادی از [http://pecl.php.net/intl افزونهٔ intl برای PECL] استفاده کنید.",
        "config-unicode-pure-php-warning": "'''هشدار:''' [http://pecl.php.net/intl intl PECL extension] برای کنترل یونیکد عادی در دسترس نیست،اجرای کاملاً آهسته به تعویق می‌افتد.\n\nاگر شما یک سایت پر‌ ترافیک را اجرا می‌کنید، باید کمی [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization] را بخوانید.",
        "config-unicode-update-warning": "'''هشدار:''' نسخهٔ نصب شدهٔ پوشهٔ یونیکد عادی از ورژن قدیمی‌تر کتابخانه [http://site.icu-project.org/ the ICU project's] استفاده می‌کند.\n\nاگر کلاً علاقه‌مند به استفاده از یونیکد هستید باید [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations upgrade].",
        "config-no-db": "درایور پایگاه اطلاعاتی مناسب پیدا نشد! شما لازم دارید یک درایور پایگاه اطلاعاتی  برای پی‌اچ‌پی نصب کنید.انواع پایگاه اطلاعاتی زیر پشتیبانی شده‌اند:$1.\nاگر شما در گروه اشتراک‌گذاری هستید، از تهیه کنندهٔ گروه خود برای نصب یک درایور پایگاه اطلاعاتی مناسب {{PLURAL:$2|سوأل کنید.|سوأل کنید.}}\nاگر خود، پی‌اچ‌پی را تهیه کرده‌اید، با یک پردازشگر فعال دوباره پیکربندی کنید، برای مثال از <code>./configure --with-mysql</code> استفاده کنید.\nاگر پی‌اچ‌پی را از یک بستهٔ دبیان یا آبونتو نصب کرده‌اید، بنابراین لازم دارید بخش php5-mysql را نصب کنید.",
@@ -72,6 +72,7 @@
        "config-memory-bad": "'''هشدار:''' PHP's <code>memory_limit</code> نسخهٔ $1 است.\nاین ممکن است خیلی پایین باشد.\nممکن است نصب با مشکل رو‌به‌رو شود.",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] نصب شده‌است.",
        "config-apc": "[http://www.php.net/apc APC] نصب شده‌است.",
+       "config-apcu": "[http://www.php.net/apcu APCu] نصب شده‌است",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] نصب شده‌است.",
        "config-no-cache-apcu": "<strong>هشدار:</strong> پیوند [http://www.php.net/apcu APCu]، [http://xcache.lighttpd.net/ XCache] یا [http://www.iis.net/download/WinCacheForPhp WinCache] یافت نشد. ذخیره شی فعال نیست.",
        "config-mod-security": "'''هشدار:''' وب سرور شما [http://modsecurity.org/ mod_security] فعال است.اگر اشتباه پیکربندی شده‌‌ باشد،می تواند باعث ایجاد مشکلاتی برای مدیاویکی یا دیگر نرم‌افزاری شود که به کاربران اجازه می‌دهد پیام دلخواه ارسال کنند.\nبه [http://modsecurity.org/documentation/ mod_security documentation] مراجعه کنید یا اگر با خطاهای اتفاقی مواجه شدید با پشتیبانی میزبان خود در تماس باشید.",
        "config-type-mssql": "سرور مایکروسافت اس‌کیو‌ال",
        "config-support-info": "مدیاویکی سامانه‌های پایگاه اطلاعاتی زیر را حمایت می‌کند:\n$1\nاگر متوجه سامانه پایگاه اطلاعاتی که سعی دارید از فهرست زیر استفاده کنید، نمی‌شوید، بنابراین دستورالعمل‌های مرتبط در بالا را برای فعال کردن پشتیبانی دنبال کنید.",
        "config-dbsupport-mysql": "*[{{int:version-db-mysql-url}} MySQL] مهم‌ترین هدف برای مدیاویکی است و بهترین پشتیبانی. مدیاویکی همچنین کار می‌کند با [{{int:version-db-mariadb-url}} MariaDB] و [{{int:version-db-percona-url}} Percona Server] که با MySQL سازگار هستند.([http://www.php.net/manual/en/mysqli.installation.php چگونه php را با MySQL کامپایل کنیم])",
-       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] یک منبع آزاد پر‌طرفدار دستگاه پایگاه اطلاعاتی به عنوان یک غیرمتعارف برای مای‌اس‌کیوال است.ممکن است عیوب بارز مختصری باشد، و برای استفاده در یک محیط تولیدی توصیه نمی‌شود.([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} پستگرس‌کیوال] یک سامانه پایگاه اطلاعات متن‌باز پر‌طرفدار است که جایگزینی برای مای‌اس‌کیوال است. ([http://www.php.net/manual/en/pgsql.installation.php راهنمای تنظیم کردن پی‌اچ‌پی به همراه پستگرس‌کیوال])",
        "config-dbsupport-sqlite": "*[{{int:version-db-sqlite-url}} اس‌کیولایت] یک سامانه پایگاه اطلاعاتی کم حجمی است که بسیار خوب پشتیبانی شده‌است.\n([http://www.php.net/manual/en/pdo.installation.php چگونگی کامپایل پی‌اچ‌پی با اس‌کیولایت]، از PDO استفاده می‌کند)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] یک پایگاه اطلاعاتی کار تبلیغاتی است.\n([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])",
        "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] یک پایگاه اطلاعاتی موسسهٔ تبلیغاتی برای وینذوز است. ([http://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
        "config-cache-options": "تنظیمات برای ذخیره شی:",
        "config-cache-help": "کش شی برای بهتر شدن سرعت مدیا ویکی، از طریق کش کردن داده‌های با بیشترین استفاده انجام می‌گیرد.\nوبگاه‌های متوسط تا بزرگ به انجام این کار شدیدا توصیه می‌شوند، در عین حال وبگاه‌های کوچک نیز می‌توانند از مزایای ایم مورد بهره ببرند.",
        "config-cache-none": "بدون ذخیره (هیچ کارآمدی پاک نشده‌است، اما ممکن است سرعت در سایت‌های بزرگتر ویکی تأثیر داشته باشد)",
-       "config-cache-accel": "ذخیرهٔ موضوع پی‌اچ‌پی (ای‌پی‌سی، ایکس‌کیچ یا وین‌کیچ)",
+       "config-cache-accel": "کاشهٔ اشیای پی‌اچ‌پی (APC یا APCu یا XCache یا WinCahe)",
        "config-cache-memcached": "از ممکچد (که نیازمند تنظیمات اضافی و پیکربندی) استفاده کنید",
        "config-memcached-servers": "سرورهای دریافت حافظه:",
        "config-memcached-help": "فهرست آدرس‌های آی‌پی برای استفاده از ممکچد.\nباید هر یک خط را تعیین کند و درگاه مورد استفاده را تعیین کند.برای مثال: \n۱۲۷.۰.۰.۱:۱۱۲۱۱\n۱۹۲.۱۶۸.۱.۲۵:۱۲۳۴",
        "config-nofile": "پروندهٔ «$1» یافت نشد. آیا حذف شده‌است؟",
        "config-extension-link": "آیا می‌دانستید که ویکی شما [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions] را پشتیبانی می‌کند؟\nشما می‌توانید [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category]",
        "mainpagetext": "'''مدیاویکی با موفقیت نصب شد.'''",
-       "mainpagedocfooter": "از [https://meta.wikimedia.org/wiki/Help:Contents راهنمای کاربران]\nبرای استفاده از نرم‌افزار ویکی کمک بگیرید.\n\n== آغاز به کار ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings تنظیم پیکربندی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki پرسش‌های متداول]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست ارسال نسخه‌های مدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise مدیاویکی برای زبان شما]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam آموزش مقابله به هرزنگاری در ویکی شما]"
+       "mainpagedocfooter": "از [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents]\nبرای اطلاعات بیشتر در مورد به‌کارگیری نرم‌افزار ویکی استفاده کنید.\n\n== آغاز به کار ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings فهرست تنظیمات پیکربندی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسش‌های متداول مدیاویکی]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست ایمیلی نسخه‌های مدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources محلی‌سازی مدیاویکی به زبان شما]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam یادگیری روش‌های مقابله با هرزنگاری در ویکی]"
 }
index 82a2373..6257691 100644 (file)
        "config-nofile": "Le fichier « $1 » est introuvable. A-t-il été supprimé ?",
        "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]"
+       "mainpagedocfooter": "Consultez le [https://meta.wikimedia.org/wiki/Help:Contents/fr Guide de l’utilisateur du contenu] 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]"
 }
index 2a09893..cf4eb6d 100644 (file)
@@ -20,7 +20,8 @@
                        "Otokoume",
                        "Rxy",
                        "Foresttttttt",
-                       "ネイ"
+                       "ネイ",
+                       "Suchichi02"
                ]
        },
        "config-desc": "MediaWiki のインストーラー",
        "config-cache-options": "オブジェクトのキャッシュの設定:",
        "config-cache-help": "オブジェクトのキャッシュを使用すると、頻繁に使用するデータをキャッシュするため MediaWiki の動作速度を改善できます。\n中〜大規模サイトではこれを有効にすることを強くお勧めします。小規模サイトでも同様に効果があります。",
        "config-cache-none": "キャッシングしない(機能は取り払われます、しかもより大きなウィキサイト上でスピードの問題が発生します)",
-       "config-cache-accel": "PHP オブジェクト キャッシュ (APC、XCache、WinCache のいずれか)",
+       "config-cache-accel": "PHPオブジェクトキャッシュ (APC、APCu、XCache、WinCache のいずれか)",
        "config-cache-memcached": "memcached を使用 (追加の設定が必要)",
        "config-memcached-servers": "memcached サーバー:",
        "config-memcached-help": "Memcachedを使用するIPアドレスの一覧。\nカンマ区切りで、利用する特定のポートの指定が必要です。例:\n127.0.0.1:11211\n192.168.1.25:1234",
index ce3ae88..317e4ee 100644 (file)
        "config-install-schema": "스키마를 만드는 중",
        "config-install-pg-schema-not-exist": "PostgreSQL 스키마가 존재하지 않습니다.",
        "config-install-pg-schema-failed": "테이블을 만드는 데 실패했습니다.\n\"$2\" 스키마에 쓸 수 있는 \"$1\" 사용자가 있는지 확인하세요.",
-       "config-install-pg-commit": "ë°\94ë\80\90 사항을 적용하는 중",
+       "config-install-pg-commit": "ë³\80ê²½사항을 적용하는 중",
        "config-install-pg-plpgsql": "PL/pgSQL 언어에 대해 확인하는 중",
        "config-pg-no-plpgsql": "$1 데이터베이스에 PL/pgSQL 언어를 설치해야 합니다",
        "config-pg-no-create-privs": "설치를 위한 지정한 계정에 계정을 만드는 데 충분한 권한이 없습니다,",
-       "config-pg-not-in-role": "웹 사용자로 지정한 계정이 이미 존재합니다.\n설치를 위해 지정한 사용자는 슈퍼 사용자와 웹 사용자의 역할 구성원이 아니므로 웹 사용자가 소유한 개체를 만들 수 없습니다.\n\n현재 미디어위키는 테이블을 웹 사용자가 소유해야 합니다. 다른 웹 계정 이름을 지정하거나 \"뒤로\"를 클릭하고 적절한 권한의 설치할 사용자를 지정하세요.",
+       "config-pg-not-in-role": "웹 사용자로 지정한 계정이 이미 존재합니다.\n설치를 위해 지정한 사용자는 슈퍼 사용자와 웹 사용자의 역할 원이 아니므로 웹 사용자가 소유한 개체를 만들 수 없습니다.\n\n현재 미디어위키는 테이블을 웹 사용자가 소유해야 합니다. 다른 웹 계정 이름을 지정하거나 \"뒤로\"를 클릭하고 적절한 권한의 설치할 사용자를 지정하세요.",
        "config-install-user": "데이터베이스 사용자를 만드는 중",
        "config-install-user-alreadyexists": "\"$1\" 사용자가 이미 존재합니다",
        "config-install-user-create-failed": "\"$1\" 사용자 만드는 데 실패: $2",
index d7e86be..7b60ed0 100644 (file)
@@ -17,7 +17,8 @@
                        "Siebrand",
                        "Umherirrender",
                        "Waldir",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Liuxinyu970226"
                ]
        },
        "config-desc": "Short description of the installer.",
        "config-help": "This is used in help boxes.\n{{Identical|Help}}",
        "config-help-tooltip": "Tooltip for the 'help' links ({{msg-mw|config-help}}), to make it clear they'll expand in place rather than open a new page",
        "config-nofile": "Used as failure message. Parameters:\n* $1 - filename",
-       "config-extension-link": "Shown on last page of installation to inform about possible extensions.",
+       "config-extension-link": "Shown on last page of installation to inform about possible extensions.\n{{Identical|Did you know}}",
        "mainpagetext": "Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.",
        "mainpagedocfooter": "Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.\nThis might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example."
 }
index 5c4b1c3..6dab088 100644 (file)
@@ -21,7 +21,7 @@
        "config-page-welcome": "ذريعات‌وڪي تي ڀلي ڪري آيا!",
        "config-page-dbconnect": "اعدادخاني سان جُڙو",
        "config-page-upgrade": "هاڻوڪي تنصيبڪاريءَ کي سڌاريو",
-       "config-page-dbsettings": "اعدادخاÙ\86Ù\8a Ø¬Ù\88Ù\86 Ø³Ù\8aٽڱس",
+       "config-page-dbsettings": "اعدادخاÙ\86Ù\8a Ø¬Ù\88Ù\86 ØªØ±ØªÙ\8aبÙ\88Ù\86",
        "config-page-name": "نالو",
        "config-page-options": "آپشنس",
        "config-page-install": "تنصيبيو",
index e68a079..ecb8e87 100644 (file)
@@ -3,7 +3,8 @@
                "authors": [
                        "Korrawit",
                        "Horus",
-                       "Octahedron80"
+                       "Octahedron80",
+                       "Aefgh39622"
                ]
        },
        "config-desc": "ตัวติดตั้งสำหรับมีเดียวิกิ",
@@ -74,6 +75,7 @@
        "config-admin-password": "รหัสผ่าน:",
        "config-admin-password-confirm": "รหัสผ่านอีกครั้ง:",
        "config-admin-email": "ที่อยู่อีเมล:",
+       "config-optional-continue": "ถามคำถามฉันอีก",
        "config-license-pd": "สาธารณสมบัติ",
        "config-extensions": "ส่วนขยาย",
        "config-install-step-done": "เสร็จสิ้น",
index 3a96701..6a7172a 100644 (file)
@@ -67,5 +67,5 @@
        "config-install-step-failed": "булмады",
        "config-help": "ярдәм",
        "mainpagetext": "<strong>«MediaWiki» куелды.</strong>",
-       "mainpagedocfooter": "Бу вики турында мәгълүматны [https://meta.wikimedia.org/wiki/Help:Contents/ru биредә] табып була.\n\n== Кайбер файдалы ресурслар ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Көйләнмәләр исемлеге (рус.)];\n* [https://www.mediawiki.org/wiki/Manual:FAQ/ru MediaWiki турында еш бирелгән сораулар һәм җаваплар (рус.)];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki сәхифәсенең яңа юрамалары турында хәбәрләр яздырып алу].\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki сәхифәсен туган телегезгә тәрҗемә итү]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Үзегезнең викида ничек спам белән көрәшү турында мәгълүмат]"
+       "mainpagedocfooter": "Бу вики турында мәгълүматны [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/ru биредә] табып була.\n\n== Кайбер файдалы ресурслар ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Көйләнмәләр исемлеге (рус.)];\n* [https://www.mediawiki.org/wiki/Manual:FAQ/ru MediaWiki турында еш бирелгән сораулар һәм җаваплар (рус.)];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki сәхифәсенең яңа юрамалары турында хәбәрләр яздырып алу].\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki сәхифәсен туган телегезгә тәрҗемә итү]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Үзегезнең викида ничек спам белән көрәшү турында мәгълүмат]"
 }
index 55654bc..d2d5d3c 100644 (file)
        "config-profile-help": "如果您允许尽量多的人编写wiki,网站上的内容会更加丰富。在MediaWiki中,您可以轻松地审查最近更改,并轻易回退掉新手或破坏者造成的损害。\n\n然而,许多人觉得让MediaWiki存在多种角色将更加好用;同时,要说服所有人都愿以wiki的方式作贡献并非一件易事。因此,您可以有以下选择:\n\n<strong>{{int:config-profile-wiki}}</strong>模式允许包括未登录用户在内的所有人编辑。<strong>{{int:config-profile-no-anon}}</strong>的wiki需要额外的注册流程,这有可能会阻碍随意贡献者。\n\n<strong>{{int:config-profile-fishbowl}}</strong>方案只允许获批准的用户编辑,但对公众开放页面浏览(包括历史记录)。<strong>{{int:config-profile-private}}</strong>则只允许获批准的用户浏览、编辑页面。\n\n安装完成后,您还可以对用户权限进行更多、更复杂的配置,参见[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights 相关的使用手册]。",
        "config-license": "版权和许可证:",
        "config-license-none": "页脚无许可证",
-       "config-license-cc-by-sa": "知识共享“署名-相同方式共享”",
+       "config-license-cc-by-sa": "知识共享署名-相同方式共享",
        "config-license-cc-by": "知识共享署名",
        "config-license-cc-by-nc-sa": "知识共享署名-非商业性使用-相同方式共享",
        "config-license-cc-0": "知识共享Zero(公有领域)",
        "config-nofile": "找不到文件“$1”。它是否已被删除?",
        "config-extension-link": "您是否知道您的wiki支持[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 扩展]?\n\n您可以浏览[https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category 扩展分类]或[https://www.mediawiki.org/wiki/Extension_Matrix 扩展矩阵]以查看完整的扩展列表。",
        "mainpagetext": "<strong>已安装MediaWiki。</strong>",
-       "mainpagedocfooter": "请查阅[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents]以获取使用本wiki软件的信息!\n\n== 入门 ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki配置设置列表]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh-hans MediaWiki常见问题]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 本地化MediaWiki到您的语言]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 了解如何在您的wiki上打击破坏]"
+       "mainpagedocfooter": "请查阅[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 用户指导]以获取使用本wiki软件的信息!\n\n== 入门 ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki配置设置列表]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh-hans MediaWiki常见问题]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 本地化MediaWiki到您的语言]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 了解如何在您的wiki上打击破坏]"
 }
index 2f2e934..b977af5 100644 (file)
        "config-profile-help": "Wiki 最佳的運作方式是盡可能讓大家都可以編輯文件。\n在 MediaWiki,可以很輕易的審查最近做的所有變更動作,並且可以還原由新手或惡意使用者造成的損害。\n\n不論如何,很多人發現 MediaWiki 可以廣泛的運用在各種地方,但並不是很容易可以說服每個人都遵守對 Wiki 有益的方式。\n所以您必須做出以下選擇。\n\n使用 <strong>{{int:config-profile-wiki}}</strong> 模式,允許所有人編輯文章,包含未匿名使用者。\n使用 <strong>{{int:config-profile-no-anon}}</strong> 模式,允許所有人編輯文章,不包含未登入的使用者。此模式較能管理所有使用者的言論,但會扼殺臨時使用者的貢獻機會。\n\n使用 <strong>{{int:config-profile-fishbowl}}</strong> 模式,僅經核准的使用者可以編輯,所有人可以檢視頁面,包含修訂的記錄。\n使用 <strong>{{int:config-profile-private}}</strong> 模式,僅經核准的使用者可以編輯、檢視頁面。\n\n有關更多複雜的使用者權限設定可在安裝程序結束後設定,請參考 [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights 相關文件說明]。",
        "config-license": "版權聲明與授權條款:",
        "config-license-none": "無授權條款頁腳",
-       "config-license-cc-by-sa": "創作共用 姓名標示-相同方式分享",
-       "config-license-cc-by": "創作共用 Attribution",
+       "config-license-cc-by-sa": "創用 CC 姓名標示-相同方式分享",
+       "config-license-cc-by": "創用 CC 姓名標示",
        "config-license-cc-by-nc-sa": "創作共用 Attribution-NonCommercial-ShareAlike",
        "config-license-cc-0": "創作共用 Zero (公有領域)",
        "config-license-gfdl": "GNU 自由文件授權條款 1.3 或更高版本",
index 910a7ca..8f5700a 100644 (file)
  * @ingroup HTTP
  */
 
+/**
+ * Cookie jar to use with MWHttpRequest. Does not handle cookie unsetting.
+ */
 class CookieJar {
+       /** @var Cookie[] */
        private $cookie = [];
 
        /**
index 4ba1bc3..1c141ab 100644 (file)
 class HtmlArmor {
 
        /**
-        * @var string
+        * @var string|null
         */
        private $value;
 
        /**
-        * @param string $value
+        * @param string|null $value
         */
        public function __construct( $value ) {
                $this->value = $value;
@@ -44,7 +44,8 @@ class HtmlArmor {
         * and get safe HTML back
         *
         * @param string|HtmlArmor $input
-        * @return string safe for usage in HTML
+        * @return string|null safe for usage in HTML, or null
+        *         if the HtmlArmor instance was wrapping null.
         */
        public static function getHtml( $input ) {
                if ( $input instanceof HtmlArmor ) {
index 16163fb..4a6e3fb 100644 (file)
@@ -253,7 +253,6 @@ class IEUrlExtension {
         *
         * @param $serverSoftware
         * @return bool
-        *
         */
        public static function haveUndecodedRequestUri( $serverSoftware ) {
                static $whitelist = [
index 2f5a454..db6869b 100644 (file)
@@ -58,7 +58,7 @@ class MapCacheLRU {
         * @return void
         */
        public function set( $key, $value ) {
-               if ( array_key_exists( $key, $this->cache ) ) {
+               if ( $this->has( $key ) ) {
                        $this->ping( $key );
                } elseif ( count( $this->cache ) >= $this->maxCacheKeys ) {
                        reset( $this->cache );
@@ -75,6 +75,9 @@ class MapCacheLRU {
         * @return bool
         */
        public function has( $key ) {
+               if ( !is_int( $key ) && !is_string( $key ) ) {
+                       throw new MWException( __METHOD__ . ' called with invalid key. Must be string or integer.' );
+               }
                return array_key_exists( $key, $this->cache );
        }
 
@@ -87,7 +90,7 @@ class MapCacheLRU {
         * @return mixed Returns null if the key was not found
         */
        public function get( $key ) {
-               if ( !array_key_exists( $key, $this->cache ) ) {
+               if ( !$this->has( $key ) ) {
                        return null;
                }
 
index 9c1ec8e..016c9b1 100644 (file)
  * <https://github.com/phacility/xhprof>. XHProf can be installed as a PECL
  * package for use with PHP5 (Zend PHP) and is built-in to HHVM 3.3.0.
  *
+ * This also supports using the Tideways profiler
+ * <https://github.com/tideways/php-profiler-extension>, which additionally
+ * has support for PHP7.
+ *
  * @since 1.28
  */
 class Xhprof {
@@ -43,10 +47,16 @@ class Xhprof {
         */
        public static function enable( $flags = 0, $options = [] ) {
                if ( self::isEnabled() ) {
-                       throw new Exception( 'Xhprof profiling is already enabled.' );
+                       throw new Exception( 'Profiling is already enabled.' );
                }
                self::$enabled = true;
-               xhprof_enable( $flags, $options );
+               if ( function_exists( 'xhprof_enable' ) ) {
+                       xhprof_enable( $flags, $options );
+               } elseif ( function_exists( 'tideways_enable' ) ) {
+                       tideways_enable( $flags, $options );
+               } else {
+                       throw new Exception( "Neither xhprof nor tideways are installed" );
+               }
        }
 
        /**
@@ -57,7 +67,12 @@ class Xhprof {
        public static function disable() {
                if ( self::isEnabled() ) {
                        self::$enabled = false;
-                       return xhprof_disable();
+                       if ( function_exists( 'xhprof_disable' ) ) {
+                               return xhprof_disable();
+                       } else {
+                               // tideways
+                               return tideways_disable();
+                       }
                }
        }
 }
index bee34dc..c629e7d 100644 (file)
@@ -34,7 +34,7 @@ use Wikimedia\WaitConditionLoop;
  *
  * Locks on resource keys can either be shared or exclusive.
  *
- * Implementations must keep track of what is locked by this proccess
+ * Implementations must keep track of what is locked by this process
  * in-memory and support nested locking calls (using reference counting).
  * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
  * Locks should either be non-blocking or have low wait timeouts.
@@ -170,7 +170,7 @@ abstract class LockManager {
        /**
         * Get the base 36 SHA-1 of a string, padded to 31 digits.
         * Before hashing, the path will be prefixed with the domain ID.
-        * This should be used interally for lock key or file names.
+        * This should be used internally for lock key or file names.
         *
         * @param string $path
         * @return string
@@ -182,7 +182,7 @@ abstract class LockManager {
        /**
         * Get the base 16 SHA-1 of a string, padded to 31 digits.
         * Before hashing, the path will be prefixed with the domain ID.
-        * This should be used interally for lock key or file names.
+        * This should be used internally for lock key or file names.
         *
         * @param string $path
         * @return string
index 3958f8c..7f2bf5e 100644 (file)
@@ -291,8 +291,6 @@ class XmlTypeCheck {
                $this->stackDepth++;
        }
 
-       /**
-        */
        private function elementClose() {
                list( $name, $attribs ) = array_pop( $this->elementDataContext );
                $data = array_pop( $this->elementData );
index 02b3c92..6e6a3ad 100644 (file)
@@ -72,7 +72,7 @@ class APCUBagOStuff extends APCBagOStuff {
                if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
                        return apcu_inc( $key . self::KEY_SUFFIX, $value );
                } else {
-                       return apcu_set( $key . self::KEY_SUFFIX, $value );
+                       return false;
                }
        }
 
@@ -85,7 +85,7 @@ class APCUBagOStuff extends APCBagOStuff {
                if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
                        return apcu_dec( $key . self::KEY_SUFFIX, $value );
                } else {
-                       return apcu_set( $key . self::KEY_SUFFIX, -$value );
+                       return false;
                }
        }
 }
diff --git a/includes/libs/rdbms/connectionmanager/ConnectionManager.php b/includes/libs/rdbms/connectionmanager/ConnectionManager.php
new file mode 100644 (file)
index 0000000..4f72f77
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+
+namespace Wikimedia\Rdbms;
+
+use Database;
+use DBConnRef;
+use IDatabase;
+use InvalidArgumentException;
+use LoadBalancer;
+
+/**
+ * Database connection manager.
+ *
+ * This manages access to master and replica databases.
+ *
+ * @since 1.29
+ *
+ * @license GPL-2.0+
+ * @author Addshore
+ */
+class ConnectionManager {
+
+       /**
+        * @var LoadBalancer
+        */
+       private $loadBalancer;
+
+       /**
+        * The symbolic name of the target database, or false for the local wiki's database.
+        *
+        * @var string|false
+        */
+       private $domain;
+
+       /**
+        * @var string[]
+        */
+       private $groups = [];
+
+       /**
+        * @param LoadBalancer $loadBalancer
+        * @param string|bool $domain Optional logical DB name, defaults to current wiki.
+        *        This follows the convention for database names used by $loadBalancer.
+        * @param string[] $groups see LoadBalancer::getConnection
+        *
+        * @throws InvalidArgumentException
+        */
+       public function __construct( LoadBalancer $loadBalancer, $domain = false, array $groups = [] ) {
+               if ( !is_string( $domain ) && $domain !== false ) {
+                       throw new InvalidArgumentException( '$dbName must be a string, or false.' );
+               }
+
+               $this->loadBalancer = $loadBalancer;
+               $this->domain = $domain;
+               $this->groups = $groups;
+       }
+
+       /**
+        * @param int $i
+        * @param string[]|null $groups
+        *
+        * @return Database
+        */
+       private function getConnection( $i, array $groups = null ) {
+               $groups = $groups === null ? $this->groups : $groups;
+               return $this->loadBalancer->getConnection( $i, $groups, $this->domain );
+       }
+
+       /**
+        * @param int $i
+        * @param string[]|null $groups
+        *
+        * @return DBConnRef
+        */
+       private function getConnectionRef( $i, array $groups = null ) {
+               $groups = $groups === null ? $this->groups : $groups;
+               return $this->loadBalancer->getConnectionRef( $i, $groups, $this->domain );
+       }
+
+       /**
+        * Returns a connection to the master DB, for updating. The connection should later be released
+        * by calling releaseConnection().
+        *
+        * @since 1.29
+        *
+        * @return Database
+        */
+       public function getWriteConnection() {
+               return $this->getConnection( DB_MASTER );
+       }
+
+       /**
+        * Returns a database connection for reading. The connection should later be released by
+        * calling releaseConnection().
+        *
+        * @since 1.29
+        *
+        * @param string[]|null $groups
+        *
+        * @return Database
+        */
+       public function getReadConnection( array $groups = null ) {
+               $groups = $groups === null ? $this->groups : $groups;
+               return $this->getConnection( DB_REPLICA, $groups );
+       }
+
+       /**
+        * @since 1.29
+        *
+        * @param IDatabase $db
+        */
+       public function releaseConnection( IDatabase $db ) {
+               $this->loadBalancer->reuseConnection( $db );
+       }
+
+       /**
+        * Returns a connection ref to the master DB, for updating.
+        *
+        * @since 1.29
+        *
+        * @return DBConnRef
+        */
+       public function getWriteConnectionRef() {
+               return $this->getConnectionRef( DB_MASTER );
+       }
+
+       /**
+        * Returns a database connection ref for reading.
+        *
+        * @since 1.29
+        *
+        * @param string[]|null $groups
+        *
+        * @return DBConnRef
+        */
+       public function getReadConnectionRef( array $groups = null ) {
+               $groups = $groups === null ? $this->groups : $groups;
+               return $this->getConnectionRef( DB_REPLICA, $groups );
+       }
+
+}
diff --git a/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php b/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php
new file mode 100644 (file)
index 0000000..fb03182
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+
+namespace Wikimedia\Rdbms;
+
+use Database;
+use DBConnRef;
+
+/**
+ * Database connection manager.
+ *
+ * This manages access to master and replica databases. It also manages state that indicates whether
+ * the replica databases are possibly outdated after a write operation, and thus the master database
+ * should be used for subsequent read operations.
+ *
+ * @note: Services that access overlapping sets of database tables, or interact with logically
+ * related sets of data in the database, should share a SessionConsistentConnectionManager.
+ * Services accessing unrelated sets of information may prefer to not share a
+ * SessionConsistentConnectionManager, so they can still perform read operations against replica
+ * databases after a (unrelated, per the assumption) write operation to the master database.
+ * Generally, sharing a SessionConsistentConnectionManager improves consistency (by avoiding race
+ * conditions due to replication lag), but can reduce performance (by directing more read
+ * operations to the master database server).
+ *
+ * @since 1.29
+ *
+ * @license GPL-2.0+
+ * @author Daniel Kinzler
+ * @author Addshore
+ */
+class SessionConsistentConnectionManager extends ConnectionManager {
+
+       /**
+        * @var bool
+        */
+       private $forceWriteConnection = false;
+
+       /**
+        * Forces all future calls to getReadConnection() to return a write connection.
+        * Use this before performing read operations that are critical for a future update.
+        *
+        * @since 1.29
+        */
+       public function prepareForUpdates() {
+               $this->forceWriteConnection = true;
+       }
+
+       /**
+        * @since 1.29
+        *
+        * @param string[]|null $groups
+        *
+        * @return Database
+        */
+       public function getReadConnection( array $groups = null ) {
+               if ( $this->forceWriteConnection ) {
+                       return parent::getWriteConnection();
+               }
+
+               return parent::getReadConnection( $groups );
+       }
+
+       /**
+        * @since 1.29
+        *
+        * @return Database
+        */
+       public function getWriteConnection() {
+               $this->prepareForUpdates();
+               return parent::getWriteConnection();
+       }
+
+       /**
+        * @since 1.29
+        *
+        * @param string[]|null $groups
+        *
+        * @return DBConnRef
+        */
+       public function getReadConnectionRef( array $groups = null ) {
+               if ( $this->forceWriteConnection ) {
+                       return parent::getWriteConnectionRef();
+               }
+
+               return parent::getReadConnectionRef( $groups );
+       }
+
+       /**
+        * @since 1.29
+        *
+        * @return DBConnRef
+        */
+       public function getWriteConnectionRef() {
+               $this->prepareForUpdates();
+               return parent::getWriteConnectionRef();
+       }
+
+}
index 7317d54..a06aad2 100644 (file)
@@ -41,7 +41,7 @@ class DatabaseSqlite extends Database {
        /** @var resource */
        protected $mLastResult;
 
-       /** @var $mConn PDO */
+       /** @var PDO */
        protected $mConn;
 
        /** @var FSLockManager (hopefully on the same server as the DB) */
index 48d76c4..c6055db 100644 (file)
@@ -1183,7 +1183,7 @@ interface IDatabase {
        /**
         * DELETE query wrapper.
         *
-        * @param array $table Table name
+        * @param string $table Table name
         * @param string|array $conds Array of conditions. See $conds in IDatabase::select()
         *   for the format. Use $conds == "*" to delete all rows
         * @param string $fname Name of the calling function
index d42fed9..634993a 100644 (file)
@@ -31,7 +31,7 @@ use Wikimedia\ScopedCallback;
 class LoadBalancer implements ILoadBalancer {
        /** @var array[] Map of (server index => server config array) */
        private $mServers;
-       /** @var array[] Map of (local/foreignUsed/foreignFree => server index => IDatabase array) */
+       /** @var IDatabase[][] Map of (local/foreignUsed/foreignFree => server index => IDatabase array) */
        private $mConns;
        /** @var float[] Map of (server index => weight) */
        private $mLoads;
@@ -390,6 +390,9 @@ class LoadBalancer implements ILoadBalancer {
                return $i;
        }
 
+       /**
+        * @param DBMasterPos|false $pos
+        */
        public function waitFor( $pos ) {
                $this->mWaitForPos = $pos;
                $i = $this->mReadIndex;
@@ -436,6 +439,10 @@ class LoadBalancer implements ILoadBalancer {
                return $ok;
        }
 
+       /**
+        * @param int $i
+        * @return IDatabase
+        */
        public function getAnyOpenConnection( $i ) {
                foreach ( $this->mConns as $connsByServer ) {
                        if ( !empty( $connsByServer[$i] ) ) {
@@ -1447,6 +1454,11 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
+       /**
+        * @param IDatabase $conn
+        * @param DBMasterPos|false $pos
+        * @param int $timeout
+        */
        public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
                if ( $this->getServerCount() <= 1 || !$conn->getLBInfo( 'replica' ) ) {
                        return true; // server is not a replica DB
index 45b1d83..e374794 100644 (file)
@@ -55,8 +55,6 @@ class LoadMonitorMySQL extends LoadMonitor {
                                // https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html
                                if ( $s->Innodb_buffer_pool_pages_total > 0 ) {
                                        $ratio = $s->Innodb_buffer_pool_pages_data / $s->Innodb_buffer_pool_pages_total;
-                               } elseif ( $s->Qcache_total_blocks > 0 ) {
-                                       $ratio = 1.0 - $s->Qcache_free_blocks / $s->Qcache_total_blocks;
                                } else {
                                        $ratio = 1.0;
                                }
index 3b272e2..fee792f 100644 (file)
@@ -15,7 +15,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         *
         * @param string|array $key The metric(s) to set.
         * @param float $time The elapsed time (ms) to log
-        **/
+        */
        public function timing( $key, $time ) {
        }
 
@@ -24,7 +24,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         *
         * @param string|array $key The metric(s) to set.
         * @param float $value The value for the stats.
-        **/
+        */
        public function gauge( $key, $value ) {
        }
 
@@ -44,7 +44,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         * @param  float $value The value for the stats.
         *
         * @return array
-        **/
+        */
        public function set( $key, $value ) {
                return [];
        }
@@ -56,7 +56,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         * @param float|1      $sampleRate The rate (0-1) for sampling.
         *
         * @return array
-        **/
+        */
        public function increment( $key ) {
                return [];
        }
@@ -69,7 +69,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         * @param float|1      $sampleRate The rate (0-1) for sampling.
         *
         * @return mixed
-        **/
+        */
        public function decrement( $key ) {
                return [];
        }
@@ -81,7 +81,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         * @param integer $delta The delta to add to the each metric
         *
         * @return mixed
-        **/
+        */
        public function updateCount( $key, $delta ) {
                return [];
        }
@@ -95,7 +95,7 @@ class NullStatsdDataFactory implements StatsdDataFactoryInterface {
         *                      ("c" for count, "ms" for timing, "g" for gauge, "s" for set)
         *
         * @return StatsdDataInterface
-        **/
+        */
        public function produceStatsdData(
                $key,
                $value = 1,
index 29bbf40..a657a33 100644 (file)
@@ -49,7 +49,6 @@ use Wikimedia\ScopedCallback;
  * Note XMP kind of looks like rdf. They are not the same thing - XMP is
  * encoded as a specific subset of rdf. This class can read XMP. It cannot
  * read rdf.
- *
  */
 class XMPReader implements LoggerAwareInterface {
        /** @var array XMP item configuration array */
@@ -492,7 +491,7 @@ class XMPReader implements LoggerAwareInterface {
         * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
         * and are processing the 0/10 bit.
         *
-        * @param XMLParser $parser XMLParser reference to the xml parser
+        * @param resource $parser XMLParser reference to the xml parser
         * @param string $data Character data
         * @throws RuntimeException On invalid data
         */
@@ -777,7 +776,7 @@ class XMPReader implements LoggerAwareInterface {
         * Ignores the outer wrapping elements that are optional in
         * xmp and have no meaning.
         *
-        * @param XMLParser $parser
+        * @param resource $parser
         * @param string $elm Namespace . ' ' . element name
         * @throws RuntimeException
         */
@@ -980,7 +979,6 @@ class XMPReader implements LoggerAwareInterface {
         * Called when processing the <rdf:value> or <foo:someQualifier>.
         *
         * @param string $elm Namespace and tag name separated by a space.
-        *
         */
        private function startElementModeQDesc( $elm ) {
                if ( $elm === self::NS_RDF . ' value' ) {
@@ -1190,7 +1188,7 @@ class XMPReader implements LoggerAwareInterface {
         * Generally just calls a helper based on what MODE we're in.
         * Also does some initial set up for the wrapper element
         *
-        * @param XMLParser $parser
+        * @param resource $parser
         * @param string $elm Namespace "<space>" element
         * @param array $attribs Attribute name => value
         * @throws RuntimeException
index 21e40ec..c390232 100644 (file)
@@ -22,6 +22,8 @@
  * @since 1.25
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * This class formats block log entries.
  *
@@ -91,6 +93,7 @@ class BlockLogFormatter extends LogFormatter {
 
        public function getActionLinks() {
                $subtype = $this->entry->getSubtype();
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
                        || !( $subtype === 'block' || $subtype === 'reblock' )
                        || !$this->context->getUser()->isAllowed( 'block' )
@@ -101,13 +104,13 @@ class BlockLogFormatter extends LogFormatter {
                // Show unblock/change block link
                $title = $this->entry->getTarget();
                $links = [
-                       Linker::linkKnown(
+                       $linkRenderer->makeKnownLink(
                                SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ),
-                               $this->msg( 'unblocklink' )->escaped()
+                               $this->msg( 'unblocklink' )->text()
                        ),
-                       Linker::linkKnown(
+                       $linkRenderer->makeKnownLink(
                                SpecialPage::getTitleFor( 'Block', $title->getDBkey() ),
-                               $this->msg( 'change-blocklink' )->escaped()
+                               $this->msg( 'change-blocklink' )->text()
                        )
                ];
 
index f130740..861ea30 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+
 class ContentModelLogFormatter extends LogFormatter {
        protected function getMessageParameters() {
                $lang = $this->context->getLanguage();
@@ -18,9 +20,9 @@ class ContentModelLogFormatter extends LogFormatter {
                }
 
                $params = $this->extractParameters();
-               $revert = Linker::linkKnown(
+               $revert = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                        SpecialPage::getTitleFor( 'ChangeContentModel' ),
-                       $this->msg( 'logentry-contentmodel-change-revertlink' )->escaped(),
+                       $this->msg( 'logentry-contentmodel-change-revertlink' )->text(),
                        [],
                        [
                                'pagetitle' => $this->entry->getTarget()->getPrefixedText(),
index dc9378f..05973df 100644 (file)
@@ -23,6 +23,8 @@
  * @since 1.22
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * This class formats delete log entries.
  *
@@ -114,6 +116,7 @@ class DeleteLogFormatter extends LogFormatter {
 
        public function getActionLinks() {
                $user = $this->context->getUser();
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                if ( !$user->isAllowed( 'deletedhistory' )
                        || $this->entry->isDeleted( LogPage::DELETED_ACTION )
                ) {
@@ -128,9 +131,9 @@ class DeleteLogFormatter extends LogFormatter {
                                } else {
                                        $message = 'undeleteviewlink';
                                }
-                               $revert = Linker::linkKnown(
+                               $revert = $linkRenderer->makeKnownLink(
                                        SpecialPage::getTitleFor( 'Undelete' ),
-                                       $this->msg( $message )->escaped(),
+                                       $this->msg( $message )->text(),
                                        [],
                                        [ 'target' => $this->entry->getTarget()->getPrefixedDBkey() ]
                                );
@@ -156,9 +159,9 @@ class DeleteLogFormatter extends LogFormatter {
                                if ( count( $ids ) == 1 ) {
                                        // Live revision diffs...
                                        if ( $key == 'oldid' || $key == 'revision' ) {
-                                               $links[] = Linker::linkKnown(
+                                               $links[] = $linkRenderer->makeKnownLink(
                                                        $this->entry->getTarget(),
-                                                       $this->msg( 'diff' )->escaped(),
+                                                       $this->msg( 'diff' )->text(),
                                                        [],
                                                        [
                                                                'diff' => intval( $ids[0] ),
@@ -167,9 +170,9 @@ class DeleteLogFormatter extends LogFormatter {
                                                );
                                                // Deleted revision diffs...
                                        } elseif ( $key == 'artimestamp' || $key == 'archive' ) {
-                                               $links[] = Linker::linkKnown(
+                                               $links[] = $linkRenderer->makeKnownLink(
                                                        SpecialPage::getTitleFor( 'Undelete' ),
-                                                       $this->msg( 'diff' )->escaped(),
+                                                       $this->msg( 'diff' )->text(),
                                                        [],
                                                        [
                                                                'target' => $this->entry->getTarget()->getPrefixedDBkey(),
@@ -181,9 +184,9 @@ class DeleteLogFormatter extends LogFormatter {
                                }
 
                                // View/modify link...
-                               $links[] = Linker::linkKnown(
+                               $links[] = $linkRenderer->makeKnownLink(
                                        SpecialPage::getTitleFor( 'Revisiondelete' ),
-                                       $this->msg( 'revdel-restore' )->escaped(),
+                                       $this->msg( 'revdel-restore' )->text(),
                                        [],
                                        [
                                                'target' => $this->entry->getTarget()->getPrefixedText(),
@@ -206,9 +209,9 @@ class DeleteLogFormatter extends LogFormatter {
                                        $query = implode( ',', $query );
                                }
                                // Link to each hidden object ID, $params[1] is the url param
-                               $revert = Linker::linkKnown(
+                               $revert = $linkRenderer->makeKnownLink(
                                        SpecialPage::getTitleFor( 'Revisiondelete' ),
-                                       $this->msg( 'revdel-restore' )->escaped(),
+                                       $this->msg( 'revdel-restore' )->text(),
                                        [],
                                        [
                                                'target' => $this->entry->getTarget()->getPrefixedText(),
index 21864ee..c9f1345 100644 (file)
@@ -428,7 +428,7 @@ class ManualLogEntry extends LogEntryBase {
        /** @var int ID of the log entry */
        protected $id;
 
-       /** @var Can this log entry be patrolled? */
+       /** @var bool Can this log entry be patrolled? */
        protected $isPatrollable = false;
 
        /** @var bool Whether this is a legacy log entry */
index 0cf584b..6665336 100644 (file)
@@ -23,6 +23,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 class LogEventsList extends ContextSource {
        const NO_ACTION_LINK = 1;
        const NO_EXTRA_USER_LINKS = 2;
@@ -91,7 +93,7 @@ class LogEventsList extends ContextSource {
                // For B/C, we take strings, but make sure they are converted...
                $types = ( $types === '' ) ? [] : (array)$types;
 
-               $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
+               $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter, false, $this->getContext() );
 
                $html = Html::hidden( 'title', $title->getPrefixedDBkey() );
 
@@ -142,10 +144,11 @@ class LogEventsList extends ContextSource {
         */
        private function getFilterLinks( $filter ) {
                // show/hide links
-               $messages = [ $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ];
+               $messages = [ $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ];
                // Option value -> message mapping
                $links = [];
                $hiddens = ''; // keep track for "go" button
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                foreach ( $filter as $type => $val ) {
                        // Should the below assignment be outside the foreach?
                        // Then it would have to be copied. Not certain what is more expensive.
@@ -155,7 +158,7 @@ class LogEventsList extends ContextSource {
                        $hideVal = 1 - intval( $val );
                        $query[$queryKey] = $hideVal;
 
-                       $link = Linker::linkKnown(
+                       $link = $linkRenderer->makeKnownLink(
                                $this->getTitle(),
                                $messages[$hideVal],
                                [],
@@ -672,9 +675,9 @@ class LogEventsList extends ContextSource {
                                $urlParam = array_merge( $urlParam, $extraUrlParams );
                        }
 
-                       $s .= Linker::linkKnown(
+                       $s .= MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
                                SpecialPage::getTitleFor( 'Log' ),
-                               $context->msg( 'log-fulllog' )->escaped(),
+                               $context->msg( 'log-fulllog' )->text(),
                                [],
                                $urlParam
                        );
index daaff07..64102b7 100644 (file)
@@ -27,7 +27,6 @@
  * Class to simplify the use of log pages.
  * The logs are now kept in a table which is easier to manage and trim
  * than ever-growing wiki pages.
- *
  */
 class LogPage {
        const DELETED_ACTION = 1;
index c86eabd..ac0564d 100644 (file)
@@ -541,7 +541,7 @@ class BitmapHandler extends TransformationalImageHandler {
         * @param array $params Rotate parameters.
         *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
         * @since 1.21
-        * @return bool
+        * @return bool|MediaTransformError
         */
        public function rotate( $file, $params ) {
                global $wgImageMagickConvertCommand;
index 18f75ec..a852215 100644 (file)
@@ -170,7 +170,7 @@ class DjVuHandler extends ImageHandler {
                                'thumbnail_error',
                                $width,
                                $height,
-                               wfMessage( 'thumbnail_dest_directory' )->text()
+                               wfMessage( 'thumbnail_dest_directory' )
                        );
                }
 
@@ -197,7 +197,7 @@ class DjVuHandler extends ImageHandler {
 
                        return new MediaTransformError( 'thumbnail_error',
                                $params['width'], $params['height'],
-                               wfMessage( 'filemissing' )->text()
+                               wfMessage( 'filemissing' )
                        );
                }
 
index b8b6f6c..6c857a8 100644 (file)
@@ -130,7 +130,7 @@ class JpegHandler extends ExifBitmapHandler {
         * @param array $params Rotate parameters.
         *    'rotation' clockwise rotation in degrees, allowed are multiples of 90
         * @since 1.21
-        * @return bool
+        * @return bool|MediaTransformError
         */
        public function rotate( $file, $params ) {
                global $wgJpegTran;
index b3a555a..5366c4f 100644 (file)
@@ -439,19 +439,12 @@ class ThumbnailImage extends MediaTransformOutput {
  * @ingroup Media
  */
 class MediaTransformError extends MediaTransformOutput {
-       /** @var string HTML formatted version of the error */
-       private $htmlMsg;
-
-       /** @var string Plain text formatted version of the error */
-       private $textMsg;
+       /** @var Message */
+       private $msg;
 
        function __construct( $msg, $width, $height /*, ... */ ) {
                $args = array_slice( func_get_args(), 3 );
-               $htmlArgs = array_map( 'htmlspecialchars', $args );
-               $htmlArgs = array_map( 'nl2br', $htmlArgs );
-
-               $this->htmlMsg = wfMessage( $msg )->rawParams( $htmlArgs )->escaped();
-               $this->textMsg = wfMessage( $msg )->rawParams( $htmlArgs )->text();
+               $this->msg = wfMessage( $msg )->params( $args );
                $this->width = intval( $width );
                $this->height = intval( $height );
                $this->url = false;
@@ -461,21 +454,29 @@ class MediaTransformError extends MediaTransformOutput {
        function toHtml( $options = [] ) {
                return "<div class=\"MediaTransformError\" style=\"" .
                        "width: {$this->width}px; height: {$this->height}px; display:inline-block;\">" .
-                       $this->htmlMsg .
+                       $this->getHtmlMsg() .
                        "</div>";
        }
 
        function toText() {
-               return $this->textMsg;
+               return $this->msg->text();
        }
 
        function getHtmlMsg() {
-               return $this->htmlMsg;
+               return $this->msg->escaped();
+       }
+
+       function getMsg() {
+               return $this->msg;
        }
 
        function isError() {
                return true;
        }
+
+       function getHttpStatusCode() {
+               return 500;
+       }
 }
 
 /**
@@ -488,7 +489,12 @@ class TransformParameterError extends MediaTransformError {
                parent::__construct( 'thumbnail_error',
                        max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
                        max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
-                       wfMessage( 'thumbnail_invalid_params' )->text() );
+                       wfMessage( 'thumbnail_invalid_params' )
+               );
+       }
+
+       function getHttpStatusCode() {
+               return 400;
        }
 }
 
@@ -501,14 +507,18 @@ class TransformParameterError extends MediaTransformError {
 class TransformTooBigImageAreaError extends MediaTransformError {
        function __construct( $params, $maxImageArea ) {
                $msg = wfMessage( 'thumbnail_toobigimagearea' );
+               $msg->rawParams(
+                       $msg->getLanguage()->formatComputingNumbers( $maxImageArea, 1000, "size-$1pixel" )
+               );
 
                parent::__construct( 'thumbnail_error',
                        max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
                        max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
-                       $msg->rawParams(
-                               $msg->getLanguage()->formatComputingNumbers(
-                                       $maxImageArea, 1000, "size-$1pixel" )
-                               )->text()
-                       );
+                       $msg
+               );
+       }
+
+       function getHttpStatusCode() {
+               return 400;
        }
 }
index f3b33ac..0cea6d8 100644 (file)
@@ -178,14 +178,14 @@ class SvgHandler extends ImageHandler {
 
                $metadata = $this->unpackMetadata( $image->getMetadata() );
                if ( isset( $metadata['error'] ) ) { // sanity check
-                       $err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+                       $err = wfMessage( 'svg-long-error', $metadata['error']['message'] );
 
                        return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
                }
 
                if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
                        return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
-                               wfMessage( 'thumbnail_dest_directory' )->text() );
+                               wfMessage( 'thumbnail_dest_directory' ) );
                }
 
                $srcPath = $image->getLocalRefPath();
@@ -196,7 +196,7 @@ class SvgHandler extends ImageHandler {
 
                        return new MediaTransformError( 'thumbnail_error',
                                $params['width'], $params['height'],
-                               wfMessage( 'filemissing' )->text()
+                               wfMessage( 'filemissing' )
                        );
                }
 
@@ -219,7 +219,7 @@ class SvgHandler extends ImageHandler {
                                        wfHostname(), $lnPath, $srcPath ) );
                        return new MediaTransformError( 'thumbnail_error',
                                $params['width'], $params['height'],
-                               wfMessage( 'thumbnail-temp-create' )->text()
+                               wfMessage( 'thumbnail-temp-create' )
                        );
                }
 
index e33c27e..60aec45 100644 (file)
@@ -217,7 +217,7 @@ abstract class TransformationalImageHandler extends ImageHandler {
 
                        return new MediaTransformError( 'thumbnail_error',
                                $scalerParams['clientWidth'], $scalerParams['clientHeight'],
-                               wfMessage( 'filemissing' )->text()
+                               wfMessage( 'filemissing' )
                        );
                }
 
@@ -267,7 +267,7 @@ abstract class TransformationalImageHandler extends ImageHandler {
                        # Thumbnail was zero-byte and had to be removed
                        return new MediaTransformError( 'thumbnail_error',
                                $scalerParams['clientWidth'], $scalerParams['clientHeight'],
-                               wfMessage( 'unknown-error' )->text()
+                               wfMessage( 'unknown-error' )
                        );
                } elseif ( $mto ) {
                        return $mto;
@@ -565,7 +565,7 @@ abstract class TransformationalImageHandler extends ImageHandler {
         * @param array $params Rotate parameters.
         *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
         * @since 1.24 Is non-static. From 1.21 it was static
-        * @return bool
+        * @return bool|MediaTransformError
         */
        public function rotate( $file, $params ) {
                return new MediaTransformError( 'thumbnail_error', 0, 0,
index 47dae78..de49fc3 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use \MediaWiki\MediaWikiServices;
+use \Wikimedia\WaitConditionLoop;
 
 /**
  * Class to store objects in the database
index 9a2a8e2..6e3bace 100644 (file)
@@ -316,46 +316,6 @@ class Article implements Page {
                return $oldid;
        }
 
-       /**
-        * Get text of an article from database
-        * Does *NOT* follow redirects.
-        *
-        * @protected
-        * @note This is really internal functionality that should really NOT be
-        * used by other functions. For accessing article content, use the WikiPage
-        * class, especially WikiBase::getContent(). However, a lot of legacy code
-        * uses this method to retrieve page text from the database, so the function
-        * has to remain public for now.
-        *
-        * @return string|bool String containing article contents, or false if null
-        * @deprecated since 1.21, use WikiPage::getContent() instead
-        */
-       function fetchContent() {
-               // BC cruft!
-
-               wfDeprecated( __METHOD__, '1.21' );
-
-               if ( $this->mContentLoaded && $this->mContent ) {
-                       return $this->mContent;
-               }
-
-               $content = $this->fetchContentObject();
-
-               if ( !$content ) {
-                       return false;
-               }
-
-               // @todo Get rid of mContent everywhere!
-               $this->mContent = ContentHandler::getContentText( $content );
-               ContentHandler::runLegacyHooks(
-                       'ArticleAfterFetchContent',
-                       [ &$this, &$this->mContent ],
-                       '1.21'
-               );
-
-               return $this->mContent;
-       }
-
        /**
         * Get text content object
         * Does *NOT* follow redirects.
@@ -621,21 +581,9 @@ class Article implements Page {
                                        # Preload timestamp to avoid a DB hit
                                        $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
 
-                                       # Pages containing custom CSS or JavaScript get special treatment
-                                       if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
-                                               wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
-                                               $this->showCssOrJsPage();
-                                               $outputDone = true;
-                                       } elseif ( !Hooks::run( 'ArticleContentViewCustom',
+                                       if ( !Hooks::run( 'ArticleContentViewCustom',
                                                        [ $this->fetchContentObject(), $this->getTitle(), $outputPage ] ) ) {
 
-                                               # Allow extensions do their own custom view for certain pages
-                                               $outputDone = true;
-                                       } elseif ( !ContentHandler::runLegacyHooks(
-                                               'ArticleViewCustom',
-                                               [ $this->fetchContentObject(), $this->getTitle(), $outputPage ],
-                                               '1.21'
-                                       ) ) {
                                                # Allow extensions do their own custom view for certain pages
                                                $outputDone = true;
                                        }
@@ -736,7 +684,6 @@ class Article implements Page {
        /**
         * Show a diff page according to current request variables. For use within
         * Article::view() only, other callers should use the DifferenceEngine class.
-        *
         */
        protected function showDiffPage() {
                $request = $this->getContext()->getRequest();
@@ -781,47 +728,6 @@ class Article implements Page {
                $this->mPage->doViewUpdates( $user, (int)$new );
        }
 
-       /**
-        * Show a page view for a page formatted as CSS or JavaScript. To be called by
-        * Article::view() only.
-        *
-        * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
-        * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
-        * more flexibility.
-        *
-        * @param bool $showCacheHint Whether to show a message telling the user
-        *   to clear the browser cache (default: true).
-        */
-       protected function showCssOrJsPage( $showCacheHint = true ) {
-               $outputPage = $this->getContext()->getOutput();
-
-               if ( $showCacheHint ) {
-                       $dir = $this->getContext()->getLanguage()->getDir();
-                       $lang = $this->getContext()->getLanguage()->getHtmlCode();
-
-                       $outputPage->wrapWikiMsg(
-                               "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
-                               'clearyourcache'
-                       );
-               }
-
-               $this->fetchContentObject();
-
-               if ( $this->mContentObject ) {
-                       // Give hooks a chance to customise the output
-                       if ( ContentHandler::runLegacyHooks(
-                               'ShowRawCssJs',
-                               [ $this->mContentObject, $this->getTitle(), $outputPage ],
-                               '1.24'
-                       ) ) {
-                               // If no legacy hooks ran, display the content of the parser output, including RL modules,
-                               // but excluding metadata like categories and language links
-                               $po = $this->mContentObject->getParserOutput( $this->getTitle() );
-                               $outputPage->addParserOutputContent( $po );
-                       }
-               }
-       }
-
        /**
         * Get the robot policy to be used for the current view
         * @param string $action The action= GET parameter
@@ -2331,16 +2237,6 @@ class Article implements Page {
                return $this->mPage->getRevision();
        }
 
-       /**
-        * Call to WikiPage function for backwards compatibility.
-        * @see WikiPage::getText
-        * @deprecated since 1.21 use WikiPage::getContent() instead
-        */
-       public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               wfDeprecated( __METHOD__, '1.21' );
-               return $this->mPage->getText( $audience, $user );
-       }
-
        /**
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::getTimestamp
@@ -2503,15 +2399,6 @@ class Article implements Page {
                );
        }
 
-       /**
-        * Call to WikiPage function for backwards compatibility.
-        * @deprecated since 1.21, use prepareContentForEdit
-        * @see WikiPage::prepareTextForEdit
-        */
-       public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
-               return $this->mPage->prepareTextForEdit( $text, $revid, $user );
-       }
-
        /**
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::protectDescription
@@ -2744,15 +2631,5 @@ class Article implements Page {
                WikiPage::onArticleEdit( $title );
        }
 
-       /**
-        * @param string $oldtext
-        * @param string $newtext
-        * @param int $flags
-        * @return string
-        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
-        */
-       public static function getAutosummary( $oldtext, $newtext, $flags ) {
-               return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
-       }
        // ******
 }
index 6ab3ffc..58f1666 100644 (file)
@@ -42,6 +42,12 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
                $this->mImg = null;
                $this->mHist = [];
                $this->mRange = [ 0, 0 ]; // display range
+
+               // Only display 10 revisions at once by default, otherwise the list is overwhelming
+               $this->mLimitsShown = array_merge( [ 10 ], $this->mLimitsShown );
+               $this->mDefaultLimit = 10;
+               list( $this->mLimit, /* $offset */ ) =
+                       $this->mRequest->getLimitOffset( $this->mDefaultLimit, '' );
        }
 
        /**
index 74566cb..1fa4bfa 100644 (file)
@@ -224,4 +224,20 @@ class WikiFilePage extends WikiPage {
 
                return TitleArray::newFromResult( $res );
        }
+
+       /**
+        * @since 1.28
+        * @return string
+        */
+       public function getWikiDisplayName() {
+               return $this->getFile()->getRepo()->getDisplayName();
+       }
+
+       /**
+        * @since 1.28
+        * @return string
+        */
+       public function getSourceURL() {
+               return $this->getFile()->getDescriptionUrl();
+       }
 }
index dc65549..1c11b4f 100644 (file)
@@ -685,28 +685,6 @@ class WikiPage implements Page, IDBAccessObject {
                return null;
        }
 
-       /**
-        * Get the text of the current revision. No side-effects...
-        *
-        * @param int $audience One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to the given user
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return string|bool The text of the current revision
-        * @deprecated since 1.21, getContent() should be used instead.
-        */
-       public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               wfDeprecated( __METHOD__, '1.21' );
-
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getText( $audience, $user );
-               }
-               return false;
-       }
-
        /**
         * @return string MW timestamp of last article revision
         */
@@ -1173,8 +1151,22 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                       $messageCache = MessageCache::singleton();
-                       $messageCache->updateMessageOverride( $this->mTitle, $this->getContent() );
+                       // @todo move this logic to MessageCache
+                       if ( $this->exists() ) {
+                               // NOTE: use transclusion text for messages.
+                               //       This is consistent with  MessageCache::getMsgFromNamespace()
+
+                               $content = $this->getContent();
+                               $text = $content === null ? null : $content->getWikitextForTransclusion();
+
+                               if ( $text === null ) {
+                                       $text = false;
+                               }
+                       } else {
+                               $text = false;
+                       }
+
+                       MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
                }
 
                return true;
@@ -1413,7 +1405,7 @@ class WikiPage implements Page, IDBAccessObject {
        }
 
        /**
-        * @param string|number|null|bool $sectionId Section identifier as a number or string
+        * @param string|int|null|bool $sectionId Section identifier as a number or string
         * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
         * or 'new' for a new section.
         * @param Content $sectionContent New content of the section.
@@ -1453,7 +1445,7 @@ class WikiPage implements Page, IDBAccessObject {
        }
 
        /**
-        * @param string|number|null|bool $sectionId Section identifier as a number or string
+        * @param string|int|null|bool $sectionId Section identifier as a number or string
         * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
         * or 'new' for a new section.
         * @param Content $sectionContent New content of the section.
@@ -2062,22 +2054,6 @@ class WikiPage implements Page, IDBAccessObject {
                return $options;
        }
 
-       /**
-        * Prepare text which is about to be saved.
-        * Returns a stdClass with source, pst and output members
-        *
-        * @param string $text
-        * @param int|null $revid
-        * @param User|null $user
-        * @deprecated since 1.21: use prepareContentForEdit instead.
-        * @return object
-        */
-       public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
-               wfDeprecated( __METHOD__, '1.21' );
-               $content = ContentHandler::makeContent( $text, $this->getTitle() );
-               return $this->prepareContentForEdit( $content, $revid, $user );
-       }
-
        /**
         * Prepare content which is about to be saved.
         * Returns a stdClass with source, pst and output members
@@ -2236,7 +2212,7 @@ class WikiPage implements Page, IDBAccessObject {
         *   - 'no-change': don't update the article count, ever
         */
        public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
-               global $wgRCWatchCategoryMembership;
+               global $wgRCWatchCategoryMembership, $wgContLang;
 
                $options += [
                        'changed' => true,
@@ -2374,7 +2350,17 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                       MessageCache::singleton()->updateMessageOverride( $this->mTitle, $content );
+                       // XXX: could skip pseudo-messages like js/css here, based on content model.
+                       $msgtext = $content ? $content->getWikitextForTransclusion() : null;
+                       if ( $msgtext === false || $msgtext === null ) {
+                               $msgtext = '';
+                       }
+
+                       MessageCache::singleton()->replace( $shortTitle, $msgtext );
+
+                       if ( $wgContLang->hasVariants() ) {
+                               $wgContLang->updateConversionTable( $this->mTitle );
+                       }
                }
 
                if ( $options['created'] ) {
@@ -3372,6 +3358,8 @@ class WikiPage implements Page, IDBAccessObject {
         * @param Title $title
         */
        public static function onArticleDelete( Title $title ) {
+               global $wgContLang;
+
                // Update existence markers on article/talk tabs...
                $other = $title->getOtherPage();
 
@@ -3388,7 +3376,11 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Messages
                if ( $title->getNamespace() == NS_MEDIAWIKI ) {
-                       MessageCache::singleton()->updateMessageOverride( $title, null );
+                       MessageCache::singleton()->replace( $title->getDBkey(), false );
+
+                       if ( $wgContLang->hasVariants() ) {
+                               $wgContLang->updateConversionTable( $title );
+                       }
                }
 
                // Images
@@ -3489,28 +3481,6 @@ class WikiPage implements Page, IDBAccessObject {
                return $result;
        }
 
-       /**
-        * Return an applicable autosummary if one exists for the given edit.
-        * @param string|null $oldtext The previous text of the page.
-        * @param string|null $newtext The submitted text of the page.
-        * @param int $flags Bitmask: a bitmask of flags submitted for the edit.
-        * @return string An appropriate autosummary, or an empty string.
-        *
-        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
-        */
-       public static function getAutosummary( $oldtext, $newtext, $flags ) {
-               // NOTE: stub for backwards-compatibility. assumes the given text is
-               // wikitext. will break horribly if it isn't.
-
-               wfDeprecated( __METHOD__, '1.21' );
-
-               $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
-               $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
-               $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
-
-               return $handler->getAutosummary( $oldContent, $newContent, $flags );
-       }
-
        /**
         * Auto-generates a deletion reason
         *
@@ -3719,4 +3689,30 @@ class WikiPage implements Page, IDBAccessObject {
        public function isLocal() {
                return true;
        }
+
+       /**
+        * The display name for the site this content
+        * come from. If a subclass overrides isLocal(),
+        * this could return something other than the
+        * current site name
+        *
+        * @since 1.28
+        * @return string
+        */
+       public function getWikiDisplayName() {
+               global $wgSitename;
+               return $wgSitename;
+       }
+
+       /**
+        * Get the source URL for the content on this page,
+        * typically the canonical URL, but may be a remote
+        * link if the content comes from another site
+        *
+        * @since 1.28
+        * @return string
+        */
+       public function getSourceURL() {
+               return $this->getTitle()->getCanonicalURL();
+       }
 }
index 10dfd26..7418547 100644 (file)
@@ -88,7 +88,7 @@ class Parser {
        # Constants needed for external link processing
        # Everything except bracket, space, or control characters
        # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
-       # as well as U+3000 is IDEOGRAPHIC SPACE for bug 19052
+       # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
        const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
        # Simplified expression to match an IPv4 or IPv6 address, or
        # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
@@ -224,7 +224,7 @@ class Parser {
        /**
         * @var string Deprecated accessor for the strip marker prefix.
         * @deprecated since 1.26; use Parser::MARKER_PREFIX instead.
-        **/
+        */
        public $mUniqPrefix = Parser::MARKER_PREFIX;
 
        /**
@@ -271,7 +271,7 @@ class Parser {
                        # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
                        $this->mPreprocessorClass = 'Preprocessor_Hash';
                } elseif ( extension_loaded( 'domxml' ) ) {
-                       # PECL extension that conflicts with the core DOM extension (bug 13770)
+                       # PECL extension that conflicts with the core DOM extension (T15770)
                        wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
                        $this->mPreprocessorClass = 'Preprocessor_Hash';
                } elseif ( extension_loaded( 'dom' ) ) {
@@ -300,7 +300,7 @@ class Parser {
        public function __clone() {
                $this->mInParse = false;
 
-               // Bug 56226: When you create a reference "to" an object field, that
+               // T58226: When you create a reference "to" an object field, that
                // makes the object field itself be a reference too (until the other
                // reference goes out of scope). When cloning, any field that's a
                // reference is copied as a reference in the new object. Both of these
@@ -357,7 +357,7 @@ class Parser {
 
                $this->mStripState = new StripState;
 
-               # Clear these on every parse, bug 4549
+               # Clear these on every parse, T6549
                $this->mTplRedirCache = $this->mTplDomCache = [];
 
                $this->mShowToc = true;
@@ -672,7 +672,7 @@ class Parser {
        }
 
        /**
-        * Process the wikitext for the "?preload=" feature. (bug 5210)
+        * Process the wikitext for the "?preload=" feature. (T7210)
         *
         * "<noinclude>", "<includeonly>" etc. are parsed as for template
         * transclusion, comments, templates, arguments, tags hooks and parser
@@ -1181,7 +1181,7 @@ class Parser {
                                        # A cell could contain both parameters and data
                                        $cell_data = explode( '|', $cell, 2 );
 
-                                       # Bug 553: Note that a '|' inside an invalid link should not
+                                       # T2553: Note that a '|' inside an invalid link should not
                                        # be mistaken as delimiting cell parameters
                                        if ( strpos( $cell_data[0], '[[' ) !== false ) {
                                                $cell = "{$previous}<{$last_tag}>{$cell}";
@@ -1319,12 +1319,12 @@ class Parser {
 
                # Clean up special characters, only run once, next-to-last before doBlockLevels
                $fixtags = [
-                       # french spaces, last one Guillemet-left
+                       # French spaces, last one Guillemet-left
                        # only if there is something before the space
                        '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
                        # french spaces, Guillemet-right
                        '/(\\302\\253) /' => '\\1&#160;',
-                       '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
+                       '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
                ];
                $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
 
@@ -1367,14 +1367,14 @@ class Parser {
                        }
                } else {
                        # attempt to sanitize at least some nesting problems
-                       # (bug #2702 and quite a few others)
+                       # (T4702 and quite a few others)
                        $tidyregs = [
                                # ''Something [http://www.cool.com cool''] -->
                                # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
                                '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
                                '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
                                # fix up an anchor inside another anchor, only
-                               # at least for a single single nested link (bug 3695)
+                               # at least for a single single nested link (T5695)
                                '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
                                '\\1\\2</a>\\3</a>\\1\\4</a>',
                                # fix div inside inline elements- doBlockLevels won't wrap a line which
@@ -1639,7 +1639,7 @@ class Parser {
                        $thislen = strlen( $arr[$i] );
                        // If there are ever four apostrophes, assume the first is supposed to
                        // be text, and the remaining three constitute mark-up for bold text.
-                       // (bug 13227: ''''foo'''' turns into ' ''' foo ' ''')
+                       // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
                        if ( $thislen == 4 ) {
                                $arr[$i - 1] .= "'";
                                $arr[$i] = "'''";
@@ -1647,7 +1647,7 @@ class Parser {
                        } elseif ( $thislen > 5 ) {
                                // If there are more than 5 apostrophes in a row, assume they're all
                                // text except for the last 5.
-                               // (bug 13227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
+                               // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
                                $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
                                $arr[$i] = "'''''";
                                $thislen = 5;
@@ -2169,9 +2169,9 @@ class Parser {
                                # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
                                # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
                                # the real problem is with the $e1 regex
-                               # See bug 1300.
+                               # See T1500.
                                # Still some problems for cases where the ] is meant to be outside punctuation,
-                               # and no image is in sight. See bug 2095.
+                               # and no image is in sight. See T4095.
                                if ( $text !== ''
                                        && substr( $m[3], 0, 1 ) === ']'
                                        && strpos( $text, '[' ) !== false
@@ -2276,7 +2276,7 @@ class Parser {
                        if ( $wasblank ) {
                                $text = $link;
                        } else {
-                               # Bug 4598 madness. Handle the quotes only if they come from the alternate part
+                               # T6598 madness. Handle the quotes only if they come from the alternate part
                                # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
                                # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
                                #    -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
@@ -2292,7 +2292,7 @@ class Parser {
                                                in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
                                        )
                                ) {
-                                       # Bug 24502: filter duplicates
+                                       # T26502: filter duplicates
                                        if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
                                                $this->mLangLinkLanguages[$iw] = true;
                                                $this->mOutput->addLanguageLink( $nt->getFullText() );
@@ -2324,7 +2324,7 @@ class Parser {
                                                continue;
                                        }
                                } elseif ( $ns == NS_CATEGORY ) {
-                                       $s = rtrim( $s . "\n" ); # bug 87
+                                       $s = rtrim( $s . "\n" ); # T2087
 
                                        if ( $wasblank ) {
                                                $sortkey = $this->getDefaultSort();
@@ -2337,7 +2337,7 @@ class Parser {
                                        $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
 
                                        /**
-                                        * Strip the whitespace Category links produce, see bug 87
+                                        * Strip the whitespace Category links produce, see T2087
                                         */
                                        $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
 
@@ -2606,7 +2606,7 @@ class Parser {
                                $subjPage = $this->mTitle->getSubjectPage();
                                $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
                                break;
-                       case 'pageid': // requested in bug 23427
+                       case 'pageid': // requested in T25427
                                $pageid = $this->getTitle()->getArticleID();
                                if ( $pageid == 0 ) {
                                        # 0 means the page doesn't exist in the database,
@@ -2717,7 +2717,7 @@ class Parser {
                                $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
                                break;
                        case 'currentweek':
-                               # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
+                               # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
                                # int to remove the padding
                                $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
                                break;
@@ -2743,7 +2743,7 @@ class Parser {
                                $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
                                break;
                        case 'localweek':
-                               # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
+                               # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
                                # int to remove the padding
                                $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
                                break;
@@ -2840,7 +2840,7 @@ class Parser {
         *     included. Default is to assume a direct page view.
         *
         * The generated DOM tree must depend only on the input text and the flags.
-        * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
+        * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of T6899.
         *
         * Any flag added to the $flags parameter here, or any other parameter liable to cause a
         * change in the DOM tree for a given text, must be passed through the section identifier
@@ -3275,7 +3275,7 @@ class Parser {
                        && !$piece['lineStart']
                        && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
                ) {
-                       # Bug 529: if the template begins with a table or block-level
+                       # T2529: if the template begins with a table or block-level
                        # element, it should be treated as beginning a new line.
                        # This behavior is somewhat controversial.
                        $text = "\n" . $text;
@@ -3284,7 +3284,7 @@ class Parser {
                if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
                        # Error, oversize inclusion
                        if ( $titleText !== false ) {
-                               # Make a working, properly escaped link if possible (bug 23588)
+                               # Make a working, properly escaped link if possible (T25588)
                                $text = "[[:$titleText]]";
                        } else {
                                # This will probably not be a working link, but at least it may
@@ -3968,9 +3968,8 @@ class Parser {
                ) {
                        $this->addTrackingCategory( 'hidden-category-category' );
                }
-               # (bug 8068) Allow control over whether robots index a page.
-               # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here!  This
-               # is not desirable, the last one on the page should win.
+               # (T10068) Allow control over whether robots index a page.
+               # __INDEX__ always overrides __NOINDEX__, see T16899
                if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
                        $this->mOutput->setIndexPolicy( 'noindex' );
                        $this->addTrackingCategory( 'noindex-category' );
@@ -4177,11 +4176,11 @@ class Parser {
 
                        # Strip out HTML (first regex removes any tag not allowed)
                        # Allowed tags are:
-                       # * <sup> and <sub> (bug 8393)
-                       # * <i> (bug 26375)
+                       # * <sup> and <sub> (T10393)
+                       # * <i> (T28375)
                        # * <b> (r105284)
-                       # * <bdi> (bug 72884)
-                       # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
+                       # * <bdi> (T74884)
+                       # * <span dir="rtl"> and <span dir="ltr"> (T37167)
                        # * <s> and <strike> (T35715)
                        # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
                        # to allow setting directionality in toc items.
@@ -4232,7 +4231,7 @@ class Parser {
                                        'noninitial' );
                        }
 
-                       # HTML names must be case-insensitively unique (bug 10721).
+                       # HTML names must be case-insensitively unique (T12721).
                        # This does not apply to Unicode characters per
                        # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
                        # @todo FIXME: We may be changing them depending on the current locale.
@@ -4463,7 +4462,7 @@ class Parser {
                # the database, we use $wgContLang here in order to give
                # everyone the same signature and use the default one rather
                # than the one selected in each user's preferences.
-               # (see also bug 12815)
+               # (see also T14815)
                $ts = $this->mOptions->getTimestamp();
                $timestamp = MWTimestamp::getLocalInstance( $ts );
                $ts = $timestamp->format( 'YmdHis' );
@@ -5199,7 +5198,7 @@ class Parser {
                                                        $validated = true;
                                                }
                                        }
-                                       # else no validation -- bug 13436
+                                       # else no validation -- T15436
                                } else {
                                        if ( $type === 'handler' ) {
                                                # Validate handler parameter
@@ -5438,14 +5437,14 @@ class Parser {
         * External callers should use the getSection and replaceSection methods.
         *
         * @param string $text Page wikitext
-        * @param string|number $sectionId A section identifier string of the form:
+        * @param string|int $sectionId A section identifier string of the form:
         *   "<flag1> - <flag2> - ... - <section number>"
         *
         * Currently the only recognised flag is "T", which means the target section number
         * was derived during a template inclusion parse, in other words this is a template
         * section edit link. If no flags are given, it was an ordinary section edit link.
         * This flag is required to avoid a section numbering mismatch when a section is
-        * enclosed by "<includeonly>" (bug 6563).
+        * enclosed by "<includeonly>" (T8563).
         *
         * The section number 0 pulls the text before the first heading; other numbers will
         * pull the given section along with its lower-level subsections. If the section is
@@ -5579,7 +5578,7 @@ class Parser {
         * If a section contains subsections, these are also returned.
         *
         * @param string $text Text to look in
-        * @param string|number $sectionId Section identifier as a number or string
+        * @param string|int $sectionId Section identifier as a number or string
         * (e.g. 0, 1 or 'T-1').
         * @param string $defaultText Default to return if section is not found
         *
@@ -5595,7 +5594,7 @@ class Parser {
         * section does not exist, $oldtext is returned unchanged.
         *
         * @param string $oldText Former text of the article
-        * @param string|number $sectionId Section identifier as a number or string
+        * @param string|int $sectionId Section identifier as a number or string
         * (e.g. 0, 1 or 'T-1').
         * @param string $newText Replacing text
         *
@@ -5986,7 +5985,7 @@ class Parser {
                        return $parsedWidthParam;
                }
                $m = [];
-               # (bug 13500) In both cases (width/height and width only),
+               # (T15500) In both cases (width/height and width only),
                # permit trailing "px" for backward compatibility.
                if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
                        $width = intval( $m[1] );
index d2ef5e3..b0768b6 100644 (file)
@@ -823,7 +823,6 @@ class ParserOutput extends CacheTime {
         * @code
         *    $parser->getOutput()->my_ext_foo = '...';
         * @endcode
-        *
         */
        public function setProperty( $name, $value ) {
                $this->mProperties[$name] = $value;
index 954d403..c929797 100644 (file)
@@ -90,7 +90,9 @@ abstract class ParameterizedPassword extends Password {
                        $str .= $this->getDelimiter();
                }
 
-               return $str . $this->hash;
+               $res = $str . $this->hash;
+               $this->assertIsSafeSize( $res );
+               return $res;
        }
 
        /**
index 4e395b5..13d1e6d 100644 (file)
@@ -81,6 +81,11 @@ abstract class Password {
         */
        protected $config;
 
+       /**
+        * Hash must fit in user_password, which is a tinyblob
+        */
+       const MAX_HASH_SIZE = 255;
+
        /**
         * Construct the Password object using a string hash
         *
@@ -168,9 +173,28 @@ abstract class Password {
         * are considered equivalent.
         *
         * @return string
+        * @throws PasswordError if password cannot be serialized to fit a tinyblob.
         */
        public function toString() {
-               return ':' . $this->config['type'] . ':' . $this->hash;
+               $result = ':' . $this->config['type'] . ':' . $this->hash;
+               $this->assertIsSafeSize( $result );
+               return $result;
+       }
+
+       /**
+        * Assert that hash will fit in a tinyblob field.
+        *
+        * This prevents MW from inserting it into the DB
+        * and having MySQL silently truncating it, locking
+        * the user out of their account.
+        *
+        * @param string $hash The hash in question.
+        * @throws PasswordError If hash does not fit in DB.
+        */
+       final protected function assertIsSafeSize( $hash ) {
+               if ( strlen( $hash ) > self::MAX_HASH_SIZE ) {
+                       throw new PasswordError( "Password hash is too big" );
+               }
        }
 
        /**
index 5584f6f..bf1f8ac 100644 (file)
@@ -67,12 +67,11 @@ class UserPasswordPolicy {
         * Check if a passwords meets the effective password policy for a User.
         * @param User $user who's policy we are checking
         * @param string $password the password to check
-        * @param string $purpose one of 'login', 'create', 'reset'
         * @return Status error to indicate the password didn't meet the policy, or fatal to
         *      indicate the user shouldn't be allowed to login.
         */
-       public function checkUserPassword( User $user, $password, $purpose = 'login' ) {
-               $effectivePolicy = $this->getPoliciesForUser( $user, $purpose );
+       public function checkUserPassword( User $user, $password ) {
+               $effectivePolicy = $this->getPoliciesForUser( $user );
                return $this->checkPolicies(
                        $user,
                        $password,
@@ -134,20 +133,16 @@ class UserPasswordPolicy {
         * Get the policy for a user, based on their group membership. Public so
         * UI elements can access and inform the user.
         * @param User $user
-        * @param string $purpose one of 'login', 'create', 'reset'
         * @return array the effective policy for $user
         */
-       public function getPoliciesForUser( User $user, $purpose = 'login' ) {
-               $effectivePolicy = $this->policies['default'];
-               if ( $purpose !== 'create' ) {
-                       $effectivePolicy = self::getPoliciesForGroups(
-                               $this->policies,
-                               $user->getEffectiveGroups(),
-                               $this->policies['default']
-                       );
-               }
+       public function getPoliciesForUser( User $user ) {
+               $effectivePolicy = self::getPoliciesForGroups(
+                       $this->policies,
+                       $user->getEffectiveGroups(),
+                       $this->policies['default']
+               );
 
-               Hooks::run( 'PasswordPoliciesForUser', [ $user, &$effectivePolicy, $purpose ] );
+               Hooks::run( 'PasswordPoliciesForUser', [ $user, &$effectivePolicy ] );
 
                return $effectivePolicy;
        }
index 29016a8..534e86b 100644 (file)
@@ -147,6 +147,7 @@ class PoolWorkArticleView extends PoolCounterWork {
                        $logger->info( '{time} {title}', [
                                'time' => number_format( $time, 2 ),
                                'title' => $this->page->getTitle()->getPrefixedDBkey(),
+                               'ns' => $this->page->getTitle()->getNamespace(),
                                'trigger' => 'view',
                        ] );
                }
index 8fc0b77..1bf4f54 100644 (file)
  * ($wgProfiler['exclude']) containing an array of function names.
  * Shell-style patterns are also accepted.
  *
+ * It is also possible to use the Tideways PHP extension, which is mostly
+ * a drop-in replacement for Xhprof. Just change the XHPROF_FLAGS_* constants
+ * to TIDEWAYS_FLAGS_*.
+ *
  * @author Bryan Davis <bd808@wikimedia.org>
  * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
  * @ingroup Profiler
  * @see Xhprof
  * @see https://php.net/xhprof
  * @see https://github.com/facebook/hhvm/blob/master/hphp/doc/profiling.md
+ * @see https://github.com/tideways/php-profiler-extension
  */
 class ProfilerXhprof extends Profiler {
        /**
diff --git a/includes/registration/ExtensionJsonValidationError.php b/includes/registration/ExtensionJsonValidationError.php
new file mode 100644 (file)
index 0000000..897d284
--- /dev/null
@@ -0,0 +1,22 @@
+<?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
+ */
+class ExtensionJsonValidationError extends Exception {
+}
diff --git a/includes/registration/ExtensionJsonValidator.php b/includes/registration/ExtensionJsonValidator.php
new file mode 100644 (file)
index 0000000..f6e76af
--- /dev/null
@@ -0,0 +1,122 @@
+<?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
+ */
+
+use Composer\Spdx\SpdxLicenses;
+use JsonSchema\Validator;
+
+/**
+ * @since 1.29
+ */
+class ExtensionJsonValidator {
+
+       /**
+        * @var callable
+        */
+       private $missingDepCallback;
+
+       /**
+        * @param callable $missingDepCallback
+        */
+       public function __construct( callable $missingDepCallback ) {
+               $this->missingDepCallback = $missingDepCallback;
+       }
+
+       /**
+        * @return bool
+        */
+       public function checkDependencies() {
+               if ( !class_exists( Validator::class ) ) {
+                       call_user_func( $this->missingDepCallback,
+                               'The JsonSchema library cannot be found, please install it through composer.'
+                       );
+                       return false;
+               } elseif ( !class_exists( SpdxLicenses::class ) ) {
+                       call_user_func( $this->missingDepCallback,
+                               'The spdx-licenses library cannot be found, please install it through composer.'
+                       );
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * @param string $path file to validate
+        * @return bool true if passes validation
+        * @throws ExtensionJsonValidationError on any failure
+        */
+       public function validate( $path ) {
+               $data = json_decode( file_get_contents( $path ) );
+               if ( !is_object( $data ) ) {
+                       throw new ExtensionJsonValidationError( "$path is not valid JSON" );
+               }
+
+               if ( !isset( $data->manifest_version ) ) {
+                       throw new ExtensionJsonValidationError(
+                               "$path does not have manifest_version set." );
+               }
+
+               $version = $data->manifest_version;
+               if ( $version !== ExtensionRegistry::MANIFEST_VERSION ) {
+                       $schemaPath = __DIR__ . "/../../docs/extension.schema.v$version.json";
+               } else {
+                       $schemaPath = __DIR__ . '/../../docs/extension.schema.json';
+               }
+
+               // Not too old
+               if ( $version < ExtensionRegistry::OLDEST_MANIFEST_VERSION ) {
+                       throw new ExtensionJsonValidationError(
+                               "$path is using a non-supported schema version"
+                       );
+               } elseif ( $version > ExtensionRegistry::MANIFEST_VERSION ) {
+                       throw new ExtensionJsonValidationError(
+                               "$path is using a non-supported schema version"
+                       );
+               }
+
+               $licenseError = false;
+               // Check if it's a string, if not, schema validation will display an error
+               if ( isset( $data->{'license-name'} ) && is_string( $data->{'license-name'} ) ) {
+                       $licenses = new SpdxLicenses();
+                       $valid = $licenses->validate( $data->{'license-name'} );
+                       if ( !$valid ) {
+                               $licenseError = '[license-name] Invalid SPDX license identifier, '
+                                       . 'see <https://spdx.org/licenses/>';
+                       }
+               }
+
+               $validator = new Validator;
+               $validator->check( $data, (object)[ '$ref' => 'file://' . $schemaPath ] );
+               if ( $validator->isValid() && !$licenseError ) {
+                       // All good.
+                       return true;
+               } else {
+                       $out = "$path did pass validation.\n";
+                       foreach ( $validator->getErrors() as $error ) {
+                               $out .= "[{$error['property']}] {$error['message']}\n";
+                       }
+                       if ( $licenseError ) {
+                               $out .= "$licenseError\n";
+                       }
+                       throw new ExtensionJsonValidationError( $out );
+               }
+       }
+}
index 207f884..d967132 100644 (file)
@@ -141,6 +141,7 @@ class ExtensionProcessor implements Processor {
 
        /**
         * Things to be called once registration of these extensions are done
+        * keyed by the name of the extension that it belongs to
         *
         * @var callable[]
         */
@@ -180,11 +181,11 @@ class ExtensionProcessor implements Processor {
                $this->extractResourceLoaderModules( $dir, $info );
                $this->extractServiceWiringFiles( $dir, $info );
                $this->extractParserTestFiles( $dir, $info );
+               $name = $this->extractCredits( $path, $info );
                if ( isset( $info['callback'] ) ) {
-                       $this->callbacks[] = $info['callback'];
+                       $this->callbacks[$name] = $info['callback'];
                }
 
-               $this->extractCredits( $path, $info );
                foreach ( $info as $key => $val ) {
                        if ( in_array( $key, self::$globalSettings ) ) {
                                $this->storeToArray( $path, "wg$key", $val, $this->globals );
@@ -335,6 +336,7 @@ class ExtensionProcessor implements Processor {
        /**
         * @param string $path
         * @param array $info
+        * @return string Name of thing
         * @throws Exception
         */
        protected function extractCredits( $path, array $info ) {
@@ -360,6 +362,8 @@ class ExtensionProcessor implements Processor {
 
                $this->credits[$name] = $credits;
                $this->globals['wgExtensionCredits'][$credits['type']][] = $credits;
+
+               return $name;
        }
 
        /**
index b5c70e9..70dc624 100644 (file)
@@ -31,7 +31,7 @@ class ExtensionRegistry {
        /**
         * Bump whenever the registration cache needs resetting
         */
-       const CACHE_VERSION = 3;
+       const CACHE_VERSION = 4;
 
        /**
         * Special key that defines the merge strategy
@@ -59,6 +59,13 @@ class ExtensionRegistry {
         */
        protected $queued = [];
 
+       /**
+        * Whether we are done loading things
+        *
+        * @var bool
+        */
+       private $finished = false;
+
        /**
         * Items in the JSON file that aren't being
         * set as globals
@@ -114,12 +121,23 @@ class ExtensionRegistry {
                $this->queued[$path] = $mtime;
        }
 
+       /**
+        * @throws MWException If the queue is already marked as finished (no further things should
+        *  be loaded then).
+        */
        public function loadFromQueue() {
                global $wgVersion;
                if ( !$this->queued ) {
                        return;
                }
 
+               if ( $this->finished ) {
+                       throw new MWException(
+                               "The following paths tried to load late: "
+                               . implode( ', ', array_keys( $this->queued ) )
+                       );
+               }
+
                // A few more things to vary the cache on
                $versions = [
                        'registration' => self::CACHE_VERSION,
@@ -164,6 +182,15 @@ class ExtensionRegistry {
                $this->queued = [];
        }
 
+       /**
+        * After this is called, no more extensions can be loaded
+        *
+        * @since 1.29
+        */
+       public function finish() {
+               $this->finished = true;
+       }
+
        /**
         * Process a queue of extensions and return their extracted data
         *
@@ -285,9 +312,6 @@ class ExtensionRegistry {
                foreach ( $info['autoloaderPaths'] as $path ) {
                        require_once $path;
                }
-               foreach ( $info['callbacks'] as $cb ) {
-                       call_user_func( $cb );
-               }
 
                $this->loaded += $info['credits'];
                if ( $info['attributes'] ) {
@@ -297,6 +321,10 @@ class ExtensionRegistry {
                                $this->attributes = array_merge_recursive( $this->attributes, $info['attributes'] );
                        }
                }
+
+               foreach ( $info['callbacks'] as $name => $cb ) {
+                       call_user_func( $cb, $info['credits'][$name] );
+               }
        }
 
        /**
index d624c7c..f9b4b3d 100644 (file)
@@ -391,8 +391,6 @@ class ResourceLoader implements LoggerAwareInterface {
                }
        }
 
-       /**
-        */
        public function registerTestModules() {
                global $IP;
 
index 91e0b02..ef2827c 100644 (file)
@@ -56,7 +56,7 @@ class ResourceLoaderClientHtml {
 
        /**
         * @param ResourceLoaderContext $context
-        * @param aray $target [optional] Custom 'target' parameter for the startup module
+        * @param string|null $target [optional] Custom 'target' parameter for the startup module
         */
        public function __construct( ResourceLoaderContext $context, $target = null ) {
                $this->context = $context;
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
deleted file mode 100644 (file)
index aef1c74..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-/**
- * ResourceLoader module for user preference customizations.
- *
- * 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
- * @author Trevor Parscal
- * @author Roan Kattouw
- */
-
-/**
- * Module for user preference customizations
- */
-class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
-
-       protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
-
-       /**
-        * @return bool
-        */
-       public function enableModuleContentVersion() {
-               return true;
-       }
-
-       /**
-        * @param ResourceLoaderContext $context
-        * @return array
-        */
-       public function getStyles( ResourceLoaderContext $context ) {
-               if ( !$this->getConfig()->get( 'AllowUserCssPrefs' ) ) {
-                       return [];
-               }
-
-               $options = $context->getUserObj()->getOptions();
-
-               // Build CSS rules
-               $rules = [];
-
-               // Underline: 2 = skin default, 1 = always, 0 = never
-               if ( $options['underline'] < 2 ) {
-                       $rules[] = "a { text-decoration: " .
-                               ( $options['underline'] ? 'underline' : 'none' ) . "; }";
-               }
-               $style = implode( "\n", $rules );
-               if ( $this->getFlip( $context ) ) {
-                       $style = CSSJanus::transform( $style, true, false );
-               }
-               return [ 'all' => $style ];
-       }
-
-       /**
-        * @param ResourceLoaderContext $context
-        * @return bool
-        */
-       public function isKnownEmpty( ResourceLoaderContext $context ) {
-               $styles = $this->getStyles( $context );
-               return isset( $styles['all'] ) && $styles['all'] === '';
-       }
-
-       /**
-        * @return string
-        */
-       public function getGroup() {
-               return 'private';
-       }
-
-       /**
-        * @return string
-        */
-       public function getType() {
-               return self::LOAD_STYLES;
-       }
-}
index 2d0d690..ab74dbd 100644 (file)
@@ -68,14 +68,14 @@ class RevDelArchiveItem extends RevDelRevisionItem {
        }
 
        protected function getRevisionLink() {
-               $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
-                       $this->revision->getTimestamp(), $this->list->getUser() ) );
+               $date = $this->list->getLanguage()->userTimeAndDate(
+                       $this->revision->getTimestamp(), $this->list->getUser() );
 
                if ( $this->isDeleted() && !$this->canViewContent() ) {
-                       return $date;
+                       return htmlspecialchars( $date );
                }
 
-               return Linker::link(
+               return $this->getLinkRenderer()->makeLink(
                        SpecialPage::getTitleFor( 'Undelete' ),
                        $date,
                        [],
@@ -91,9 +91,9 @@ class RevDelArchiveItem extends RevDelRevisionItem {
                        return $this->list->msg( 'diff' )->escaped();
                }
 
-               return Linker::link(
+               return $this->getLinkRenderer()->makeLink(
                        SpecialPage::getTitleFor( 'Undelete' ),
-                       $this->list->msg( 'diff' )->escaped(),
+                       $this->list->msg( 'diff' )->text(),
                        [],
                        [
                                'target' => $this->list->title->getPrefixedText(),
index 52df2e3..decabba 100644 (file)
@@ -69,16 +69,16 @@ class RevDelArchivedFileItem extends RevDelFileItem {
        }
 
        protected function getLink() {
-               $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
-                       $this->file->getTimestamp(), $this->list->getUser() ) );
+               $date = $this->list->getLanguage()->userTimeAndDate(
+                       $this->file->getTimestamp(), $this->list->getUser() );
 
                # Hidden files...
                if ( !$this->canViewContent() ) {
-                       $link = $date;
+                       $link = htmlspecialchars( $date );
                } else {
                        $undelete = SpecialPage::getTitleFor( 'Undelete' );
                        $key = $this->file->getKey();
-                       $link = Linker::link( $undelete, $date, [],
+                       $link = $this->getLinkRenderer()->makeLink( $undelete, $date, [],
                                [
                                        'target' => $this->list->title->getPrefixedText(),
                                        'file' => $key,
index ff01cee..06b596e 100644 (file)
@@ -116,19 +116,19 @@ class RevDelFileItem extends RevDelItem {
         * @return string
         */
        protected function getLink() {
-               $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
-                       $this->file->getTimestamp(), $this->list->getUser() ) );
+               $date = $this->list->getLanguage()->userTimeAndDate(
+                       $this->file->getTimestamp(), $this->list->getUser() );
 
                if ( !$this->isDeleted() ) {
                        # Regular files...
-                       return Html::rawElement( 'a', [ 'href' => $this->file->getUrl() ], $date );
+                       return Html::element( 'a', [ 'href' => $this->file->getUrl() ], $date );
                }
 
                # Hidden files...
                if ( !$this->canViewContent() ) {
-                       $link = $date;
+                       $link = htmlspecialchars( $date );
                } else {
-                       $link = Linker::link(
+                       $link = $this->getLinkRenderer()->makeLink(
                                SpecialPage::getTitleFor( 'Revisiondelete' ),
                                $date,
                                [],
index 1ea7271..9e76f4c 100644 (file)
@@ -92,9 +92,9 @@ class RevDelLogItem extends RevDelItem {
                $formatter->setAudience( LogFormatter::FOR_THIS_USER );
 
                // Log link for this page
-               $loglink = Linker::link(
+               $loglink = $this->getLinkRenderer()->makeLink(
                        SpecialPage::getTitleFor( 'Log' ),
-                       $this->list->msg( 'log' )->escaped(),
+                       $this->list->msg( 'log' )->text(),
                        [],
                        [ 'page' => $title->getPrefixedText() ]
                );
index d799113..7fff366 100644 (file)
@@ -107,14 +107,14 @@ class RevDelRevisionItem extends RevDelItem {
         * @return string
         */
        protected function getRevisionLink() {
-               $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
-                       $this->revision->getTimestamp(), $this->list->getUser() ) );
+               $date = $this->list->getLanguage()->userTimeAndDate(
+                       $this->revision->getTimestamp(), $this->list->getUser() );
 
                if ( $this->isDeleted() && !$this->canViewContent() ) {
-                       return $date;
+                       return htmlspecialchars( $date );
                }
 
-               return Linker::linkKnown(
+               return $this->getLinkRenderer()->makeKnownLink(
                        $this->list->title,
                        $date,
                        [],
@@ -134,9 +134,9 @@ class RevDelRevisionItem extends RevDelItem {
                if ( $this->isDeleted() && !$this->canViewContent() ) {
                        return $this->list->msg( 'diff' )->escaped();
                } else {
-                       return Linker::linkKnown(
+                       return $this->getLinkRenderer()->makeKnownLink(
                                        $this->list->title,
-                                       $this->list->msg( 'diff' )->escaped(),
+                                       $this->list->msg( 'diff' )->text(),
                                        [],
                                        [
                                                'diff' => $this->revision->getId(),
index 350b780..df58e71 100644 (file)
@@ -2,7 +2,6 @@
 
 /**
  * Augment search results.
- *
  */
 interface ResultAugmentor {
        /**
index 94710a8..e2d79a9 100644 (file)
@@ -2,7 +2,6 @@
 
 /**
  * Augment search results.
- *
  */
 interface ResultSetAugmentor {
        /**
index 80a437b..4e7c782 100644 (file)
  * 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
- *
  */
 
 /**
  * A search suggestion
- *
  */
 class SearchSuggestion {
        /**
index 5a24fd6..caad388 100644 (file)
@@ -17,7 +17,6 @@
  * 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
- *
  */
 
 /**
index e6763ca..69f2e49 100644 (file)
@@ -919,19 +919,18 @@ class SkinTemplate extends Skin {
                                        $content_navigation['views']['view']['redundant'] = true;
                                }
 
-                               $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
-                                       $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
+                               $page = $this->canUseWikiPage() ? $this->getWikiPage() : false;
+                               $isRemoteContent = $page && !$page->isLocal();
 
                                // If it is a non-local file, show a link to the file in its own repository
                                // @todo abstract this for remote content that isn't a file
-                               if ( $isForeignFile ) {
-                                       $file = $this->getWikiPage()->getFile();
+                               if ( $isRemoteContent ) {
                                        $content_navigation['views']['view-foreign'] = [
                                                'class' => '',
                                                'text' => wfMessageFallback( "$skname-view-foreign", 'view-foreign' )->
                                                        setContext( $this->getContext() )->
-                                                       params( $file->getRepo()->getDisplayName() )->text(),
-                                               'href' => $file->getDescriptionUrl(),
+                                                       params( $page->getWikiDisplayName() )->text(),
+                                               'href' => $page->getSourceURL(),
                                                'primary' => false,
                                        ];
                                }
@@ -955,9 +954,9 @@ class SkinTemplate extends Skin {
                                                        && $title->getDefaultMessageText() !== false
                                                )
                                        ) {
-                                               $msgKey = $isForeignFile ? 'edit-local' : 'edit';
+                                               $msgKey = $isRemoteContent ? 'edit-local' : 'edit';
                                        } else {
-                                               $msgKey = $isForeignFile ? 'create-local' : 'create';
+                                               $msgKey = $isRemoteContent ? 'create-local' : 'create';
                                        }
                                        $content_navigation['views']['edit'] = [
                                                'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection )
@@ -967,7 +966,7 @@ class SkinTemplate extends Skin {
                                                'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )
                                                        ->setContext( $this->getContext() )->text(),
                                                'href' => $title->getLocalURL( $this->editUrlOptions() ),
-                                               'primary' => !$isForeignFile, // don't collapse this in vector
+                                               'primary' => !$isRemoteContent, // don't collapse this in vector
                                        ];
 
                                        // section link
index cb13840..00efeae 100644 (file)
@@ -140,16 +140,22 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                $opts = new FormOptions();
 
                $opts->add( 'hideminor', false );
+               $opts->add( 'hidemajor', false );
                $opts->add( 'hidebots', false );
+               $opts->add( 'hidehumans', false );
                $opts->add( 'hideanons', false );
                $opts->add( 'hideliu', false );
                $opts->add( 'hidepatrolled', false );
+               $opts->add( 'hideunpatrolled', false );
                $opts->add( 'hidemyself', false );
                $opts->add( 'hidebyothers', false );
 
                if ( $config->get( 'RCWatchCategoryMembership' ) ) {
                        $opts->add( 'hidecategorization', false );
                }
+               $opts->add( 'hidepageedits', false );
+               $opts->add( 'hidenewpages', false );
+               $opts->add( 'hidelog', false );
 
                $opts->add( 'namespace', '', FormOptions::INTNULL );
                $opts->add( 'invert', false );
@@ -230,13 +236,24 @@ abstract class ChangesListSpecialPage extends SpecialPage {
 
                // Toggles
                if ( $opts['hideminor'] ) {
-                       $conds['rc_minor'] = 0;
+                       $conds[] = 'rc_minor = 0';
+               }
+               if ( $opts['hidemajor'] ) {
+                       $conds[] = 'rc_minor = 1';
                }
                if ( $opts['hidebots'] ) {
                        $conds['rc_bot'] = 0;
                }
-               if ( $user->useRCPatrol() && $opts['hidepatrolled'] ) {
-                       $conds['rc_patrolled'] = 0;
+               if ( $opts['hidehumans'] ) {
+                       $conds[] = 'rc_bot = 1';
+               }
+               if ( $user->useRCPatrol() ) {
+                       if ( $opts['hidepatrolled'] ) {
+                               $conds[] = 'rc_patrolled = 0';
+                       }
+                       if ( $opts['hideunpatrolled'] ) {
+                               $conds[] = 'rc_patrolled = 1';
+                       }
                }
                if ( $botsonly ) {
                        $conds['rc_bot'] = 1;
@@ -269,6 +286,15 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                ) {
                        $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE );
                }
+               if ( $opts['hidepageedits'] ) {
+                       $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_EDIT );
+               }
+               if ( $opts['hidenewpages'] ) {
+                       $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_NEW );
+               }
+               if ( $opts['hidelog'] ) {
+                       $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_LOG );
+               }
 
                // Namespace filtering
                if ( $opts['namespace'] !== '' ) {
@@ -495,4 +521,23 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        protected function getGroupName() {
                return 'changes';
        }
+
+       /**
+        * Get filters that can be rendered.
+        *
+        * Filters with 'msg' => false can be used to filter data but won't
+        * be presented as show/hide toggles in the UI. They are not returned
+        * by this function.
+        *
+        * @param array $allFilters Map of filter URL param names to properties (msg/default)
+        * @return array Map of filter URL param names to properties (msg/default)
+        */
+       protected function getRenderableCustomFilters( $allFilters ) {
+               return array_filter(
+                       $allFilters,
+                       function( $filter ) {
+                               return isset( $filter['msg'] ) && ( $filter['msg'] !== false );
+                       }
+               );
+       }
 }
index 73a1bdd..540ce4b 100644 (file)
@@ -149,9 +149,9 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
                // bc: provide login link as a parameter for messages where the translation
                // was not updated
-               $loginreqlink = Linker::linkKnown(
+               $loginreqlink = $this->getLinkRenderer()->makeKnownLink(
                        $this->getPageTitle(),
-                       $this->msg( 'loginreqlink' )->escaped(),
+                       $this->msg( 'loginreqlink' )->text(),
                        [],
                        [
                                'returnto' => $this->mReturnTo,
@@ -1134,9 +1134,9 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                                        'type' => 'info',
                                        'raw' => true,
                                        'cssclass' => 'mw-form-related-link-container',
-                                       'default' => Linker::link(
+                                       'default' => $this->getLinkRenderer()->makeLink(
                                                SpecialPage::getTitleFor( 'PasswordReset' ),
-                                               $this->msg( 'userlogin-resetpassword-link' )->escaped()
+                                               $this->msg( 'userlogin-resetpassword-link' )->text()
                                        ),
                                        'weight' => 230,
                                ];
@@ -1315,9 +1315,9 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                $targetLanguage = Language::factory( $lang );
                $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
 
-               return Linker::linkKnown(
+               return $this->getLinkRenderer()->makeKnownLink(
                        $this->getPageTitle(),
-                       htmlspecialchars( $text ),
+                       $text,
                        $attr,
                        $query
                );
index 39e3649..00fca12 100644 (file)
@@ -64,14 +64,15 @@ abstract class WantedQueryPage extends QueryPage {
         * @return string
         */
        public function formatResult( $skin, $result ) {
+               $linkRenderer = $this->getLinkRenderer();
                $title = Title::makeTitleSafe( $result->namespace, $result->title );
                if ( $title instanceof Title ) {
                        if ( $this->isCached() || $this->forceExistenceCheck() ) {
                                $pageLink = $this->existenceCheck( $title )
-                                       ? '<del>' . Linker::link( $title ) . '</del>'
-                                       : Linker::link( $title );
+                                       ? '<del>' . $linkRenderer->makeLink( $title ) . '</del>'
+                                       : $linkRenderer->makeLink( $title );
                        } else {
-                               $pageLink = Linker::link(
+                               $pageLink = $linkRenderer->makeLink(
                                        $title,
                                        null,
                                        [],
index 7e29be0..a01e9b2 100644 (file)
@@ -86,7 +86,7 @@ class SpecialActiveUsers extends SpecialPage {
                $groups = User::getAllGroups();
 
                foreach ( $groups as $group ) {
-                       $msg = User::getGroupName( $group );
+                       $msg = htmlspecialchars( User::getGroupName( $group ) );
                        $options[$msg] = $group;
                }
 
index 4a2a619..4b8446a 100644 (file)
@@ -204,6 +204,7 @@ class SpecialAllPages extends IncludableSpecialPage {
                                ]
                        );
 
+                       $linkRenderer = $this->getLinkRenderer();
                        if ( $res->numRows() > 0 ) {
                                $out = Html::openElement( 'ul', [ 'class' => 'mw-allpages-chunk' ] );
 
@@ -213,7 +214,7 @@ class SpecialAllPages extends IncludableSpecialPage {
                                                $out .= '<li' .
                                                        ( $s->page_is_redirect ? ' class="allpagesredirect"' : '' ) .
                                                        '>' .
-                                                       Linker::link( $t ) .
+                                                       $linkRenderer->makeLink( $t ) .
                                                        "</li>\n";
                                        } else {
                                                $out .= '<li>[[' . htmlspecialchars( $s->page_title ) . "]]</li>\n";
@@ -269,6 +270,7 @@ class SpecialAllPages extends IncludableSpecialPage {
                $navLinks = [];
                $self = $this->getPageTitle();
 
+               $linkRenderer = $this->getLinkRenderer();
                // Generate a "previous page" link if needed
                if ( $prevTitle ) {
                        $query = [ 'from' => $prevTitle->getText() ];
@@ -281,9 +283,9 @@ class SpecialAllPages extends IncludableSpecialPage {
                                $query['hideredirects'] = $hideredirects;
                        }
 
-                       $navLinks[] = Linker::linkKnown(
+                       $navLinks[] = $linkRenderer->makeKnownLink(
                                $self,
-                               $this->msg( 'prevpage', $prevTitle->getText() )->escaped(),
+                               $this->msg( 'prevpage', $prevTitle->getText() )->text(),
                                [],
                                $query
                        );
@@ -304,9 +306,9 @@ class SpecialAllPages extends IncludableSpecialPage {
                                $query['hideredirects'] = $hideredirects;
                        }
 
-                       $navLinks[] = Linker::linkKnown(
+                       $navLinks[] = $linkRenderer->makeKnownLink(
                                $self,
-                               $this->msg( 'nextpage', $t->getText() )->escaped(),
+                               $this->msg( 'nextpage', $t->getText() )->text(),
                                [],
                                $query
                        );
index 9ee1b75..ecc030e 100644 (file)
@@ -78,9 +78,10 @@ class AncientPagesPage extends QueryPage {
 
                $d = $this->getLanguage()->userTimeAndDate( $result->value, $this->getUser() );
                $title = Title::makeTitle( $result->namespace, $result->title );
-               $link = Linker::linkKnown(
+               $linkRenderer = $this->getLinkRenderer();
+               $link = $linkRenderer->makeKnownLink(
                        $title,
-                       htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
+                       $wgContLang->convert( $title->getPrefixedText() )
                );
 
                return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
index 74b474a..5448013 100644 (file)
@@ -77,6 +77,11 @@ class SpecialApiHelp extends UnlistedSpecialPage {
                $main = new ApiMain( $this->getContext(), false );
                try {
                        $module = $main->getModuleFromPath( $moduleName );
+               } catch ( ApiUsageException $ex ) {
+                       $this->getOutput()->addHTML( Html::rawElement( 'span', [ 'class' => 'error' ],
+                               $this->msg( 'apihelp-no-such-module', $moduleName )->inContentLanguage()->parse()
+                       ) );
+                       return;
                } catch ( UsageException $ex ) {
                        $this->getOutput()->addHTML( Html::rawElement( 'span', [ 'class' => 'error' ],
                                $this->msg( 'apihelp-no-such-module', $moduleName )->inContentLanguage()->parse()
index ce7d24e..585f70b 100644 (file)
@@ -372,12 +372,13 @@ class SpecialBlock extends FormSpecialPage {
 
                $this->getOutput()->addModuleStyles( 'mediawiki.special' );
 
+               $linkRenderer = $this->getLinkRenderer();
                # Link to the user's contributions, if applicable
                if ( $this->target instanceof User ) {
                        $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
-                       $links[] = Linker::link(
+                       $links[] = $linkRenderer->makeLink(
                                $contribsPage,
-                               $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->escaped()
+                               $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
                        );
                }
 
@@ -392,21 +393,24 @@ class SpecialBlock extends FormSpecialPage {
                        $message = $this->msg( 'ipb-unblock' )->parse();
                        $list = SpecialPage::getTitleFor( 'Unblock' );
                }
-               $links[] = Linker::linkKnown( $list, $message, [] );
+               $links[] = $linkRenderer->makeKnownLink(
+                       $list,
+                       new HtmlArmor( $message )
+               );
 
                # Link to the block list
-               $links[] = Linker::linkKnown(
+               $links[] = $linkRenderer->makeKnownLink(
                        SpecialPage::getTitleFor( 'BlockList' ),
-                       $this->msg( 'ipb-blocklist' )->escaped()
+                       $this->msg( 'ipb-blocklist' )->text()
                );
 
                $user = $this->getUser();
 
                # Link to edit the block dropdown reasons, if applicable
                if ( $user->isAllowed( 'editinterface' ) ) {
-                       $links[] = Linker::linkKnown(
+                       $links[] = $linkRenderer->makeKnownLink(
                                $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
-                               $this->msg( 'ipb-edit-dropdown' )->escaped(),
+                               $this->msg( 'ipb-edit-dropdown' )->text(),
                                [],
                                [ 'action' => 'edit' ]
                        );
index 1753396..b730ecd 100644 (file)
@@ -109,12 +109,13 @@ class BrokenRedirectsPage extends QueryPage {
                        }
                }
 
+               $linkRenderer = $this->getLinkRenderer();
                // $toObj may very easily be false if the $result list is cached
                if ( !is_object( $toObj ) ) {
-                       return '<del>' . Linker::link( $fromObj ) . '</del>';
+                       return '<del>' . $linkRenderer->makeLink( $fromObj ) . '</del>';
                }
 
-               $from = Linker::linkKnown(
+               $from = $linkRenderer->makeKnownLink(
                        $fromObj,
                        null,
                        [],
@@ -128,28 +129,22 @@ class BrokenRedirectsPage extends QueryPage {
                        // check, if the content model is editable through action=edit
                        ContentHandler::getForTitle( $fromObj )->supportsDirectEditing()
                ) {
-                       $links[] = Linker::linkKnown(
+                       $links[] = $linkRenderer->makeKnownLink(
                                $fromObj,
-                               $this->msg( 'brokenredirects-edit' )->escaped(),
+                               $this->msg( 'brokenredirects-edit' )->text(),
                                [],
                                [ 'action' => 'edit' ]
                        );
                }
-               $to = Linker::link(
-                       $toObj,
-                       null,
-                       [],
-                       [],
-                       [ 'broken' ]
-               );
+               $to = $linkRenderer->makeBrokenLink( $toObj );
                $arr = $this->getLanguage()->getArrow();
 
                $out = $from . $this->msg( 'word-separator' )->escaped();
 
                if ( $this->getUser()->isAllowed( 'delete' ) ) {
-                       $links[] = Linker::linkKnown(
+                       $links[] = $linkRenderer->makeKnownLink(
                                $fromObj,
-                               $this->msg( 'brokenredirects-delete' )->escaped(),
+                               $this->msg( 'brokenredirects-delete' )->text(),
                                [],
                                [ 'action' => 'delete' ]
                        );
index 3cd4d4d..40277ca 100644 (file)
@@ -492,7 +492,8 @@ class SpecialContributions extends IncludableSpecialPage {
                        $form .= "\t" . Html::hidden( $name, $value ) . "\n";
                }
 
-               $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
+               $tagFilter = ChangeTags::buildTagFilterSelector(
+                       $this->opts['tagfilter'], false, $this->getContext() );
 
                if ( $tagFilter ) {
                        $filterSelection = Html::rawElement(
index ad12046..5c8b3a6 100644 (file)
  * @ingroup SpecialPage
  */
 class DeletedContributionsPage extends SpecialPage {
+       /** @var FormOptions */
+       protected $mOpts;
+
        function __construct() {
-               parent::__construct( 'DeletedContributions', 'deletedhistory',
-                       /*listed*/true, /*function*/false, /*file*/false );
+               parent::__construct( 'DeletedContributions', 'deletedhistory' );
        }
 
        /**
@@ -40,40 +42,43 @@ class DeletedContributionsPage extends SpecialPage {
        function execute( $par ) {
                $this->setHeaders();
                $this->outputHeader();
+               $this->checkPermissions();
 
                $user = $this->getUser();
 
-               if ( !$this->userCanExecute( $user ) ) {
-                       $this->displayRestrictionError();
-
-                       return;
-               }
-
-               $request = $this->getRequest();
                $out = $this->getOutput();
                $out->setPageTitle( $this->msg( 'deletedcontributions-title' ) );
 
-               $options = [];
+               $opts = new FormOptions();
+
+               $opts->add( 'target', '' );
+               $opts->add( 'namespace', '' );
+               $opts->add( 'limit', 20 );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+               $opts->validateIntBounds( 'limit', 0, $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
 
                if ( $par !== null ) {
-                       $target = $par;
-               } else {
-                       $target = $request->getVal( 'target' );
+                       $opts->setValue( 'target', $par );
                }
 
+               $ns = $opts->getValue( 'namespace' );
+               if ( $ns !== null && $ns !== '' ) {
+                       $opts->setValue( 'namespace', intval( $ns ) );
+               }
+
+               $this->mOpts = $opts;
+
+               $target = $opts->getValue( 'target' );
                if ( !strlen( $target ) ) {
-                       $out->addHTML( $this->getForm( '' ) );
+                       $this->getForm();
 
                        return;
                }
 
-               $options['limit'] = $request->getInt( 'limit',
-                       $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
-               $options['target'] = $target;
-
                $userObj = User::newFromName( $target, false );
                if ( !$userObj ) {
-                       $out->addHTML( $this->getForm( '' ) );
+                       $this->getForm();
 
                        return;
                }
@@ -82,16 +87,9 @@ class DeletedContributionsPage extends SpecialPage {
                $target = $userObj->getName();
                $out->addSubtitle( $this->getSubTitle( $userObj ) );
 
-               $ns = $request->getVal( 'namespace', null );
-               if ( $ns !== null && $ns !== '' ) {
-                       $options['namespace'] = intval( $ns );
-               } else {
-                       $options['namespace'] = '';
-               }
-
-               $out->addHTML( $this->getForm( $options ) );
+               $this->getForm();
 
-               $pager = new DeletedContribsPager( $this->getContext(), $target, $options['namespace'] );
+               $pager = new DeletedContribsPager( $this->getContext(), $target, $opts->getValue( 'namespace' ) );
                if ( !$pager->getNumRows() ) {
                        $out->addWikiMsg( 'nocontribs' );
 
@@ -187,76 +185,35 @@ class DeletedContributionsPage extends SpecialPage {
 
        /**
         * Generates the namespace selector form with hidden attributes.
-        * @param array $options The options to be included.
-        * @return string
         */
-       function getForm( $options ) {
-               $options['title'] = $this->getPageTitle()->getPrefixedText();
-               if ( !isset( $options['target'] ) ) {
-                       $options['target'] = '';
-               } else {
-                       $options['target'] = str_replace( '_', ' ', $options['target'] );
-               }
-
-               if ( !isset( $options['namespace'] ) ) {
-                       $options['namespace'] = '';
-               }
-
-               if ( !isset( $options['contribs'] ) ) {
-                       $options['contribs'] = 'user';
-               }
-
-               if ( $options['contribs'] == 'newbie' ) {
-                       $options['target'] = '';
-               }
-
-               $f = Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] );
-
-               foreach ( $options as $name => $value ) {
-                       if ( in_array( $name, [ 'namespace', 'target', 'contribs' ] ) ) {
-                               continue;
-                       }
-                       $f .= "\t" . Html::hidden( $name, $value ) . "\n";
-               }
+       function getForm() {
+               $opts = $this->mOpts;
+
+               $formDescriptor = [
+                       'target' => [
+                               'type' => 'user',
+                               'name' => 'target',
+                               'label-message' => 'sp-contributions-username',
+                               'default' => $opts->getValue( 'target' ),
+                               'ipallowed' => true,
+                       ],
 
-               $this->getOutput()->addModules( 'mediawiki.userSuggest' );
-
-               $f .= Xml::openElement( 'fieldset' );
-               $f .= Xml::element( 'legend', [], $this->msg( 'sp-contributions-search' )->text() );
-               $f .= Xml::tags(
-                       'label',
-                       [ 'for' => 'target' ],
-                       $this->msg( 'sp-contributions-username' )->parse()
-               ) . ' ';
-               $f .= Html::input(
-                       'target',
-                       $options['target'],
-                       'text',
-                       [
-                               'size' => '20',
-                               'required' => '',
-                               'class' => [
-                                       'mw-autocomplete-user', // used by mediawiki.userSuggest
-                               ],
-                       ] + ( $options['target'] ? [] : [ 'autofocus' ] )
-               ) . ' ';
-               $f .= Html::namespaceSelector(
-                       [
-                               'selected' => $options['namespace'],
+                       'namespace' => [
+                               'type' => 'namespaceselect',
+                               'name' => 'namespace',
+                               'label-message' => 'namespace',
                                'all' => '',
-                               'label' => $this->msg( 'namespace' )->text()
                        ],
-                       [
-                               'name' => 'namespace',
-                               'id' => 'namespace',
-                               'class' => 'namespaceselector',
-                       ]
-               ) . ' ';
-               $f .= Xml::submitButton( $this->msg( 'sp-contributions-submit' )->text() );
-               $f .= Xml::closeElement( 'fieldset' );
-               $f .= Xml::closeElement( 'form' );
-
-               return $f;
+               ];
+
+               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setWrapperLegendMsg( 'sp-contributions-search' )
+                       ->setSubmitTextMsg( 'sp-contributions-submit' )
+                       // prevent setting subpage and 'target' parameter at the same time
+                       ->setAction( $this->getPageTitle()->getLocalURL() )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
        }
 
        /**
index 0cec9d0..9140bf1 100644 (file)
@@ -137,14 +137,15 @@ class DoubleRedirectsPage extends QueryPage {
                                $result = $dbr->fetchObject( $res );
                        }
                }
+               $linkRenderer = $this->getLinkRenderer();
                if ( !$result ) {
-                       return '<del>' . Linker::link( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
+                       return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
                }
 
                $titleB = Title::makeTitle( $result->nsb, $result->tb );
                $titleC = Title::makeTitle( $result->nsc, $result->tc, '', $result->iwc );
 
-               $linkA = Linker::linkKnown(
+               $linkA = $linkRenderer->makeKnownLink(
                        $titleA,
                        null,
                        [],
@@ -158,26 +159,24 @@ class DoubleRedirectsPage extends QueryPage {
                        // check, if the content model is editable through action=edit
                        ContentHandler::getForTitle( $titleA )->supportsDirectEditing()
                ) {
-                       $edit = Linker::linkKnown(
+                       $edit = $linkRenderer->makeKnownLink(
                                $titleA,
-                               $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->escaped(),
+                               $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
                                [],
-                               [
-                                       'action' => 'edit'
-                               ]
+                               [ 'action' => 'edit' ]
                        );
                } else {
                        $edit = '';
                }
 
-               $linkB = Linker::linkKnown(
+               $linkB = $linkRenderer->makeKnownLink(
                        $titleB,
                        null,
                        [],
                        [ 'redirect' => 'no' ]
                );
 
-               $linkC = Linker::linkKnown( $titleC );
+               $linkC = $linkRenderer->makeKnownLink( $titleC );
 
                $lang = $this->getLanguage();
                $arr = $lang->getArrow() . $lang->getDirMark();
index 252d076..476c452 100644 (file)
@@ -158,10 +158,11 @@ class SpecialEditTags extends UnlistedSpecialPage {
                        // Also set header tabs to be for the target.
                        $this->getSkin()->setRelevantTitle( $this->targetObj );
 
+                       $linkRenderer = $this->getLinkRenderer();
                        $links = [];
-                       $links[] = Linker::linkKnown(
+                       $links[] = $linkRenderer->makeKnownLink(
                                SpecialPage::getTitleFor( 'Log' ),
-                               $this->msg( 'viewpagelogs' )->escaped(),
+                               $this->msg( 'viewpagelogs' )->text(),
                                [],
                                [
                                        'page' => $this->targetObj->getPrefixedText(),
@@ -170,17 +171,17 @@ class SpecialEditTags extends UnlistedSpecialPage {
                        );
                        if ( !$this->targetObj->isSpecialPage() ) {
                                // Give a link to the page history
-                               $links[] = Linker::linkKnown(
+                               $links[] = $linkRenderer->makeKnownLink(
                                        $this->targetObj,
-                                       $this->msg( 'pagehist' )->escaped(),
+                                       $this->msg( 'pagehist' )->text(),
                                        [],
                                        [ 'action' => 'history' ]
                                );
                        }
                        // Link to Special:Tags
-                       $links[] = Linker::linkKnown(
+                       $links[] = $linkRenderer->makeKnownLink(
                                SpecialPage::getTitleFor( 'Tags' ),
-                               $this->msg( 'tags-edit-manage-link' )->escaped()
+                               $this->msg( 'tags-edit-manage-link' )->text()
                        );
                        // Logs themselves don't have histories or archived revisions
                        $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) );
index 0defcd1..347f0c0 100644 (file)
@@ -149,7 +149,6 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
 
        /**
         * Executes an edit mode for the watchlist view, from which you can manage your watchlist
-        *
         */
        protected function executeViewEditWatchlist() {
                $out = $this->getOutput();
index a550e88..085b68d 100644 (file)
@@ -53,13 +53,14 @@ class SpecialEmailUser extends UnlistedSpecialPage {
        }
 
        protected function getFormFields() {
+               $linkRenderer = $this->getLinkRenderer();
                return [
                        'From' => [
                                'type' => 'info',
                                'raw' => 1,
-                               'default' => Linker::link(
+                               'default' => $linkRenderer->makeLink(
                                        $this->getUser()->getUserPage(),
-                                       htmlspecialchars( $this->getUser()->getName() )
+                                       $this->getUser()->getName()
                                ),
                                'label-message' => 'emailfrom',
                                'id' => 'mw-emailuser-sender',
@@ -67,9 +68,9 @@ class SpecialEmailUser extends UnlistedSpecialPage {
                        'To' => [
                                'type' => 'info',
                                'raw' => 1,
-                               'default' => Linker::link(
+                               'default' => $linkRenderer->makeLink(
                                        $this->mTargetObj->getUserPage(),
-                                       htmlspecialchars( $this->mTargetObj->getName() )
+                                       $this->mTargetObj->getName()
                                ),
                                'label-message' => 'emailto',
                                'id' => 'mw-emailuser-recipient',
@@ -306,7 +307,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
         * @since 1.20
         * @param array $data
         * @param HTMLForm $form
-        * @return Status|string|bool
+        * @return Status|bool
         */
        public static function uiSubmit( array $data, HTMLForm $form ) {
                return self::submit( $data, $form->getContext() );
@@ -319,8 +320,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
         *
         * @param array $data
         * @param IContextSource $context
-        * @return Status|string|bool Status object, or potentially a String on error
-        * or maybe even true on success if anything uses the EmailUser hook.
+        * @return Status|bool
         */
        public static function submit( array $data, IContextSource $context ) {
                $config = $context->getConfig();
@@ -328,7 +328,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
                $target = self::getTarget( $data['Target'] );
                if ( !$target instanceof User ) {
                        // Messages used here: notargettext, noemailtext, nowikiemailtext
-                       return $context->msg( $target . 'text' )->parseAsBlock();
+                       return Status::newFatal( $target . 'text' );
                }
 
                $to = MailAddress::newFromUser( $target );
@@ -341,9 +341,33 @@ class SpecialEmailUser extends UnlistedSpecialPage {
                $text .= $context->msg( 'emailuserfooter',
                        $from->name, $to->name )->inContentLanguage()->text();
 
-               $error = '';
+               $error = false;
                if ( !Hooks::run( 'EmailUser', [ &$to, &$from, &$subject, &$text, &$error ] ) ) {
-                       return $error;
+                       if ( $error instanceof Status ) {
+                               return $error;
+                       } elseif ( $error === false || $error === '' || $error === [] ) {
+                               // Possibly to tell HTMLForm to pretend there was no submission?
+                               return false;
+                       } elseif ( $error === true ) {
+                               // Hook sent the mail itself and indicates success?
+                               return Status::newGood();
+                       } elseif ( is_array( $error ) ) {
+                               $status = Status::newGood();
+                               foreach ( $error as $e ) {
+                                       $status->fatal( $e );
+                               }
+                               return $status;
+                       } elseif ( $error instanceof MessageSpecifier ) {
+                               return Status::newFatal( $error );
+                       } else {
+                               // Ugh. Either a raw HTML string, or something that's supposed
+                               // to be treated like one.
+                               $type = is_object( $error ) ? get_class( $error ) : gettype( $error );
+                               wfDeprecated( "EmailUser hook returning a $type as \$error", '1.29' );
+                               return Status::newFatal( new ApiRawMessage(
+                                       [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted'
+                               ) );
+                       }
                }
 
                if ( $config->get( 'UserEmailUseReplyTo' ) ) {
index b86a95e..f20829f 100644 (file)
@@ -53,12 +53,6 @@ class FewestrevisionsPage extends QueryPage {
                                'page_namespace' => MWNamespace::getContentNamespaces(),
                                'page_id = rev_page' ],
                        'options' => [
-                               'HAVING' => 'COUNT(*) > 1',
-                               // ^^^ This was probably here to weed out redirects.
-                               // Since we mark them as such now, it might be
-                               // useful to remove this. People _do_ create pages
-                               // and never revise them, they aren't necessarily
-                               // redirects.
                                'GROUP BY' => [ 'page_namespace', 'page_title', 'page_is_redirect' ]
                        ]
                ];
@@ -88,14 +82,14 @@ class FewestrevisionsPage extends QueryPage {
                                )
                        );
                }
+               $linkRenderer = $this->getLinkRenderer();
+               $text = $wgContLang->convert( $nt->getPrefixedText() );
+               $plink = $linkRenderer->makeLink( $nt, $text );
 
-               $text = htmlspecialchars( $wgContLang->convert( $nt->getPrefixedText() ) );
-               $plink = Linker::linkKnown( $nt, $text );
-
-               $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->escaped();
+               $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->text();
                $redirect = isset( $result->redirect ) && $result->redirect ?
                        ' - ' . $this->msg( 'isredirect' )->escaped() : '';
-               $nlink = Linker::linkKnown(
+               $nlink = $linkRenderer->makeKnownLink(
                        $nt,
                        $nl,
                        [],
index 6de127d..8021bc2 100644 (file)
@@ -208,11 +208,12 @@ class FileDuplicateSearchPage extends QueryPage {
        function formatResult( $skin, $result ) {
                global $wgContLang;
 
+               $linkRenderer = $this->getLinkRenderer();
                $nt = $result->getTitle();
                $text = $wgContLang->convert( $nt->getText() );
-               $plink = Linker::link(
+               $plink = $linkRenderer->makeLink(
                        $nt,
-                       htmlspecialchars( $text )
+                       $text
                );
 
                $userText = $result->getUser( 'text' );
index c58af60..ce88624 100644 (file)
@@ -24,6 +24,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * MediaWiki page data importer
  *
@@ -592,12 +594,12 @@ class ImportReporter extends ContextSource {
                }
 
                $this->mPageCount++;
-
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                if ( $successCount > 0 ) {
                        // <bdi> prevents jumbling of the versions count
                        // in RTL wikis in case the page title is LTR
                        $this->getOutput()->addHTML(
-                               "<li>" . Linker::linkKnown( $title ) . " " .
+                               "<li>" . $linkRenderer->makeLink( $title ) . " " .
                                        "<bdi>" .
                                        $this->msg( 'import-revision-count' )->numParams( $successCount )->escaped() .
                                        "</bdi>" .
@@ -656,7 +658,7 @@ class ImportReporter extends ContextSource {
                                );
                        }
                } else {
-                       $this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
+                       $this->getOutput()->addHTML( "<li>" . $linkRenderer->makeKnownLink( $title ) . " " .
                                $this->msg( 'import-nonewrevisions' )->escaped() . "</li>\n" );
                }
        }
index 39c8ae8..2c92410 100644 (file)
@@ -71,8 +71,14 @@ class SpecialListGrants extends SpecialPage {
 
                        $id = \Sanitizer::escapeId( $grant );
                        $out->addHTML( \Html::rawElement( 'tr', [ 'id' => $id ],
-                               "<td>" . $this->msg( "grant-$grant" )->escaped() . "</td>" .
-                               "<td>" . $grantCellHtml . '</td>'
+                               "<td>" .
+                               $this->msg(
+                                       "listgrants-grant-display",
+                                       \User::getGrantName( $grant ),
+                                       "<span class='mw-listgrants-grant-name'>" . $id . "</span>"
+                               )->parse() .
+                               "</td>" .
+                               "<td>" . $grantCellHtml . "</td>"
                        ) );
                }
 
index d64306b..f3d3a77 100644 (file)
@@ -71,6 +71,8 @@ class SpecialListGroupRights extends SpecialPage {
                ) );
                asort( $allGroups );
 
+               $linkRenderer = $this->getLinkRenderer();
+
                foreach ( $allGroups as $group ) {
                        $permissions = isset( $groupPermissions[$group] )
                                ? $groupPermissions[$group]
@@ -92,22 +94,22 @@ class SpecialListGroupRights extends SpecialPage {
                                // Do not make a link for the generic * group or group with invalid group page
                                $grouppage = htmlspecialchars( $groupnameLocalized );
                        } else {
-                               $grouppage = Linker::link(
+                               $grouppage = $linkRenderer->makeLink(
                                        $grouppageLocalizedTitle,
-                                       htmlspecialchars( $groupnameLocalized )
+                                       $groupnameLocalized
                                );
                        }
 
                        if ( $group === 'user' ) {
                                // Link to Special:listusers for implicit group 'user'
-                               $grouplink = '<br />' . Linker::linkKnown(
+                               $grouplink = '<br />' . $linkRenderer->makeKnownLink(
                                        SpecialPage::getTitleFor( 'Listusers' ),
-                                       $this->msg( 'listgrouprights-members' )->escaped()
+                                       $this->msg( 'listgrouprights-members' )->text()
                                );
                        } elseif ( !in_array( $group, $config->get( 'ImplicitGroups' ) ) ) {
-                               $grouplink = '<br />' . Linker::linkKnown(
+                               $grouplink = '<br />' . $linkRenderer->makeKnownLink(
                                        SpecialPage::getTitleFor( 'Listusers' ),
-                                       $this->msg( 'listgrouprights-members' )->escaped(),
+                                       $this->msg( 'listgrouprights-members' )->text(),
                                        [],
                                        [ 'group' => $group ]
                                );
@@ -165,7 +167,7 @@ class SpecialListGroupRights extends SpecialPage {
                                $this->msg( 'listgrouprights-namespaceprotection-restrictedto' )->text()
                        )
                );
-
+               $linkRenderer = $this->getLinkRenderer();
                ksort( $namespaceProtection );
                foreach ( $namespaceProtection as $namespace => $rights ) {
                        if ( !in_array( $namespace, MWNamespace::getValidNamespaces() ) ) {
@@ -183,9 +185,9 @@ class SpecialListGroupRights extends SpecialPage {
                                Html::rawElement(
                                        'td',
                                        [],
-                                       Linker::link(
+                                       $linkRenderer->makeLink(
                                                SpecialPage::getTitleFor( 'Allpages' ),
-                                               htmlspecialchars( $namespaceText ),
+                                               $namespaceText,
                                                [],
                                                [ 'namespace' => $namespace ]
                                        )
index 1d02a4f..d8a468f 100644 (file)
@@ -119,7 +119,7 @@ class MIMEsearchPage extends QueryPage {
                        ],
                ];
 
-               $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
                        ->setWrapperLegendMsg( 'mimesearch' )
                        ->setSubmitTextMsg( 'ilsubmit' )
                        ->setAction( $this->getPageTitle()->getLocalURL() )
@@ -153,11 +153,12 @@ class MIMEsearchPage extends QueryPage {
        function formatResult( $skin, $result ) {
                global $wgContLang;
 
+               $linkRenderer = $this->getLinkRenderer();
                $nt = Title::makeTitle( $result->namespace, $result->title );
                $text = $wgContLang->convert( $nt->getText() );
-               $plink = Linker::link(
+               $plink = $linkRenderer->makeLink(
                        Title::newFromText( $nt->getPrefixedText() ),
-                       htmlspecialchars( $text )
+                       $text
                );
 
                $download = Linker::makeMediaLinkObj( $nt, $this->msg( 'download' )->escaped() );
@@ -166,9 +167,9 @@ class MIMEsearchPage extends QueryPage {
                $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
                $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
                        $result->img_height )->escaped();
-               $user = Linker::link(
+               $user = $linkRenderer->makeLink(
                        Title::makeTitle( NS_USER, $result->img_user_text ),
-                       htmlspecialchars( $result->img_user_text )
+                       $result->img_user_text
                );
 
                $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
index 7683ad8..dc88cbe 100644 (file)
@@ -22,6 +22,8 @@
  * @author Brian Wolff
  */
 
+use stdClass;
+
 /**
  * @ingroup SpecialPage
  */
@@ -174,10 +176,11 @@ class MediaStatisticsPage extends QueryPage {
         */
        protected function outputTableRow( $mime, $count, $bytes ) {
                $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
+               $linkRenderer = $this->getLinkRenderer();
                $row = Html::rawElement(
                        'td',
                        [],
-                       Linker::link( $mimeSearch, htmlspecialchars( $mime ) )
+                       $linkRenderer->makeLink( $mimeSearch, $mime )
                );
                $row .= Html::element(
                        'td',
@@ -338,7 +341,7 @@ class MediaStatisticsPage extends QueryPage {
         * we need to implement since abstract in parent class.
         *
         * @param Skin $skin
-        * @param stdObject $result Result row
+        * @param stdClass $result Result row
         * @return bool|string|void
         * @throws MWException
         */
index b916c1f..f122db8 100644 (file)
@@ -277,6 +277,8 @@ class SpecialMergeHistory extends SpecialPage {
        function formatRevisionRow( $row ) {
                $rev = new Revision( $row );
 
+               $linkRenderer = $this->getLinkRenderer();
+
                $stxt = '';
                $last = $this->msg( 'last' )->escaped();
 
@@ -285,9 +287,9 @@ class SpecialMergeHistory extends SpecialPage {
 
                $user = $this->getUser();
 
-               $pageLink = Linker::linkKnown(
+               $pageLink = $linkRenderer->makeKnownLink(
                        $rev->getTitle(),
-                       htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ),
+                       $this->getLanguage()->userTimeAndDate( $ts, $user ),
                        [],
                        [ 'oldid' => $rev->getId() ]
                );
@@ -299,9 +301,9 @@ class SpecialMergeHistory extends SpecialPage {
                if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
                        $last = $this->msg( 'last' )->escaped();
                } elseif ( isset( $this->prevId[$row->rev_id] ) ) {
-                       $last = Linker::linkKnown(
+                       $last = $linkRenderer->makeKnownLink(
                                $rev->getTitle(),
-                               $this->msg( 'last' )->escaped(),
+                               $this->msg( 'last' )->text(),
                                [],
                                [
                                        'diff' => $row->rev_id,
@@ -359,7 +361,9 @@ class SpecialMergeHistory extends SpecialPage {
                        return false;
                }
 
-               $targetLink = Linker::link(
+               $linkRenderer = $this->getLinkRenderer();
+
+               $targetLink = $linkRenderer->makeLink(
                        $targetTitle,
                        null,
                        [],
index 015701d..6095412 100644 (file)
@@ -91,10 +91,11 @@ class MostcategoriesPage extends QueryPage {
                        );
                }
 
+               $linkRenderer = $this->getLinkRenderer();
                if ( $this->isCached() ) {
-                       $link = Linker::link( $title );
+                       $link = $linkRenderer->makeLink( $title );
                } else {
-                       $link = Linker::linkKnown( $title );
+                       $link = $linkRenderer->makeKnownLink( $title );
                }
 
                $count = $this->msg( 'ncategories' )->numParams( $result->value )->escaped();
index 3e78352..210c4a2 100644 (file)
@@ -97,10 +97,11 @@ class MostinterwikisPage extends QueryPage {
                        );
                }
 
+               $linkRenderer = $this->getLinkRenderer();
                if ( $this->isCached() ) {
-                       $link = Linker::link( $title );
+                       $link = $linkRenderer->makeLink( $title );
                } else {
-                       $link = Linker::linkKnown( $title );
+                       $link = $linkRenderer->makeKnownLink( $title );
                }
 
                $count = $this->msg( 'ninterwikis' )->numParams( $result->value )->escaped();
index 01eb39e..712574c 100644 (file)
@@ -91,7 +91,8 @@ class MostlinkedPage extends QueryPage {
        function makeWlhLink( $title, $caption ) {
                $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
 
-               return Linker::linkKnown( $wlh, $caption );
+               $linkRenderer = $this->getLinkRenderer();
+               return $linkRenderer->makeKnownLink( $wlh, $caption );
        }
 
        /**
@@ -115,10 +116,11 @@ class MostlinkedPage extends QueryPage {
                        );
                }
 
-               $link = Linker::link( $title );
+               $linkRenderer = $this->getLinkRenderer();
+               $link = $linkRenderer->makeLink( $title );
                $wlh = $this->makeWlhLink(
                        $title,
-                       $this->msg( 'nlinks' )->numParams( $result->value )->escaped()
+                       $this->msg( 'nlinks' )->numParams( $result->value )->text()
                );
 
                return $this->getLanguage()->specialList( $link, $wlh );
index 7fcb9d8..d102791 100644 (file)
@@ -104,7 +104,7 @@ class MostlinkedTemplatesPage extends QueryPage {
                }
 
                return $this->getLanguage()->specialList(
-                       Linker::link( $title ),
+                       $this->getLinkRenderer()->makeLink( $title ),
                        $this->makeWlhLink( $title, $result )
                );
        }
@@ -118,9 +118,9 @@ class MostlinkedTemplatesPage extends QueryPage {
         */
        private function makeWlhLink( $title, $result ) {
                $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
-               $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->escaped();
+               $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->text();
 
-               return Linker::link( $wlh, $label );
+               return $this->getLinkRenderer()->makeLink( $wlh, $label );
        }
 
        protected function getGroupName() {
index 077a5d2..9e3a750 100644 (file)
@@ -108,7 +108,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
                        unset( $formDescriptor['hidepatrolled'] );
                }
 
-               $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
                        ->setWrapperLegendMsg( 'newimages-legend' )
                        ->setSubmitTextMsg( 'ilsubmit' )
                        ->setMethod( 'get' )
index 706a1d7..37006d8 100644 (file)
@@ -133,7 +133,7 @@ class SpecialPagesWithProp extends QueryPage {
         */
        function formatResult( $skin, $result ) {
                $title = Title::newFromRow( $result );
-               $ret = Linker::link( $title, null, [], [], [ 'known' ] );
+               $ret = $this->getLinkRenderer()->makeKnownLink( $title );
                if ( $result->pp_value !== '' ) {
                        // Do not show very long or binary values on the special page
                        $valueLength = strlen( $result->pp_value );
index 5e3e430..4671591 100644 (file)
@@ -228,9 +228,9 @@ class SpecialPrefixindex extends SpecialAllPages {
                                                $displayed = substr( $displayed, $prefixLength );
                                        }
                                        $link = ( $title->isRedirect() ? '<div class="allpagesredirect">' : '' ) .
-                                               Linker::linkKnown(
+                                               $this->getLinkRenderer()->makeKnownLink(
                                                        $title,
-                                                       htmlspecialchars( $displayed )
+                                                       $displayed
                                                ) .
                                                ( $title->isRedirect() ? '</div>' : '' );
 
@@ -275,9 +275,9 @@ class SpecialPrefixindex extends SpecialAllPages {
                                $query['namespace'] = $namespace;
                        }
 
-                       $nextLink = Linker::linkKnown(
+                       $nextLink = $this->getLinkRenderer()->makeKnownLink(
                                $this->getPageTitle(),
-                               $this->msg( 'nextpage', str_replace( '_', ' ', $nextRow->page_title ) )->escaped(),
+                               $this->msg( 'nextpage', str_replace( '_', ' ', $nextRow->page_title ) )->text(),
                                [],
                                $query
                        );
index c800d96..fa9033c 100644 (file)
@@ -84,7 +84,7 @@ class SpecialProtectedtitles extends SpecialPage {
                        ) . "\n";
                }
 
-               $link = Linker::link( $title );
+               $link = $this->getLinkRenderer()->makeLink( $title );
                $description_items = [];
                // Messages: restriction-level-sysop, restriction-level-autoconfirmed
                $protType = $this->msg( 'restriction-level-' . $row->pt_create_perm )->escaped();
index cd3299c..8530eb1 100644 (file)
@@ -95,7 +95,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
        }
 
        /**
-        * Get custom show/hide filters
+        * Get all custom filters
         *
         * @return array Map of filter URL param names to properties (msg/default)
         */
@@ -501,7 +501,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        $extraOpts['category'] = $this->categoryFilterForm( $opts );
                }
 
-               $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
+               $tagFilter = ChangeTags::buildTagFilterSelector(
+                       $opts['tagfilter'], false, $this->getContext() );
                if ( count( $tagFilter ) ) {
                        $extraOpts['tagfilter'] = $tagFilter;
                }
@@ -664,12 +665,11 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                }
                unset( $value );
 
-               $text = htmlspecialchars( $title );
                if ( $active ) {
-                       $text = '<strong>' . $text . '</strong>';
+                       $title = new HtmlArmor( '<strong>' . htmlspecialchars( $title ) . '</strong>' );
                }
 
-               return Linker::linkKnown( $this->getPageTitle(), $text, [], $params );
+               return $this->getLinkRenderer()->makeKnownLink( $this->getPageTitle(), $title, [], $params );
        }
 
        /**
@@ -747,9 +747,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                $showhide = [ 'show', 'hide' ];
 
-               foreach ( $this->getCustomFilters() as $key => $params ) {
+               foreach ( $this->getRenderableCustomFilters( $this->getCustomFilters() ) as $key => $params ) {
                        $filters[$key] = $params['msg'];
                }
+
                // Disable some if needed
                if ( !$user->useRCPatrol() ) {
                        unset( $filters['hidepatrolled'] );
index dcaff4d..4b0fa00 100644 (file)
@@ -241,32 +241,33 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
         * Show some useful links in the subtitle
         */
        protected function showConvenienceLinks() {
+               $linkRenderer = $this->getLinkRenderer();
                # Give a link to the logs/hist for this page
                if ( $this->targetObj ) {
                        // Also set header tabs to be for the target.
                        $this->getSkin()->setRelevantTitle( $this->targetObj );
 
                        $links = [];
-                       $links[] = Linker::linkKnown(
+                       $links[] = $linkRenderer->makeKnownLink(
                                SpecialPage::getTitleFor( 'Log' ),
-                               $this->msg( 'viewpagelogs' )->escaped(),
+                               $this->msg( 'viewpagelogs' )->text(),
                                [],
                                [ 'page' => $this->targetObj->getPrefixedText() ]
                        );
                        if ( !$this->targetObj->isSpecialPage() ) {
                                # Give a link to the page history
-                               $links[] = Linker::linkKnown(
+                               $links[] = $linkRenderer->makeKnownLink(
                                        $this->targetObj,
-                                       $this->msg( 'pagehist' )->escaped(),
+                                       $this->msg( 'pagehist' )->text(),
                                        [],
                                        [ 'action' => 'history' ]
                                );
                                # Link to deleted edits
                                if ( $this->getUser()->isAllowed( 'undelete' ) ) {
                                        $undelete = SpecialPage::getTitleFor( 'Undelete' );
-                                       $links[] = Linker::linkKnown(
+                                       $links[] = $linkRenderer->makeKnownLink(
                                                $undelete,
-                                               $this->msg( 'deletedhist' )->escaped(),
+                                               $this->msg( 'deletedhist' )->text(),
                                                [],
                                                [ 'target' => $this->targetObj->getPrefixedDBkey() ]
                                        );
@@ -465,9 +466,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
                                Xml::closeElement( 'form' ) . "\n";
                        // Show link to edit the dropdown reasons
                        if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
-                               $link = Linker::linkKnown(
+                               $link = $this->getLinkRenderer()->makeKnownLink(
                                        $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->getTitle(),
-                                       $this->msg( 'revdelete-edit-reasonlist' )->escaped(),
+                                       $this->msg( 'revdelete-edit-reasonlist' )->text(),
                                        [],
                                        [ 'action' => 'edit' ]
                                );
index 9280b04..727179a 100644 (file)
@@ -295,12 +295,12 @@ class SpecialSearch extends SpecialPage {
                $textStatus = null;
                if ( $textMatches instanceof Status ) {
                        $textStatus = $textMatches;
-                       $textMatches = null;
+                       $textMatches = $textStatus->getValue();
                }
 
                // did you mean... suggestions
                $didYouMeanHtml = '';
-               if ( $showSuggestion && $textMatches && !$textStatus ) {
+               if ( $showSuggestion && $textMatches ) {
                        if ( $textMatches->hasRewrittenQuery() ) {
                                $didYouMeanHtml = $this->getDidYouMeanRewrittenHtml( $term, $textMatches );
                        } elseif ( $textMatches->hasSuggestion() ) {
@@ -360,6 +360,25 @@ class SpecialSearch extends SpecialPage {
 
                $out->addHTML( "<div class='searchresults'>" );
 
+               $hasErrors = $textStatus && $textStatus->getErrors();
+               if ( $hasErrors ) {
+                       list( $error, $warning ) = $textStatus->splitByErrorType();
+                       if ( $error->getErrors() ) {
+                               $out->addHTML( Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'errorbox' ],
+                                       $error->getHTML( 'search-error' )
+                               ) );
+                       }
+                       if ( $warning->getErrors() ) {
+                               $out->addHTML( Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'warningbox' ],
+                                       $warning->getHTML( 'search-warning' )
+                               ) );
+                       }
+               }
+
                // prev/next links
                $prevnext = null;
                if ( $num || $this->offset ) {
@@ -388,7 +407,8 @@ class SpecialSearch extends SpecialPage {
                        }
                        $titleMatches->free();
                }
-               if ( $textMatches && !$textStatus ) {
+
+               if ( $textMatches ) {
                        // output appropriate heading
                        if ( $numTextMatches > 0 && $numTitleMatches > 0 ) {
                                $out->addHTML( '<div class="mw-search-visualclear"></div>' );
@@ -412,22 +432,18 @@ class SpecialSearch extends SpecialPage {
                $hasOtherResults = $textMatches &&
                        $textMatches->hasInterwikiResults( SearchResultSet::INLINE_RESULTS );
 
-               if ( $num === 0 ) {
-                       if ( $textStatus ) {
-                               $out->addHTML( '<div class="error">' .
-                                       $textStatus->getMessage( 'search-error' ) . '</div>' );
-                       } else {
-                               if ( !$this->offset ) {
-                                       // If we have an offset the create link was rendered earlier in this function.
-                                       // This class needs a good de-spaghettification, but for now this will
-                                       // do the job.
-                                       $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
-                               }
-                               $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>",
-                                       [ $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
-                                                       wfEscapeWikiText( $term )
-                                       ] );
+               // If we have no results and we have not already displayed an error message
+               if ( $num === 0 && !$hasErrors ) {
+                       if ( !$this->offset ) {
+                               // If we have an offset the create link was rendered earlier in this function.
+                               // This class needs a good de-spaghettification, but for now this will
+                               // do the job.
+                               $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
                        }
+                       $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
+                               $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
+                               wfEscapeWikiText( $term )
+                       ] );
                }
 
                if ( $hasOtherResults ) {
@@ -483,9 +499,16 @@ class SpecialSearch extends SpecialPage {
                }
                $stParams = array_merge( $params, $this->powerSearchOptions() );
 
-               $suggest = Linker::linkKnown(
+               $linkRenderer = $this->getLinkRenderer();
+
+               $snippet = $textMatches->getSuggestionSnippet() ?: null;
+               if ( $snippet !== null ) {
+                       $snippet = new HtmlArmor( $snippet );
+               }
+
+               $suggest = $linkRenderer->makeKnownLink(
                        $this->getPageTitle(),
-                       $textMatches->getSuggestionSnippet() ?: null,
+                       $snippet,
                        [ 'id' => 'mw-search-DYM-suggestion' ],
                        $stParams
                );
@@ -519,18 +542,25 @@ class SpecialSearch extends SpecialPage {
                }
                $stParams = array_merge( $params, $this->powerSearchOptions() );
 
-               $rewritten = Linker::linkKnown(
+               $linkRenderer = $this->getLinkRenderer();
+
+               $snippet = $textMatches->getQueryAfterRewriteSnippet() ?: null;
+               if ( $snippet !== null ) {
+                       $snippet = new HtmlArmor( $snippet );
+               }
+
+               $rewritten = $linkRenderer->makeKnownLink(
                        $this->getPageTitle(),
-                       $textMatches->getQueryAfterRewriteSnippet() ?: null,
+                       $snippet,
                        [ 'id' => 'mw-search-DYM-rewritten' ],
                        $stParams
                );
 
                $stParams['search'] = $term;
                $stParams['runsuggestion'] = 0;
-               $original = Linker::linkKnown(
+               $original = $linkRenderer->makeKnownLink(
                        $this->getPageTitle(),
-                       htmlspecialchars( $term ),
+                       $term,
                        [ 'id' => 'mw-search-DYM-original' ],
                        $stParams
                );
@@ -750,7 +780,13 @@ class SpecialSearch extends SpecialPage {
                Hooks::run( 'ShowSearchHitTitle',
                        [ &$link_t, &$titleSnippet, $result, $terms, $this, &$query ] );
 
-               $link = Linker::linkKnown(
+               $linkRenderer = $this->getLinkRenderer();
+
+               if ( $titleSnippet !== null ) {
+                       $titleSnippet = new HtmlArmor( $titleSnippet );
+               }
+
+               $link = $linkRenderer->makeKnownLink(
                        $link_t,
                        $titleSnippet,
                        [ 'data-serp-pos' => $position ], // HTML attributes
@@ -784,9 +820,13 @@ class SpecialSearch extends SpecialPage {
                                $redirectText = null;
                        }
 
+                       if ( $redirectText !== null ) {
+                               $redirectText = new HtmlArmor( $redirectText );
+                       }
+
                        $redirect = "<span class='searchalttitle'>" .
                                $this->msg( 'search-redirect' )->rawParams(
-                                       Linker::linkKnown( $redirectTitle, $redirectText ) )->text() .
+                                       $linkRenderer->makeKnownLink( $redirectTitle, $redirectText ) )->text() .
                                "</span>";
                }
 
@@ -796,9 +836,13 @@ class SpecialSearch extends SpecialPage {
                                $sectionText = null;
                        }
 
+                       if ( $sectionText !== null ) {
+                               $sectionText = new HtmlArmor( $sectionText );
+                       }
+
                        $section = "<span class='searchalttitle'>" .
                                $this->msg( 'search-section' )->rawParams(
-                                       Linker::linkKnown( $sectionTitle, $sectionText ) )->text() .
+                                       $linkRenderer->makeKnownLink( $sectionTitle, $sectionText ) )->text() .
                                "</span>";
                }
 
@@ -955,6 +999,8 @@ class SpecialSearch extends SpecialPage {
                        return '';
                }
 
+               $linkRenderer = $this->getLinkRenderer();
+
                $title = $result->getTitle();
 
                $titleSnippet = $result->getTitleSnippet();
@@ -963,7 +1009,11 @@ class SpecialSearch extends SpecialPage {
                        $titleSnippet = null;
                }
 
-               $link = Linker::linkKnown(
+               if ( $titleSnippet !== null ) {
+                       $titleSnippet = new HtmlArmor( $titleSnippet );
+               }
+
+               $link = $linkRenderer->makeKnownLink(
                        $title,
                        $titleSnippet
                );
@@ -977,9 +1027,13 @@ class SpecialSearch extends SpecialPage {
                                $redirectText = null;
                        }
 
+                       if ( $redirectText !== null ) {
+                               $redirectText = new HtmlArmor( $redirectText );
+                       }
+
                        $redirect = "<span class='searchalttitle'>" .
                                $this->msg( 'search-redirect' )->rawParams(
-                                       Linker::linkKnown( $redirectTitle, $redirectText ) )->text() .
+                                       $linkRenderer->makeKnownLink( $redirectTitle, $redirectText ) )->text() .
                                "</span>";
                }
 
@@ -997,7 +1051,7 @@ class SpecialSearch extends SpecialPage {
                        }
                        // "more results" link (special page stuff could be localized, but we might not know target lang)
                        $searchTitle = Title::newFromText( $title->getInterwiki() . ":Special:Search" );
-                       $searchLink = Linker::linkKnown(
+                       $searchLink = $linkRenderer->makeKnownLink(
                                $searchTitle,
                                $this->msg( 'search-interwiki-more' )->text(),
                                [],
index 688a6ef..b18b370 100644 (file)
@@ -118,7 +118,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
                                        $pageClasses[] = 'mw-specialpagerestricted';
                                }
 
-                               $link = Linker::linkKnown( $title, htmlspecialchars( $desc ) );
+                               $link = $this->getLinkRenderer()->makeKnownLink( $title, $desc );
                                $out->addHTML( Html::rawElement(
                                                'li',
                                                [ 'class' => implode( ' ', $pageClasses ) ],
index 86f1e20..3342c32 100644 (file)
@@ -112,18 +112,20 @@ class SpecialStatistics extends SpecialPage {
         * @return string
         */
        private function getPageStats() {
+               $linkRenderer = $this->getLinkRenderer();
+
                $specialAllPagesTitle = SpecialPage::getTitleFor( 'Allpages' );
                $pageStatsHtml = Xml::openElement( 'tr' ) .
                        Xml::tags( 'th', [ 'colspan' => '2' ], $this->msg( 'statistics-header-pages' )
                                ->parse() ) .
                        Xml::closeElement( 'tr' ) .
-                               $this->formatRow( Linker::linkKnown( $specialAllPagesTitle,
-                                       $this->msg( 'statistics-articles' )->parse(), [], [ 'hideredirects' => 1 ] ),
+                               $this->formatRow( $linkRenderer->makeKnownLink( $specialAllPagesTitle,
+                                       $this->msg( 'statistics-articles' )->text(), [], [ 'hideredirects' => 1 ] ),
                                        $this->getLanguage()->formatNum( $this->good ),
                                        [ 'class' => 'mw-statistics-articles' ],
                                        'statistics-articles-desc' ) .
-                               $this->formatRow( Linker::linkKnown( $specialAllPagesTitle,
-                                       $this->msg( 'statistics-pages' )->parse() ),
+                               $this->formatRow( $linkRenderer->makeKnownLink( $specialAllPagesTitle,
+                                       $this->msg( 'statistics-pages' )->text() ),
                                        $this->getLanguage()->formatNum( $this->total ),
                                        [ 'class' => 'mw-statistics-pages' ],
                                        'statistics-pages-desc' );
@@ -131,8 +133,8 @@ class SpecialStatistics extends SpecialPage {
                // Show the image row only, when there are files or upload is possible
                if ( $this->images !== 0 || $this->getConfig()->get( 'EnableUploads' ) ) {
                        $pageStatsHtml .= $this->formatRow(
-                               Linker::linkKnown( SpecialPage::getTitleFor( 'MediaStatistics' ),
-                               $this->msg( 'statistics-files' )->parse() ),
+                               $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'MediaStatistics' ),
+                               $this->msg( 'statistics-files' )->text() ),
                                $this->getLanguage()->formatNum( $this->images ),
                                [ 'class' => 'mw-statistics-files' ] );
                }
@@ -166,9 +168,9 @@ class SpecialStatistics extends SpecialPage {
                                [ 'class' => 'mw-statistics-users' ]
                        ) .
                        $this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
-                               Linker::linkKnown(
+                               $this->getLinkRenderer()->makeKnownLink(
                                        SpecialPage::getTitleFor( 'Activeusers' ),
-                                       $this->msg( 'listgrouprights-members' )->escaped()
+                                       $this->msg( 'listgrouprights-members' )->text()
                                ),
                                $this->getLanguage()->formatNum( $this->activeUsers ),
                                [ 'class' => 'mw-statistics-users-active' ],
@@ -178,6 +180,7 @@ class SpecialStatistics extends SpecialPage {
        }
 
        private function getGroupStats() {
+               $linkRenderer = $this->getLinkRenderer();
                $text = '';
                foreach ( $this->getConfig()->get( 'GroupPermissions' ) as $group => $permissions ) {
                        # Skip generic * and implicit groups
@@ -200,17 +203,17 @@ class SpecialStatistics extends SpecialPage {
                        $linkTarget = Title::newFromText( $grouppageLocalized );
 
                        if ( $linkTarget ) {
-                               $grouppage = Linker::link(
+                               $grouppage = $linkRenderer->makeLink(
                                        $linkTarget,
-                                       htmlspecialchars( $groupnameLocalized )
+                                       $groupnameLocalized
                                );
                        } else {
                                $grouppage = htmlspecialchars( $groupnameLocalized );
                        }
 
-                       $grouplink = Linker::linkKnown(
+                       $grouplink = $linkRenderer->makeKnownLink(
                                SpecialPage::getTitleFor( 'Listusers' ),
-                               $this->msg( 'listgrouprights-members' )->escaped(),
+                               $this->msg( 'listgrouprights-members' )->text(),
                                [],
                                [ 'group' => $group ]
                        );
index 4c6a345..8ff0527 100644 (file)
@@ -89,19 +89,21 @@ class SpecialTrackingCategories extends SpecialPage {
 
                Hooks::run( 'SpecialTrackingCategories::preprocess', [ $this, $trackingCategories ] );
 
+               $linkRenderer = $this->getLinkRenderer();
+
                foreach ( $trackingCategories as $catMsg => $data ) {
                        $allMsgs = [];
                        $catDesc = $catMsg . '-desc';
 
-                       $catMsgTitleText = Linker::link(
+                       $catMsgTitleText = $linkRenderer->makeLink(
                                $data['msg'],
-                               htmlspecialchars( $catMsg )
+                               $catMsg
                        );
 
                        foreach ( $data['cats'] as $catTitle ) {
-                               $html = Linker::link(
+                               $html = $linkRenderer->makeLink(
                                        $catTitle,
-                                       htmlspecialchars( $catTitle->getText() )
+                                       $catTitle->getText()
                                );
 
                                Hooks::run( 'SpecialTrackingCategories::generateCatLink',
index cff8bf4..4e683f6 100644 (file)
@@ -119,14 +119,14 @@ class SpecialUnblock extends SpecialPage {
                                $fields['Target']['type'] = 'hidden';
                                switch ( $type ) {
                                        case Block::TYPE_IP:
-                                               $fields['Name']['default'] = Linker::linkKnown(
+                                               $fields['Name']['default'] = $this->getLinkRenderer()->makeKnownLink(
                                                        SpecialPage::getTitleFor( 'Contributions', $target->getName() ),
                                                        $target->getName()
                                                );
                                                $fields['Name']['raw'] = true;
                                                break;
                                        case Block::TYPE_USER:
-                                               $fields['Name']['default'] = Linker::link(
+                                               $fields['Name']['default'] = $this->getLinkRenderer()->makeLink(
                                                        $target->getUserPage(),
                                                        $target->getName()
                                                );
@@ -175,7 +175,7 @@ class SpecialUnblock extends SpecialPage {
         * @param array $data
         * @param IContextSource $context
         * @throws ErrorPageError
-        * @return array|bool Array(message key, parameters) on failure, True on success
+        * @return array|bool Array( Array( message key, parameters ) ) on failure, True on success
         */
        public static function processUnblock( array $data, IContextSource $context ) {
                $performer = $context->getUser();
@@ -211,7 +211,7 @@ class SpecialUnblock extends SpecialPage {
 
                # Delete block
                if ( !$block->delete() ) {
-                       return [ 'ipb_cant_unblock', htmlspecialchars( $block->getTarget() ) ];
+                       return [ [ 'ipb_cant_unblock', htmlspecialchars( $block->getTarget() ) ] ];
                }
 
                # Unset _deleted fields as needed
index 1cc40a9..86d8f89 100644 (file)
@@ -42,6 +42,6 @@ class UncategorizedCategoriesPage extends UncategorizedPagesPage {
                $title = Title::makeTitle( NS_CATEGORY, $result->title );
                $text = $title->getText();
 
-               return Linker::linkKnown( $title, htmlspecialchars( $text ) );
+               return $this->getLinkRenderer()->makeKnownLink( $title, $text );
        }
 }
index efac615..04f5be4 100644 (file)
@@ -997,14 +997,15 @@ class SpecialUndelete extends SpecialPage {
 
                $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
 
+               $linkRenderer = $this->getLinkRenderer();
                $undelete = $this->getPageTitle();
                $out->addHTML( "<ul>\n" );
                foreach ( $result as $row ) {
                        $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
                        if ( $title !== null ) {
-                               $item = Linker::linkKnown(
+                               $item = $linkRenderer->makeKnownLink(
                                        $undelete,
-                                       htmlspecialchars( $title->getPrefixedText() ),
+                                       $title->getPrefixedText(),
                                        [],
                                        [ 'target' => $title->getPrefixedText() ]
                                );
@@ -1083,9 +1084,9 @@ class SpecialUndelete extends SpecialPage {
                        }
                }
 
-               $link = Linker::linkKnown(
+               $link = $this->getLinkRenderer()->makeKnownLink(
                        $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
-                       htmlspecialchars( $this->mTargetObj->getPrefixedText() )
+                       $this->mTargetObj->getPrefixedText()
                );
 
                $lang = $this->getLanguage();
@@ -1259,14 +1260,14 @@ class SpecialUndelete extends SpecialPage {
                // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
                // and partially #showDiffPage, but worse
                return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
-                       Linker::link(
+                       $this->getLinkRenderer()->makeLink(
                                $targetPage,
                                $this->msg(
                                        'revisionasof',
                                        $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
                                        $lang->userDate( $rev->getTimestamp(), $user ),
                                        $lang->userTime( $rev->getTimestamp(), $user )
-                               )->escaped(),
+                               )->text(),
                                [],
                                $targetQuery
                        ) .
@@ -1550,9 +1551,9 @@ class SpecialUndelete extends SpecialPage {
                                $last = $this->msg( 'diff' )->escaped();
                        } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
                                $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
-                               $last = Linker::linkKnown(
+                               $last = $this->getLinkRenderer()->makeKnownLink(
                                        $titleObj,
-                                       $this->msg( 'diff' )->escaped(),
+                                       $this->msg( 'diff' )->text(),
                                        [],
                                        [
                                                'target' => $this->mTargetObj->getPrefixedText(),
@@ -1672,9 +1673,9 @@ class SpecialUndelete extends SpecialPage {
                        return '<span class="history-deleted">' . $time . '</span>';
                }
 
-               $link = Linker::linkKnown(
+               $link = $this->getLinkRenderer()->makeKnownLink(
                        $titleObj,
-                       htmlspecialchars( $time ),
+                       $time,
                        [],
                        [
                                'target' => $this->mTargetObj->getPrefixedText(),
@@ -1707,9 +1708,9 @@ class SpecialUndelete extends SpecialPage {
                        return '<span class="history-deleted">' . $time . '</span>';
                }
 
-               $link = Linker::linkKnown(
+               $link = $this->getLinkRenderer()->makeKnownLink(
                        $titleObj,
-                       htmlspecialchars( $time ),
+                       $time,
                        [],
                        [
                                'target' => $this->mTargetObj->getPrefixedText(),
@@ -1796,7 +1797,7 @@ class SpecialUndelete extends SpecialPage {
                                        $this->getUser(), $this->mComment ] );
                        }
 
-                       $link = Linker::linkKnown( $this->mTargetObj );
+                       $link = $this->getLinkRenderer()->makeKnownLink( $this->mTargetObj );
                        $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
                } else {
                        $out->setPageTitle( $this->msg( 'undelete-error' ) );
index 88c0e21..ec39ccf 100644 (file)
@@ -70,7 +70,7 @@ class UnusedCategoriesPage extends QueryPage {
        function formatResult( $skin, $result ) {
                $title = Title::makeTitle( NS_CATEGORY, $result->title );
 
-               return Linker::link( $title, htmlspecialchars( $title->getText() ) );
+               return $this->getLinkRenderer()->makeLink( $title, $title->getText() );
        }
 
        protected function getGroupName() {
index ae375b2..96878a3 100644 (file)
@@ -116,10 +116,12 @@ class UnwatchedpagesPage extends QueryPage {
 
                $text = $wgContLang->convert( $nt->getPrefixedText() );
 
-               $plink = Linker::linkKnown( $nt, htmlspecialchars( $text ) );
-               $wlink = Linker::linkKnown(
+               $linkRenderer = $this->getLinkRenderer();
+
+               $plink = $linkRenderer->makeKnownLink( $nt, $text );
+               $wlink = $linkRenderer->makeKnownLink(
                        $nt,
-                       $this->msg( 'watch' )->escaped(),
+                       $this->msg( 'watch' )->text(),
                        [ 'class' => 'mw-watch-link' ],
                        [ 'action' => 'watch' ]
                );
index 6ded6d9..1d9c057 100644 (file)
@@ -88,6 +88,10 @@ class UserrightsPage extends SpecialPage {
                        $this->mTarget = trim( $this->mTarget );
                }
 
+               if ( $this->mTarget !== null && User::getCanonicalName( $this->mTarget ) === $user->getName() ) {
+                       $this->isself = true;
+               }
+
                $fetchedStatus = $this->fetchUser( $this->mTarget, true );
                if ( $fetchedStatus->isOK() ) {
                        $this->mFetchedUser = $fetchedStatus->value;
@@ -331,7 +335,7 @@ class UserrightsPage extends SpecialPage {
         * @param bool $writing
         * @return Status
         */
-       public function fetchUser( $username, $writing ) {
+       public function fetchUser( $username, $writing = true ) {
                $parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username );
                if ( count( $parts ) < 2 ) {
                        $name = trim( $username );
@@ -549,9 +553,14 @@ class UserrightsPage extends SpecialPage {
                        Xml::element(
                                'legend',
                                [],
-                               $this->msg( 'userrights-editusergroup', $user->getName() )->text()
+                               $this->msg(
+                                       $canChangeAny ? 'userrights-editusergroup' : 'userrights-viewusergroup',
+                                       $user->getName()
+                               )->text()
                        ) .
-                       $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )
+                       $this->msg(
+                               $canChangeAny ? 'editinguser' : 'viewinguserrights'
+                       )->params( wfEscapeWikiText( $user->getName() ) )
                                ->rawParams( $userToolLinks )->parse()
                );
                if ( $canChangeAny ) {
index 2cd492e..272f074 100644 (file)
@@ -785,12 +785,12 @@ class SpecialVersion extends SpecialPage {
                if ( isset( $extension['name'] ) ) {
                        $licenseName = null;
                        if ( isset( $extension['license-name'] ) ) {
-                               $licenseName = $out->parseInline( $extension['license-name'] );
+                               $licenseName = new HtmlArmor( $out->parseInline( $extension['license-name'] ) );
                        } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
-                               $licenseName = $this->msg( 'version-ext-license' )->escaped();
+                               $licenseName = $this->msg( 'version-ext-license' )->text();
                        }
                        if ( $licenseName !== null ) {
-                               $licenseLink = Linker::link(
+                               $licenseLink = $this->getLinkRenderer()->makeLink(
                                        $this->getPageTitle( 'License/' . $extension['name'] ),
                                        $licenseName,
                                        [
@@ -956,6 +956,7 @@ class SpecialVersion extends SpecialPage {
         */
        public function listAuthors( $authors, $extName, $extDir ) {
                $hasOthers = false;
+               $linkRenderer = $this->getLinkRenderer();
 
                $list = [];
                foreach ( (array)$authors as $item ) {
@@ -963,9 +964,9 @@ class SpecialVersion extends SpecialPage {
                                $hasOthers = true;
 
                                if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
-                                       $text = Linker::link(
+                                       $text = $linkRenderer->makeLink(
                                                $this->getPageTitle( "Credits/$extName" ),
-                                               $this->msg( 'version-poweredby-others' )->escaped()
+                                               $this->msg( 'version-poweredby-others' )->text()
                                        );
                                } else {
                                        $text = $this->msg( 'version-poweredby-others' )->escaped();
@@ -982,9 +983,9 @@ class SpecialVersion extends SpecialPage {
                }
 
                if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
-                       $list[] = $text = Linker::link(
+                       $list[] = $text = $linkRenderer->makeLink(
                                $this->getPageTitle( "Credits/$extName" ),
-                               $this->msg( 'version-poweredby-others' )->escaped()
+                               $this->msg( 'version-poweredby-others' )->text()
                        );
                }
 
index 9bf44ad..fc0c312 100644 (file)
@@ -91,20 +91,17 @@ class WantedCategoriesPage extends WantedQueryPage {
                global $wgContLang;
 
                $nt = Title::makeTitle( $result->namespace, $result->title );
-               $text = htmlspecialchars( $wgContLang->convert( $nt->getText() ) );
+               $text = $wgContLang->convert( $nt->getText() );
 
                if ( !$this->isCached() ) {
                        // We can assume the freshest data
-                       $plink = Linker::link(
+                       $plink = $this->getLinkRenderer()->makeBrokenLink(
                                $nt,
-                               $text,
-                               [],
-                               [],
-                               [ 'broken' ]
+                               $text
                        );
                        $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
                } else {
-                       $plink = Linker::link( $nt, $text );
+                       $plink = $this->getLinkRenderer()->makeLink( $nt, $text );
 
                        $currentValue = isset( $this->currentCategoryCounts[$result->title] )
                                ? $this->currentCategoryCounts[$result->title]
index 4824961..85ac2de 100644 (file)
@@ -52,6 +52,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $this->addHelpLink( 'Help:Watching pages' );
                $output->addModules( [
                        'mediawiki.special.changeslist.visitedstatus',
+                       'mediawiki.special.watchlist',
                ] );
 
                $mode = SpecialEditWatchlist::getMode( $request, $subpage );
@@ -130,7 +131,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
        }
 
        /**
-        * Get custom show/hide filters
+        * Get all custom filters
         *
         * @return array Map of filter URL param names to properties (msg/default)
         */
@@ -421,12 +422,6 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $user = $this->getUser();
                $out = $this->getOutput();
 
-               // if the user wishes, that the watchlist is reloaded, whenever a filter changes,
-               // add the module for that
-               if ( $user->getBoolOption( 'watchlistreloadautomatically' ) ) {
-                       $out->addModules( [ 'mediawiki.special.watchlist' ] );
-               }
-
                $out->addSubtitle(
                        $this->msg( 'watchlistfor2', $user->getName() )
                                ->rawParams( SpecialEditWatchlist::buildTools(
@@ -465,9 +460,10 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        $filters['hidecategorization'] = 'wlshowhidecategorization';
                }
 
-               foreach ( $this->getCustomFilters() as $key => $params ) {
+               foreach ( $this->getRenderableCustomFilters( $this->getCustomFilters() ) as $key => $params ) {
                        $filters[$key] = $params['msg'];
                }
+
                // Disable some if needed
                if ( !$user->useRCPatrol() ) {
                        unset( $filters['hidepatrolled'] );
@@ -608,7 +604,8 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        $form .= Xml::openElement( 'form', [ 'method' => 'post',
                                'action' => $this->getPageTitle()->getLocalURL(),
                                'id' => 'mw-watchlist-resetbutton' ] ) . "\n" .
-                       Xml::submitButton( $this->msg( 'enotif_reset' )->text(), [ 'name' => 'dummy' ] ) . "\n" .
+                       Xml::submitButton( $this->msg( 'enotif_reset' )->text(),
+                               [ 'name' => 'mw-watchlist-reset-submit' ] ) . "\n" .
                        Html::hidden( 'reset', 'all' ) . "\n";
                        foreach ( $nondefaults as $key => $value ) {
                                $form .= Html::hidden( $key, $value ) . "\n";
index 1ead290..439b6ab 100644 (file)
@@ -327,7 +327,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                        $query = [];
                }
 
-               $link = Linker::linkKnown(
+               $link = $this->getLinkRenderer()->makeKnownLink(
                        $nt,
                        null,
                        $row->page_is_redirect ? [ 'class' => 'mw-redirect' ] : [],
@@ -376,9 +376,15 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                        $title = $this->getPageTitle();
                }
 
+               $linkRenderer = $this->getLinkRenderer();
+
+               if ( $text !== null ) {
+                       $text = new HtmlArmor( $text );
+               }
+
                // always show a "<- Links" link
                $links = [
-                       'links' => Linker::linkKnown(
+                       'links' => $linkRenderer->makeKnownLink(
                                $title,
                                $text,
                                [],
@@ -393,7 +399,11 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                        // check, if the content model is editable through action=edit
                        ContentHandler::getForTitle( $target )->supportsDirectEditing()
                ) {
-                       $links['edit'] = Linker::linkKnown(
+                       if ( $editText !== null ) {
+                               $editText = new HtmlArmor( $editText );
+                       }
+
+                       $links['edit'] = $linkRenderer->makeKnownLink(
                                $target,
                                $editText,
                                [],
@@ -406,7 +416,11 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
        }
 
        function makeSelfLink( $text, $query ) {
-               return Linker::linkKnown(
+               if ( $text !== null ) {
+                       $text = new HtmlArmor( $text );
+               }
+
+               return $this->getLinkRenderer()->makeKnownLink(
                        $this->selfTitle,
                        $text,
                        [],
index 5609310..efc51ef 100644 (file)
@@ -25,6 +25,8 @@
  *
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class AllMessagesTablePager extends TablePager {
 
        protected $filter, $prefix, $langcode, $displayPrefix;
@@ -297,6 +299,7 @@ class AllMessagesTablePager extends TablePager {
        }
 
        function formatValue( $field, $value ) {
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                switch ( $field ) {
                        case 'am_title' :
                                $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
@@ -313,25 +316,19 @@ class AllMessagesTablePager extends TablePager {
                                );
 
                                if ( $this->mCurrentRow->am_customised ) {
-                                       $title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) );
+                                       $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
                                } else {
-                                       $title = Linker::link(
+                                       $title = $linkRenderer->makeBrokenLink(
                                                $title,
-                                               $this->getLanguage()->lcfirst( $value ),
-                                               [],
-                                               [],
-                                               [ 'broken' ]
+                                               $this->getLanguage()->lcfirst( $value )
                                        );
                                }
                                if ( $this->mCurrentRow->am_talk_exists ) {
-                                       $talk = Linker::linkKnown( $talk, $this->talk );
+                                       $talk = $linkRenderer->makeKnownLink( $talk, $this->talk );
                                } else {
-                                       $talk = Linker::link(
+                                       $talk = $linkRenderer->makeBrokenLink(
                                                $talk,
-                                               $this->talk,
-                                               [],
-                                               [],
-                                               [ 'broken' ]
+                                               $this->talk
                                        );
                                }
 
index d822976..a4124db 100644 (file)
@@ -22,6 +22,8 @@
 /**
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class BlockListPager extends TablePager {
 
        protected $conds;
@@ -72,7 +74,7 @@ class BlockListPager extends TablePager {
                        ];
 
                        foreach ( $keys as $key ) {
-                               $msg[$key] = $this->msg( $key )->escaped();
+                               $msg[$key] = $this->msg( $key )->text();
                        }
                }
 
@@ -83,6 +85,8 @@ class BlockListPager extends TablePager {
 
                $formatted = '';
 
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+
                switch ( $name ) {
                        case 'ipb_timestamp':
                                $formatted = htmlspecialchars( $language->userTimeAndDate( $value, $this->getUser() ) );
@@ -117,18 +121,18 @@ class BlockListPager extends TablePager {
                                ) );
                                if ( $this->getUser()->isAllowed( 'block' ) ) {
                                        if ( $row->ipb_auto ) {
-                                               $links[] = Linker::linkKnown(
+                                               $links[] = $linkRenderer->makeKnownLink(
                                                        SpecialPage::getTitleFor( 'Unblock' ),
                                                        $msg['unblocklink'],
                                                        [],
                                                        [ 'wpTarget' => "#{$row->ipb_id}" ]
                                                );
                                        } else {
-                                               $links[] = Linker::linkKnown(
+                                               $links[] = $linkRenderer->makeKnownLink(
                                                        SpecialPage::getTitleFor( 'Unblock', $row->ipb_address ),
                                                        $msg['unblocklink']
                                                );
-                                               $links[] = Linker::linkKnown(
+                                               $links[] = $linkRenderer->makeKnownLink(
                                                        SpecialPage::getTitleFor( 'Block', $row->ipb_address ),
                                                        $msg['change-blocklink']
                                                );
@@ -174,21 +178,21 @@ class BlockListPager extends TablePager {
                        case 'ipb_params':
                                $properties = [];
                                if ( $row->ipb_anon_only ) {
-                                       $properties[] = $msg['anononlyblock'];
+                                       $properties[] = htmlspecialchars( $msg['anononlyblock'] );
                                }
                                if ( $row->ipb_create_account ) {
-                                       $properties[] = $msg['createaccountblock'];
+                                       $properties[] = htmlspecialchars( $msg['createaccountblock'] );
                                }
                                if ( $row->ipb_user && !$row->ipb_enable_autoblock ) {
-                                       $properties[] = $msg['noautoblockblock'];
+                                       $properties[] = htmlspecialchars( $msg['noautoblockblock'] );
                                }
 
                                if ( $row->ipb_block_email ) {
-                                       $properties[] = $msg['emailblock'];
+                                       $properties[] = htmlspecialchars( $msg['emailblock'] );
                                }
 
                                if ( !$row->ipb_allow_usertalk ) {
-                                       $properties[] = $msg['blocklist-nousertalk'];
+                                       $properties[] = htmlspecialchars( $msg['blocklist-nousertalk'] );
                                }
 
                                $formatted = $language->commaList( $properties );
index b78fed8..345577d 100644 (file)
@@ -52,7 +52,6 @@ class CategoryPager extends AlphabeticPager {
                return [
                        'tables' => [ 'category' ],
                        'fields' => [ 'cat_title', 'cat_pages' ],
-                       'conds' => [ 'cat_pages > 0' ],
                        'options' => [ 'USE INDEX' => 'cat_title' ],
                ];
        }
index a145e45..39c55c8 100644 (file)
@@ -23,6 +23,8 @@
  * Pager for Special:Contributions
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class ContribsPager extends ReverseChronologicalPager {
 
        public $mDefaultDirection = IndexPager::DIR_DESCENDING;
@@ -347,6 +349,8 @@ class ContribsPager extends ReverseChronologicalPager {
                $ret = '';
                $classes = [];
 
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+
                /*
                 * There may be more than just revision rows. To make sure that we'll only be processing
                 * revisions here, let's _try_ to build a revision out of our row (without displaying
@@ -367,9 +371,9 @@ class ContribsPager extends ReverseChronologicalPager {
                        $classes = [];
 
                        $page = Title::newFromRow( $row );
-                       $link = Linker::link(
+                       $link = $linkRenderer->makeLink(
                                $page,
-                               htmlspecialchars( $page->getPrefixedText() ),
+                               $page->getPrefixedText(),
                                [ 'class' => 'mw-contributions-title' ],
                                $page->isRedirect() ? [ 'redirect' => 'no' ] : []
                        );
@@ -389,9 +393,9 @@ class ContribsPager extends ReverseChronologicalPager {
                        }
                        # Is there a visible previous revision?
                        if ( $rev->userCan( Revision::DELETED_TEXT, $user ) && $rev->getParentId() !== 0 ) {
-                               $difftext = Linker::linkKnown(
+                               $difftext = $linkRenderer->makeKnownLink(
                                        $page,
-                                       $this->messages['diff'],
+                                       new HtmlArmor( $this->messages['diff'] ),
                                        [],
                                        [
                                                'diff' => 'prev',
@@ -401,9 +405,9 @@ class ContribsPager extends ReverseChronologicalPager {
                        } else {
                                $difftext = $this->messages['diff'];
                        }
-                       $histlink = Linker::linkKnown(
+                       $histlink = $linkRenderer->makeKnownLink(
                                $page,
-                               $this->messages['hist'],
+                               new HtmlArmor( $this->messages['hist'] ),
                                [],
                                [ 'action' => 'history' ]
                        );
@@ -434,9 +438,9 @@ class ContribsPager extends ReverseChronologicalPager {
                        $comment = $lang->getDirMark() . Linker::revComment( $rev, false, true );
                        $date = $lang->userTimeAndDate( $row->rev_timestamp, $user );
                        if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
-                               $d = Linker::linkKnown(
+                               $d = $linkRenderer->makeKnownLink(
                                        $page,
-                                       htmlspecialchars( $date ),
+                                       $date,
                                        [ 'class' => 'mw-changeslist-date' ],
                                        [ 'oldid' => intval( $row->rev_id ) ]
                                );
index 1acbba1..9ffcce9 100644 (file)
@@ -22,6 +22,8 @@
 /**
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class DeletedContribsPager extends IndexPager {
 
        public $mDefaultDirection = IndexPager::DIR_DESCENDING;
@@ -39,7 +41,7 @@ class DeletedContribsPager extends IndexPager {
                parent::__construct( $context );
                $msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
                foreach ( $msgs as $msg ) {
-                       $this->messages[$msg] = $this->msg( $msg )->escaped();
+                       $this->messages[$msg] = $this->msg( $msg )->text();
                }
                $this->target = $target;
                $this->namespace = $namespace;
@@ -240,6 +242,8 @@ class DeletedContribsPager extends IndexPager {
        function formatRevisionRow( $row ) {
                $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
 
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+
                $rev = new Revision( [
                        'title' => $page,
                        'id' => $row->ar_rev_id,
@@ -254,7 +258,7 @@ class DeletedContribsPager extends IndexPager {
                $undelete = SpecialPage::getTitleFor( 'Undelete' );
 
                $logs = SpecialPage::getTitleFor( 'Log' );
-               $dellog = Linker::linkKnown(
+               $dellog = $linkRenderer->makeKnownLink(
                        $logs,
                        $this->messages['deletionlog'],
                        [],
@@ -264,7 +268,7 @@ class DeletedContribsPager extends IndexPager {
                        ]
                );
 
-               $reviewlink = Linker::linkKnown(
+               $reviewlink = $linkRenderer->makeKnownLink(
                        SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
                        $this->messages['undeleteviewlink']
                );
@@ -272,7 +276,7 @@ class DeletedContribsPager extends IndexPager {
                $user = $this->getUser();
 
                if ( $user->isAllowed( 'deletedtext' ) ) {
-                       $last = Linker::linkKnown(
+                       $last = $linkRenderer->makeKnownLink(
                                $undelete,
                                $this->messages['diff'],
                                [],
@@ -283,17 +287,16 @@ class DeletedContribsPager extends IndexPager {
                                ]
                        );
                } else {
-                       $last = $this->messages['diff'];
+                       $last = htmlspecialchars( $this->messages['diff'] );
                }
 
                $comment = Linker::revComment( $rev );
                $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user );
-               $date = htmlspecialchars( $date );
 
                if ( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
-                       $link = $date; // unusable link
+                       $link = htmlspecialchars( $date ); // unusable link
                } else {
-                       $link = Linker::linkKnown(
+                       $link = $linkRenderer->makeKnownLink(
                                $undelete,
                                $date,
                                [ 'class' => 'mw-changeslist-date' ],
@@ -308,7 +311,7 @@ class DeletedContribsPager extends IndexPager {
                        $link = '<span class="history-deleted">' . $link . '</span>';
                }
 
-               $pagelink = Linker::link(
+               $pagelink = $linkRenderer->makeLink(
                        $page,
                        null,
                        [ 'class' => 'mw-changeslist-title' ]
index 7fc4a95..59dea02 100644 (file)
@@ -22,6 +22,8 @@
 /**
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class ImageListPager extends TablePager {
 
        protected $mFieldNames = null;
@@ -422,6 +424,7 @@ class ImageListPager extends TablePager {
         * @throws MWException
         */
        function formatValue( $field, $value ) {
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
                switch ( $field ) {
                        case 'thumb':
                                $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
@@ -449,9 +452,9 @@ class ImageListPager extends TablePager {
                                // Weird files can maybe exist? Bug 22227
                                $filePage = Title::makeTitleSafe( NS_FILE, $value );
                                if ( $filePage ) {
-                                       $link = Linker::linkKnown(
+                                       $link = $linkRenderer->makeKnownLink(
                                                $filePage,
-                                               htmlspecialchars( $filePage->getText() )
+                                               $filePage->getText()
                                        );
                                        $download = Xml::element( 'a',
                                                [ 'href' => wfLocalFile( $filePage )->getUrl() ],
@@ -462,9 +465,9 @@ class ImageListPager extends TablePager {
                                        // Add delete links if allowed
                                        // From https://github.com/Wikia/app/pull/3859
                                        if ( $filePage->userCan( 'delete', $this->getUser() ) ) {
-                                               $deleteMsg = $this->msg( 'listfiles-delete' )->escaped();
+                                               $deleteMsg = $this->msg( 'listfiles-delete' )->text();
 
-                                               $delete = Linker::linkKnown(
+                                               $delete = $linkRenderer->makeKnownLink(
                                                        $filePage, $deleteMsg, [], [ 'action' => 'delete' ]
                                                );
                                                $delete = $this->msg( 'parentheses' )->rawParams( $delete )->escaped();
@@ -479,9 +482,9 @@ class ImageListPager extends TablePager {
                        case 'img_user_text':
                                if ( $this->mCurrentRow->img_user ) {
                                        $name = User::whoIs( $this->mCurrentRow->img_user );
-                                       $link = Linker::link(
+                                       $link = $linkRenderer->makeLink(
                                                Title::makeTitle( NS_USER, $name ),
-                                               htmlspecialchars( $name )
+                                               $name
                                        );
                                } else {
                                        $link = htmlspecialchars( $value );
index 41819fc..9f6c58c 100644 (file)
@@ -22,6 +22,8 @@
 /**
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class NewFilesPager extends ReverseChronologicalPager {
 
        /**
@@ -142,7 +144,10 @@ class NewFilesPager extends ReverseChronologicalPager {
                $user = User::newFromId( $row->img_user );
 
                $title = Title::makeTitle( NS_FILE, $name );
-               $ul = Linker::link( $user->getUserPage(), $user->getName() );
+               $ul = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
+                       $user->getUserPage(),
+                       $user->getName()
+               );
                $time = $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() );
 
                $this->gallery->add(
index 08cf434..449fc05 100644 (file)
@@ -222,7 +222,7 @@ class UploadFromChunks extends UploadFromFile {
                                        $this->verifyChunk();
                                        $this->mTempPath = $oldTemp;
                                } catch ( UploadChunkVerificationException $e ) {
-                                       return Status::newFatal( $e->getMessage() );
+                                       return Status::newFatal( $e->msg );
                                }
                                $status = $this->outputChunk( $chunkPath );
                                if ( $status->isGood() ) {
@@ -364,7 +364,7 @@ class UploadFromChunks extends UploadFromFile {
                $this->mDesiredDestName = $oldDesiredDestName;
                $this->mTitle = false;
                if ( is_array( $res ) ) {
-                       throw new UploadChunkVerificationException( $res[0] );
+                       throw new UploadChunkVerificationException( $res );
                }
        }
 }
@@ -376,4 +376,10 @@ class UploadChunkFileException extends MWException {
 }
 
 class UploadChunkVerificationException extends MWException {
+       public $msg;
+       public function __construct( $res ) {
+               $this->msg = call_user_func_array( 'wfMessage', $res );
+               parent::__construct( call_user_func_array( 'wfMessage', $res )
+                       ->inLanguage( 'en' )->useDatabase( false )->text() );
+       }
 }
index 82d8806..f1742b3 100644 (file)
@@ -677,7 +677,7 @@ class User implements IDBAccessObject {
                );
                if ( !$row ) {
                        // No user. Create it?
-                       return $options['create'] ? self::createNew( $name ) : null;
+                       return $options['create'] ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] ) : null;
                }
                $user = self::newFromRow( $row );
 
@@ -1003,11 +1003,10 @@ class User implements IDBAccessObject {
         * able to set their password to this.
         *
         * @param string $password Desired password
-        * @param string $purpose one of 'login', 'create', 'reset'
         * @return Status
         * @since 1.23
         */
-       public function checkPasswordValidity( $password, $purpose = 'login' ) {
+       public function checkPasswordValidity( $password ) {
                global $wgPasswordPolicy;
 
                $upp = new UserPasswordPolicy(
@@ -1024,7 +1023,7 @@ class User implements IDBAccessObject {
                }
 
                if ( $result === false ) {
-                       $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) );
+                       $status->merge( $upp->checkUserPassword( $this, $password ) );
                        return $status;
                } elseif ( $result === true ) {
                        return $status;
@@ -1098,20 +1097,6 @@ class User implements IDBAccessObject {
                return $name;
        }
 
-       /**
-        * Count the number of edits of a user
-        *
-        * @param int $uid User ID to check
-        * @return int The user's edit count
-        *
-        * @deprecated since 1.21 in favour of User::getEditCount
-        */
-       public static function edits( $uid ) {
-               wfDeprecated( __METHOD__, '1.21' );
-               $user = self::newFromId( $uid );
-               return $user->getEditCount();
-       }
-
        /**
         * Return a random password.
         *
@@ -2806,7 +2791,7 @@ class User implements IDBAccessObject {
         * @param string $oname The option to check
         * @param string $defaultOverride A default value returned if the option does not exist
         * @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs
-        * @return string User's current value for the option
+        * @return string|null User's current value for the option
         * @see getBoolOption()
         * @see getIntOption()
         */
@@ -5066,13 +5051,27 @@ class User implements IDBAccessObject {
        /**
         * Get the description of a given right
         *
+        * @since 1.29
         * @param string $right Right to query
         * @return string Localized description of the right
         */
        public static function getRightDescription( $right ) {
                $key = "right-$right";
                $msg = wfMessage( $key );
-               return $msg->isBlank() ? $right : $msg->text();
+               return $msg->isDisabled() ? $right : $msg->text();
+       }
+
+       /**
+        * Get the name of a given grant
+        *
+        * @since 1.29
+        * @param string $grant Grant to query
+        * @return string Localized name of the grant
+        */
+       public static function getGrantName( $grant ) {
+               $key = "grant-$grant";
+               $msg = wfMessage( $key );
+               return $msg->isDisabled() ? $grant : $msg->text();
        }
 
        /**
index 516e9ae..212f325 100644 (file)
@@ -26,7 +26,6 @@
  *
  * Only a functional interface is provided: ZipFileReader::read(). No access is
  * given to object instances.
- *
  */
 class ZipDirectoryReader {
        /**
index 2e4ef89..3eb6546 100644 (file)
@@ -466,6 +466,7 @@ class Language {
                        $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
                        $validNamespaces = MWNamespace::getCanonicalNamespaces();
 
+                       /** @suppress PhanTypeInvalidLeftOperand */
                        $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
 
                        $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
index a15d910..4594385 100644 (file)
@@ -3110,7 +3110,6 @@ public static $zh2Hant = [
 '一干部下' => '一干部下',
 '一年' => '一年',
 '一年里' => '一年裡',
-'一别头' => '一彆頭',
 '一斗斗' => '一斗斗',
 '一树百获' => '一樹百穫',
 '一准' => '一準',
@@ -3342,7 +3341,6 @@ public static $zh2Hant = [
 '干嚎' => '乾嚎',
 '干回付' => '乾回付',
 '干圆洁净' => '乾圓潔淨',
-'干地' => '乾地',
 '干坞' => '乾塢',
 '干女' => '乾女',
 '干奴才' => '乾奴才',
@@ -4124,6 +4122,8 @@ public static $zh2Hant = [
 '划過' => '划過',
 '划龍舟' => '划龍舟',
 '划龙舟' => '划龍舟',
+'划龍船' => '划龍船',
+'划龙船' => '划龍船',
 '判断发' => '判斷發',
 '别辟' => '別闢',
 '利欲' => '利慾',
@@ -4430,7 +4430,6 @@ public static $zh2Hant = [
 '吸干' => '吸乾',
 '吹干' => '吹乾',
 '吹发' => '吹髮',
-'吹胡' => '吹鬍',
 '吾为之范我驰驱' => '吾爲之範我馳驅',
 '吕后' => '呂后',
 '呂后' => '呂后',
@@ -4460,6 +4459,8 @@ public static $zh2Hant = [
 '哀挽' => '哀輓',
 '品鉴' => '品鑑',
 '哄堂大笑' => '哄堂大笑',
+'哈啰喂' => '哈囉喂',
+'哈囉喂' => '哈囉喂',
 '員山庄' => '員山庄',
 '哪里' => '哪裡',
 '唁吊' => '唁弔',
@@ -4483,7 +4484,6 @@ public static $zh2Hant = [
 '喂!' => '喂!',
 '喂,' => '喂,',
 '善于' => '善於',
-'喜向往' => '喜向往',
 '喜欢表' => '喜歡錶',
 '喜欢钟' => '喜歡鐘',
 '喜欢钟表' => '喜歡鐘錶',
@@ -4494,6 +4494,8 @@ public static $zh2Hant = [
 '乔岳' => '喬嶽',
 '单于' => '單于',
 '單于' => '單于',
+'单向' => '單向',
+'單向' => '單向',
 '单单于' => '單單於',
 '单干' => '單幹',
 '单打独斗' => '單打獨鬥',
@@ -4583,7 +4585,6 @@ public static $zh2Hant = [
 '在于' => '在於',
 '地图里' => '地圖裡',
 '地心历表' => '地心曆表',
-'地方志' => '地方志',
 '地志' => '地誌',
 '地丑德齐' => '地醜德齊',
 '坏于' => '坏於',
@@ -4722,7 +4723,6 @@ public static $zh2Hant = [
 '天后宫' => '天后宮',
 '天地志狼' => '天地志狼',
 '天地为范' => '天地為範',
-'天干地支' => '天干地支',
 '天后来' => '天後來',
 '天后半' => '天後半',
 '天后天' => '天後天',
@@ -4866,7 +4866,6 @@ public static $zh2Hant = [
 '封為后' => '封為后',
 '封面里' => '封面裡',
 '射雕' => '射鵰',
-'专向往' => '專向往',
 '专辑里' => '專輯裡',
 '尊后' => '尊后',
 '对不准' => '對不準',
@@ -4947,6 +4946,7 @@ public static $zh2Hant = [
 '山里的' => '山裡的',
 '山谷' => '山谷',
 '山重水复' => '山重水複',
+'岩松了' => '岩松了',
 '岫岩' => '岫巖',
 '岱岳' => '岱嶽',
 '峇里海' => '峇里海',
@@ -5011,6 +5011,7 @@ public static $zh2Hant = [
 '年代里' => '年代裡',
 '年历' => '年曆',
 '年历史' => '年歷史',
+'年历次' => '年歷次',
 '年谷' => '年穀',
 '年里' => '年裡',
 '年鉴' => '年鑑',
@@ -5166,8 +5167,6 @@ public static $zh2Hant = [
 '强奸' => '強姦',
 '强干' => '強幹',
 '强于' => '強於',
-'别口气' => '彆口氣',
-'别强' => '彆強',
 '别扭' => '彆扭',
 '别拗' => '彆拗',
 '别气' => '彆氣',
@@ -5403,6 +5402,7 @@ public static $zh2Hant = [
 '怠于' => '怠於',
 '急于' => '急於',
 '急冲而下' => '急衝而下',
+'性别扭曲' => '性別扭曲',
 '性征' => '性徵',
 '性欲' => '性慾',
 '怨气冲天' => '怨氣衝天',
@@ -5573,7 +5573,6 @@ public static $zh2Hant = [
 '抓奸' => '抓姦',
 '抓斗' => '抓鬥',
 '抗御' => '抗禦',
-'折向往' => '折向往',
 '折子戏' => '折子戲',
 '折子戲' => '折子戲',
 '折戟沈河' => '折戟沈河',
@@ -5682,6 +5681,7 @@ public static $zh2Hant = [
 '卷款' => '捲款',
 '卷毛' => '捲毛',
 '卷烟盒' => '捲煙盒',
+'卷瓣' => '捲瓣',
 '卷积云' => '捲積雲',
 '卷筒' => '捲筒',
 '卷帘' => '捲簾',
@@ -5927,10 +5927,8 @@ public static $zh2Hant = [
 '断发' => '斷髮',
 '断发文身' => '斷髮文身',
 '方便面' => '方便麵',
-'方向往' => '方向往',
-'方志恒' => '方志恒',
+'方向' => '方向',
 '方法里' => '方法裡',
-'方志' => '方誌',
 '于后' => '於後',
 '于征' => '於徵',
 '于海上' => '於海上',
@@ -6625,6 +6623,7 @@ public static $zh2Hant = [
 '煮面' => '煮麵',
 '熊杰' => '熊杰',
 '荧郁' => '熒鬱',
+'熬制' => '熬製',
 '炖制' => '燉製',
 '燎发' => '燎髮',
 '烧干' => '燒乾',
@@ -6791,7 +6790,6 @@ public static $zh2Hant = [
 '白里透红' => '白裡透紅',
 '白面包青天' => '白面包青天',
 '白发' => '白髮',
-'白胡' => '白鬍',
 '白霉' => '白黴',
 '百个' => '百個',
 '百只可' => '百只可',
@@ -6836,7 +6834,6 @@ public static $zh2Hant = [
 '皮里阳秋' => '皮裡陽秋',
 '皮制' => '皮製',
 '皮松' => '皮鬆',
-'皱别' => '皺彆',
 '皱折' => '皺摺',
 '盆吊' => '盆弔',
 '盈余' => '盈餘',
@@ -7224,7 +7221,6 @@ public static $zh2Hant = [
 '编码表' => '編碼表',
 '编钟' => '編鐘',
 '编余' => '編餘',
-'编发' => '編髮',
 '缓征' => '緩徵',
 '缓冲' => '緩衝',
 '致密' => '緻密',
@@ -7272,6 +7268,7 @@ public static $zh2Hant = [
 '系紧' => '繫緊',
 '系绳' => '繫繩',
 '系累' => '繫纍',
+'系膜' => '繫膜',
 '系舟' => '繫舟',
 '系船' => '繫船',
 '系辞' => '繫辭',
@@ -7468,7 +7465,6 @@ public static $zh2Hant = [
 '艸木丰丰' => '艸木丰丰',
 '芒果干' => '芒果乾',
 '花不要采' => '花不要採',
-'花卷' => '花捲',
 '花盆里' => '花盆裡',
 '花菴词选' => '花菴詞選',
 '花药' => '花葯',
@@ -7851,7 +7847,10 @@ public static $zh2Hant = [
 '西昆' => '西崑',
 '西岳' => '西嶽',
 '西历' => '西曆',
+'西历代' => '西歷代',
+'西历任' => '西歷任',
 '西历史' => '西歷史',
+'西历次' => '西歷次',
 '西湖里' => '西湖里',
 '西西里' => '西西里',
 '西谷米' => '西谷米',
@@ -8154,7 +8153,8 @@ public static $zh2Hant = [
 '轻松松' => '輕鬆鬆',
 '轮奸' => '輪姦',
 '轮回' => '輪迴',
-'转向往' => '轉向往',
+'轉向' => '轉向',
+'转向' => '轉向',
 '转托' => '轉託',
 '转斗千里' => '轉鬥千里',
 '辛丑' => '辛丑',
@@ -8627,7 +8627,6 @@ public static $zh2Hant = [
 '长发公主' => '長髮公主',
 '长发妹' => '長髮妹',
 '长发姑娘' => '長髮姑娘',
-'长胡' => '長鬍',
 '门帘' => '門帘',
 '门吊儿' => '門弔兒',
 '门里' => '門裡',
@@ -13453,6 +13452,7 @@ public static $zh2Hans = [
 '以微知著' => '以微知著',
 '仰屋著書' => '仰屋著书',
 '彷彿' => '仿佛',
+'伊東豊雄' => '伊东丰雄',
 '夥計' => '伙计',
 '佛頭著糞' => '佛头著粪',
 '偵蒐' => '侦搜',
index a905b3c..d435dbb 100644 (file)
        "passwordreset-emaildisabled": "E-posfunksies is afgeskakel op hierdie wiki.",
        "passwordreset-username": "Gebruiker:",
        "passwordreset-domain": "Domein:",
-       "passwordreset-capture": "Wys resulterende e-pos?",
-       "passwordreset-capture-help": "As u die boks merk, word die e-pos (met die tydelike wagwoord) aan u getoon en aan die gebruiker gestuur.",
        "passwordreset-email": "E-posadres:",
        "passwordreset-emailtitle": "Gebruiker se details op {{site name}}",
        "passwordreset-emailtext-ip": "Iemand, waarskynlik u vanaf die IP-adres $1, het u gebruikersgegewens vir {{SITENAME}} ($4) opgevra.\nDie volgende {{PLURAL:$3|gebruiker is|gebruikers is}} aan die e-posadres gekoppel:\n\n$2\n\n{{PLURAL:$3|Die tydelike wagwoord verval|Hierdie tydelike wagwoorde verval}} oor {{PLURAL:$5|een dag|$5 dae}}.\nMeld asseblief nou aan en wysig u wagwoord. As u dit nie versoek het nie, of as u die oorspronklike wagwoord nog ken en dit nie wil verander nie, ignoreer die berig en hou aan om u ou wagwoord te gebruik.",
        "userrights-reason": "Rede:",
        "userrights-no-interwiki": "U het nie toestemming om gebruikersregte op ander wiki's te verander nie.",
        "userrights-nodatabase": "Databasis $1 bestaan nie of is nie hier beskikbaar nie.",
-       "userrights-nologin": "U moet as 'n administrateur [[Special:UserLogin|aanmeld]] om gebruikersregte te kan toeken.",
-       "userrights-notallowed": "U het nie magtiging om gebruikersregte by te sit of weg te neem nie.",
        "userrights-changeable-col": "Groepe wat u kan verander",
        "userrights-unchangeable-col": "Groepe wat u nie kan verander nie",
        "userrights-conflict": "Konflik met gebruikersregte! Pas asseblief weer u wysigings toe.",
-       "userrights-removed-self": "U het u eie regte suksesvol verwyder. Gevolglik het u nie meer toegang tot hierdie bladsy nie.",
        "group": "Groep:",
        "group-user": "Gebruikers",
        "group-autoconfirmed": "Bevestigde gebruikers",
        "right-siteadmin": "Sluit en ontsluit die datbasis",
        "right-override-export-depth": "Eksporteer bladsye insluitend geskakelde bladsye tot 'n diepte van 5",
        "right-sendemail": "Stuur e-pos aan ander gebruikers",
-       "right-passwordreset": "Wys e-posse vir herstel van wagwoord",
        "newuserlogpage": "Logboek van nuwe gebruikers",
        "newuserlogpagetext": "Dit is 'n logboek van gebruikers wat onlangs ingeteken het.",
        "rightslog": "Gebruikersregtelogboek",
        "trackingcategories-disabled": "Kategorie is gedeaktiveer",
        "mailnologin": "Geen versendadres beskikbaar",
        "mailnologintext": "U moet [[Special:UserLogin|ingeteken]] wees en 'n geldige e-posadres in u [[Special:Preferences|voorkeure]] hê om e-pos aan ander gebruikers te kan stuur.",
-       "emailuser": "Stuur e-pos na hierdie gebruiker",
+       "emailuser": "Stuur e-pos na dié gebruiker",
        "emailuser-title-target": "E-pos die {{GENDER:$1|gebruiker}}",
        "emailuser-title-notarget": "E-pos gebruiker",
        "emailpagetext": "As {{GENDER:$1|dié gebruiker}} 'n geldige e-posadres in sy/haar gebruikersvoorkeure het, sal hierdie vorm 'n enkele boodskap stuur. Die e-posadres in u [[Special:Preferences|gebruikersvoorkeure]] sal verskyn as die \"Van\"-adres van die pos. Dus sal die ontvanger kan terug antwoord.",
        "namespace_association": "Gekoppelde naamruimte",
        "tooltip-namespace_association": "Merk die boks om die besprekings- en onderwerpnaamruimte die by die geselekteerde naamruimte in te sluit",
        "blanknamespace": "(Hoof)",
-       "contributions": "{{GENDER:$1|Gebruikersbydraes}}",
+       "contributions": "{{GENDER:$1|Gebruikers­bydraes}}",
        "contributions-title": "$1 se bydraes",
        "mycontris": "Bydraes",
        "anoncontribs": "Bydraes",
        "specialpage-securitylevel-not-allowed-title": "Nie toegestaan",
        "cannotauth-not-allowed-title": "Geen toegang",
        "cannotauth-not-allowed": "U word nie toegelaat om die bladsy te gebruik nie",
-       "credentialsform-account": "Gebruikersnaam:",
-       "edit-error-short": "Fout: $1",
-       "edit-error-long": "Foute:\n\n$1"
+       "credentialsform-account": "Gebruikersnaam:"
 }
index 5e03d50..7ca93e8 100644 (file)
        "search-external": "بحث خارجي",
        "searchdisabled": "البحث في {{SITENAME}} معطل.\nيمكنك البحث من خلال جوجل في الوقت الحالي.\nلاحظ أن فهارسه لمحتوى {{SITENAME}} ربما تكون غير محدثة.",
        "search-error": "حدث خطأ ما أثناء البحث: $1",
+       "search-warning": "حدث خطأ أثناء البحث: $1",
        "preferences": "تفضيلات",
        "mypreferences": "تفضيلات",
        "prefs-edits": "عدد التعديلات:",
        "userrights-user-editname": "أدخل اسم مستخدم:",
        "editusergroup": "تحميل مجموعات المستخدم",
        "editinguser": "تغيير صلاحيات {{GENDER:$1|المستخدم|المستخدمة}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "عرض صلاحيات المستخدم {{GENDER:$1|للمستخدم|للمستخدمة}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "تعديل مجموعات المستخدم",
+       "userrights-viewusergroup": "عرض مجموعات المستخدم",
        "saveusergroups": "احفظ مجموعات {{GENDER:$1|المستخدم|المستخدمة}}",
        "userrights-groupsmember": "عضو في:",
        "userrights-groupsmember-auto": "عضو ضمني في:",
        "action-upload_by_url": "رفع هذا الملف من عنوان مسار",
        "action-writeapi": "استخدام API الكتابة",
        "action-delete": "حذف هذه الصفحة",
-       "action-deleterevision": "حذف هذه المراجعة",
-       "action-deletedhistory": "رؤية تاريخ هذه الصفحة المحذوف",
+       "action-deleterevision": "حذف المراجعات",
+       "action-deletelogentry": "حذف مدخلات السجل",
+       "action-deletedhistory": "رؤية تاريخ الصفحات المحذوف",
+       "action-deletedtext": "رؤية تاريخ المراجعات المحذوف",
        "action-browsearchive": "البحث في الصفحات المحذوفة",
-       "action-undelete": "استرجاع هذه الصفحة",
-       "action-suppressrevision": "مراجعة واسترجاع هذه المراجعة المخفية",
+       "action-undelete": "استرجاع الصفحات",
+       "action-suppressrevision": "مراجعة واسترجاع المراجعات المخفية",
        "action-suppressionlog": "رؤية هذا السجل الخاص",
        "action-block": "منع هذا المستخدم من التعديل",
        "action-protect": "تغيير مستويات الحماية لهذه الصفحة",
        "action-userrights-interwiki": "تعديل صلاحيات المستخدم للمستخدمين في الويكيات الأخرى",
        "action-siteadmin": "غلق أو رفع غلق قاعدة البيانات",
        "action-sendemail": "إرسال رسائل بريد إلكتروني",
+       "action-editmyoptions": "تعديل تفضيلاتك",
        "action-editmywatchlist": "تعديل قائمة مراقبتك",
        "action-viewmywatchlist": "مشاهدة قائمة مراقبتك",
        "action-viewmyprivateinfo": "مشاهدة معلوماتك الخاصة",
        "rcshowhidebots": "$1 البوتات",
        "rcshowhidebots-show": "أظهر",
        "rcshowhidebots-hide": "أخف",
-       "rcshowhideliu": "$1 {{GENDER:$1|مستخدمين مسجلين|مستخدمات مسجلات|مستخدمون مسجلون}}",
+       "rcshowhideliu": "$1 المستخدمين المسجلين",
        "rcshowhideliu-show": "أظهر",
        "rcshowhideliu-hide": "أخف",
        "rcshowhideanons": "$1 المستخدمين المجهولين",
        "emailccsubject": "نسخة من رسالتك إلى $1: $2",
        "emailsent": "أُرسل البريد الإلكتروني",
        "emailsenttext": "أُرسلت رسالتك الإلكترونية.",
-       "emailuserfooter": "هذا البريد الإلكتروني {{GENDER:$1|تم إرساله}} بواسطة $1 إلى {{GENDER:$2|$2}} بواسطة وظيفة \"{{int:emailuser}}\" في {{SITENAME}}.",
+       "emailuserfooter": "هذا البريد الإلكتروني {{GENDER:$1|تم إرساله}} بواسطة $1 إلى {{GENDER:$2|$2}} بواسطة وظيفة \"{{int:emailuser}}\" في {{SITENAME}}. عنوان البريد الخاص {{GENDER:$2|بك}} سيتم إرساله مباشرة {{GENDER:$1|للمرسل الأصلي|للمرسلة الأصلية}}، مما يكشف عنوان البريد الإلكتروني الخاص {{GENDER:$2|بك}} {{GENDER:$1|لهم}}.",
        "usermessage-summary": "ترك رسالة نظام.",
        "usermessage-editor": "مراسل النظام",
        "watchlist": "قائمة مراقبتي",
        "wlshowhidemine": "تعديلاتي",
        "wlshowhidecategorization": "تصنيف الصفحات",
        "watchlist-options": "خيارات قائمة المراقبة",
+       "watchlist-mark-all-visited": "هل أنت متأكد أنك تريد إعادة ضبط تغييرات قائمة المراقبة غير المرئية عن طريق التعليم على كل الصفحات كمزارة؟",
        "watching": "يراقب...",
        "unwatching": "إزالة المراقبة...",
        "watcherrortext": "حدث خطأ أثناء تغيير إعدادات الرصد الخاصة بك \"$1\".",
        "modifiedarticleprotection": "غير مستوى حماية \"[[$1]]\"",
        "unprotectedarticle": "أزال الحماية من \"[[$1]]\"",
        "movedarticleprotection": "نقل إعدادات الحماية من \"[[$2]]\" إلى \"[[$1]]\"",
-       "protectedarticle-comment": "{{GENDER:$2|محمي}} \"[[$1]]\"",
+       "protectedarticle-comment": "{{GENDER:$2|حمى}} \"[[$1]]\"",
        "modifiedarticleprotection-comment": "{{GENDER:$2|غير مستوى الحماية|غيرت مستوى الحماية}} ل\"[[$1]]\"",
        "unprotectedarticle-comment": "{{GENDER:$2|أزال الحماية|أزالت الحماية}} من \"[[$1]]\"",
        "protect-title": "ضبط حماية \"$1\"",
        "special-characters-title-emdash": "واصلة طويلة",
        "special-characters-title-minus": "علامة الطرح",
        "mw-widgets-dateinput-no-date": "لا تاريخ تم اختياره",
+       "mw-widgets-mediasearch-input-placeholder": "البحث عن ملفات",
+       "mw-widgets-mediasearch-noresults": "لم يتم العثور على نتائج.",
        "mw-widgets-titleinput-description-new-page": "الصفحة غير موجودة بعد",
        "mw-widgets-titleinput-description-redirect": "تحويل إلى $1",
        "mw-widgets-categoryselector-add-category-placeholder": "أضف تصنيفا...",
        "log-action-filter-newusers": "نوع إنشاء الحساب:",
        "log-action-filter-patrol": "نوع الخفر:",
        "log-action-filter-protect": "نوع الحماية:",
-       "log-action-filter-rights": "نوع تغيير الصلاحية",
+       "log-action-filter-rights": "نوع تغيير الصلاحية:",
        "log-action-filter-suppress": "نوع الإخفاء:",
        "log-action-filter-upload": "نوع الرفع:",
        "log-action-filter-all": "الكل",
        "usercssispublic": "من فضل لاحظ: صفحات الCSS الفرعية لا ينبغي أن تحتوي على بيانات سرية بما أنها يمكن رؤيتها بواسطة المستخدمين الآخرين.",
        "restrictionsfield-badip": "عنوان أيبي أو نطاق غير صحيح: $1",
        "restrictionsfield-label": "نطاقات الأيبي المسموح بها:",
-       "restrictionsfield-help": "عنوان أيبي أو نطاق CIDR واحد لكل سطر. لتفعيل كل شيء، استخدم<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "عنوان أيبي أو نطاق CIDR واحد لكل سطر. لتفعيل كل شيء، استخدم<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "معرف الصفحة $1"
 }
index 621aea0..7c67dc5 100644 (file)
        "views": "Vistes",
        "toolbox": "Ferramientes",
        "tool-link-userrights": "Cambiar los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
+       "tool-link-userrights-readonly": "Ver los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
        "tool-link-emailuser": "Unviar un corréu electrónicu a {{GENDER:$1|esti usuariu|esta usuaria}}",
        "userpage": "Ver la páxina d'usuariu",
        "projectpage": "Ver la páxina del proyeutu",
        "search-external": "Busca esterna",
        "searchdisabled": "La busca en {{SITENAME}} ta desactivada. Mentanto, pues buscar en Google.\nHas fixate en que'l conteníu de los sos índices de {{SITENAME}} pué tar desfasáu.",
        "search-error": "Hebo un error al buscar: $1",
+       "search-warning": "Hebo un avisu al buscar: $1",
        "preferences": "Preferencies",
        "mypreferences": "Preferencies",
        "prefs-edits": "Númberu d'ediciones:",
        "prefs-help-recentchangescount": "Incluye los cambios recientes, los historiales de páxines y los rexistros.",
        "prefs-help-watchlist-token2": "Esta ye la clave secreta pa la canal de noticies web de la so llista de vixilancia.\nCualquiera que la sepa podrá lleer la so llista de vixilancia; nun la comparta.\n[[Special:ResetTokens|Calque equí si necesita reaniciala]].",
        "savedprefs": "Guardáronse les preferencies.",
-       "savedrights": "Guardáronse los permisos d'{{GENDER:$1|usuariu|usuaria}} de $1.",
+       "savedrights": "Guardáronse los grupos {{GENDER:$1|del usuariu|de la usuaria}} $1.",
        "timezonelegend": "Estaya horaria:",
        "localtime": "Hora llocal:",
        "timezoneuseserverdefault": "Usar la predeterminada de la wiki ($1)",
        "prefswarning-warning": "Ficisti cambios nes tos preferencies qu'inda nun se guardaron.\nSi abandones esta páxina ensin calcar \"$1\" les preferencies nun s'anovarán.",
        "prefs-tabs-navigation-hint": "Gabitu: pue usar les tecles de flecha izquierda y drecha pa navegar peles llingüetes de la llista.",
        "userrights": "Xestión de permisos d'usuariu",
-       "userrights-lookup-user": "Xestión de grupos del usuariu",
+       "userrights-lookup-user": "Seleiciona un usuariu",
        "userrights-user-editname": "Escribe un nome d'usuariu:",
-       "editusergroup": "Editar los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
+       "editusergroup": "Cargar los grupos d'usuariu",
        "editinguser": "Camudando los permisos {{GENDER:$1|del usuariu|de la usuaria}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Viendo los permisos {{GENDER:$1|del usuariu|de la usuaria}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Editar los grupos d'usuariu",
+       "userrights-viewusergroup": "Ver los grupos d'usuariu",
        "saveusergroups": "Guardar los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
        "userrights-groupsmember": "Miembru de:",
        "userrights-groupsmember-auto": "Miembru implícitu de:",
        "action-upload_by_url": "xubir esti ficheru dende una URL",
        "action-writeapi": "usar l'API d'escritura",
        "action-delete": "desaniciar esta páxina",
-       "action-deleterevision": "desaniciar esta revisión",
-       "action-deletedhistory": "ver l'historial elimináu d'esta páxina",
+       "action-deleterevision": "desaniciar revisiones",
+       "action-deletelogentry": "desaniciar les entraes del rexistru",
+       "action-deletedhistory": "ver l'historial elimináu d'una páxina",
+       "action-deletedtext": "ver el testu d'una revisión desaniciada",
        "action-browsearchive": "buscar páxines desaniciaes",
-       "action-undelete": "restaurar esta páxina",
-       "action-suppressrevision": "revisar y restaurar esta revisión tapecida",
+       "action-undelete": "restaurar páxines",
+       "action-suppressrevision": "revisar y restaurar revisiones tapecíes",
        "action-suppressionlog": "ver esti rexistru priváu",
        "action-block": "bloquiar qu'esti usuariu edite",
        "action-protect": "camudar los niveles de proteición pa esta páxina",
        "action-userrights-interwiki": "editar los permisos d'usuariu de los usuarios d'otres wikis",
        "action-siteadmin": "candar o descandar la base de datos",
        "action-sendemail": "unviar correos",
+       "action-editmyoptions": "editar les preferencies propies",
        "action-editmywatchlist": "editar la llista de vixilancia",
        "action-viewmywatchlist": "ver la llista de vixilancia propia",
        "action-viewmyprivateinfo": "ver la so información privada",
        "emailccsubject": "Copia del to mensaxe a $1: $2",
        "emailsent": "Corréu unviáu",
        "emailsenttext": "Unviose'l to mensaxe de corréu.",
-       "emailuserfooter": "Esti corréu electrónicu {{GENDER:$1|unviólu}} $1 a {{GENDER:$2|$2}} per aciu de la función «{{int:emailuser}}» de {{SITENAME}}.",
+       "emailuserfooter": "Esti corréu electrónicu {{GENDER:$1|unviólu}} $1 a {{GENDER:$2|$2}} per aciu de la función «{{int:emailuser}}» de {{SITENAME}}. Unviaráse'l {{GENDER:$2|to}} corréu direutamente {{GENDER:$1|al remitente|a la remitente}} orixinal, {{GENDER:$1|revelando-y}} la {{GENDER:$2|to}} direición de corréu.",
        "usermessage-summary": "Dexar un mensaxe del sistema.",
        "usermessage-editor": "Mensaxería del sistema",
        "watchlist": "Llista de siguimientu",
        "htmlform-user-not-exists": "<strong>$1</strong> nun esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> nun ye un nome d'usuariu válidu.",
        "logentry-delete-delete": "$1 {{GENDER:$2|desanició}} la páxina $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|desanició}} la redireición $3 sobreescribiéndola",
        "logentry-delete-restore": "$1 {{GENDER:$2|restauró}} la páxina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|camudó}} la visibilidá {{PLURAL:$5|d'un socesu del rexistru|de $5 socesos del rexistru}} en $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|camudó}} la visibilidá {{PLURAL:$5|d'una revisión|de $5 revisiones}} na páxina $3: $4",
        "mw-widgets-dateinput-no-date": "Nenguna data seleicionada",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Buscar multimedia",
+       "mw-widgets-mediasearch-noresults": "Nun s'alcontraron resultaos.",
        "mw-widgets-titleinput-description-new-page": "la páxina inda nun esiste",
        "mw-widgets-titleinput-description-redirect": "redirixir a $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Amestar una categoría...",
        "log-action-filter-contentmodel-change": "Cambéu de modelu de conteníu",
        "log-action-filter-contentmodel-new": "Creación de páxina con modelu de conteníu non estándar",
        "log-action-filter-delete-delete": "Desaniciu de páxines",
+       "log-action-filter-delete-delete_redir": "Sobreescritura de redireición",
        "log-action-filter-delete-restore": "Restauración de páxines",
        "log-action-filter-delete-event": "Desaniciu de rexistros",
        "log-action-filter-delete-revision": "Desaniciu de revisión",
        "usercssispublic": "Atención: les subpáxines CSS nun tendríen de contener datos acutaos porque son visibles pa otros usuarios.",
        "restrictionsfield-badip": "Direición o rangu IP inválidu: $1",
        "restrictionsfield-label": "Rangos d'IP permitíos:",
-       "restrictionsfield-help": "Una única direición IP o rangu CIDR per llinia. P'activar toos, utiliza<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Una única direición IP o rangu CIDR per llinia. P'activar toos, utiliza<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "ID de páxina $1"
 }
index f146905..d06e28e 100644 (file)
        "botpasswords-updated-title": "روْبات پسووْردو آپدئیت اوْلوندو.",
        "botpasswords-deleted-title": "روْبات پسووْردو سیلیندی",
        "resetpass_forbidden": "رمزلر دَییشیلمز",
-       "resetpass-no-info": "بو صحیفه‌نی دوغرو گؤردوگونوز اوچون سیستمه گیرمه‌لیسینیز.",
+       "resetpass-no-info": "بو صفحه‌نی بیرباشا آچماق اوچون سیستمه گیرمه‌لیسینیز.",
        "resetpass-submit-loggedin": "رمزی دَییشدیر",
        "resetpass-submit-cancel": "وازگئچ",
        "resetpass-wrong-oldpass": "یانلیش گئچیجی و یا ایندیکی رمز.\nسیز باجارییلا رمزینیزی دَییشدیریب یوخسا یئنی گئچرلی رمز ایسته‌میش اوْلدوغونوز مؤحتمل‌دیر.",
        "passwordreset-emailsentemail": "بۇ ایمئیل آدرسی حسابینیزا ثبت اوْلونموشسا٬ بیر رمز یئنیله‌مه ایمئیلی گؤندریله‌جکدیر.",
        "changeemail": "ایمیل آدرسینی دَییشدیر یا سیل",
        "changeemail-header": "ایمیل آدرسیزی دَییشدیرمک اوچون بو فورمو دولدورون. حسابیزا بوتون ایمیل‌لرین ارتباطینی کسمک اوچون، یئنی ایمیل آدرسینی بوش ساخلایین.",
-       "changeemail-no-info": "بو صحیفه‌نی دوغرو گؤردوگونوز اوچون سیستمه گیرمه‌لیسینیز.",
+       "changeemail-no-info": "بو صفحه‌نی بیرباشا آچماق اوچون سیستمه گیرمه‌لیسینیز.",
        "changeemail-oldemail": "ایندیکی ایمیل آدرس:",
        "changeemail-newemail": "یئنی ایمیل آدرسی:",
        "changeemail-none": "(هئچ)",
        "accmailtext": "[[User talk:$1|$1]] اوچون بیر راست‌گله رمز یارادیلیب و $2-ه گؤندریلدی.\n\nبو یئنی حسابین رمزی، گیرندن سونرا <em>[[Special:ChangePassword|رمز دَییشدیرمه]]</em> صحیفه‌سیندن دَییشیله بیلر.",
        "newarticle": "(یئنی)",
        "newarticletext": "مووجود اوْلمايان صفحه‌‌يه اوْلان لینکی ایزله‌دینیز. \nآشاغیداکی یئره یازیلارینیزی يازیب، بۇ صفحه‌‌نی '''سیز''' يارادا بیلرسینیز. (آرتیق معلومات اۆچون [$1 کؤمک صفحه‌‌سینه] باخین). اگر بۇ صفحه‌‌يه ایشتباه گلمیسینیزسه تکجه براوزرین '''قایید''' دۆيمه‌سینه وۇرون.",
-       "anontalkpagetext": "''بو صحیفه قئیدیات‌دان کئچممیش و یا داخیل اولمامیش آنونیم ایستیفادچییه عایید موذاکیره صحیفه‌سی‌دیر.\nاونا گؤره بو ایستیفادچینی رقم‌لردن عبارت ایپ اونوانی ایله معین ائتمک مجبوریتیندییک.\nبئله ایپ اونوان بیر نئچه فرد طرفین‌دن ایستیفاده‌ده اولا بیلر.\nاگر سیز آنونیم ایستیفادچیسینیزسه و بو مئساژین سیزه عایید اولمادیغینی دوشونورسونوزسه، اوندا  [[Special:CreateAccount|قئیدیات‌دان کئچین]] و یا [[Special:UserLogin|داخی اولون]].''",
+       "anontalkpagetext": "----<em>بو صفحه حسابسیز و یا سیستمه گیرمه میش تانیلماز ایشلدنه مربوط دانیشیق صفحه‌سی‌دیر.\nاونا گؤره بو ایشلدنی رقم‌لردن عبارت ایپی آدرسی ایله بللیلشدیریلیبدیر.</em>\nبئله آیپی آدرسی بیر نئچه فرد طرفین‌دن ایستیفاده‌ده اولا بیلر.\nاگر سیز تانیلماز ایشلدنسینیز و بو پیامین سیزه مربوط اولمادیغینی دوشونورسونوزسه، اوندا  [[Special:CreateAccount|حساب یارادین]] و یا [[Special:UserLogin|سیستمه گیرین]].''",
        "noarticletext": "ایندی بو صفحه‌ده یازی یوخدور.\nسیز آیری صفحه‌‌لرده [[Special:Search/{{PAGENAME}}|بو باشلیق اوچون آختارا بیلرسیز]]،\nیا دا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} باغلی قئیدلری آختارا بیلرسیز]،\nیا دا [{{fullurl:{{FULLPAGENAME}}|action=edit}} بو صفحه‌نی دَییشدیره بیلرسیز]</span>.",
        "noarticletext-nopermission": "بو صحیفه‌‌ ایندی بوشدور. \nباشقا صحیفه‌‌لرده عینی آددا صحیفه‌‌نی  [[Special:Search/{{PAGENAME}}| آختار]], علاقه‌‌لی قئيدلره \n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} باخا],\nو يا صحیفه‌‌نی  [{{fullurl:{{FULLPAGENAME}}|action=edit}} redaktə]</span> ائده بیلرسینیز.",
        "missing-revision": "«{{FULLPAGENAME}}» صحیفه‌سی اوچون $1 نومره‌لی نوسخه یوخدور.\n\nعموماُ بو ایشکال، واختی گئچمیش بیر باغلانتی ایله سیلینمیش بیر صحیفه‌یه گلنده، قاباغا گلر.\n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیلمک سیاهی]‌سینده باشقا بیلگیلر اولا بیلر.",
        "group": "گروه:",
        "group-user": "ایستفاده‌چیلر",
        "group-autoconfirmed": "اوتوماتیک دوغرولانمیش ایستیفاده‌چیلر",
-       "group-bot": "بÙ\88تلار",
+       "group-bot": "رÙ\88باتلار",
        "group-sysop": "ایداره‌چیلر",
        "group-bureaucrat": "بوروکراتلار",
        "group-suppress": "باخانلار",
        "brokenredirectstext": "آشاغی‌داکی ایستیقامتلندیرمه‌لر مؤوجود اولمایان صحیفه‌لره کئچید وئریر:",
        "brokenredirects-edit": "دَییشدیر",
        "brokenredirects-delete": "سیل",
-       "withoutinterwiki": "دیل باغلانتیلاری اولمایان صحیفه‌لر",
+       "withoutinterwiki": "دیل باغلانتیلاری اولمایان صفحه‌لر",
        "withoutinterwiki-summary": "آشاغیداکی صفحه‌لر، آیری دیل‌لره باغلانتیلاری یوخدور.",
        "withoutinterwiki-legend": "اؤن‌اَک",
        "withoutinterwiki-submit": "گؤستر",
        "watchlist-hide": "گیزلت",
        "wlshowtime": "بو تاریخه قدر گؤستر:",
        "wlshowhideminor": "کیچیک دَییشدیرمه‌لر",
+       "wlshowhidebots": "روباتلار",
        "wlshowhideliu": "یازیلمیش ایشلدنلر",
        "wlshowhidemine": "منیم دَییشدیرمه‌لریم",
        "wlshowhidecategorization": "صفحه‌ بؤلمه‌لندیرمه‌سی",
        "protect-expiry-options": "1 ساهات:1 hour,1 گون:1 day,1 هفته:1 week,2 هفته:2 weeks,1 آی:1 month,3 آی:3 months,6 آی:6 months,1 ایل:1 year,سون سوز:infinite",
        "restriction-type": "حقوقلار",
        "restriction-level": "محدودیت درجه‌‌سی:",
-       "minimum-size": "ان کوچیک بویوت",
-       "maximum-size": "اÙ\86 Ø¨Ù\88Û\8cÙ\88Ú© Ø¨Ù\88Û\8cÙ\88ت",
+       "minimum-size": "ان کیچیک حجم",
+       "maximum-size": "اÙ\86 Ø¨Ù\88Û\8cÙ\88Ú© Ø­Ø¬Ù\85ï¼\9a",
        "pagesize": "(بایت)",
        "restriction-edit": "دَییشدیر",
        "restriction-move": "آدینی دَییشدیر",
        "sp-contributions-username": "آی‌پی آدرسی یوْخسا ایشلدن آدی:",
        "sp-contributions-toponly": "تکجه سون نوسخه اولان دییشیکلری گؤستر",
        "sp-contributions-newonly": "تکجه صفحه یاراتماق دَییشیکلیکلرینی گؤستر",
+       "sp-contributions-hideminor": "کیچیک دییشیکلیکلری گیزلت",
        "sp-contributions-submit": "آختار",
        "whatlinkshere": "بۇ صفحه‌‌يه باغلانتیلار",
        "whatlinkshere-title": "«$1»-ه باغلانان صفحه‌لر",
        "whatlinkshere-hidelinks": "$1 باغلانتیلاری",
        "whatlinkshere-hideimages": "فایل باغلانتیلارینی $1",
        "whatlinkshere-filters": "سۆزگَجلر",
+       "whatlinkshere-submit": "گئت",
        "autoblockid": "اوتوماتیک باغلانما #$1",
        "block": "ایستیفادچینی باغلاما",
        "unblock": "ایستیفاده‌چی‌نین باغلانماسین گؤتور",
        "tooltip-compareselectedversions": "بو صحیفه‌نین ایکی سئچیلمیش نوسخه‌لری‌نین فرقلرینه باخ",
        "tooltip-watch": "بو صفحه‌نی ایزله‌دیکلرینیزه آرتیر",
        "tooltip-watchlistedit-normal-submit": "باشلیغین سیلینمه‌سی",
-       "tooltip-watchlistedit-raw-submit": "ایزلدیگیم صحیفه‌لرین سیاهی‌سینین یئنی‌لنمه‌سی",
+       "tooltip-watchlistedit-raw-submit": "ایزلدیگیم صفحه‌لرین لیستینی یئنی‌له",
        "tooltip-recreate": "اول سیلینمه‌سینه باخمایا‌راق صحیفنی برپا ائت",
        "tooltip-upload": "یوکلمنی باشلات",
        "tooltip-rollback": "سوْنونجو ایشلدن طرفیندن ائدیلمیش بۆتون ديَیشیکلیک‌لری بیر دفعه‌‌يه گئری قايتار",
        "pageinfo-category-subcats": "آلت‌بؤلمه‌لرین سایی",
        "pageinfo-category-files": "فایل‌لارین سایی",
        "markaspatrolleddiff": "ایداره ائدیلمیش اولا‌راق ایشاره‌له",
-       "markaspatrolledtext": "صحیفنی پاتروللانمیش کیمی ایشاره‌له",
+       "markaspatrolledtext": "صفحه‌نی یوخلانیلمیش کیمی علامتله",
        "markedaspatrolled": "یوخلانیلدی",
        "markedaspatrolledtext": "[[:$1]] اوچون سئچیلمیش نوسخه گؤزدن کئچیریله‌رک ایشاره‌لندی.",
        "rcpatroldisabled": "سون دییشیک‌لیک‌لرین پاتروللانماسی قاداغان‌دیر",
        "lag-warn-normal": "$1 {{PLURAL:$1 | سانیيه‌دن | سانیيه‌ده}} يئنی ديَیشیکلیکلر بو سیياهیدا گؤرولمه‌يه.",
        "lag-warn-high": "وئریلنلر بازاسی سونوجوسونداکی هددیندن آرتیق گئجیکمه‌دن گؤره، $1 {{PLURAL:$1 | سانیيه‌دن | سانیيه‌دن}} يئنی ديَیشیکلیکلر بو سیياهیدا گؤرونمئيئبیلیر.",
        "watchlistedit-normal-title": "ایزله‌دیکلریم صفحه‌‌لری دَییشدیر",
-       "watchlistedit-normal-legend": "ایزله‌مه سیياهیسیندان باشلیقلارین سیلینمه‌سی",
-       "watchlistedit-normal-explain": "ایزله‌مه سیياهینیزدا باشلیقلار آشاغیدا گؤستریلمیشدیر. \nبیر باشلیغی چیخارماق اوچون، يانینداکی قوتوجوغو ایشارله‌يین و «{{int:Watchlistedit-normal-submit}}» دويمه‌سینی باسین. \n[[Special:EditWatchlist/raw|سطرلر سیياهیسینی]] ده تشکیل ائده بیلرسینیز",
+       "watchlistedit-normal-legend": "ایزله‌مه لیستیندن عونوانلارین سیلینمه‌سی",
+       "watchlistedit-normal-explain": "ایزله‌مه لیستینیزدکی عونوانلار آشاغیدا گؤستریلمیشدیر. \nبیر باشلیغی سیلمک اوچون، يانینداکی قوتونو علامتله‌يین و «{{int:Watchlistedit-normal-submit}}» دويمه‌سینی باسین. \n[[Special:EditWatchlist/raw|چی لیستی]] ده دییشدیره بیلرسینیز.",
        "watchlistedit-normal-submit": "باشلیقین سیلینمه‌سی",
        "watchlistedit-normal-done": "{{PLURAL:$1|$1 صحیفه‌‌}} ایزله‌مه صحیفه‌‌لرینیزدن سیلیندی:",
        "watchlistedit-raw-title": "چیی ایزله‌دیکلری دَییشدیر",
        "watchlistedit-raw-legend": "چیی ایزله‌دیکلری دَییشدیر",
        "watchlistedit-raw-explain": "ایزله‌مه سیياهینیزدا باشلیقلار آشاغیدا گؤستریلیر. هر سطرده بیر باشلیق اولماق عذره، باشلیقلاری علاوه‌‌ ائده‌رک يا دا سیله‌رک سیياهینی تشکیل ائده بیلرسینیز. \nبیتدیگینده «{{int:Watchlistedit-raw-submit}}» يئ تیکلايینیز. \nآيریجا [[Special:EditWatchlist|استاندارت تنزیمله‌مه صحیفه‌‌سینی]] دا ایستیفاده ائده بیلرسینیز.",
-       "watchlistedit-raw-titles": "باشÙ\84Û\8cÙ\82لار:",
-       "watchlistedit-raw-submit": "ایزلدیگیم صحیفه‌لرین سیاهی‌سینین یئنی‌لنمه‌سی",
+       "watchlistedit-raw-titles": "عÙ\88Ù\86Ù\88اÙ\86لار:",
+       "watchlistedit-raw-submit": "ایزلدیگیم صفحه‌لرین لیستینی یئنی‌له",
        "watchlistedit-raw-done": "ایزله‌دیکلرینیز گونجل‌لندی.",
        "watchlistedit-raw-added": "{{PLURAL:$1|بیر|$1}} باشلیق آرلیریلدی:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|بیر|$1}} باشلیق چیخاریلدی:",
+       "watchlistedit-clear-title": "ایزلدیکلریمین لیستین سیل",
        "watchlistedit-clear-legend": "ایزلدیکلریمین لیستین سیل",
        "watchlistedit-clear-titles": "باشلیق‌لار:",
        "watchlisttools-clear": "ایزلدیکلریمین لیستین سیل",
index e21ef13..ce5b1f9 100644 (file)
        "talk": "Әңгәмә",
        "views": "Ҡарауҙар",
        "toolbox": "Ҡоралдар",
+       "tool-link-userrights": "{{GENDER:$1|Ҡатнашыусы}} төркөмдәрен үҙгәртергә",
+       "tool-link-emailuser": "{{GENDER:$1|Ҡатнашыусыға}} хат яҙырға",
        "userpage": "Ҡулланыусы битен ҡарарға",
        "projectpage": "Проект битен ҡарарға",
        "imagepage": "Файл битен ҡарарға",
        "minoredit": "Әҙ генә үҙгәрештәр",
        "watchthis": "Күҙәтеү исемлегенә",
        "savearticle": "Яҙҙырып ҡуйырға",
+       "savechanges": "Яҙҙырып ҡуйырға",
        "publishpage": "Битте баҫтырырға",
+       "publishchanges": "Яҙҙырып ҡуйырға",
        "preview": "Ҡарап сығыу",
        "showpreview": "Ҡарап сығырға",
        "showdiff": "Индерелгән үҙгәрештәр",
        "contributions": "{{GENDER:$1|Ҡатнашыусы}} башҡарған эш",
        "contributions-title": "$1 исемле ҡатнашыусы башҡарған эш",
        "mycontris": "Башҡарған эштәр",
-       "anoncontribs": "Ð\98Ò\93Ó\99нÓ\99ләр",
+       "anoncontribs": "Ð\91аÑ\88ҡаÑ\80Ò\93ан Ñ\8dÑ\88Ñ\82әр",
        "contribsub2": "{{GENDER:$3|$1}} башҡарған эше ($2)",
        "contributions-userdoesnotexist": "«$1» исемле иҫәп яҙыуы юҡ.",
        "nocontribs": "Күрһәтелгән шарттарға яуап биргән үҙгәртеүҙәр табылманы.",
index 7624ef0..1b38668 100644 (file)
        "explainconflict": "Нехта зьмяніў старонку падчас вашага рэдагаваньня.\nУ верхнім тэкставым акне знаходзіцца цяперашні зьмест старонкі.\nВашыя зьмены паказаныя ў ніжнім акне.\nВам трэба перанесьці вашыя зьмены ў цяперашні тэкст.\nКалі вы націсьніце «{{int:savearticle}}», будзе захаваны <strong>толькі</strong> тэкст верхняга вакна.",
        "yourtext": "Ваш тэкст",
        "storedversion": "Захаваная вэрсія",
-       "nonunicodebrowser": "'''ПАПЯРЭДЖАНЬНЕ: Ваш браўзэр не працуе з кадаваньнем UTF-8 (Unicode).\nУ выніку гэтага ўсе сымбалі ня ўключаныя ў ASCII будуць замененыя на іх шаснаццаткавыя коды.'''",
-       "editingold": "'''ПАПЯРЭДЖАНЬНЕ: Вы рэдагуеце састарэлую вэрсію гэтай старонкі.\nКалі Вы паспрабуеце захаваць яе, любыя зьмены, зробленыя пасьля гэтай вэрсіі, будуць страчаныя.'''",
+       "nonunicodebrowser": "<strong>Папярэджаньне: ваш браўзэр не падтрымлівае Unicode-кадаваньне.</strong>\nУ выніку гэтага ўсе сымбалі ў полі рэдагаваньня, ня ўключаныя ў ASCII, будуць замененыя на іх шаснаццаткавыя коды.",
+       "editingold": "<strong>Папярэджаньне: вы рэдагуеце састарэлую вэрсію гэтай старонкі.</strong>\nКалі вы паспрабуеце захаваць яе, любыя зьмены, зробленыя пасьля гэтай вэрсіі, будуць страчаныя.",
        "yourdiff": "Адрозьненьні",
-       "copyrightwarning": "Ð\9aалÑ\96 Ð»Ð°Ñ\81ка, Ð·Ñ\8cвÑ\8fÑ\80нÑ\96Ñ\86е Ñ\9eвагÑ\83 Ð½Ð° Ñ\82ое, Ñ\88Ñ\82о Ñ\9eÑ\81е Ð´Ð°Ð´Ð°Ñ\82кÑ\96 Ñ\96 Ð·Ñ\8cменÑ\8b Ñ\9e {{GRAMMAR:меÑ\81нÑ\8b|{{SITENAME}}}} Ñ\80азглÑ\8fдаÑ\8eÑ\86Ñ\86а Ñ\8fк Ð²Ñ\8bдадзенÑ\8bÑ\8f Ñ\9e Ð°Ð´Ð¿Ð°Ð²ÐµÐ´Ð½Ð°Ñ\81Ñ\8cÑ\86Ñ\96 Ð· Ñ\83мовамÑ\96 Ð»Ñ\96Ñ\86Ñ\8dнзÑ\96Ñ\96 $2 (глÑ\8fдзÑ\96Ñ\86е Ð¿Ð°Ð´Ñ\80абÑ\8fзнаÑ\81Ñ\8cÑ\86Ñ\96 Ð½Ð° $1). Ð\9aалÑ\96 Ð\92Ñ\8b Ñ\81Ñ\83пÑ\80аÑ\86Ñ\8c Ñ\82аго, ÐºÐ°Ð± Ð\92аÑ\88Ñ\8bÑ\8f Ð¼Ð°Ñ\82Ñ\8dÑ\80Ñ\8bÑ\8fлÑ\8b Ð½ÐµÐ°Ð±Ð¼ÐµÐ¶Ð°Ð²Ð°Ð½Ð° Ñ\80Ñ\8dдагавалаÑ\81Ñ\8f Ñ\96 Ñ\80аÑ\81паÑ\9eÑ\81Ñ\8eджвалаÑ\81Ñ\8f, Ð½Ðµ Ð´Ð°Ð´Ð°Ð²Ð°Ð¹Ñ\86е Ñ\96Ñ\85.<br />\nÐ\92Ñ\8b Ñ\82акÑ\81ама Ð°Ð±Ð°Ð²Ñ\8fзÑ\83еÑ\86еÑ\81Ñ\8f, Ñ\88Ñ\82о Ð\92аÑ\88 Ð¼Ð°Ñ\82Ñ\8dÑ\80Ñ\8bÑ\8fл Ð½Ð°Ð¿Ñ\96Ñ\81анÑ\8b Ð°Ñ\81абÑ\96Ñ\81Ñ\82а Ð\92амÑ\96 Ð°Ð±Ð¾ Ð·Ñ\8cÑ\8fÑ\9eлÑ\8fеÑ\86Ñ\86а Ð³Ñ\80амадзкÑ\96м Ð½Ð°Ð±Ñ\8bÑ\82кам, Ð°Ð»Ñ\8cбо Ñ\9eзÑ\8fÑ\82Ñ\8b Ð· Ð¿Ð°Ð´Ð¾Ð±Ð½Ñ\8bÑ\85 Ð²Ð¾Ð»Ñ\8cнÑ\8bÑ\85 ÐºÑ\80Ñ\8bнÑ\96Ñ\86аÑ\9e.\n'''Ð\9dÐ\95Ð\9bЬÐ\93Ð\90 Ð\91Ð\95Ð\97 Ð\94Ð\90Ð\97Ð\92Ð\9eÐ\9bУ Ð\94Ð\90Ð\94Ð\90Ð\92Ð\90ЦЬ Ð\9cÐ\90ТЭРЫЯÐ\9bЫ, Ð\90Ð\91Ð\90РÐ\9eÐ\9dÐ\95Ð\9dЫЯ Ð\90Ð\8eТÐ\90РСÐ\9aÐ\86Ð\9c Ð\9fРÐ\90Ð\92Ð\90Ð\9c!'''",
-       "copyrightwarning2": "Ð\9aалÑ\96 Ð»Ð°Ñ\81ка, Ð·Ð°Ñ\9eважÑ\86е, Ñ\88Ñ\82о Ñ\9eвеÑ\81Ñ\8c Ñ\83нÑ\91Ñ\81ак Ñ\9e {{GRAMMAR:вÑ\96навалÑ\8cнÑ\8b|{{SITENAME}}}} Ð¼Ð¾Ð¶Ð° Ñ\80Ñ\8dдагаваÑ\86Ñ\86а, Ð·Ñ\8cмÑ\8fнÑ\8fÑ\86Ñ\86а Ñ\96 Ð²Ñ\8bдалÑ\8fÑ\86Ñ\86а Ñ\96нÑ\88Ñ\8bмÑ\96 Ñ\9eдзелÑ\8cнÑ\96камÑ\96.\nÐ\9aалÑ\96 Ð\92Ñ\8b Ð· Ð³Ñ\8dÑ\82Ñ\8bм Ð½Ñ\8f Ð·Ð³Ð¾Ð´Ð½Ñ\8bÑ\8f, ÐºÐ°Ð»Ñ\96 Ð»Ð°Ñ\81ка, Ð½Ðµ Ð·Ñ\8cмÑ\8fÑ\88Ñ\87айÑ\86е Ñ\81Ñ\8eдÑ\8b Ð\92аÑ\88Ñ\8bÑ\8f Ñ\82Ñ\8dкÑ\81Ñ\82Ñ\8b.<br />\nРазÑ\8cмÑ\8fÑ\88Ñ\87Ñ\8dнÑ\8cнем Ñ\82Ñ\83Ñ\82 Ñ\82Ñ\8dкÑ\81Ñ\82аÑ\9e, Ð\92Ñ\8b Ð´Ñ\8dклÑ\8fÑ\80Ñ\83еÑ\86е, Ñ\88Ñ\82о Ð\92Ñ\8b Ð·Ñ\8cÑ\8fÑ\9eлÑ\8fеÑ\86еÑ\81Ñ\8f Ñ\96Ñ\85 Ð°Ñ\9eÑ\82аÑ\80ам, Ñ\86Ñ\96 Ð\92Ñ\8b Ñ\81капÑ\96Ñ\8fвалÑ\96 Ñ\96Ñ\85 Ð· ÐºÑ\80Ñ\8bнÑ\96Ñ\86Ñ\8b, Ñ\8fкаÑ\8f Ð´Ð°Ð·Ð²Ð°Ð»Ñ\8fе Ð²Ð¾Ð»Ñ\8cнае Ð²Ñ\8bкаÑ\80Ñ\8bÑ\81Ñ\82анÑ\8cне Ñ\81ваÑ\96Ñ\85 Ñ\82Ñ\8dкÑ\81Ñ\82аÑ\9e (дзелÑ\8f Ð¿Ð°Ð´Ñ\80абÑ\8fзнаÑ\81Ñ\8cÑ\86Ñ\8fÑ\9e Ð³Ð»Ñ\8fдзÑ\96Ñ\86е $1).\n\n'''Ð\9aÐ\90Ð\9bÐ\86 Ð\9bÐ\90СÐ\9aÐ\90, Ð\9dÐ\95 Ð\97ЬÐ\9cЯШЧÐ\90Ð\99ЦÐ\95 Ð¢Ð£Ð¢ Ð\91Ð\95Ð\97 Ð\94Ð\90Ð\97Ð\92Ð\9eÐ\9bУ Ð\9cÐ\90ТЭРЫЯÐ\9bЫ, Ð¯Ð\9aÐ\86Я Ð\90Ð¥Ð\9eÐ\8eÐ\92Ð\90ЮЦЦÐ\90 Ð\90Ð\8eТÐ\90РСÐ\9aÐ\86Ð\9c Ð\9fРÐ\90Ð\92Ð\90Ð\9c!'''",
+       "copyrightwarning": "Ð\9aалÑ\96 Ð»Ð°Ñ\81ка, Ð·Ñ\8cвÑ\8fÑ\80нÑ\96Ñ\86е Ñ\9eвагÑ\83 Ð½Ð° Ñ\82ое, Ñ\88Ñ\82о Ñ\9eÑ\81е Ð´Ð°Ð´Ð°Ñ\82кÑ\96 Ñ\96 Ð·Ñ\8cменÑ\8b Ñ\9e {{GRAMMAR:меÑ\81нÑ\8b|{{SITENAME}}}} Ñ\80азглÑ\8fдаÑ\8eÑ\86Ñ\86а Ñ\8fк Ð²Ñ\8bдадзенÑ\8bÑ\8f Ñ\9e Ð°Ð´Ð¿Ð°Ð²ÐµÐ´Ð½Ð°Ñ\81Ñ\8cÑ\86Ñ\96 Ð· Ñ\83мовамÑ\96 Ð»Ñ\96Ñ\86Ñ\8dнзÑ\96Ñ\96 $2 (глÑ\8fдзÑ\96Ñ\86е Ð¿Ð°Ð´Ñ\80абÑ\8fзнаÑ\81Ñ\8cÑ\86Ñ\96 Ð½Ð° $1). Ð\9aалÑ\96 Ð²Ñ\8b Ñ\81Ñ\83пÑ\80аÑ\86Ñ\8c Ñ\82аго, ÐºÐ°Ð± Ð²Ð°Ñ\88Ñ\8bÑ\8f Ð¼Ð°Ñ\82Ñ\8dÑ\80Ñ\8bÑ\8fлÑ\8b Ð½ÐµÐ°Ð±Ð¼ÐµÐ¶Ð°Ð²Ð°Ð½Ð° Ñ\80Ñ\8dдагавалаÑ\81Ñ\8f Ñ\96 Ñ\80аÑ\81паÑ\9eÑ\81Ñ\8eджвалаÑ\81Ñ\8f, Ð½Ðµ Ð´Ð°Ð´Ð°Ð²Ð°Ð¹Ñ\86е Ñ\96Ñ\85.<br />\nÐ\92Ñ\8b Ñ\82акÑ\81ама Ð°Ð±Ð°Ð²Ñ\8fзÑ\83еÑ\86еÑ\81Ñ\8f, Ñ\88Ñ\82о Ð²Ð°Ñ\88 Ð¼Ð°Ñ\82Ñ\8dÑ\80Ñ\8bÑ\8fл Ð½Ð°Ð¿Ñ\96Ñ\81анÑ\8b Ð°Ñ\81абÑ\96Ñ\81Ñ\82а Ð²Ð°Ð¼Ñ\96 Ð°Ð±Ð¾ Ð·Ñ\8cÑ\8fÑ\9eлÑ\8fеÑ\86Ñ\86а Ð³Ñ\80амадзкÑ\96м Ð½Ð°Ð±Ñ\8bÑ\82кам, Ð°Ð»Ñ\8cбо Ñ\9eзÑ\8fÑ\82Ñ\8b Ð· Ð¿Ð°Ð´Ð¾Ð±Ð½Ñ\8bÑ\85 Ð²Ð¾Ð»Ñ\8cнÑ\8bÑ\85 ÐºÑ\80Ñ\8bнÑ\96Ñ\86аÑ\9e.\n<strong>Ð\9dелÑ\8cга Ð±ÐµÐ· Ð´Ð°Ð·Ð²Ð¾Ð»Ñ\83 Ð´Ð°Ð´Ð°Ð²Ð°Ñ\86Ñ\8c Ð¼Ð°Ñ\82Ñ\8dÑ\80Ñ\8bÑ\8fлÑ\8b, Ð°Ð±Ð°Ñ\80оненÑ\8bÑ\8f Ð°Ñ\9eÑ\82аÑ\80Ñ\81кÑ\96м Ð¿Ñ\80авам!</strong>",
+       "copyrightwarning2": "Ð\9aалÑ\96 Ð»Ð°Ñ\81ка, Ð·Ð°Ñ\9eважÑ\86е, Ñ\88Ñ\82о Ñ\9eвеÑ\81Ñ\8c Ñ\83нÑ\91Ñ\81ак Ñ\9e {{GRAMMAR:вÑ\96навалÑ\8cнÑ\8b|{{SITENAME}}}} Ð¼Ð¾Ð¶Ð° Ñ\80Ñ\8dдагаваÑ\86Ñ\86а, Ð·Ñ\8cмÑ\8fнÑ\8fÑ\86Ñ\86а Ñ\96 Ð²Ñ\8bдалÑ\8fÑ\86Ñ\86а Ñ\96нÑ\88Ñ\8bмÑ\96 Ñ\9eдзелÑ\8cнÑ\96камÑ\96.\nÐ\9aалÑ\96 Ð²Ñ\8b Ð· Ð³Ñ\8dÑ\82Ñ\8bм Ð½Ñ\8f Ð·Ð³Ð¾Ð´Ð½Ñ\8bÑ\8f, ÐºÐ°Ð»Ñ\96 Ð»Ð°Ñ\81ка, Ð½Ðµ Ð·Ñ\8cмÑ\8fÑ\88Ñ\87айÑ\86е Ñ\81Ñ\8eдÑ\8b Ð\92аÑ\88Ñ\8bÑ\8f Ñ\82Ñ\8dкÑ\81Ñ\82Ñ\8b.<br />\nРазÑ\8cмÑ\8fÑ\88Ñ\87Ñ\8dнÑ\8cнем Ñ\82Ñ\83Ñ\82 Ñ\82Ñ\8dкÑ\81Ñ\82аÑ\9e, Ð²Ñ\8b Ð´Ñ\8dклÑ\8fÑ\80Ñ\83еÑ\86е, Ñ\88Ñ\82о Ð·Ñ\8cÑ\8fÑ\9eлÑ\8fеÑ\86еÑ\81Ñ\8f Ñ\96Ñ\85 Ð°Ñ\9eÑ\82аÑ\80ам, Ñ\86Ñ\96 Ñ\81капÑ\96Ñ\8fвалÑ\96 Ñ\96Ñ\85 Ð· ÐºÑ\80Ñ\8bнÑ\96Ñ\86Ñ\8b, Ñ\8fкаÑ\8f Ð´Ð°Ð·Ð²Ð°Ð»Ñ\8fе Ð²Ð¾Ð»Ñ\8cнае Ð²Ñ\8bкаÑ\80Ñ\8bÑ\81Ñ\82анÑ\8cне Ñ\81ваÑ\96Ñ\85 Ñ\82Ñ\8dкÑ\81Ñ\82аÑ\9e (дзелÑ\8f Ð¿Ð°Ð´Ñ\80абÑ\8fзнаÑ\81Ñ\8cÑ\86Ñ\8fÑ\9e Ð³Ð»Ñ\8fдзÑ\96Ñ\86е $1).\n\n<strong>Ð\9dе Ð·Ñ\8cмÑ\8fÑ\88Ñ\87айÑ\86е Ñ\82Ñ\83Ñ\82 Ð±ÐµÐ· Ð´Ð°Ð·Ð²Ð¾Ð»Ñ\83 Ð¼Ð°Ñ\82Ñ\8dÑ\80Ñ\8bÑ\8fлÑ\8b, Ñ\8fкÑ\96Ñ\8f Ð°Ñ\85оÑ\9eваÑ\8eÑ\86Ñ\86а Ð°Ñ\9eÑ\82аÑ\80Ñ\81кÑ\96м Ð¿Ñ\80авам!</strong>",
        "editpage-cannot-use-custom-model": "Мадэль зьместу гэтай старонкі ня можа быць зьмененая.",
-       "longpageerror": "'''Памылка: Аб’ём тэксту, які Вы спрабуеце запісаць складае $1 {{PLURAL:$1|кілябайт|кілябайты|кілябайтаў}}, што болей устаноўленага абмежаваньня на $2 {{PLURAL:$2|кілябайт|кілябайты|кілябайтаў}}.'''\nСтаронка ня можа быць захаваная.",
+       "longpageerror": "<strong>Памылка: аб’ём тэксту, які вы спрабуеце запісаць складае $1 {{PLURAL:$1|кілябайт|кілябайты|кілябайтаў}}, што болей за ўсталяванае абмежаваньне на $2 {{PLURAL:$2|кілябайт|кілябайты|кілябайтаў}}.</strong>\nСтаронка ня можа быць захаваная.",
        "readonlywarning": "<strong>Папярэджаньне: База зьвестак была заблякаваная для тэхнічнага абслугоўваньня, таму немагчыма цяпер захаваць Вашыя зьмены.</strong>\nВы можаце скапіяваць тэкст у файл на Вашым кампутары, а пазьней захаваць сюды.\n\nСыстэмны адміністратар, які заблякаваў базу зьвестак, прапанаваў наступнае тлумачэньне: $1",
-       "protectedpagewarning": "'''Папярэджаньне: Гэтая старонка была абароненая, таму толькі адміністратары могуць рэдагаваць яе.'''\nАпошні запіс з журнала пададзены ніжэй для даведкі:",
-       "semiprotectedpagewarning": "'''Заўвага:''' Гэтая старонка была абароненая, і рэдагаваць яе могуць толькі зарэгістраваныя ўдзельнікі.\nАпошні запіс з журнала пададзены ніжэй для даведкі:",
+       "protectedpagewarning": "<strong>Папярэджаньне: гэтая старонка была абароненая, таму толькі адміністратары могуць рэдагаваць яе.</strong>\nАпошні запіс з журнала пададзены ніжэй для даведкі:",
+       "semiprotectedpagewarning": "<strong>Заўвага:</strong> гэтая старонка была абароненая, таму рэдагаваць яе могуць толькі зарэгістраваныя ўдзельнікі.\nАпошні запіс з журнала пададзены ніжэй для даведкі:",
        "cascadeprotectedwarning": "'''Папярэджаньне:''' гэтая старонка абароненая, толькі ўдзельнікі з правамі адміністратараў могуць рэдагаваць яе, таму што яна ўключаная ў {{PLURAL:$1|1=наступную старонку|наступныя старонкі}} з каскаднай абаронай:",
-       "titleprotectedwarning": "'''Папярэджаньне: гэтая старонка была абароненая і для яе стварэньня патрабуюцца [[Special:ListGroupRights|адпаведныя правы]].'''\nАпошні запіс з журнала пададзены ніжэй для даведкі:",
+       "titleprotectedwarning": "<strong>Папярэджаньне: гэтая старонка была абароненая і для яе стварэньня патрабуюцца [[Special:ListGroupRights|адпаведныя правы]].</strong>\nАпошні запіс з журнала пададзены ніжэй для даведкі:",
        "templatesused": "{{PLURAL:$1|Шаблён, які ўжываецца|Шаблёны, якія ўжываюцца}} на гэтай старонцы:",
        "templatesusedpreview": "У гэтым папярэднім праглядзе {{PLURAL:$1|1=выкарыстаны наступны шаблён|выкарыстаныя наступныя шаблёны}}:",
        "templatesusedsection": "У гэтай сэкцыі {{PLURAL:$1|1=выкарыстаны наступны шаблён|выкарыстаныя наступныя шаблёны}}:",
        "template-protected": "(абаронены)",
        "template-semiprotected": "(часткова абароненая)",
        "hiddencategories": "Гэтая старонка належыць да $1 {{PLURAL:$1|схаванай катэгорыі|схаваных катэгорыяў}}:",
-       "nocreatetext": "У {{GRAMMAR:месны|{{SITENAME}}}} абмежаванае стварэньне новых старонак.\nВы можаце вярнуцца і рэдагаваць існуючую старонку, альбо [[Special:UserLogin|ўвайсьці ў сыстэму ці стварыць рахунак]].",
+       "nocreatetext": "У {{GRAMMAR:месны|{{SITENAME}}}} абмежаванае стварэньне новых старонак.\nВы можаце вярнуцца і рэдагаваць існую старонку, альбо [[Special:UserLogin|ўвайсьці ў сыстэму ці стварыць рахунак]].",
        "nocreate-loggedin": "Вы ня маеце дазволу на стварэньне новых старонак.",
        "sectioneditnotsupported-title": "Рэдагаваньне сэкцыяў не падтрымліваецца",
-       "sectioneditnotsupported-text": "Рэдагаваньне сэкцыяў не падтрымліваецца ў гэтай старонцы рэдагаваньня",
+       "sectioneditnotsupported-text": "Рэдагаваньне сэкцыяў не падтрымліваецца на гэтай старонцы.",
        "permissionserrors": "Памылка дазволу",
        "permissionserrorstext": "Вы ня маеце дазволу на гэтае дзеяньне з {{PLURAL:$1|1=наступнай прычыны|наступных прычынаў}}:",
        "permissionserrorstext-withaction": "Вы ня маеце дазволу на $2 з {{PLURAL:$1|1=наступнай прычыны|наступных прычынаў}}:",
        "search-external": "Вонкавы пошук",
        "searchdisabled": "Функцыя пошуку ў {{GRAMMAR:месны|{{SITENAME}}}} адключаная.\nВы можаце пашукаць з дапамогай Google, але заўважце, што там інфармацыя пра старонкі {{GRAMMAR:родны|{{SITENAME}}}} можа быць састарэлай.",
        "search-error": "Узьнікла памылка пры пошуку: $1",
+       "search-warning": "Пры пошуку зьявілася папярэджаньне: $1",
        "preferences": "Налады",
        "mypreferences": "Налады",
        "prefs-edits": "Колькасьць рэдагаваньняў:",
        "userrights-user-editname": "Увядзіце імя ўдзельніка:",
        "editusergroup": "Загрузіць групы ўдзельніка",
        "editinguser": "Зьмена правоў {{GENDER:$1|удзельніка|удзельніцы}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Прагляд правоў {{GENDER:$1|удзельніка|удзельніцы}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Рэдагаваць групы ўдзельнікаў і ўдзельніц",
+       "userrights-viewusergroup": "Прагляд групаў удзельніка",
        "saveusergroups": "Захаваць групы {{GENDER:$1|ўдзельнікаў і ўдзельніц}}",
        "userrights-groupsmember": "Уваходзіць у:",
        "userrights-groupsmember-auto": "Няяўны чалец:",
        "action-upload_by_url": "загрузку гэтага файла з URL-адрасу",
        "action-writeapi": "выкарыстаньне API для запісаў",
        "action-delete": "выдаленьне гэтай старонкі",
-       "action-deleterevision": "выдаленьне гэтай вэрсіі",
+       "action-deleterevision": "выдаленьне вэрсіяў",
+       "action-deletelogentry": "выдаленьне запісаў у журнале",
        "action-deletedhistory": "прагляд выдаленай гісторыі гэтай старонкі",
+       "action-deletedtext": "прагляд тэксту выдаленай вэрсіі",
        "action-browsearchive": "пошук выдаленых старонак",
-       "action-undelete": "аднаўленьне гэтай старонкі",
-       "action-suppressrevision": "прагляд і аднаўленьне гэтай схаванай вэрсіі",
+       "action-undelete": "аднаўленьне старонак",
+       "action-suppressrevision": "прагляд і аднаўленьне схаваных вэрсіяў",
        "action-suppressionlog": "прагляд гэтага прыватнага журнала",
        "action-block": "блякаваньне гэтага ўдзельніка ад рэдагаваньняў",
        "action-protect": "зьмену ўзроўню абароны гэтай старонкі",
        "action-userrights-interwiki": "рэдагаваньне правоў удзельнікаў у іншых вікі",
        "action-siteadmin": "блякаваньне і разблякаваньне базы зьвестак",
        "action-sendemail": "адпраўляць лісты іншым удзельнікам",
+       "action-editmyoptions": "рэдагаваньне вашых наладаў",
        "action-editmywatchlist": "рэдагаваць ваш сьпіс назіраньня",
        "action-viewmywatchlist": "праглядаць ваш сьпіс назіраньня",
        "action-viewmyprivateinfo": "прагляд вашых прыватных зьвестак",
        "emailccsubject": "Копія Вашага ліста да $1: $2",
        "emailsent": "Ліст адасланы",
        "emailsenttext": "Ваш ліст быў адасланы.",
-       "emailuserfooter": "Гэты ліст быў дасланы {{GENDER:$1|ўдзельнікам|ўдзельніцай}} $1 да {{GENDER:$2|ўдзельніка|ўдзельніцы}} $2 з дапамогай функцыі «{{int:emailuser}}» {{GRAMMAR:родны|{{SITENAME}}}}.",
+       "emailuserfooter": "Гэты ліст быў дасланы {{GENDER:$1|ўдзельнікам|ўдзельніцай}} $1 да {{GENDER:$2|ўдзельніка|ўдзельніцы}} $2 з дапамогай функцыі «{{int:emailuser}}» {{GRAMMAR:родны|{{SITENAME}}}}. {{GENDER:$2|Ваш}} ліст у адказ будзе дасланы {{GENDER:$1|адпраўніку|адпраўніцы}}, і {{GENDER:$1|яму|ёй}} будзе бачны {{GENDER:$2|ваш}} адрас электроннай пошты.",
        "usermessage-summary": "Паведамленьне пра выхад з сыстэмы.",
        "usermessage-editor": "Дастаўка сыстэмных паведамленьняў",
        "watchlist": "Сьпіс назіраньня",
        "wlshowhidemine": "мае праўкі",
        "wlshowhidecategorization": "катэгарызацыю старонак",
        "watchlist-options": "Налады сьпісу назіраньня",
+       "watchlist-mark-all-visited": "Вы ўпэўненыя, што хочаце скінуць непрагледжаныя зьмены ў сьпісе назіраньня і пазначыць усе старонкі як прагледжаныя?",
        "watching": "Дадаецца ў сьпіс назіраньня…",
        "unwatching": "Выдаляецца са сьпісу назіраньня…",
        "watcherrortext": "Узьнікла памылка падчас зьмены Вашага сьпісу назіраньня для «$1».",
        "movelogpagetext": "Ніжэй пададзены сьпіс перанесеных старонак.",
        "movesubpage": "{{PLURAL:$1|1=Падстаронка|Падстаронкі}}",
        "movesubpagetext": "Гэтая старонка мае $1 {{PLURAL:$1|падстаронку|падстаронкі|падстаронак}}, {{PLURAL:$1|1=якая паказаная ніжэй|якія паказаныя ніжэй}}.",
+       "movesubpagetalktext": "Адпаведная старонка абмеркаваньня мае $1 {{PLURAL:$1|падстаронку, паказаную|падстаронкі, паказаныя|падстаронак, паказаныя}} ніжэй.",
        "movenosubpage": "Гэтая старонка ня мае падстаронак.",
        "movereason": "Прычына:",
        "revertmove": "адкат",
        "pageinfo-category-pages": "Колькасьць старонак",
        "pageinfo-category-subcats": "Колькасьць падкатэгорыяў",
        "pageinfo-category-files": "Колькасьць файлаў",
+       "pageinfo-user-id": "Ідэнтыфікатар удзельніка",
        "markaspatrolleddiff": "Пазначыць як «патруляваную»",
        "markaspatrolledtext": "Пазначыць гэтую старонку як «патруляваную»",
        "markaspatrolledtext-file": "Пазначыць гэтую вэрсію файлу як патруляваную",
        "log-show-hide-patrol": "$1 журнал патруляваньняў",
        "log-show-hide-tag": "$1 журнал метак",
        "confirm-markpatrolled-button": "Добра",
+       "confirm-markpatrolled-top": "Пазначыць вэрсію $3 старонкі $2 як патруляваную?",
        "deletedrevision": "Выдаленая старая вэрсія $1",
        "filedeleteerror-short": "Памылка выдаленьня файла: $1",
        "filedeleteerror-long": "У часе выдаленьня файла ўзьніклі наступныя памылкі:\n\n$1",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "htmlform-user-not-valid": "<strong>$1</strong> — некарэктнае імя карыстальніка.",
        "logentry-delete-delete": "$1 {{GENDER:$2|выдаліў|выдаліла}} старонку $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|выдаліў|выдаліла}} перанакіраваньне $3 шляхам перазапісу",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|зьмяніў|зьмяніла}} бачнасьць $5 {{PLURAL:$5|1=падзеі ў журнале|падзеяў у журнале}} на $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|зьмяніў|зьмяніла}} бачнасьць $5 {{PLURAL:$5|вэрсіі|вэрсіяў}} старонкі $3: $4",
        "mw-widgets-dateinput-no-date": "Дата не абраная",
        "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
+       "mw-widgets-mediasearch-input-placeholder": "Пошук мультымэдыя",
+       "mw-widgets-mediasearch-noresults": "Нічога ня знойдзена.",
        "mw-widgets-titleinput-description-new-page": "старонка яшчэ не існуе",
        "mw-widgets-titleinput-description-redirect": "перанакіраваньне на $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Дадаць катэгорыю…",
        "sessionmanager-tie": "Немагчыма выкарыстаць адначасова некалькі тыпаў аўтэнтыфікацыі: $1.",
        "sessionprovider-generic": "$1 сэсіі",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "сэсіі на падставе файлаў-кукі",
        "log-action-filter-contentmodel-change": "Зьмена мадэлі зьместу",
        "log-action-filter-contentmodel-new": "Стварэньне старонкі зь нестандартнай мадэльлю зьместу",
        "log-action-filter-delete-delete": "Выдаленьне старонкі",
+       "log-action-filter-delete-delete_redir": "Перазапіс перанакіраваньня",
        "log-action-filter-delete-restore": "Аднаўленьне старонкі",
        "log-action-filter-delete-event": "Выдаленьне журналу",
        "log-action-filter-delete-revision": "Выдаленьне вэрсіі",
        "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-autopromote": "Аўтаматычнае зьмяненьне",
        "log-action-filter-upload-upload": "Новая загрузка",
        "authmanager-realname-label": "Сапраўднае імя",
index 26175b3..571c340 100644 (file)
        "eauthentsent": "Пацверджанне было адасланае электроннай поштай на азначаны адрас эл.пошты.\nКаб туды, у далейшым, трапляла іншая пошта адсюль, патрабуецца выканаць інструкцыі, выкладзеныя ў тым эл.паведамленні, каб пацвердзіць сваё права на рахунак эл.пошты.",
        "throttled-mailpassword": "Нагаданне пра пароль ужо адсылалася на працягу апошн{{PLURAL:$1|яй гадзіны|іх $1 гадзін}}. Дзеля абароны ад злоўжыванняў, дазваляецца атрымліваць толькі адно такое нагаданне за {{PLURAL:$1|гадзіну|$1 гадзіны|$1 гадзін}}.",
        "mailerror": "Памылка адсылання эл.пошты: $1",
-       "acct_creation_throttle_hit": "На гэтай вікі за апошні дзень створаны {{PLURAL:$1|1 рахунак|$1 рахункаў}} наведвальнікамі з вашага адрасу IP. Больш за такі час не дазваляецца. Таму на пэўны час з гэтага адрасу IP нельга ствараць новых рахункаў.",
+       "acct_creation_throttle_hit": "Наведвальнікі гэтай вікі, якія карысталіся Вашым ІР-адрасам, ужо стварылі {{PLURAL:$1|рахунак|рахункі|рахункаў}} за $2. Больш за такі час не дазваляецца. Таму на пэўны час з гэтага адрасу IP нельга ствараць новых рахункаў.",
        "emailauthenticated": "Ваш адрас эл.пошты быў пацверджаны на пляцоўцы $2 ($3).",
        "emailnotauthenticated": "Адрас эл.пошты яшчэ не пацверджаны. \nЭл.пошта ў гэтых магчымасцях адсылацца не будзе.",
        "noemailprefs": "Патрэбны адрас эл.пошты, каб дзейнічалі гэтыя магчымасці.",
        "explainconflict": "Нехта іншы змяніў старонку пасля таго, як вы пачалі працу з ёю.\nВерхняе тэкставае поле паказвае актуальны зыходны тэкст старонкі.\nВашы праўкі паказаны ў ніжнім тэкставым полі.\nВам трэба будзе далучыць іх да актуальнага зыходнага тэксту.\nКалі націснуць \"{{int:savearticle}}\", будзе запісаны '''толькі''' тэкст у верхнім полі.",
        "yourtext": "Свой тэкст",
        "storedversion": "Захаваная версія",
-       "nonunicodebrowser": "'''УВАГА: ваш браўзер не працуе з Унікодам. Каб вы маглі карэктна  правіць старонкі, ужываецца такая штука, што знакі з па-за абсягу ASCII паказваюцца ў рэдактарскім акне як шаснаццаткавыя коды.'''",
-       "editingold": "'''УВАГА: Вы правіце такую версію артыкула, якая не з'яўляецца актуальнай.\nКалі вы яе зараз запішаце, то страціце змены ў артыкуле, зробленыя пасля колішняга запісу гэтай версіі.'''",
+       "nonunicodebrowser": "<strong>Увага: ваш браўзер не працуе з Unicode-кадаваннем.</strong> Каб вы маглі карэктна правіць старонкі, усе знакі, не ўключаныя ў ASCII, паказваюцца ў рэдактарскім акне як шаснаццаткавыя коды.",
+       "editingold": "<strong>Увага: Вы правіце састарэлую версію гэтай старонкі.</strong>\nКалі Вы паспрабуеце захаваць яе, то страціце змены ў артыкуле, зробленыя пасля колішняга запісу гэтай версіі.",
        "yourdiff": "Адрозненні",
-       "copyrightwarning": "Заўважце, што ўсе ўклады на {{SITENAME}} лічацца выданымі на ўмовах $2 (бач падрабязнасці на $1). Калі вы не жадаеце, каб вашыя матэрыялы бязлітасна правіліся, і свабодна распаўсюджваліся, то і не аддавайце іх сюды.<br />\nТаксама вы нам абяцаеце, што напісалі гэта самі, або скапіравалі з рэсурсу, які знаходзіцца ў публічнай уласнасці, або з аналагічнага свабоднага рэсурсу.\n'''НЕ КЛАДЗІЦЕ СЮДЫ, БЕЗ АДПАВЕДНАГА ДАЗВОЛУ, МАТЭРЫЯЛУ, ЯКІ АХОЎВАЕЦЦА АЎТАРСКІМ ПРАВАМ!'''",
-       "copyrightwarning2": "Заўважце, што кожны ўклад на {{SITENAME}} можа быць папраўлены, зменены або выдалены іншымі ўдзельнікамі. Калі вы не жадаеце, каб вашыя матэрыялы бязлітасна правіліся, то і не давайце іх сюды.<br />\nТаксама вы нам абяцаеце, што напісалі гэта самі, або скапіравалі з рэсурсу, які знаходзіцца ў публічнай уласнасці, або з аналагічнага свабоднага рэсурсу (бач падрабязнасці на $1).\n'''НЕ КЛАДЗІЦЕ СЮДЫ, БЕЗ АДПАВЕДНАГА ДАЗВОЛУ, МАТЭРЫЯЛУ, ЯКІ АХОЎВАЕЦЦА АЎТАРСКІМ ПРАВАМ!'''",
+       "copyrightwarning": "Заўважце, што ўсе ўклады на {{GRAMMAR:месны|{{SITENAME}}}} лічацца выданымі на ўмовах $2 (гл. падрабязнасці на $1). Калі вы не жадаеце, каб вашыя матэрыялы бязлітасна правіліся, і свабодна распаўсюджваліся, то і не аддавайце іх сюды.<br />\nТаксама вы нам абяцаеце, што напісалі гэта самі, або скапіравалі з рэсурсу, які знаходзіцца ў публічнай уласнасці, або з аналагічнага свабоднага рэсурсу.\n<strong>Нельга без дазволу дадаваць матэрыялы, абароненыя аўтарскім правам!</strong>",
+       "copyrightwarning2": "Заўважце, што кожны ўклад на {{GRAMMAR:месны|{{SITENAME}}}} можа быць папраўлены, зменены або выдалены іншымі ўдзельнікамі. Калі вы не жадаеце, каб вашыя матэрыялы бязлітасна правіліся, то і не давайце іх сюды.<br />\nТаксама вы нам абяцаеце, што напісалі гэта самі, або скапіравалі з рэсурсу, які знаходзіцца ў публічнай уласнасці, або з аналагічнага свабоднага рэсурсу (гл. падрабязнасці на $1).\n<strong>Не змяшчайце тут без дазволу матэрыялы, якія ахоўваюцца аўтарскім правам!</strong>",
        "editpage-cannot-use-custom-model": "Мадэль зместу гэтай старонкі не можа быць зменена.",
-       "longpageerror": "'''Памылка: Аб’ём тэксту, які Вы спрабуеце запісаць складае $1 {{PLURAL:$1|кілабайт|кілабайты|кілабайтаў}}, што болей устаноўленага абмежавання на $2 {{PLURAL:$2|кілабайт|кілабайты|кілабайтаў}}.'''\nСтаронка не можа быць захаваная.",
+       "longpageerror": "<strong>Памылка: Аб’ём тэксту, які Вы спрабуеце запісаць складае $1 {{PLURAL:$1|кілабайт|кілабайты|кілабайтаў}}, што болей устаноўленага абмежавання на $2 {{PLURAL:$2|кілабайт|кілабайты|кілабайтаў}}.</strong>\nСтаронка не можа быць захаваная.",
        "readonlywarning": "<strong>Увага: зараз вы не можаце запісаць свае праўкі, таму што база звестак зачынена на абслугоўванне.</strong>\nМагчыма, варта перанесці ваш тэкст у асобны файл і запісаць на потым.\n\nСістэмны адміністратар, які зачыніў базу, растлумачыў гэта так: $1",
-       "protectedpagewarning": "'''УВАГА: старонка пастаўлена пад ахову, таму яе могуць правіць толькі адміністратары.'''\nНіжэй паказаны апошні запіс з адпаведнага журнала:",
-       "semiprotectedpagewarning": "'''Увага:''' старонка пастаўлена пад ахову, таму яе могуць правіць толькі зарэгістраваныя ўдзельнікі («паў-ахова»). Ніжэй паказаны апошні запіс з адпаведнага журнала:",
+       "protectedpagewarning": "<strong>Увага: старонка пастаўлена пад ахову, таму яе могуць правіць толькі адміністратары.</strong>\nНіжэй паказаны апошні запіс з адпаведнага журнала:",
+       "semiprotectedpagewarning": "<strong>Увага:</strong> старонка пастаўлена пад ахову, таму яе могуць правіць толькі зарэгістраваныя ўдзельнікі («паў-ахова»). Ніжэй паказаны апошні запіс з адпаведнага журнала:",
        "cascadeprotectedwarning": "<strong>Увага:</strong> гэтая старонка ахоўваецца, таму яе могуць правіць толькі ўдзельнікі з правамі адміністратара. Прычына аховы: улучэнне гэтай старонкі ў {{PLURAL:$1|старонку, якая стаіць|старонкі, якія стаяць}} пад каскаднай аховай:",
        "titleprotectedwarning": "'''УВАГА: старонка пастаўлена пад ахову, таму яе могуць ствараць толькі ўдзельнікі з [[Special:ListGroupRights|адмысловымі правамі]].'''\nНіжэй паказаны апошні запіс з адпаведнага журнала:",
        "templatesused": "Шабло{{PLURAL:$1|н|ны}} на гэтай старонцы:",
        "search-external": "Вонкавы пошук",
        "searchdisabled": "Функцыя пошуку {{SITENAME}} не працуе. Тымчасова можна шукаць з дапамогай Google. Заўважце, што тамтэйшыя індэксы зместу {{SITENAME}} могуць не быць актуальнымі.",
        "search-error": "Памылка пошуку: $1",
+       "search-warning": "Пры пошуку з'явілася папярэджанне: $1",
        "preferences": "Настройкі",
        "mypreferences": "Настройкі",
        "prefs-edits": "Колькасць правак:",
        "prefs-help-recentchangescount": "Гэта ўключае ў сябе апошнія змены, гісторыі старонак, журналы.",
        "prefs-help-watchlist-token2": "Гэта сакрэтны ключ к сеціўнай стужцы з вашага спіса назірання.\nКожны, хто ведае гэты ключ, будзе мець магчымасць чытаць ваш спіс назірання, таму не дзяліцеся ім.\nКалі трэба, можна [[Special:ResetTokens|скінуць яго]].",
        "savedprefs": "Настройкі замацаваныя.",
-       "savedrights": "Ð\94азволы {{GENDER:$1|ўдзельніка|ўдзельніцы}} $1 захаваныя.",
+       "savedrights": "Ð\93Ñ\80Ñ\83пы {{GENDER:$1|ўдзельніка|ўдзельніцы}} $1 захаваныя.",
        "timezonelegend": "Часавы пояс:",
        "localtime": "Мясцовы час:",
        "timezoneuseserverdefault": "Карыстацца настаўленнямі серверу ($1)",
        "prefswarning-warning": "Вы зрабілі змены ў сваіх настройках, якія яшчэ не былі запісаныя.\nКалі вы закрыеце гэту старонку, не націснуўшы \"$1\", вашы настройкі не будуць абноўлены.",
        "prefs-tabs-navigation-hint": "Падказка: Вы можаце карыстацца клавішамі са стрэлкамі ўлева і ўправа для навігацыі паміж карткамі ў спісе картак.",
        "userrights": "Распараджэнне правамі ўдзельніка",
-       "userrights-lookup-user": "РаÑ\81паÑ\80аджаÑ\86Ñ\86а Ð³Ñ\80Ñ\83памÑ\96 Ñ\9eдзелÑ\8cнÑ\96каÑ\9e",
+       "userrights-lookup-user": "Ð\92Ñ\8bбеÑ\80Ñ\8bÑ\86е Ñ\9eдзелÑ\8cнÑ\96ка",
        "userrights-user-editname": "Увядзіце імя ўдзельніка:",
-       "editusergroup": "Ð\9fÑ\80авÑ\96Ñ\86Ñ\8c Ð³Ñ\80Ñ\83пÑ\8b {{GENDER:$1|Ñ\9eдзелÑ\8cнÑ\96каÑ\9e\9eдзелÑ\8cнÑ\96Ñ\86}}",
+       "editusergroup": "Ð\97агÑ\80Ñ\83зÑ\96Ñ\86Ñ\8c Ð³Ñ\80Ñ\83пÑ\8b Ñ\9eдзелÑ\8cнÑ\96ка",
        "editinguser": "Змена праў {{GENDER:$1|удзельніка|удзельніцы}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Прагляд правоў {{GENDER:$1|удзельніка|удзельніцы}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Распараджацца групамі ўдзельніка",
+       "userrights-viewusergroup": "Прагляд груп удзельніка",
        "saveusergroups": "Захаваць групы {{GENDER:$1|ўдзельнікаў|ўдзельніц}}",
        "userrights-groupsmember": "У групе:",
        "userrights-groupsmember-auto": "Няяўны член:",
        "action-upload_by_url": "загрузіць гэты файл з адраса URL",
        "action-writeapi": "ужываць API запісвання",
        "action-delete": "сціраць гэтую старонку",
-       "action-deleterevision": "сціраць гэтую версію",
+       "action-deleterevision": "сціраць версіі старонак",
+       "action-deletelogentry": "выдаленне запісаў у журнале",
        "action-deletedhistory": "бачыць сцёртую гісторыю гэтай старонкі",
+       "action-deletedtext": "прагляд тэксту выдаленай версіі",
        "action-browsearchive": "шукаць у сцёртых старонках",
-       "action-undelete": "аднаўляць гэтую старонку",
+       "action-undelete": "аднаўленне старонак",
        "action-suppressrevision": "бачыць і аднаўляць гэтую схаваную версію",
        "action-suppressionlog": "бачыць гэты прыватны журнал",
        "action-block": "блакаваць праўкі гэтага ўдзельніка",
        "action-userrights-interwiki": "мяняць дазволы ўдзельнікаў на іншых вікі",
        "action-siteadmin": "зачыняць і адчыняць базу даных",
        "action-sendemail": "адпраўка электронных пісем",
+       "action-editmyoptions": "змяненне Вашых наладаў",
        "action-editmywatchlist": "правіць свой спіс назірання",
        "action-viewmywatchlist": "глядзець свой спіс назірання",
        "action-viewmyprivateinfo": "бачыць свае асабістыя звесткі",
        "apisandbox-submit-invalid-fields-title": "Некаторыя палі недапушчальныя",
        "apisandbox-submit-invalid-fields-message": "Калі ласка, выпраўце адзначаныя палі і паспрабуйце ізноў.",
        "apisandbox-results": "Вынікі",
+       "apisandbox-sending-request": "Адпраўка API-запыту…",
+       "apisandbox-loading-results": "Атрымліваем API-вынікі…",
        "apisandbox-request-url-label": "URL-адрас запыту:",
        "apisandbox-request-time": "Час запыту: {{PLURAL:$1|$1 мс}}",
        "apisandbox-results-fixtoken": "Папраўце токен і паўтарыце адпраўку",
        "listgrouprights-namespaceprotection-header": "Абмежаванні прасторы назваў",
        "listgrouprights-namespaceprotection-namespace": "Прастора назваў",
        "listgrouprights-namespaceprotection-restrictedto": "Дазвол(ы), неабходныя для праўкі",
+       "listgrants": "Дазволы",
+       "listgrants-grant": "Дазвол",
        "listgrants-rights": "Правы",
        "trackingcategories": "Катэгорыі для асочвання",
        "trackingcategories-summary": "На гэтай старонцы пералічаны катэгорыі для асочвання, якія аўтаматычна напаўняюцца праграмным забеспячэннем MediaWiki. Іх можна перайменаваць, змяніўшы адпаведныя сістэмныя паведамленні ў прасторы назваў {{ns:8}}.",
        "emailccsubject": "Копія Вашага ліста да $1: $2",
        "emailsent": "Эл.пошта адаслана",
        "emailsenttext": "Ваш ліст эл.пошты быў адасланы.",
-       "emailuserfooter": "Гэты эл.ліст быў высланы ад $1 да $2 праз функцыю \"{{:{{ns:mediawiki}}:emailuser/be}}\" пляцоўкі {{SITENAME}}.",
+       "emailuserfooter": "Гэты ліст быў дасланы {{GENDER:$1|ўдзельнікам|ўдзельніцай}} $1 да {{GENDER:$2|ўдзельніка|ўдзельніцы}} $2 з дапамогай функцыі «{{int:emailuser}}» {{GRAMMAR:родны|{{SITENAME}}}}. {{GENDER:$2|Ваш}} ліст у адказ будзе дасланы {{GENDER:$1|адпраўніку|адпраўніцы}}, і {{GENDER:$1|яму|ёй}} будзе бачны {{GENDER:$2|ваш}} адрас электроннай пошты.",
        "usermessage-summary": "Пакінуць адмысловае паведамленне.",
        "usermessage-editor": "Адмысловая дастаўка",
        "watchlist": "Мой спіс назірання",
        "movepagetext": "Форма, што ніжэй, перанясе старонку пад новую назву, і таксама перанясе пад новую назву ўсю гісторыю старонкі.\nСтарая назва ператворыцца ў перасылку да новай.\nПерасылкі, што вялі да старой назвы, можна ўдакладніць аўтаматычна.\nКалі такое аўта-удакладненне не будзе рабіцца, трэба праверыць наяўнасць [[Special:DoubleRedirects|падвойных]] ці [[Special:BrokenRedirects|зламаных]] перасылак.\nАдказнасць за правільную працу спасылак ляжыць на тым, хто пераносіць.\n\nЗаўважце, што старонка <strong>не будзе</strong> перанесена, калі пад новай назвай ужо існуе старонка, і гэта не перасылка без гісторыі правак.\nТакім чынам, пры пераносе нельга перапісаць наяўную старонку, а магчымую памылку можна адразу выправіць, пераносячы старонку ў адваротным кірунку.\n\n</strong>Увага!</strong>\nДля папулярнай старонкі гэта можа стацца рэзкім і нечаканым змяненнем;\nупэўніцеся, што разумееце наступствы пераносу перад тым, як яго зрабіць.",
        "movepagetext-noredirectfixer": "Форма, што ніжэй, перанясе старонку пад новую назву, і таксама перанясе пад новую назву ўсю гісторыю старонкі.\nСтарая назва ператворыцца ў перасылку да новай.\nПерасылкі, што вялі да старой назвы, можна ўдакладніць аўтаматычна.\nКалі такое аўта-удакладненне не будзе рабіцца, трэба праверыць наяўнасць [[Special:DoubleRedirects|падвойных]] ці [[Special:BrokenRedirects|зламаных]] перасылак.\nАдказнасць за правільную працу спасылак ляжыць на тым, хто пераносіць.\n\nЗаўважце, што старонка '''не будзе''' перанесена, калі пад новай назвай ужо існуе старонка, не пустая і не перасылка і без гісторыі правак. Такім чынам, пры пераносе нельга перапісаць наяўную старонку, а магчымую памылку можна адразу выправіць, пераносячы старонку ў адваротным кірунку.\n\n'''УВАГА!'''\nДля папулярнай старонкі гэта можа стацца рэзкім і нечаканым змяненнем;\nупэўніцеся, што разумееце наступствы пераносу перад тым, як яго зрабіць.",
        "movepagetalktext": "Звязаная старонка размовы будзе аўтаматычна перанесена разам з асноўнай, '''апроч тых выпадкаў, калі:'''\n*Існуе непустая старонка размовы звязаная з новай назвай, або\n*З боксу, што ніжэй, знятая адзнака.\n\nУ такіх выпадках, калі гэта неабходна, трэба пераносіць або аб'ядноўваць старонку размовы самастойна.",
-       "moveuserpage-warning": "'''Увага.''' Вы збіраецеся пераназваць старонку ўдзельніка. Калі ласка, звернеце ўвагу, што пераназвана будзе толькі старонка, удзельнік '''не''' будзе пераназваны.",
+       "moveuserpage-warning": "<strong>Увага:</strong> Вы збіраецеся пераназваць старонку ўдзельніка. Калі ласка, звернеце ўвагу, што пераназвана будзе толькі старонка, удзельнік <em>не</em> будзе пераназваны.",
        "movecategorypage-warning": "<strong>Увага:</strong> Вы збіраецеся перанесці старонку катэгорыі. Заўважце, што толькі гэта старонка будзе перанесена, і ніводная старонка са старой катэгорыі <em>не будзе</em> катэгарызавана ў новай.",
        "movenologintext": "Вы павінны быць зарэгістраваным удзельнікам, і [[Special:UserLogin|ўвайсці ў сістэму]], каб пераносіць старонкі.",
        "movenotallowed": "Вам не дазволена пераносіць старонкі.",
        "pageinfo-category-pages": "Колькасць старонак",
        "pageinfo-category-subcats": "Колькасць падкатэгорый",
        "pageinfo-category-files": "Колькасць файлаў",
+       "pageinfo-user-id": "Ідэнтыфікатар удзельніка",
        "markaspatrolleddiff": "Пазначыць як ухваленае",
        "markaspatrolledtext": "Пазначыць старонку як ухваленую",
        "markaspatrolledtext-file": "Пазначыць версію файла як ухваленую",
        "log-show-hide-patrol": "$1 журнал ухваленняў",
        "log-show-hide-tag": "$1 журнал бірак",
        "confirm-markpatrolled-button": "Добра",
+       "confirm-markpatrolled-top": "Пазначыць версію $3 старонкі $2 як патруляваную?",
        "deletedrevision": "Сцёрта старая версія $1",
        "filedeleteerror-short": "Памылка пры сціранні файла: $1",
        "filedeleteerror-long": "Памылкі пры спробе сцірання файла:\n\n$1",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "htmlform-user-not-valid": "<strong>$1</strong> - недапушчальная назва уліковага запісу.",
        "logentry-delete-delete": "$1 {{GENDER:$2|сцёр|сцёрла}} старонку $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|выдаліў|выдаліла}} перанакіраванне $3 шляхам перазапісу",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|змяніў|змяніла}} бачнасць {{PLURAL:$5|запісу журнала|$5 запісаў журнала}} $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|змяніў|змяніла}} бачнасць {{PLURAL:$5|версіі|$5 версій|$5 версій}} старонкі $3: $4",
        "feedback-submit": "Даслаць",
        "feedback-thanks": "Дзякуй! Ваш водгук размешчаны на старонцы «[$2 $1]».",
        "feedback-thanks-title": "Дзякуем!",
-       "searchsuggest-search": "Шукаць у {{SITENAME}}",
+       "searchsuggest-search": "Шукаць у {{GRAMMAR:месны|{{SITENAME}}}}",
        "searchsuggest-containing": "змяшчае...",
        "api-error-badaccess-groups": "У Вас няма дазволу загружаць файлы ў гэтую вікі.",
        "api-error-badtoken": "Унутраная памылка: няслушны ключ.",
        "special-characters-title-emdash": "доўгі працяжнік",
        "special-characters-title-minus": "мінус",
        "mw-widgets-dateinput-no-date": "Дата не выбрана",
+       "mw-widgets-mediasearch-input-placeholder": "Пошук мультымедыя",
        "mw-widgets-titleinput-description-new-page": "старонка яшчэ не існуе",
        "mw-widgets-titleinput-description-redirect": "перанакіраванне на $1",
        "log-action-filter-all": "Усе",
        "authmanager-autocreate-noperm": "Аўтаматычнае стварэнне уліковых запісаў не дапускаецца.",
        "authmanager-autocreate-exception": "Аўтаматычнае стварэнне уліковых запісаў часова адключана з-за памылак папярэдніх.",
        "authmanager-userdoesnotexist": "Уліковы запіс удзельніка \"$1\" не зарэгістраваны.",
+       "authmanager-userlogin-remembermypassword-help": "Ці трэба памятаць пароль даўжэй за працягласць сеанса.",
        "authmanager-username-help": "Імя карыстальніка для праверкі сапраўднасці.",
        "authmanager-password-help": "Пароль для праверкі сапраўднасці.",
        "authmanager-domain-help": "Дамен для знешняй праверкі сапраўднасці.",
        "linkaccounts-success-text": "Акаўнт быў звязаны.",
        "linkaccounts-submit": "Звязаць акаўнты",
        "unlinkaccounts": "Адмяніць сувязь уліковых запісаў",
-       "unlinkaccounts-success": "Сувязь акаўнтаў была выдалена."
+       "unlinkaccounts-success": "Сувязь акаўнтаў была выдалена.",
+       "userjsispublic": "Звярніце ўвагу: укладзеныя JavaScript-старонкі не павінны ўтрымліваць канфідэнцыйныя звесткі, бо іх могуць бачыць іншыя ўдзельнікі.",
+       "usercssispublic": "Звярніце ўвагу: укладзеныя CSS-старонкі не павінны ўтрымліваць канфідэнцыйныя звесткі, бо іх могуць бачыць іншыя ўдзельнікі.",
+       "restrictionsfield-badip": "Няслушны IP-адрас ці дыяпазон: $1",
+       "restrictionsfield-label": "Дазволеныя дыяпазоны IP-адрасоў:"
 }
index 75266e8..649847c 100644 (file)
        "passwordreset-emaildisabled": "Функцията за електронна поща е изключена в това уики.",
        "passwordreset-username": "Потребителско име:",
        "passwordreset-domain": "Домейн:",
-       "passwordreset-capture": "Преглеждане на електронното писмо?",
-       "passwordreset-capture-help": "Поставянето на отметка в това поле ще покаже електронното писмо (с временната парола), което ще бъде изпратено и до потребителя.",
        "passwordreset-email": "Електронна поща:",
        "passwordreset-emailtitle": "Подробности за сметката в {{SITENAME}}",
        "passwordreset-emailtext-ip": "Някой (вероятно вие, от IP адрес $1) поиска възстановяване на паролата за сметката в {{SITENAME}} ($4). За {{PLURAL:$3|следната сметка|следните сметки}}\nе посочен този адрес за електронна поща:\n\n$2\n\n{{PLURAL:$3|Тази временна парола ще бъде активна|Тези временни пароли ще бъдат активни}} {{PLURAL:$5|един ден|$5 дни}}.\nСега би трябвало да влезете в системата и да си изберете нова парола. Ако заявката е направена от друг или пък сте си спомнили паролата и не искате да я променяте, можете да пренебрегнете това съобщение и да продължите да използвате старата си парола.",
        "previewnote": "<strong>Обърнете внимание, че това е само предварителен преглед.</strong>\nПромените все още не са съхранени!",
        "continue-editing": "Продължаване към полето за редактиране",
        "previewconflict": "Този предварителен преглед отразява текста в горната текстова кутия така, както би се показал, ако съхраните.",
-       "session_fail_preview": "За съжаление редакцията ви не успя да бъде обработена поради загуба на данните за текущата сесия.\n\nМоже би сте излезли от системата. <strong>Моля, уверете се, че сте влезли в профила си и опитайте отново. \nАко все още не работи, опитайте да [[Special:UserLogout|излезете]] и да влезете отново, също така проверете дали браузърът ви позволява бисквитки от този сайт.",
+       "session_fail_preview": "За съжаление редакцията ви не успя да бъде обработена поради загуба на данните за текущата сесия.\n\nМоже би сте излезли от системата. <strong>Моля, уверете се, че сте влезли в профила си и опитайте отново.</strong>\nАко все още не работи, опитайте да [[Special:UserLogout|излезете]] и да влезете отново, също така проверете дали браузърът ви позволява бисквитки от този сайт.",
        "session_fail_preview_html": "'''За съжаление редакцията ви не беше записана поради изтичането на сесията ви.'''\n\n''Тъй като {{SITENAME}} приема обикновен HTML, предварителният преглед е скрит като предпазна мярка срещу атаки чрез Джаваскрипт.''\n\n'''Опитайте отново. Ако все още не сработва, пробвайте да [[Special:UserLogout|излезете]] и влезете отново.'''",
        "token_suffix_mismatch": "'''Редакцията ви беше отхвърлена, защото браузърът ви е развалил пунктуационните знаци в редакционната отметка. Евентуалното съхранение би унищожило съдържанието на страницата. Понякога това се случва при използването на грешно работещи анонимни междинни сървъри.'''",
        "edit_form_incomplete": "'''Някои части от формуляра за редактиране не достигнаха до сървъра; проверете дали редакциите ви са непокътнати и опитайте отново.'''",
        "userrights-reason": "Причина:",
        "userrights-no-interwiki": "Нямате права да редактирате потребителските групи на други уикита.",
        "userrights-nodatabase": "Базата данни $1 не съществува или не е на локалния сървър.",
-       "userrights-nologin": "За управление на потребителските права е необходимо [[Special:UserLogin|влизане]] с администраторска сметка.",
-       "userrights-notallowed": "Нямате права да добавяте или премахвате потребителски права.",
        "userrights-changeable-col": "Групи, които можете да променяте",
        "userrights-unchangeable-col": "Групи, които не можете да променяте",
        "group": "Потребителска група:",
        "right-siteadmin": "заключване и отключване на базата от данни",
        "right-override-export-depth": "Изнасяне на страници, включително свързаните с тях в дълбочина до пето ниво",
        "right-sendemail": "Изпращане на е-писма до другите потребители",
-       "right-passwordreset": "Преглеждане на е-писма за възстановяване на парола",
        "grant-group-email": "Изпращане на е-писмо",
        "grant-blockusers": "Блокиране и отблокиране на потребители",
        "grant-createaccount": "Създаване на сметки",
        "authmanager-provider-temporarypassword": "Временна парола",
        "authprovider-resetpass-skip-label": "Пропусни",
        "specialpage-securitylevel-not-allowed-title": "Не е позволено",
-       "cannotauth-not-allowed-title": "Достъпът е отказан",
-       "edit-error-short": "Грешка: $1",
-       "edit-error-long": "Грешки:\n\n$1"
+       "cannotauth-not-allowed-title": "Достъпът е отказан"
 }
index e137ee9..244ac7a 100644 (file)
        "tog-numberheadings": "हेडिंग के ऑटो-नंबरिंग",
        "tog-showtoolbar": "संपादन औजारपट्टी के देखावल जाव",
        "tog-editondblclick": "दुइ क्लिक पर पन्ना सभ के संपादन करीं",
-       "tog-editsectiononrightclick": "à¤\96à¤\82ड à¤\95à¥\80 à¤¹à¥\87डिà¤\82à¤\97 à¤ªà¤° à¤¦à¤¾à¤¯à¤¾à¤\81 à¤\95à¥\8dलिà¤\95 à¤\95à¤\87 à¤\95à¥\87 à¤\96à¤\82ड à¤\95à¥\87 à¤¸à¤®à¥\8dपादित करीं",
-       "tog-watchcreations": "हमार बनावल पन्ना आ हमार अपलोड कइल फाइल सभ के हमरी धियानसूची में जोड़ दिहल जाव",
-       "tog-watchdefault": "हम जौना पन्ना आ फाइलन के संपादित करीं उनहन के हमरी धियानसूची में जोड़ दिहल जाव",
-       "tog-watchmoves": "हमरा द्वारा स्थानांतरित पन्ना आ फाइलन के हमरा धियानसूची में जोड़ दिहल जाव",
-       "tog-watchdeletion": "हमरा द्वारा हटावल पन्ना आ फाइल सभ के हमार धियानसूची में जोड़ दिहल जाव",
+       "tog-editsectiononrightclick": "हà¥\87डिà¤\82à¤\97 à¤ªà¤° à¤¦à¤¾à¤¯à¤¾à¤\81 à¤\95à¥\8dलिà¤\95 à¤\95à¤\87 à¤\95à¥\87 à¤\96à¤\82ड à¤\95à¥\87 à¤¸à¤\82पादन à¤¸à¤\82भव करीं",
+       "tog-watchcreations": "हमार बनावल पन्ना आ हमार अपलोड कइल फाइल सभ के हमरी धियानसूची में जोड़ल जाव",
+       "tog-watchdefault": "हम जौना पन्ना आ फाइलन के संपादित करीं उनहन के हमरी धियानसूची में जोड़ल जाव",
+       "tog-watchmoves": "हमरा द्वारा स्थानांतरित पन्ना आ फाइलन के हमरा धियानसूची में जोड़ल जाव",
+       "tog-watchdeletion": "हमरा द्वारा हटावल पन्ना आ फाइल सभ के हमार धियानसूची में जोड़ल जाव",
        "tog-watchuploads": "हम नया फाइल अपलोड करीं त उनहना के हमार धियानसूची में जोड़ल जाय",
-       "tog-watchrollback": "हमरा द्वारा रोलबैक कइल गइल पन्ना सभ के हमार धियानसूची में जोड़ दिहल जाव",
-       "tog-minordefault": "डिफालà¥\8dà¤\9f à¤°à¥\82प à¤¸à¥\87 à¤¸à¤\97रà¥\80 à¤¸à¤\82पादन à¤\95à¥\81ल à¤\95à¥\87 à¤\9bà¥\8bà¤\9f à¤¸à¤\82पादन à¤\95à¥\80 à¤°à¥\81प à¤®à¥\87à¤\82 à¤\9aिनà¥\8dहित à¤\95à¤\87ल à¤\9cाव",
+       "tog-watchrollback": "हमरा द्वारा रोलबैक कइल गइल पन्ना सभ के हमार धियानसूची में जोड़ल जाव",
+       "tog-minordefault": "डिफाल्ट रूप से सगरी संपादन के छोट संपादन की रुप में चिन्हित कइल जाव",
        "tog-previewontop": "झलक (प्रीव्यू) संपादन बक्सा से पहिले देखावल जाय",
        "tog-previewonfirst": "पहिला संपादन पर झलक (प्रीव्यू) देखावल जाय",
-       "tog-enotifwatchlistpages": "हमार धियानसूची में दर्ज कौनो भी पन्ना या फाइल में बदलाव होखला पर हमके ई-मेल कइल जाव",
-       "tog-enotifusertalkpages": "अगर हमरे बातचीत पन्ना पर कौनो बदलाव होखे त हमके ई-मेल कइल जाव",
-       "tog-enotifminoredits": "पन्ना आ फाइल पर छोटो बदलाव होखे त हमके ई-मेल कइल जाव",
-       "tog-enotifrevealaddr": "नोटिफिकेशन ई-मेल में हमार ई-मेल पता देखावल जाव",
-       "tog-shownumberswatching": "धियान रखे वालन सदस्यन के देखावल जाव",
+       "tog-enotifwatchlistpages": "हमार धियानसूची में दर्ज कौनो भी पन्ना या फाइल में बदलाव होखला पर हमके ईमेल कइल जाव",
+       "tog-enotifusertalkpages": "अगर हमरे बातचीत पन्ना पर कौनो बदलाव होखे त हमके ईमेल कइल जाव",
+       "tog-enotifminoredits": "पन्ना आ फाइल पर छोटो बदलाव होखे त हमके ईमेल कइल जाव",
+       "tog-enotifrevealaddr": "नोटिफिकेशन ईमेल में हमार ईमेल पता देखावल जाव",
+       "tog-shownumberswatching": "धियान à¤°à¤\96à¥\87 à¤µà¤¾à¤²à¤¨ à¤¸à¤¦à¤¸à¥\8dयन à¤\95à¥\87 à¤¸à¤\82à¤\96à¥\8dया à¤¦à¥\87à¤\96ावल à¤\9cाव",
        "tog-oldsig": "राउर वर्तमान दसखत:",
        "tog-fancysig": "दसखत के विकी पाठ के रुप में उपयोग करीं (बिना ऑटोमेटिक कड़ी के)",
        "tog-uselivepreview": "लगातार झलक (लाइव प्रीव्यू) इस्तेमाल कइल जाव",
-       "tog-forceeditsummary": "यदि à¤¸à¤\82पादन à¤¸à¤¾à¤°à¤¾à¤\82श à¤¨à¤¾ à¤¦à¤¿à¤¹ल होखे त हमके सूचित कइल जाय",
-       "tog-watchlisthideown": "हमरà¥\80 à¤§à¤¿à¤¯à¤¾à¤¨à¤¸à¥\82à¤\9aà¥\80 à¤¸à¥\87 à¤¹à¤®à¤¾à¤° à¤\96à¥\81द à¤\95à¥\87 à¤¸à¤\82पादन à¤\9bिपाà¤\88à¤\82",
+       "tog-forceeditsummary": "सà¤\82पादन à¤¸à¤\82à¤\9bà¥\87प à¤¨à¤¾ à¤­à¤°à¤² à¤\97à¤\87ल होखे त हमके सूचित कइल जाय",
+       "tog-watchlisthideown": "धियानसà¥\82à¤\9aà¥\80 à¤¸à¥\87 à¤¹à¤®à¤¾à¤° à¤\96à¥\81द à¤\95à¥\87 à¤¸à¤\82पादन à¤\9bिपावल à¤\9cाय",
        "tog-watchlisthidebots": "धियानसूची से बॉट संपादन छिपावल जाय",
        "tog-watchlisthideminor": "धियानसूची से छोट संपादन छिपावल जाय",
-       "tog-watchlisthideliu": "धियानसà¥\82à¤\9aà¥\80 à¤®à¥\87à¤\82 à¤²à¥\89à¤\97-à¤\87न à¤­à¤\87ल à¤¸à¤¦à¤¸à¥\8dयन à¤\95à¥\87 à¤¸à¤\82पादन छिपावल जाय",
+       "tog-watchlisthideliu": "à¤\96ाता à¤®à¥\87à¤\82 à¤ªà¥\8dरवà¥\87श à¤­à¤\87ल à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤\95à¥\87 à¤¸à¤\82पादन à¤§à¤¿à¤¯à¤¾à¤¨à¤¸à¥\82à¤\9aà¥\80 à¤¸à¥\87 छिपावल जाय",
        "tog-watchlistreloadautomatically": "जब कौनों फिल्टर बदलल जाय तब धियानसूची ऑटोमेटिक दोबारा लोड होखे (जावास्क्रिप्ट जरूरी)",
-       "tog-watchlisthideanons": "à¤\86à¤\87॰पà¥\80॰ à¤¸à¤¦à¤¸à¥\8dयन à¤¦à¥\8dवारा à¤\95रल à¤\97à¤\87ल à¤¸à¤®à¥\8dपादन à¤\95à¥\87 à¤¹à¤®à¤¾à¤° à¤§à¤¿à¤¯à¤¾à¤¨à¤¸à¥\82à¤\9aà¥\80 à¤®à¥\87à¤\82 से छिपावल जाय",
-       "tog-watchlisthidepatrolled": "à¤\9cाà¤\81à¤\9aल à¤\97à¤\87ल à¤¸à¤®à¥\8dपादन à¤\95à¥\87 à¤¹à¤®à¤¾à¤° à¤§à¥\8dयानसà¥\82à¤\9aà¥\80 à¤®à¥\87à¤\82 से छिपावल जाय",
+       "tog-watchlisthideanons": "बà¥\87नाम à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤\95à¥\87 à¤¸à¤\82पादन à¤§à¤¿à¤¯à¤¾à¤¨à¤¸à¥\82à¤\9aà¥\80 से छिपावल जाय",
+       "tog-watchlisthidepatrolled": "à¤\9cाà¤\81à¤\9aल à¤\97à¤\87ल à¤¸à¤\82पादन à¤\95à¥\87 à¤§à¤¿à¤¯à¤¾à¤¨à¤¸à¥\82à¤\9aà¥\80 से छिपावल जाय",
        "tog-watchlisthidecategorization": "पन्ना श्रेणीकरण छिपावल जाय",
-       "tog-ccmeonemails": "हमरा à¤¦à¥\8dवारा à¤\85नà¥\8dय à¤¸à¤¦à¤¸à¥\8dयन à¤\95à¥\87 à¤­à¥\87à¤\9cल à¤\97à¤\87ल à¤\88मà¥\87ल à¤\95à¥\87 à¤\95à¥\89पà¥\80 à¤¹à¤®à¤°à¥\8b के भेजल जाय",
+       "tog-ccmeonemails": "हमरा à¤¦à¥\8dवारा à¤\85नà¥\8dय à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤\95à¥\87 à¤­à¥\87à¤\9cल à¤\97à¤\87ल à¤\88मà¥\87ल à¤\95à¥\87 à¤\95à¥\89पà¥\80 à¤¹à¤®के भेजल जाय",
        "tog-diffonly": "अंतर देखावत समय नीचे पन्ना के सामग्री न देखावल जाय",
        "tog-showhiddencats": "छिपल श्रेणियन के भी देखावल जाय",
        "tog-norollbackdiff": "रोलबैक कइला के बाद अंतर न देखावल जाय",
        "tog-useeditwarning": "जो हम कौनों पन्ना पर संपादन करत घरी परिवर्तन के बिना सहेजले छोड़ देईं त हमके खबर कइल जाय",
-       "tog-prefershttps": "लà¥\89à¤\97िन रहले पर हमेशा सुरक्षित कनेक्शन के प्रयोग कइल जाय",
+       "tog-prefershttps": "à¤\96ाता à¤®à¥\87à¤\82 à¤ªà¥\8dरवà¥\87श रहले पर हमेशा सुरक्षित कनेक्शन के प्रयोग कइल जाय",
        "underline-always": "हमेशा",
        "underline-never": "कभी ना",
        "underline-default": "जिल्द (स्किन) या ब्राउजर डिफॉल्ट",
        "december-date": "दिसंबर $1",
        "period-am": "AM",
        "period-pm": "PM",
-       "pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणी}}",
-       "category_header": "\"$1\" श्रेणी में पन्ना",
+       "pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणीसभ}}",
+       "category_header": "\"$1\" à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤®à¥\8cà¤\9cà¥\82द à¤ªà¤¨à¥\8dना",
        "subcategories": "उपश्रेणी",
-       "category-media-header": "\"$1\" श्रेणी में मीडिया",
-       "category-empty": "''इ श्रेणी में इ समय कउनो पन्ना या मीडिया नइखे।''",
+       "category-media-header": "\"$1\" à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤®à¥\8cà¤\9cà¥\82द à¤®à¥\80डिया",
+       "category-empty": "<em>ए श्रेणी में एह समय कौनों पन्ना भा मीडिया नइखे।</em>",
        "hidden-categories": "{{PLURAL:$1|छिपावल गइल श्रेणी|छिपावल गइल श्रेणी सब}}",
        "hidden-category-category": "छिपावल गइल श्रेणी",
-       "category-subcat-count": "{{PLURAL:$2|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤\96ालà¥\80 à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित à¤¶à¥\8dरà¥\87णà¥\80 à¤¬à¤¾|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤\95à¥\81ल $2 à¤®à¥\87à¤\82 à¤¸à¥\87 {{PLURAL:$1|à¤\89पशà¥\8dरà¥\87णà¥\80|$1 à¤\89पशà¥\8dरà¥\87णà¥\80 à¤¸à¤¬}} बा।}}",
-       "category-subcat-count-limited": "à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित {{PLURAL:$1|à¤\89पशà¥\8dरà¥\87णà¥\80 à¤¬à¤¾|$1 à¤\89पशà¥\8dरà¥\87णà¥\80 बाड़ीं।}}",
-       "category-article-count": "{{PLURAL:$2|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤®à¤¾à¤¤à¥\8dर à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित à¤ªà¤¨à¥\8dन à¤¬à¤¾à¥¤|à¤\87 à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित {{PLURAL:$1|पनà¥\8dना à¤¬à¤¾|$1 à¤ªà¤¨à¥\8dना à¤¬à¤¾à¤¡à¤¼à¥\87}}, à¤\95à¥\81ल à¤ªà¤¨à¥\8dना $2}}",
-       "category-article-count-limited": "निमà¥\8dनलिà¤\96ित {{PLURAL:$1|पनà¥\8dना|$1 à¤ªà¤¨à¥\8dना}} à¤\87 à¤¶à¥\8dरà¥\87णà¥\80à¤\82 à¤®à¥\87à¤\82 à¤¬à¤¾।",
-       "category-file-count": "{{PLURAL:$2|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤\96ालà¥\80 à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित à¤«à¤\87ल à¤¬à¤¾à¥¤|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित à¤\95à¥\81ल à¤«à¤¼à¤¾à¤\87लà¤\82 $2 {{PLURAL:$1|फाà¤\87ल|$1फाà¤\87लà¤\82}} à¤¬à¤¾à¤¡à¤¼à¥\87}}",
-       "category-file-count-limited": "वरà¥\8dतमान à¤®à¥\87à¤\82 à¤¨à¤¿à¤®à¥\8dनलिà¤\96ित {{PLURAL:$1|पनà¥\8dना|$1 à¤ªà¤¨à¥\8dनाà¤\82}} à¤\87 à¤¶à¥\8dरà¥\87णà¥\80à¤\82 à¤®à¥\87à¤\82 à¤¬à¤¾à¤¡à¤¼à¥\87।",
+       "category-subcat-count": "{{PLURAL:$2|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\96ालà¥\80 à¤\8fà¤\95 à¤ à¥\8b à¤¶à¥\8dरà¥\87णà¥\80 à¤¬à¤¾|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤®à¥\8cà¤\9cà¥\82द à¤\95à¥\81ल $2 à¤®à¥\87à¤\82 à¤¸à¥\87 {{PLURAL:$1|à¤\89पशà¥\8dरà¥\87णà¥\80|$1 à¤\89पशà¥\8dरà¥\87णà¥\80 à¤¸à¤¬}} à¤¨à¥\80à¤\9aà¥\87 à¤¦à¥\87à¤\96ावल à¤\9cात बा।}}",
+       "category-subcat-count-limited": "à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¥\80à¤\9aà¥\87 à¤¦à¥\87à¤\96ावल {{PLURAL:$1|à¤\89पशà¥\8dरà¥\87णà¥\80 à¤¬à¤¾|$1 à¤\89पशà¥\8dरà¥\87णà¥\80 à¤\95à¥\81ल बाड़ीं।}}",
+       "category-article-count": "{{PLURAL:$2|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\8fà¤\95हà¥\80 à¤ à¥\8b à¤ªà¤¨à¥\8dना à¤­à¤° à¤¬à¤¾à¤\9fà¥\87।|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤®à¥\8cà¤\9cà¥\82द à¤\95à¥\81ल $2 à¤®à¥\87à¤\82 à¤¸à¥\87 {{PLURAL:$1|पनà¥\8dना à¤¨à¥\80à¤\9aà¥\87 à¤¦à¥\87à¤\96ावल à¤\9cात à¤¬à¤¾|$1 à¤ªà¤¨à¥\8dनासभ à¤¨à¥\80à¤\9aà¥\87 à¤¦à¥\87à¤\96ावल à¤\9cात à¤¬à¤¾à¤¡à¤¼à¥\87à¤\82}}।}}",
+       "category-article-count-limited": "वरà¥\8dतमान à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² {{PLURAL:$1|पनà¥\8dना à¤¬à¤¾|$1 à¤ªà¤¨à¥\8dना à¤¬à¤¾à¤¡à¤¼à¥\87à¤\82}}।",
+       "category-file-count": "{{PLURAL:$2|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\8fà¤\95हà¥\80 à¤ à¥\8b à¤«à¤¾à¤\87ल à¤­à¤° à¤¬à¤¾à¤\9fà¥\87।|à¤\8f à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤®à¥\8cà¤\9cà¥\82द à¤\95à¥\81ल $2 à¤®à¥\87à¤\82 à¤¸à¥\87 {{PLURAL:$1|फाà¤\87ल à¤¨à¥\80à¤\9aà¥\87 à¤¦à¥\87à¤\96ावल à¤\9cात à¤¬à¤¾|$1 à¤«à¤¾à¤\87लसभ à¤¨à¥\80à¤\9aà¥\87 à¤¦à¥\87à¤\96ावल à¤\9cात à¤¬à¤¾à¤¡à¤¼à¥\80à¤\82}}।}}",
+       "category-file-count-limited": "वरà¥\8dतमान à¤¶à¥\8dरà¥\87णà¥\80 à¤®à¥\87à¤\82 à¤¨à¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² {{PLURAL:$1|फाà¤\87ल à¤¬à¤¾|$1 à¤«à¤¾à¤\87ल à¤¬à¤¾à¤¡à¤¼à¥\80à¤\82}}।",
        "listingcontinuesabbrev": "जारी...",
        "index-category": "सूचीबद्ध पन्ना",
        "noindex-category": "बिनासूचीबद्ध पन्ना",
        "moredotdotdot": "अउर...",
        "morenotlisted": "हो सकेला कि ई लिस्ट पूरा न होखे।",
        "mypage": "पन्ना",
-       "mytalk": "हमार à¤¬à¤¾à¤¤à¤\9aà¥\80त à¤ªà¤¨à¥\8dना",
+       "mytalk": "बातà¤\9aà¥\80त",
        "anontalk": "बातचीत",
        "navigation": "नेविगेशन",
        "and": "&#32;अउर",
        "qbedit": "संपादन",
        "qbpageoptions": "ई पन्ना",
        "qbmyoptions": "हमार पन्ना",
-       "faq": "साधारण सवाल",
+       "faq": "à¤\86म सवाल",
        "faqpage": "Project:अक्सर पूछल जाए वाला सवाल",
-       "actions": "à¤\95ारà¥\8dयवाहà¥\80",
+       "actions": "à¤\8fà¤\95à¥\8dशन",
        "namespaces": "नाँवस्थान",
        "variants": "अउरी प्रकार",
-       "navigation-heading": "नà¥\87विà¤\97à¥\87शन à¤®à¥\87नà¥\81",
+       "navigation-heading": "नà¥\87विà¤\97à¥\87शन à¤®à¥\87नà¥\82",
        "errorpagetitle": "खराबी",
-       "returnto": "$1 à¤ªà¤° à¤²à¥\8cà¤\9f à¤\9cाà¤\88ं।",
+       "returnto": "$1 à¤ªà¤° à¤²à¤µà¤\9fà¥\80ं।",
        "tagline": "भोजपुरी {{SITENAME}} से",
        "help": "मदद",
        "search": "खोज",
        "searcharticle": "जाईं",
        "history": "पन्ना के इतिहास",
        "history_short": "इतिहास",
-       "updatedmarker": "हमार à¤\85नà¥\8dतिम à¤\86à¤\97मन à¤¸े बदलाव",
+       "updatedmarker": "हमरà¥\87 à¤\85à¤\82तिम à¤¬à¥\87र à¤¦à¥\87à¤\96लà¥\87 à¤\95à¥\87 à¤¬à¤¾à¤¦ à¤\95े बदलाव",
        "printableversion": "छापे लायक संस्करण",
        "permalink": "स्थायी कड़ी",
        "print": "छापीं",
-       "view": "दà¥\87à¤\96à¥\80à¤\82",
+       "view": "वà¥\8dयà¥\82",
        "view-foreign": "$1 पर देखीं",
        "edit": "संपादन",
        "edit-local": "लोकल विवरण देखीं",
        "create": "बनाईं",
        "create-local": "लोकल विवरण जोड़ीं",
-       "editthispage": "à¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¸à¤®à¥\8dपादन करीं",
-       "create-this-page": "à¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¨à¤¿à¤°à¥\8dमाण à¤\95रà¥\80ं",
+       "editthispage": "à¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¸à¤\82पादन करीं",
+       "create-this-page": "à¤\88 à¤ªà¤¨à¥\8dना à¤¬à¤¨à¤¾à¤\88ं",
        "delete": "मिटाईं",
        "deletethispage": "ई पन्ना के मिटाईं",
        "undeletethispage": "ई पन्ना के फिर से स्थापित करीं",
-       "undelete_short": "{{PLURAL:$1|à¤\8fà¤\97à¥\8b à¤¹à¤\9fावल à¤\97à¤\88ल|$1 à¤¹à¤\9fावल à¤\97à¤\88लà¤\82}} à¤¬à¤¦à¤²à¤¾à¤µ à¤µà¤¾à¤ªà¤¸ à¤²à¤¾à¤\88ं",
-       "viewdeleted_short": "देखें {{PLURAL:$1|एगो हटावल गईल सम्पादन|$1 हटावल गईल कुल सम्पादन}}",
-       "protect": "सà¤\82रà¤\95à¥\8dषण करीं",
+       "undelete_short": "{{PLURAL:$1|à¤\8fà¤\95 à¤ à¥\8b à¤¹à¤\9fावल à¤\97à¤\87ल à¤¸à¤\82पादन|$1 à¤ à¥\87 à¤¹à¤\9fावल à¤\97à¤\87ल à¤¸à¤\82पादन à¤\95à¥\81ल}} à¤¬à¤¿à¤¨à¤¾à¤®à¥\87à¤\9fावल à¤\95रà¥\80ं",
+       "viewdeleted_short": "{{PLURAL:$1|एक ठो हटावल गइल संपादन|$1 हटावल गइल संपादन कुल}} देखीं",
+       "protect": "सà¥\81रà¤\95à¥\8dषित करीं",
        "protect_change": "बदलीं",
-       "protectthispage": "à¤\87 पन्ना के सुरक्षित करीं।",
+       "protectthispage": "à¤\8f पन्ना के सुरक्षित करीं।",
        "unprotect": "सुरक्षा बदलीं",
-       "unprotectthispage": "à¤\87 पन्ना के सुरक्षा बदलीं",
+       "unprotectthispage": "à¤\8f पन्ना के सुरक्षा बदलीं",
        "newpage": "नया पन्ना",
-       "talkpage": "à¤\87 पन्ना पर चर्चा करीं",
-       "talkpagelinktext": "बात-चीत",
+       "talkpage": "à¤\8f पन्ना पर चर्चा करीं",
+       "talkpagelinktext": "बातचीत",
        "specialpage": "खास पन्ना",
-       "personaltools": "वà¥\8dयà¤\95à¥\8dतिà¤\97त औजार",
+       "personaltools": "निà¤\9cà¥\80 औजार",
        "articlepage": "सामग्री पन्ना देखीं",
-       "talk": "बात-चीत",
-       "views": "à¤\95à¤\87सन à¤²à¤\89à¤\95à¥\80?",
+       "talk": "बातचीत",
+       "views": "वà¥\8dयà¥\82",
        "toolbox": "औजार",
-       "tool-link-userrights": "{{GENDER:$1|पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता}} à¤\95à¥\87 à¤¸à¤¦à¤¸à¥\8dयसमà¥\82ह बदलीं",
+       "tool-link-userrights": "{{GENDER:$1|पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता}} à¤\95à¥\87 à¤®à¤\82डलà¥\80 बदलीं",
        "tool-link-emailuser": "{{GENDER:$1|प्रयोगकर्ता}} के ईमेल करीं",
        "userpage": "प्रयोगकर्ता पन्ना देखीं",
        "projectpage": "परियोजना पन्ना देखीं",
        "imagepage": "फाइल पन्ना देखीं",
-       "mediawikipage": "सनà¥\8dदà¥\87श पन्ना देखीं",
+       "mediawikipage": "सनà¥\87सा पन्ना देखीं",
        "templatepage": "टेम्पलेट पन्ना देखीं",
        "viewhelppage": "मदद पन्ना देखीं",
        "categorypage": "श्रेणी पन्ना देखीं",
-       "viewtalkpage": "बात-à¤\9aà¥\80त देखीं",
+       "viewtalkpage": "à¤\9aरà¥\8dà¤\9aा देखीं",
        "otherlanguages": "दुसरी भाषा में",
        "redirectedfrom": "($1 से अनुप्रेषित)",
        "redirectpagesub": "अनुप्रेषण पन्ना",
        "redirectto": "अनुप्रेषित:",
-       "lastmodifiedat": "$1 à¤\95à¥\87 $2 à¤ªà¤° à¤\8f à¤ªà¤¨à¥\8dना à¤ªà¤° à¤\85नà¥\8dतिम बेर बदलाव भइल।",
-       "viewcount": "à¤\88 à¤ªà¤¨à¥\8dना {{PLURAL:$1|à¤\8fà¤\95|$1}} à¤¬à¤¾à¤° à¤¦à¥\87à¤\96ल à¤\97à¤\88ल बा।",
+       "lastmodifiedat": "$1 à¤\95à¥\87 $2 à¤¬à¤\9cà¥\87 à¤\8f à¤ªà¤¨à¥\8dना à¤ªà¤° à¤\85à¤\82तिम बेर बदलाव भइल।",
+       "viewcount": "à¤\88 à¤ªà¤¨à¥\8dना {{PLURAL:$1|à¤\8fà¤\95|$1}} à¤¬à¥\87र à¤¦à¥\87à¤\96ल à¤\97à¤\87ल बा।",
        "protectedpage": "सुरक्षित पन्ना",
        "jumpto": "इहाँ जाईं:",
        "jumptonavigation": "नेविगेशन",
        "jumptosearch": "खोजीं",
-       "view-pool-error": "à¤\95à¥\8dषमा à¤\95रà¥\80à¤\82, à¤\88 à¤¸à¤®à¤¯ à¤¸à¤°à¥\8dवर à¤ªà¤° à¤¬à¤¹à¥\81त à¤\9cà¥\8dयादा à¤²à¥\8bड à¤¬à¤¢à¤¼ à¤\97à¤\88ल à¤¬à¤¾à¥¤\nà¤\88 à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¬à¤¹à¥\81तà¥\87 à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤¦à¥\87à¤\96à¥\87 à¤\95à¥\87 à¤\95à¥\8bशिश à¤\95र à¤°à¤¹à¤² à¤¬à¤¾à¤¨à¥\80।\nà¤\88 à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤«à¤¿à¤° à¤¸à¥\87 à¤¦à¥\87à¤\96à¥\87 à¤¸à¥\87 à¤ªà¤¹à¤¿à¤²à¥\87 à¤\95à¥\83पया à¤\95à¥\81à¤\9b à¤¦à¥\87र à¤¤à¤\95 à¤\87नà¥\8dतजार करीं।\n\n$1",
-       "generic-pool-error": "à¤\95à¥\8dषमा à¤\95रà¥\80à¤\82, à¤\88 à¤¸à¤®à¤¯ à¤¸à¤°à¥\8dवर à¤ªà¤° à¤¬à¤¹à¥\81त à¤\9cà¥\8dयादा à¤²à¥\8bड à¤¬à¤¢à¤¼ à¤\97à¤\88ल à¤¬à¤¾à¥¤\nà¤\88 à¤¸à¤\82साधन à¤\95à¥\87 à¤¬à¤¹à¥\81तà¥\87 à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤¦à¥\87à¤\96à¥\87 à¤\95à¥\87 à¤\95à¥\8bशिश à¤\95र à¤°à¤¹à¤² à¤¬à¤¾à¤¨à¥\80।\nà¤\88 à¤¸à¤\82साधन à¤¤à¤\95 à¤ªà¤¹à¥\81à¤\81à¤\9a à¤¬à¤¨à¤¾à¤µà¥\87 à¤\95à¥\87 à¤\95à¥\8bशिश à¤¸à¥\87 à¤ªà¤¹à¤¿à¤²à¥\87 à¤\95à¥\83पया à¤\95à¥\81à¤\9b à¤¦à¥\87र à¤¤à¤\95 à¤\87नà¥\8dतजार करीं।",
-       "pool-timeout": "तालाबनà¥\8dदà¥\80 à¤\96ातिर à¤ªà¥\8dरतà¥\80à¤\95à¥\8dषा समय समाप्त",
+       "view-pool-error": "माफ à¤\95रà¥\80à¤\82, à¤\8f à¤¸à¤®à¤¯ à¤¸à¤°à¥\8dवर à¤ªà¤° à¤¬à¤¹à¥\81त à¤\9cà¥\8dयादा à¤²à¥\8bड à¤¬à¤¢à¤¼ à¤\97à¤\87ल à¤¬à¤¾à¥¤\nà¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¬à¤¹à¥\81तà¥\87 à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤¦à¥\87à¤\96à¥\87 à¤\95à¥\87 à¤\95à¥\8bशिश à¤\95र à¤°à¤¹à¤² à¤¬à¤¾à¥¤\nà¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤«à¤¿à¤° à¤¸à¥\87 à¤¦à¥\87à¤\96à¥\87 à¤¸à¥\87 à¤ªà¤¹à¤¿à¤²à¥\87 à¤\95à¥\83पया à¤\95à¥\81à¤\9b à¤¦à¥\87र à¤¤à¤\95 à¤\87à¤\82तजार करीं।\n\n$1",
+       "generic-pool-error": "माफ à¤\95रà¥\80à¤\82, à¤\8f à¤¸à¤®à¤¯ à¤¸à¤°à¥\8dवर à¤ªà¤° à¤¬à¤¹à¥\81त à¤\9cà¥\8dयादा à¤²à¥\8bड à¤¬à¤¢à¤¼ à¤\97à¤\87ल à¤¬à¤¾à¥¤\nà¤\8f à¤¸à¤\82साधन à¤\95à¥\87 à¤¬à¤¹à¥\81तà¥\87 à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤¦à¥\87à¤\96à¥\87 à¤\95à¥\87 à¤\95à¥\8bशिश à¤\95र à¤°à¤¹à¤² à¤¬à¤¾à¥¤\nà¤\8f à¤¸à¤\82साधन à¤¤à¤\95 à¤ªà¤¹à¥\81à¤\81à¤\9a à¤¬à¤¨à¤¾à¤µà¥\87 à¤\95à¥\87 à¤\95à¥\8bशिश à¤¸à¥\87 à¤ªà¤¹à¤¿à¤²à¥\87 à¤\95à¥\83पया à¤\95à¥\81à¤\9b à¤¦à¥\87र à¤¤à¤\95 à¤\87à¤\82तजार करीं।",
+       "pool-timeout": "तालाबनà¥\8dदà¥\80 à¤\96ातिर à¤\87à¤\82तà¤\9cार समय समाप्त",
        "pool-queuefull": "पूल पंक्ति भर गइल",
-       "pool-errorunknown": "à¤\85à¤\9cà¥\8dà¤\9eात à¤¤à¥\8dरà¥\81à¤\9fि",
-       "pool-servererror": "पà¥\82ल à¤\95ाà¤\89à¤\82à¤\9fर à¤¸à¥\87वा à¤\89पलबà¥\8dध à¤¨à¤¾à¤¹à¥\80 à¤¬à¤¾ ($1)।",
-       "poolcounter-usage-error": "à¤\89पयà¥\8bà¤\97 à¤¤à¥\8dरà¥\81à¤\9fि: $1",
+       "pool-errorunknown": "नामालà¥\82म à¤\96राबà¥\80",
+       "pool-servererror": "पà¥\82ल à¤\95ाà¤\89à¤\82à¤\9fर à¤¸à¤°à¥\8dविस à¤\89पलबà¥\8dध à¤¨à¤\87à¤\96à¥\87 ($1)।",
+       "poolcounter-usage-error": "à¤\87सà¥\8dतमाल à¤\96राबà¥\80: $1",
        "aboutsite": "{{SITENAME}} के बारे में",
        "aboutpage": "Project:बारे में",
-       "copyright": "à¤\89पलबà¥\8dध à¤¸à¤¾à¤®à¤\97à¥\8dरà¥\80 $1 à¤\95à¥\87 à¤\85धà¥\80न à¤\89पलबà¥\8dध à¤¬à¤¾ à¤\9cब à¤¤à¤\95 à¤\95à¥\80 à¤\85लà¤\97 à¤¸à¥\87 à¤\89लà¥\8dलà¥\87à¤\96 à¤¨à¤¾ à¤\95रल à¤\97à¤\88ल à¤¹à¥\8bà¤\96à¥\87 ।",
+       "copyright": "à¤\89पलबà¥\8dध à¤¸à¤¾à¤®à¤\97à¥\8dरà¥\80 $1 à¤\95à¥\87 à¤\85धà¥\80न à¤\89पलबà¥\8dध à¤¬à¤¾ à¤\9cब à¤¤à¤\95 à¤\95à¥\80 à¤\85लà¤\97 à¤¸à¥\87 à¤\89लà¥\8dलà¥\87à¤\96 à¤¨à¤¾ à¤\95à¤\87ल à¤\97à¤\87ल à¤¹à¥\8bà¤\96à¥\87।",
        "copyrightpage": "{{ns:project}}:कापीराइट सब",
        "currentevents": "हाल के घटना सब",
        "currentevents-url": "Project:हाल के घटना सब",
        "portal-url": "Project:सदस्य-समाज मुख्यपन्ना",
        "privacy": "गोपनीयता नीति",
        "privacypage": "Project:गोपनीयता नीति",
-       "badaccess": "à¤\85नà¥\81मति à¤¤à¥\8dरà¥\81à¤\9fी",
-       "badaccess-group0": "रà¤\89à¤\86 à¤\9cवन à¤\95ारà¥\8dरवाà¤\88 à¤\96ातिर à¤\85नà¥\81रà¥\8bध à¤\95à¤\88लà¥\87 à¤¬à¤¾à¤¨à¥\80 à¤\89 à¤\95à¥\87 à¤¨à¤¿à¤·à¥\8dपादन à¤\95रà¥\87 à¤\95à¥\87 à¤\85नà¥\81मति à¤¨à¤\88खे।",
-       "badaccess-groups": "रà¤\89à¤\86 à¤\9cà¥\8cन à¤\95à¥\8dरिया à¤\95à¥\87 à¤¨à¤¿à¤µà¥\87दन à¤\95à¤\87लà¥\87 à¤¬à¤¾à¤¨à¥\80 à¤\89 à¤®à¤¾à¤¤à¥\8dर {{PLURAL:$2|$1 à¤¸à¤®à¥\82ह|$1 à¤¸à¤®à¥\82हà¤\82}} à¤\95à¥\87 à¤¸à¤¦à¤¸à¥\8dय à¤¹à¥\80 à¤\95र à¤¸à¤\95त à¤¬à¤¾à¤¡à¤¼à¥\87।",
+       "badaccess": "परमà¥\80शन à¤\96राबी",
+       "badaccess-group0": "à¤\9cवन à¤\95ारà¥\8dरवाà¤\88 à¤\95à¥\87 à¤®à¤¾à¤\81à¤\97 à¤\95à¤\87लà¥\87 à¤¬à¤¾à¤¨à¥\80, à¤\93à¤\95रा à¤²à¤¾à¤\97à¥\82 à¤\95रà¥\87 à¤\95à¥\87 à¤\87à¤\9cाà¤\9cत à¤°à¤\89à¤\86à¤\81 à¤\95à¥\87 à¤¨à¤\87खे।",
+       "badaccess-groups": "रà¤\89à¤\86 à¤\9cà¥\8cन à¤\95ारवाà¤\88 à¤\95à¥\87 à¤®à¤¾à¤\81à¤\97 à¤\95à¤\87लà¥\87 à¤¬à¤¾à¤¨à¥\80 à¤\8a {{PLURAL:$2|$1 à¤®à¤\82डलà¥\80|$1 à¤®à¤\82डलà¥\80 à¤¸à¤­}} à¤\95à¥\87 à¤¸à¤¦à¤¸à¥\8dय à¤²à¥\8bà¤\97 à¤­à¤° à¤\95र à¤¸à¤\95त à¤¬à¤¾।",
        "versionrequired": "मिडीयाविकी के संस्करण $1 के होखल जरुरी बा",
-       "versionrequiredtext": "à¤\87 à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤ªà¥\8dरयà¥\8bà¤\97 à¤\95रà¥\87 à¤\96ातिर à¤®à¥\80डियाविà¤\95à¥\80 à¤\95à¥\87 $1 à¤¸à¤\82सà¥\8dà¤\95रण à¤\9c़रà¥\82रà¥\80 à¤¬à¤¾à¥¤\nदà¥\87à¤\96à¥\80à¤\82 [[Special:Version|सà¤\82सà¥\8dà¤\95रण à¤ªà¤¨à¥\8dना]]।",
-       "ok": "ठिक",
+       "versionrequiredtext": "à¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤ªà¥\8dरयà¥\8bà¤\97 à¤\95रà¥\87 à¤\96ातिर à¤®à¥\80डियाविà¤\95à¥\80 à¤\95à¥\87 à¤µà¤°à¥\8dशन $1 à¤\9cरà¥\82रà¥\80 à¤¬à¤¾à¥¤\n[[Special:Version|वरà¥\8dशन à¤ªà¤¨à¥\8dना]] à¤¦à¥\87à¤\96à¥\80à¤\82।",
+       "ok": "ठà¥\80क",
        "retrievedfrom": "\"$1\" से लिहल गइल",
        "youhavenewmessages": "रउआ लगे बा $1 ($2).",
-       "youhavenewmessagesfromusers": "रउआ खातिर {{PLURAL:$3|एगो अन्य सदस्य|$3 अन्य सदस्यन}} के $1 बा। ($2)",
-       "youhavenewmessagesmanyusers": "रà¤\89à¤\86 à¤\96ातिर à¤\95à¤\88 à¤¸à¤¦à¤¸à¥\8dयन à¤¦à¥\8dवारा $1 à¤¬à¤¾à¥¤ ($2)",
-       "newmessageslinkplural": "{{PLURAL:$1|à¤\8fà¤\95 à¤¨à¤¯à¤¾ à¤¸à¤¨à¥\8dदà¥\87श|999=नयà¤\95ा à¤¸à¤¨à¥\8dदà¥\87श}}",
-       "newmessagesdifflinkplural": "पिछला {{PLURAL:$1|बदलाव|999=बदलाव}}",
-       "youhavenewmessagesmulti": "रà¤\89à¤\86 à¤²à¤\97à¥\87 $1 à¤ªà¤° à¤¨à¤¯à¤¾ à¤¸à¤¨à¥\8dदà¥\87श बा",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|रउवाँ खातिर}}{{PLURAL:$3|अउरी प्रयोगकर्ता के|$3 प्रयोगकर्ता लोग}} के $1 बा ($2)।",
+       "youhavenewmessagesmanyusers": "रà¤\89वाà¤\81 à¤\96ातिर à¤\95à¤\88 à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤\95à¥\87 à¤­à¥\87à¤\9cल $1 à¤¬à¤¾ ($2)।",
+       "newmessageslinkplural": "{{PLURAL:$1|नया à¤¸à¤¨à¥\87सा|999=नया à¤¸à¤¨à¥\87सा à¤¸à¤­}}",
+       "newmessagesdifflinkplural": "पिछला {{PLURAL:$1|बदलाव|999=बदलाव}}",
+       "youhavenewmessagesmulti": "राà¤\89वाà¤\81 à¤\96ातिर $1 à¤ªà¤° à¤¨à¤¯à¤¾ à¤¸à¤¨à¥\87सा बा",
        "editsection": "संपादन",
        "editold": "संपादन",
        "viewsourceold": "स्रोत देखीं",
        "editlink": "संपादन",
        "viewsourcelink": "स्रोत देखीं",
-       "editsectionhint": "सà¤\82पादन à¤\96à¤\82ड: $1",
-       "toc": "सामà¤\97à¥\8dरी",
-       "showtoc": "दà¥\87à¤\96ाà¤\88ं",
-       "hidetoc": "à¤\9bà¥\81पाईं",
+       "editsectionhint": "à¤\96à¤\82ड à¤\95à¥\87 à¤¸à¤\82पादन: $1",
+       "toc": "बिसयसà¥\82à¤\9aी",
+       "showtoc": "दà¥\87à¤\96à¥\80ं",
+       "hidetoc": "à¤\9bिपाईं",
        "collapsible-collapse": "सेकुड़ीं",
-       "collapsible-expand": "फà¥\88लाईं",
+       "collapsible-expand": "फà¤\87लाईं",
        "confirmable-confirm": "का {{GENDER:$1|आप}} निश्चित बानी?",
        "confirmable-yes": "जी",
        "confirmable-no": "ना",
-       "thisisdeleted": "दà¥\87à¤\96à¥\80à¤\82 à¤¯à¤¾ à¤­à¤\82डार करीं $1?",
-       "viewdeleted": "$1 à¤¦à¥\87à¤\96ब?",
-       "restorelink": "देखीं {{PLURAL:$1|एगो हटावल गईल सम्पादन|$1 हटावल गईल कुल सम्पादन}}",
-       "feedlinks": "फ़à¥\80ड:",
-       "feed-invalid": "à¤\97लत à¤¸à¤¬à¥\8dसà¥\8dà¤\95à¥\8dरà¥\80पà¥\8dशन à¤«à¤¼ीड प्रकार",
-       "feed-unavailable": "सà¤\82à¤\98 à¤«à¤¼à¥\80ड à¤\89पलबà¥\8dध à¤¨à¤\87à¤\96à¥\87",
-       "site-rss-feed": "$1 आर एस एस फिड",
+       "thisisdeleted": "दà¥\87à¤\96à¥\80à¤\82 à¤¯à¤¾ à¤°à¤¿à¤¸à¥\8dà¤\9fà¥\8bर करीं $1?",
+       "viewdeleted": "$1 à¤¦à¥\87à¤\96ावल à¤\9cाय?",
+       "restorelink": "{{PLURAL:$1|एक ठो हटावल संपादन|$1 ठे हटावल संपादन}}",
+       "feedlinks": "फीड:",
+       "feed-invalid": "à¤\85वà¥\88ध à¤¸à¤¬à¤¸à¥\8dà¤\95à¥\8dरिपà¥\8dशन à¤«ीड प्रकार",
+       "feed-unavailable": "सिà¤\82डिà¤\95à¥\87शन à¤«à¥\80ड à¤\89पलबà¥\8dध à¤¨à¤\87à¤\96à¥\87à¤\82",
+       "site-rss-feed": "$1 आरएसएस फीड",
        "site-atom-feed": "$1 एटम फीड",
-       "page-rss-feed": "\"$1\" आर एस एस फिड",
+       "page-rss-feed": "\"$1\" आरएसएस फिड",
        "page-atom-feed": "\"$1\" एटम फीड",
        "red-link-title": "$1 (पन्ना मौजूद नइखे)।",
        "sort-descending": "उतरत क्रम में",
        "sort-ascending": "चढ़त क्रम में",
        "nstab-main": "पन्ना",
-       "nstab-user": "सदसà¥\8dय पन्ना",
+       "nstab-user": "पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता पन्ना",
        "nstab-media": "मीडिया पन्ना",
        "nstab-special": "विशेष पन्ना",
        "nstab-project": "प्रोजेक्ट पन्ना",
        "nstab-image": "फाइल",
-       "nstab-mediawiki": "सनà¥\8dदà¥\87श",
+       "nstab-mediawiki": "सनà¥\87सा",
        "nstab-template": "टेम्पलेट",
        "nstab-help": "मदद पन्ना",
        "nstab-category": "श्रेणी",
        "mainpage-nstab": "मुख्य पन्ना",
-       "nosuchaction": "à¤\85à¤\88सन à¤\95à¥\8cनà¥\8b à¤\95ारà¥\8dरवाà¤\88 à¤¨à¤¾à¤¹à¤¿",
-       "nosuchactiontext": "à¤\87 à¤¯à¥\82॰à¤\86र॰à¤\8fल à¤¦à¥\8dवारा à¤¨à¤¿à¤°à¥\8dदिषà¥\8dà¤\9f à¤\95à¥\8dरिया à¤\85वà¥\88ध à¤¬à¤¾à¥¤\nरà¤\89à¤\86 à¤¯à¥\82॰à¤\86र॰à¤\8fल à¤\97लत à¤²à¤¿à¤\96लà¥\87 à¤¹à¥\8bà¤\96ब, à¤¯à¤¾ à¤\95à¤\89नà¥\8b à¤\97लत à¤\95ड़à¥\80 à¤\95à¥\87 à¤ªà¥\8dरयà¥\8bà¤\97 à¤\95à¤\87लà¥\87 à¤¹à¥\8bà¤\96ब।\nà¤\87 {{SITENAME}} à¤\95à¥\87 à¤¸à¥\89फ़à¥\8dà¤\9fवà¥\87यर à¤®à¥\87à¤\82 à¤¤à¥\8dरà¥\81à¤\9fि भी हो सकत बा।",
-       "nosuchspecialpage": "à¤\85à¤\88सन à¤\95à¥\8cनà¥\8b à¤\96़ाश à¤ªà¤¨à¥\8dना à¤¨à¤¾à¤¹à¤¿",
-       "nospecialpagetext": "<strong>रउआ एगो अवैद्य विशेष पन्ना के अनुरोध कईले बानी।</strong>\n\nवैद्य विशेष पन्ना के सूची मिल सकत बा [[Special:SpecialPages|{{int:specialpages}}]] पर।",
-       "error": "तà¥\8dरà¥\81à¤\9fी",
-       "databaseerror": "डà¥\87à¤\9fाबà¥\87स à¤¤à¥\8dरà¥\81à¤\9fी",
-       "databaseerror-text": "डाà¤\9fाबà¥\87स à¤\85नà¥\81रà¥\8bध à¤¤à¥\8dरà¥\81à¤\9fि  à¤­à¤\87ल à¤¬à¤¾à¥¤\nसà¤\82भवतà¤\83 à¤¸à¥\89फ़्टवेयर में गड़बड़ी बा।",
-       "databaseerror-textcl": "डाà¤\9fाबà¥\87स à¤\85नà¥\81रà¥\8bध à¤¤à¥\8dरà¥\81à¤\9fि à¤\89तà¥\8dतà¥\8dपनà¥\8dन à¤¹à¥\8b à¤\97à¤\88ल बा।",
+       "nosuchaction": "à¤\85à¤\87सन à¤\95à¥\8cनà¥\8b à¤\95ारà¥\8dरवाà¤\88 à¤¨à¤\87à¤\96à¥\87",
+       "nosuchactiontext": "à¤\8f à¤¯à¥\82॰à¤\86र॰à¤\8fल à¤¦à¥\8dवारा à¤¬à¤¤à¤¾à¤µà¤² à¤\95ारà¥\8dरवाà¤\88 à¤\85वà¥\88ध à¤¬à¤¾à¥¤\nरà¤\89वाà¤\81 à¤¯à¥\82॰à¤\86र॰à¤\8fल à¤\97लत à¤²à¤¿à¤\96लà¥\87 à¤¹à¥\8bà¤\96ब, à¤¯à¤¾ à¤\95वà¥\8dनà¥\8b à¤\97लत à¤\95ड़à¥\80 à¤\95à¥\87 à¤\87सà¥\8dतà¥\87माल à¤\95à¤\87लà¥\87 à¤¹à¥\8bà¤\96ब।\n{{SITENAME}} à¤®à¥\87à¤\82 à¤\87सà¥\8dतमाल à¤¹à¥\8b à¤°à¤¹à¤² à¤¸à¥\89फà¥\8dà¤\9fवà¥\87यर à¤®à¥\87à¤\82 à¤\96राबà¥\80 à¤\95à¥\87 à¤²à¤\9aà¥\8dà¤\9bन भी हो सकत बा।",
+       "nosuchspecialpage": "à¤\85à¤\87सन à¤\95à¥\8cनà¥\8b à¤\96ास à¤ªà¤¨à¥\8dना à¤¨à¤\87à¤\96à¥\87",
+       "nospecialpagetext": "<strong>रउआँ एगो अवैध खास पन्ना के अनुरोध कइले बानी।</strong>\n\nबैध खास पन्नासभ के लिस्ट [[Special:SpecialPages|{{int:specialpages}}]] पर देखल जा सकत बा।",
+       "error": "à¤\96राबी",
+       "databaseerror": "डà¥\87à¤\9fाबà¥\87स à¤\96राबी",
+       "databaseerror-text": "à¤\95à¥\8cनà¥\8bà¤\82 à¤¡à¤¾à¤\9fाबà¥\87स à¤\95à¥\8dवà¥\88रà¥\80 à¤\96राबà¥\80 à¤­à¤\87ल à¤¬à¤¾à¥¤\nसà¤\82भवतà¤\83 à¤¸à¥\89फ्टवेयर में गड़बड़ी बा।",
+       "databaseerror-textcl": "à¤\95à¥\8cनà¥\8bà¤\82 à¤¡à¤¾à¤\9fाबà¥\87स à¤\85नà¥\81रà¥\8bध à¤\96राबà¥\80 à¤\89तà¥\8dतà¥\8dपनà¥\8dन à¤¹à¥\8b à¤\97à¤\87ल बा।",
        "databaseerror-query": "अनुरोध: $1",
-       "databaseerror-function": "फ़à¤\82à¤\95à¥\8dशन: $1",
-       "databaseerror-error": "तà¥\8dरà¥\81à¤\9fि: $1",
-       "transaction-duration-limit-exceeded": "हाई रिप्लिकेशन लैग बनावे से बचे खातिर ई ट्रांजेक्शन निरस्त कर दिहल गइल, काहें से की राइट करे में लागे वाला समय ($1), $2 के सीमा से अधिक रहल।\nअगर आ कई ठो आइटम एकही साथ बदलत होखीं, तब कई टुकड़ा में ई काम करे के कोसिस करीं।",
-       "laggedslavemode": "'''चेतावनी:''' इ पन्ना पर हाल के बदलाव ना होखे के आशंका बा।",
-       "readonly": "डà¥\87à¤\9fाबà¥\87स à¤²à¥\89à¤\95 बा",
-       "enterlockreason": "लà¥\89à¤\95 à¤\95रà¥\87 à¤\95à¥\87 à¤\95ारण à¤¦à¤¿à¤¹à¥\80à¤\82, à¤¸à¤¾à¤¥à¥\87 à¤²à¥\89à¤\95 à¤\96à¥\81लà¥\87 à¤\95à¥\87 à¤¸à¤®à¤¯ à¤\95à¥\87 à¤²à¤\97भà¤\97 à¤\86à¤\95लन à¤¦à¤¿à¤¹à¥\80à¤\82।",
-       "readonlytext": "डाà¤\9fाबà¥\87स à¤¨à¤¯à¤¾ à¤¸à¤\82पादन à¤\86 à¤\85नà¥\8dय à¤¬à¤¦à¤²à¤¾à¤µ à¤\96ातिर à¤²à¥\89à¤\95 à¤\95रल à¤\97à¤\87ल à¤¬à¤¾, à¤¶à¤¾à¤¯à¤¦ à¤°à¥\81à¤\9fà¥\80न à¤®à¥\87à¤\82à¤\9fà¥\87ननà¥\8dस à¤\95à¥\87 à¤\9aलतà¥\87, à¤\9cà¥\87à¤\95रा à¤¬à¤¾à¤¦ à¤\8f à¤\95à¥\87 à¤¸à¤¾à¤®à¤¾à¤¨à¥\8dय à¤¸à¥\8dथितà¥\80 à¤®à¥\87à¤\82 à¤\86 à¤\9cायà¥\87 à¤\95à¥\87 à¤\9aाहà¥\80à¤\82।\n\nà¤\9cà¤\89न à¤¸à¤¿à¤¸à¥\8dà¤\9fम à¤ªà¥\8dरबà¤\82धà¤\95 à¤\8fह à¤\95à¥\87 à¤²à¥\89à¤\95 à¤\95à¤\87लà¥\87 à¤°à¤¹à¤²à¤¨ à¤\95ारण à¤¦à¥\87हलà¥\87 à¤¬à¤¾à¤¡à¤¼à¤¨ à¤\95ि: $1",
-       "missing-article": "डà¥\87à¤\9fाबास à¤\8a à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤ªà¤¾à¤ à¥\8dय à¤\95à¥\87 à¤¨à¤¾ à¤\96à¥\8bà¤\9c à¤ªà¤¾à¤\88ल à¤\9cà¥\8cन à¤\88 à¤\95à¥\87 à¤\96à¥\8bà¤\9cà¥\87 à¤\95à¥\87 à¤°à¤¹à¤², à¤¨à¤¾à¤®à¤¿à¤¤ \"$1\" $2.\nà¤\88 à¤¸à¤¬ à¤¸à¤¾à¤§à¤¾à¤°à¤£à¤¤: à¤¨à¤¿à¤®à¥\8dनलिà¤\96à¥\80त à¤\85पà¥\8dरà¤\9aलित à¤\85नà¥\8dतर à¤\85थवा à¤\8fà¤\97à¥\8b à¤ªà¤¨à¥\8dना à¤ªà¤° à¤\87तिहास à¤\95à¥\87 à¤²à¤¿à¤\82à¤\95 à¤\9cà¥\8cन à¤®à¤¿à¤\9fा à¤¦à¤¿à¤¹à¤² à¤\97à¤\88ल à¤¬à¤¾ à¤\95à¥\87 à¤\95ारण à¤­à¤\88ल।\n\nयदि à¤\88 à¤¬à¤¾à¤¤ à¤¨à¤\88à¤\96à¥\87, à¤¤ à¤¹à¥\8b à¤¸à¤\95त à¤¬à¤¾ à¤¸à¥\89फà¥\8dà¤\9fवà¥\87यर à¤®à¥\87à¤\82 à¤¬à¤\97 à¤ªà¤¾à¤µà¤¤ à¤¹à¥\8bà¤\96ब।\nà¤\95à¥\83पया à¤\88 à¤\8fà¤\97à¥\8b  [[Special:ListUsers/sysop|पà¥\8dरबनà¥\8dधà¤\95]] à¤\95à¥\87 à¤¯à¥\82 à¤\86र à¤\8fल à¤\95à¥\87 à¤¬à¤¾à¤°à¥\87 à¤®à¥\87à¤\82 à¤\8fà¤\97à¥\8b à¤¨à¥\8bà¤\9f à¤¬à¤¨à¤¾के खबर करीं।",
-       "missingarticle-rev": "(सà¤\82शà¥\8bधन#: $1)",
+       "databaseerror-function": "फंक्शन: $1",
+       "databaseerror-error": "à¤\96राबà¥\80: $1",
+       "transaction-duration-limit-exceeded": "हाई रिप्लिकेशन लैग बनावे से बचे खातिर ई ट्रांजेक्शन निरस्त कर दिहल गइल, काहें से की राइट करे में लागे वाला समय ($1), $2 के सीमा से अधिक रहल ह।\nअगर आप कई ठो आइटम एकही साथ बदलत होखीं, तब कई टुकड़ा में ई काम करे के कोसिस करीं।",
+       "laggedslavemode": "<strong>चेतावनी:</strong> अइसन भी हो सकेला कि पन्ना पर हाल के अपडेट न होखे।",
+       "readonly": "डà¥\87à¤\9fाबà¥\87स à¤¤à¤¾à¤²à¤¾à¤¬à¤\82दà¥\80 बा",
+       "enterlockreason": "तालाबà¤\82दà¥\80 à¤\95à¥\87 à¤\95ारण à¤¦à¥\80à¤\82, à¤\86 à¤\85नà¥\81मान à¤¬à¤¤à¤¾à¤\88à¤\82 à¤\95ि à¤\95ब à¤¤à¤¾à¤²à¤¾à¤¬à¤\82दà¥\80 à¤¹à¤\9fà¥\80",
+       "readonlytext": "नया à¤¸à¤\82पादन à¤\86 à¤\85नà¥\8dय à¤¬à¤¦à¤²à¤¾à¤µ à¤\96ातिर à¤¡à¤¾à¤\9fाबà¥\87स à¤ªà¤° à¤¤à¤¾à¤²à¤¾à¤¬à¤\82दà¥\80 à¤¬à¤¾, à¤¶à¤¾à¤¯à¤¦ à¤°à¥\81à¤\9fà¥\80न à¤®à¥\87à¤\82à¤\9fà¥\87ननà¥\8dस à¤\95à¥\87 à¤\9aलतà¥\87, à¤\9cà¥\87à¤\95रा à¤¬à¤¾à¤¦ à¤\8f à¤\95à¥\87 à¤¸à¤¾à¤®à¤¾à¤¨à¥\8dय à¤¸à¥\8dथितà¥\80 à¤®à¥\87à¤\82 à¤\86 à¤\9cायà¥\87 à¤\95à¥\87 à¤\9aाहà¥\80à¤\82।\n\nतालाबà¤\82दà¥\80 à¤\95रà¥\87 à¤µà¤¾à¤²à¤¾ à¤¸à¤¿à¤¸à¥\8dà¤\9fम à¤ªà¥\8dरबà¤\82धà¤\95 à¤\95à¥\87 à¤¬à¤¤à¤¾à¤µà¤² à¤\95ारण: $1",
+       "missing-article": "डà¥\87à¤\9fाबास à¤\95à¥\87 à¤\93 à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤ªà¤¾à¤  à¤¨à¤¾ à¤®à¤¿à¤²à¤² à¤\9cवन à¤®à¤¿à¤²à¥\87 à¤\95à¥\87 à¤\9aाहत à¤°à¤¹à¤², à¤\8fà¤\95र à¤¨à¤¾à¤\81व à¤°à¤¹à¤² \"$1\" $2।\nà¤\86मतà¥\8cर à¤ªà¤° à¤\85à¤\87सन à¤¤à¤¬ à¤¹à¥\8bला à¤ªà¥\81रान à¤¹à¥\8b à¤\9aà¥\81à¤\95ल à¤\85à¤\82तर à¤¯à¤¾ à¤¹à¤\9fावल à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤\87तिहास à¤\95à¥\87 à¤\95ड़à¥\80 à¤\95à¥\87 à¤ªà¥\80à¤\9bा à¤\95à¤\87ल à¤\9cा à¤°à¤¹à¤² à¤¹à¥\8bà¤\96à¥\87।\n\nयदि à¤\88 à¤¬à¤¾à¤¤ à¤¨à¤\87à¤\96à¥\87, à¤¤à¤¬ à¤¹à¥\8b à¤¸à¤\95à¥\87ला à¤\86पà¤\95à¥\87 à¤\95à¥\8cनà¥\8bà¤\82 à¤¸à¥\89फà¥\8dà¤\9fवà¥\87यर à¤¬à¤\97 à¤®à¤¿à¤² à¤\97à¤\87ल à¤¹à¥\8bà¤\96à¥\87।\n[[Special:ListUsers/sysop|पà¥\8dरबà¤\82धà¤\95]] à¤\95à¥\87 à¤\88 à¤¯à¥\82à¤\86रà¤\8fल à¤¦à¥\87 के खबर करीं।",
+       "missingarticle-rev": "(बदलाव#: $1)",
        "missingarticle-diff": "(अंतर: $1, $2)",
-       "readonly_lag": "à¤\89पमà¥\81à¤\96à¥\8dय à¤¡à¤¾à¤\9fाबà¥\87स à¤¸à¤°à¥\8dवर à¤®à¥\81à¤\96à¥\8dय à¤¡à¤¾à¤\9fाबà¥\87स à¤\95à¥\87 à¤¬à¤°à¤¾à¤¬à¤° à¤ªà¤°à¤¾à¤µà¤°à¥\8dतित à¤¹à¥\8bत à¤¸à¤®à¤¯ à¤®à¥\81à¤\96à¥\8dय à¤¡à¤¾à¤\9fाबà¥\87स à¤¸à¤°à¥\8dवर अपने आप लॉक हो गइल।",
+       "readonly_lag": "निà¤\9aला à¤¡à¤¾à¤\9fाबà¥\87स à¤¸à¤°à¥\8dवर à¤\9cबलà¥\87 à¤®à¥\81à¤\96à¥\8dय à¤¡à¤¾à¤\9fाबà¥\87स à¤¸à¤°à¥\8dवर à¤\95à¥\87 à¤\97ति à¤ªà¤\95ड़ à¤ªà¤¾à¤µà¥\87, à¤¡à¤¾à¤\9fाबà¥\87स अपने आप लॉक हो गइल।",
        "nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' ऍचटीटीपी हेडर भेजल गइल रहल बाकी ई रिक्वेस्ट एपीआइ राइट मॉड्यूल खातिर रहल।",
-       "internalerror": "à¤\86नà¥\8dतरिà¤\95 à¤¤à¥\8dरà¥\81à¤\9fि",
-       "internalerror_info": "à¤\86नà¥\8dतरिà¤\95 à¤¤à¥\8dरà¥\81à¤\9fि: $1",
-       "internalerror-fatal-exception": "प्रकार के गंभीर अपवाद \"$1\"",
-       "filecopyerror": "\"$1\" फ़ाइल के \"$2\" पर प्रतिलिपि ना बन पाईल।",
-       "filerenameerror": "\"$1\" फ़ाइल के नाम बदल के \"$2\" नइखे रखल जा सकत।",
-       "filedeleteerror": "\"$1\" फ़ाइल के ना हटावल जा सकल।",
+       "internalerror": "à¤\85à¤\82दरà¥\82नà¥\80 à¤\96राबà¥\80",
+       "internalerror_info": "à¤\85à¤\82दरà¥\82नà¥\80 à¤\96राबà¥\80: $1",
+       "internalerror-fatal-exception": "\"$1\" प्रकार के घातक अपवाद",
+       "filecopyerror": "फाइल \"$1\" के \"$2\" पर नकल ना बन पावल।",
+       "filerenameerror": "फाइल \"$1\" के नाँव बदल के \"$2\" नइखे रखल जा सकत।",
+       "filedeleteerror": "फाइल \"$1\" के हटावल ना जा सकल।",
        "directorycreateerror": "\"$1\" डाइरेक्टरी ना बनावल जा सकल।",
-       "directoryreadonlyerror": "निरà¥\8dदà¥\87शिà¤\95ा \"$1\" à¤¸à¤¿à¤°à¥\8dफ à¤ªà¤ à¤¨à¥\80य बा।",
-       "directorynotreadableerror": "निरà¥\8dदà¥\87शिà¤\95ा \"$1\" à¤ªà¤ à¤¨à¥\80य नइखे।",
-       "filenotfound": "\"$1\" फ़ाइल ना मिलल।",
-       "unexpected": "à¤\85नपà¥\87à¤\95à¥\8dषित à¤®à¥\82लà¥\8dय: \"$1\"=\"$2\".",
-       "formerror": "तà¥\8dरà¥\81à¤\9fि: à¤«à¤¼à¥\89रà¥\8dम à¤¸à¤¬à¤®à¤¿à¤\9f à¤¨à¤¾ à¤\95रल जा सकल।",
-       "badarticleerror": "à¤\87 à¤ªà¤¨à¥\8dना à¤ªà¤° à¤\87 à¤\95ारà¥\8dय à¤¨à¤\87à¤\96à¥\87 à¤\95रल à¤\9cा à¤¸à¤\95त।",
-       "cannotdelete": "\"$1\" à¤ªà¤¨à¥\8dना à¤¯à¤¾ à¤«à¤¾à¤\87ल à¤\95à¥\87 à¤¹à¤\9fावल à¤¨à¤\87à¤\96à¥\87 à¤\9cा à¤¸à¤\95त।\nशायद à¤\95à¥\87हà¥\81 à¤\85à¤\89र à¤\87 à¤\95à¥\87 à¤ªà¤¹à¤¿à¤²à¥\87 à¤¹à¤¿ हटा चुकल होखे।",
+       "directoryreadonlyerror": "डाà¤\87रà¥\87à¤\95à¥\8dà¤\9fरà¥\80 \"$1\" à¤\96ालà¥\80 à¤ªà¤¢à¤¼à¥\87 à¤\96ातिर बा।",
+       "directorynotreadableerror": "डाà¤\87रà¥\87à¤\95à¥\8dà¤\9fरà¥\80 \"$1\" à¤ªà¤¢à¤¼à¥\87 à¤²à¤¾à¤¯à¤\95 नइखे।",
+       "filenotfound": "फाइल \"$1\" ना मिलल।",
+       "unexpected": "à¤\89मà¥\87द à¤¸à¥\87 à¤¹à¤\9f à¤\95à¥\87 à¤µà¥\88लà¥\8dयà¥\82: \"$1\"=\"$2\".",
+       "formerror": "à¤\96राबà¥\80: à¤«à¤¾à¤°à¥\8dम à¤\9cमा à¤¨à¤¾ à¤\95à¤\87ल जा सकल।",
+       "badarticleerror": "à¤\8f à¤ªà¤¨à¥\8dना à¤ªà¤° à¤\88 à¤\95ाम à¤¨à¤¾ à¤¹à¥\8b à¤¸à¤\95à¥\80।",
+       "cannotdelete": "\"$1\" à¤¨à¤¾à¤\81व à¤\95à¥\87 à¤ªà¤¨à¥\8dना à¤¯à¤¾ à¤«à¤¾à¤\87ल à¤\95à¥\87 à¤¨à¤¾ à¤¹à¤\9fावल à¤\9cा à¤¸à¤\95त à¤¬à¤¾à¥¤\nहà¥\8b à¤¸à¤\95à¥\87ला à¤\95à¥\87हà¥\82 à¤ªà¤¹à¤¿à¤²à¤¹à¥\80à¤\82 à¤\8fà¤\95रा à¤\95à¥\87 हटा चुकल होखे।",
        "cannotdelete-title": "\"$1\" पन्ना के हटावल नइखे जा सकत",
-       "delete-hook-aborted": "हà¥\81à¤\95 à¤¦à¥\8dवारा à¤¹à¤\9fायà¥\87à¤\95à¥\87 à¤\95à¥\8dरिया à¤¬à¥\80à¤\9aà¥\87 à¤®à¥\87à¤\82 à¤\9bà¥\8bड़ल à¤\97à¤\88ल।\nà¤\87 à¤\95à¤\89नà¥\8b à¤\95ारण à¤¨à¤\88à¤\96à¥\87 बतवले।",
-       "no-null-revision": "पनà¥\8dना \"$1\" à¤\96ातिर à¤¨à¤¯à¤¾ à¤\85शà¤\95à¥\8dत à¤¸à¤\82शोधन ना बन सकल",
+       "delete-hook-aborted": "हà¥\81à¤\95 à¤¦à¥\8dवारा à¤¹à¤\9fावà¥\87 à¤\95à¥\87 à¤\95ारà¥\8dरवाà¤\88 à¤¬à¥\80à¤\9aà¥\87 à¤®à¥\87à¤\82 à¤\9bà¥\8bड़ल à¤\97à¤\87ल।\nबिना à¤\95à¥\8cनà¥\8bà¤\82 à¤\95ारण बतवले।",
+       "no-null-revision": "पनà¥\8dना \"$1\" à¤\96ातिर à¤¨à¤¯à¤¾ à¤\96ालà¥\80 à¤¸à¤\82सोधन ना बन सकल",
        "badtitle": "खराब टाइटिल",
-       "badtitletext": "रà¤\89à¤\86 à¤¦à¥\8dवारा à¤\85नà¥\81रà¥\8bधित à¤¶à¥\80रà¥\8dषà¤\95 à¤\85यà¥\8bà¤\97à¥\8dय, à¤\96़ालà¥\80 à¤¯à¤¾ à¤\97लत à¤\9cà¥\81ड़ल à¤\85à¤\82तर-भाषà¥\80य à¤¯à¤¾ à¤\85à¤\82तर-विà¤\95ि à¤¶à¥\80रà¥\8dषà¤\95 à¤¬à¤¾à¥¤\nà¤\8f à¤®à¥\87à¤\82 à¤\8fà¤\95 à¤¯à¤¾ à¤\8fà¤\95 à¤¸à¥\87 à¤¢à¥\87र à¤\85à¤\87सन à¤\95à¥\85रà¥\87à¤\95à¥\8dà¤\9fर à¤¹à¥\8b à¤¸à¤\95त à¤¬à¤¾ à¤\9cवन à¤¶à¥\80रà¥\8dषà¤\95 à¤®à¥\87à¤\82 à¤ªà¥\8dरयà¥\8bà¤\97 à¤¨à¤\87à¤\96à¥\87 à¤\95à¤\87ल à¤\9cा à¤¸à¤\95त।",
-       "title-invalid-empty": "माà¤\81à¤\97ल à¤\9cा à¤°à¤¹à¤² à¤ªà¤¨à¥\8dना à¤\9fाà¤\87à¤\9fिल à¤¯à¤¾ à¤¤ à¤\96ालà¥\80 à¤¬à¤¾ à¤¯à¤¾ à¤«à¤¿à¤° à¤\96ालà¥\80 à¤\95à¥\8cनà¥\8bà¤\82 à¤¨à¤¾à¤\81वसà¥\8dथान à¤\95à¥\87 à¤¨à¤¾à¤\81व à¤­à¤° à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¬à¤¾à¤\9fà¥\87।",
+       "badtitletext": "à¤\85नà¥\81रà¥\8bधित à¤\9fाà¤\87à¤\9fिल à¤\85वà¥\88ध, à¤\96ालà¥\80 à¤¯à¤¾ à¤\97लत à¤\9cà¥\81ड़ल à¤\85à¤\82तर-भाषà¥\80य à¤¯à¤¾ à¤\85à¤\82तर-विà¤\95ि à¤\9fाà¤\87à¤\9fिल à¤¬à¤¾à¥¤\nबà¥\81à¤\9dात à¤¬à¤¾ à¤\95ि à¤\8fह à¤®à¥\87à¤\82 à¤\9fाà¤\87à¤\9fिल à¤®à¥\87à¤\82 à¤¨à¤¾ à¤\87सà¥\8dतà¥\87माल à¤¹à¥\8b à¤¸à¤\95à¥\87 à¤²à¤¾à¤¯à¤\95 à¤\8fà¤\95 à¤¯à¤¾ à¤\8fà¤\95 à¤¸à¥\87 à¤¢à¥\87र à¤\95à¥\85रà¥\87à¤\95à¥\8dà¤\9fर à¤¬à¤¾।",
+       "title-invalid-empty": "माँगल जा रहल पन्ना टाइटिल या त खाली बा या फिर कौनों नाँवस्थान के नाँव भर दिहल गइल बाटे।",
        "title-invalid-utf8": "माँगल जा रहल पन्ना टाइटिल में अइसन UTF-8 सीक्वेंस बा जेवन मान्य नइखे।",
        "title-invalid-interwiki": "माँगल जा रहल पन्ना टाइटिल में इंटरविकि कड़ी बा जेवन टाइटिल में ना प्रयोग कइल जा सकत बा।",
        "title-invalid-talk-namespace": "माँगल जा रहल पन्ना टाइटिल एगो अइसन वार्ता पन्ना के रेफर करत बा जेवना के होखल संभव नइखे।",
        "title-invalid-magic-tilde": "माँगल जा रहल पन्ना टाइटिल में अमान्य जादुई टिल्ड सीक्वेंस (<nowiki>~~~</nowiki>) बाटे।",
        "title-invalid-too-long": "माँगल जा रहल पन्ना टाइटिल बहुत ढेर लंबा बा। ई UTF-8 की एनकोडिंग में $1 {{PLURAL:$1|बाइट|बाइट्स}} से ढेर ना होखे के चाहीं।",
        "title-invalid-leading-colon": "माँगल जा रहल पन्ना टाइटिल में सुरुआते में अमान्य कोलन (:) बाटे।",
-       "perfcached": "नà¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¡à¥\87à¤\9fा à¤\95à¥\88शà¥\87 à¤®à¥\87मà¥\8bरà¥\80 à¤¸à¥\87 à¤²à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¬à¤¾, à¤\85तà¤\83 à¤¹à¥\8b à¤¸à¤\95ता à¤¬à¤¾ à¤\95ि à¤\87 à¤\95à¥\87 à¤ªà¥\82रà¥\8dण à¤\85दà¥\8dयतन à¤¨à¤¾ à¤­à¤\87ल à¤¹à¥\8bà¤\96à¥\87। à¤\95à¥\88शà¥\87 à¤®à¥\87मà¥\8bरà¥\80 à¤®à¥\87à¤\82 à¤\85धिà¤\95तम {{PLURAL:$1|à¤\8fà¤\95  à¤¨à¤¤à¥\80à¤\9cा|$1 à¤¨à¤¤à¥\80à¤\9cà¤\82}} à¤\89पलबà¥\8dध à¤¬à¤¾à¤¡à¤¼à¥\87।",
-       "perfcachedts": "नà¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¡à¥\87à¤\9fा à¤\95à¥\88शà¥\87 à¤®à¥\87मà¥\8bरà¥\80 à¤¸à¥\87 à¤¬à¤¾, à¤\86 à¤\8fà¤\95र à¤\85à¤\82तिम à¤\85पडà¥\87à¤\9f $1 à¤\95à¥\87 à¤­à¤\87ल à¤°à¤¹à¤²à¥¤ à¤\95à¥\88श à¤®à¥\87मà¥\8bरà¥\80 à¤®à¥\87à¤\82 à¤\85धिà¤\95तम {{PLURAL:$4|à¤\8fà¤\95  à¤¨à¤¤à¥\80à¤\9cा|$4 à¤¨à¤¤à¥\80à¤\9cाà¤\82}} उपलब्ध बा।",
-       "querypage-no-updates": "à¤\87 à¤ªà¥\83षà¥\8dठ à¤\95à¥\87 à¤\85पडà¥\87à¤\9f à¤\95रल à¤\85यà¥\8bà¤\97à¥\8dय à¤¬à¤¾à¥¤ à¤\85भà¥\80 à¤\85हिà¤\9cा à¤\95à¥\87 à¤¡à¤¾à¤\9fा à¤\95à¥\87 à¤¤à¤¾à¤\9c़ा à¤¨à¤\87à¤\96à¥\87 à¤\95रल जा सकत।",
+       "perfcached": "नà¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤\86à¤\81à¤\95ड़ा à¤\95à¥\88शà¥\87 à¤®à¥\87मà¥\8bरà¥\80 à¤¸à¥\87 à¤²à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¬à¤¾, à¤¹à¥\8b à¤¸à¤\95ता à¤¬à¤¾ à¤\95ि à¤\88 à¤\8fà¤\95दम à¤\85पडà¥\87à¤\9f à¤¨à¤¾ à¤¹à¥\8bà¤\96à¥\87। à¤\95à¥\88शà¥\87 à¤®à¥\87मà¥\8bरà¥\80 à¤®à¥\87à¤\82 à¤\85धिà¤\95तम {{PLURAL:$1|à¤\8fà¤\95 à¤ à¥\8b  à¤¨à¤¤à¥\80à¤\9cा|$1 à¤¨à¤¤à¥\80à¤\9cा}} à¤\89पलबà¥\8dध à¤¬à¤¾।",
+       "perfcachedts": "नà¥\80à¤\9aà¥\87 à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤\86à¤\81à¤\95ड़ा à¤\95à¥\88शà¥\87 à¤®à¥\87मà¥\8bरà¥\80 à¤¸à¥\87 à¤¬à¤¾, à¤\86 à¤\8fà¤\95र à¤\85à¤\82तिम à¤\85पडà¥\87à¤\9f $1 à¤\95à¥\87 à¤­à¤\87ल à¤°à¤¹à¤²à¥¤ à¤\95à¥\88श à¤®à¥\87मà¥\8bरà¥\80 à¤®à¥\87à¤\82 à¤\85धिà¤\95तम {{PLURAL:$4|à¤\8fà¤\95 à¤ à¥\8b à¤¨à¤¤à¥\80à¤\9cा|$4 à¤¨à¤¤à¥\80à¤\9cा}} उपलब्ध बा।",
+       "querypage-no-updates": "à¤\8fह à¤ªà¤¨à¥\8dना à¤¸à¥\87 à¤¸à¤\82बà¤\82धित à¤\85पडà¥\87à¤\9f à¤µà¤°à¥\8dतमान à¤®à¥\87à¤\82 à¤¨à¤¿à¤°à¤¸à¥\8dत à¤¬à¤¾à¥¤ à¤\85भà¥\80 à¤\85हिà¤\9cा à¤\95à¥\87 à¤¡à¤¾à¤\9fा à¤\95à¥\87 à¤¤à¤¾à¤\9cा à¤¨à¤\87à¤\96à¥\87 à¤\95à¤\87ल जा सकत।",
        "viewsource": "स्रोत देखीं",
        "viewsource-title": "$1 के स्रोत देखीं",
-       "actionthrottled": "à¤\95ारà¥\8dय समाप्त कर दिहल गइल बा",
-       "actionthrottledtext": "दुरुपयोग रोकथाम उपाय के रूप में, एह काम के बहुत कम समय में एक सीमा से अधिक बे करे के मना बा, आ रउआ ई सीमा के पार कर चुकल बानी।\nकृपया कुछ समय बाद दोबारा कोसिस करीं।",
-       "protectedpagetext": "à¤\87 à¤ªà¤¨à¥\8dना à¤¸à¤\82पादन à¤\86 à¤\85नà¥\8dय à¤\95ारà¥\8dयà¤\82 à¤¸à¥\87 à¤¬à¤\9aाव खातिर सुरक्षित कर दिहल गइल बा।",
+       "actionthrottled": "à¤\95ारà¥\8dरवाà¤\88 समाप्त कर दिहल गइल बा",
+       "actionthrottledtext": "दुरुपयोग रोकथाम उपाय के रूप में, एह काम के बहुत कम समय में एक सीमा से अधिक बे करे के मना बा, आ रउआ ई सीमा के पार कर चुकल बानी।\nकृपया कुछ समय बाद दोबारा कोसिस करीं।",
+       "protectedpagetext": "à¤\8f à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¸à¤\82पादन à¤\86 à¤\85नà¥\8dय à¤\95ारà¥\8dरवाà¤\88 à¤¸à¥\87 à¤¬à¤\9aावà¥\87 खातिर सुरक्षित कर दिहल गइल बा।",
        "viewsourcetext": "रउआँ एह पन्ना के स्रोत देख सकत बानी आ एकर नकल उतार सकत बानी:",
-       "viewyourtext": "à¤\8fह à¤ªà¤¨à¥\8dना à¤ªà¤° <strong>राà¤\89र à¤\86पन à¤¸à¤\82पादन à¤¸à¤¬</strong>के स्रोत देख सकत बानी आ ओकर नकल ले सकत बानी।",
-       "protectedinterface": "à¤\87 à¤ªà¤¨à¥\8dना à¤\87 à¤µà¤¿à¤\95à¥\80 à¤\95à¥\87 à¤¸à¥\89फ़à¥\8dà¤\9fवà¥\87यर à¤\95à¥\87 à¤\87à¤\82à¤\9fरफ़à¥\87स à¤ªà¤¾à¤ à¥\8dय à¤\95à¥\87 à¤¦à¥\87वà¥\87ला, à¤\86 à¤\87 à¤\95à¥\87 à¤\97लत à¤ªà¥\8dरयà¥\8bà¤\97 à¤¸à¥\87 à¤¬à¤\9aावà¥\87 à¤\96ातिर à¤¸à¥\81रà¤\95à¥\8dषित à¤\95र à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¬à¤¾à¥¤\nसभन à¤µà¤¿à¤\95ियन à¤\96ातिर à¤\85नà¥\81वाद à¤\9cà¥\8bड़à¥\87 à¤¯à¤¾ à¤¬à¤¦à¤²à¥\87 à¤\96ातिर à¤\95à¥\83पया à¤®à¥\80डियाविà¤\95ि à¤\95à¥\87 à¤\95à¥\8dषà¥\87तà¥\8dरà¥\80यà¤\95रण à¤ªà¥\8dरà¤\95लà¥\8dप [https://translatewiki.net/ translatewiki.net] à¤\95à¥\87 à¤ªà¥\8dरयà¥\8bà¤\97 करीं।",
-       "editinginterface": "<strong>à¤\9aà¥\87तावनà¥\80:</strong> à¤\86प à¤\8fà¤\97à¥\8b à¤\85à¤\87सन à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¬à¤¦à¤² à¤¬à¤¦à¤² à¤°à¤¹à¤² à¤¬à¤¾à¤¨à¥\80 à¤\9cवन à¤¸à¥\89फ़à¥\8dà¤\9fवà¥\87यर à¤\95à¥\87 à¤\87à¤\82à¤\9fरफ़à¥\87स à¤ªà¤¾à¤  à¤ªà¥\8dरदान à¤\95रà¥\87ला। à¤\87 à¤ªà¥\83षà¥\8dठ à¤\95à¥\87 à¤¬à¤¦à¤²à¥\87 à¤¸à¥\87 à¤\85नà¥\8dय à¤¸à¤¦à¤¸à¥\8dयवन à¤\95à¥\87 à¤ªà¥\8dरदरà¥\8dशित à¤\87à¤\82à¤\9fरफ़à¥\87स à¤\95à¥\87 à¤¶à¤\95à¥\8dलà¥\8bसà¥\82रत à¤®à¥\87à¤\82 à¤¬à¤¦à¤²à¤¾à¤µ à¤\86à¤\88।",
+       "viewyourtext": "à¤\8fह à¤ªà¤¨à¥\8dना à¤ªà¤° <strong>à¤\86पन à¤\96à¥\81द à¤\95à¥\87 à¤¸à¤\82पादन</strong>के स्रोत देख सकत बानी आ ओकर नकल ले सकत बानी।",
+       "protectedinterface": "à¤\88 à¤ªà¤¨à¥\8dना à¤\8fह à¤µà¤¿à¤\95ि à¤\95à¥\87 à¤¸à¥\89फà¥\8dà¤\9fवà¥\87यर à¤\95à¥\87 à¤\87à¤\82à¤\9fरफà¥\87स à¤ªà¤¾à¤  à¤\89पलबà¥\8dध à¤\95रावà¥\87 à¤²à¤¾, à¤\86 à¤¦à¥\81रà¥\82पयà¥\8bà¤\97 à¤°à¥\8bà¤\95à¥\87 à¤\96ातिर à¤\8fà¤\95रा à¤\95à¥\87 à¤¸à¥\81रà¤\95à¥\8dषित à¤\95र à¤¦à¤¿à¤¹à¤² à¤\97à¤\87ल à¤¬à¤¾à¥¤\nबिनमà¥\8dर à¤\85नà¥\81रà¥\8bध à¤¬à¤¾ à¤\95ि, à¤¸à¤\97रà¥\80 à¤µà¤¿à¤\95ि à¤¸à¤­ à¤\96ातिर à¤\85नà¥\81वाद à¤\95रà¥\87 à¤¯à¤¾ à¤\85नà¥\81वाद à¤®à¥\87à¤\82 à¤¬à¤¦à¤²à¤¾à¤µ à¤\95रà¥\87 à¤\96ातिर à¤®à¥\80डियाविà¤\95ि à¤\95à¥\87 à¤²à¥\8bà¤\95लाà¤\87à¤\9cà¥\87शन à¤ªà¥\8dरà¥\8bà¤\9cà¥\87à¤\95à¥\8dà¤\9f [https://translatewiki.net/ translatewiki.net] à¤\95à¥\87 à¤\87सà¥\8dतà¥\87माल करीं।",
+       "editinginterface": "<strong>à¤\9aà¥\87तावनà¥\80:</strong> à¤\86प à¤\8fà¤\97à¥\8b à¤\85à¤\87सन à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¸à¤\82पादन à¤\95र à¤°à¤¹à¤² à¤¬à¤¾à¤¨à¥\80 à¤\9cवन à¤¸à¥\89फà¥\8dà¤\9fवà¥\87यर à¤\95à¥\87 à¤\87à¤\82à¤\9fरफà¥\87स à¤ªà¤¾à¤  à¤\89पलबà¥\8dध à¤\95रावà¥\87 à¤²à¤¾à¥¤ à¤\8fह à¤ªà¤¨à¥\8dना à¤ªà¤° à¤¹à¥\8bà¤\96à¥\87 à¤µà¤¾à¤²à¤¾ à¤¬à¤¦à¤²à¤¾à¤µ à¤\8fह à¤µà¤¿à¤\95ि à¤ªà¤° à¤\95à¥\87 à¤\85नà¥\8dय à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤\95à¥\87 à¤²à¤\89à¤\95à¥\87 à¤µà¤¾à¤²à¤¾ à¤\87à¤\82à¤\9fरफà¥\87स à¤\95à¥\87 à¤¸à¤\95लसà¥\82रत à¤\95à¥\87 à¤ªà¤°à¤­à¤¾à¤µà¤¿à¤¤ à¤\95रà¥\80।",
        "translateinterface": "सभन विकियन खातिर अनुवाद जोड़े या बदले खातिर मीडियाविकि क्षेत्रीयकरण परियोजना [https://translatewiki.net/ translatewiki.net] के प्रयोग करीं।",
        "cascadeprotected": "ए पन्ना के संपादन कइल सुरक्षित क दिहल गइल बा काहें कि ई {{PLURAL:$1|पन्ना में, जौना के|पन्ना सब में, जिन्हन के}} \"कैस्केडिंग\" (बिस्तारित) सुरक्षा चालू क के सुरक्षित कइल गइल बा, में समाइल बाटे:\n$2",
        "namespaceprotected": "रउआ के '''$1''' नामस्थान के पन्नं में सम्पादन करे के अधिकार नइखे दिहल गइल।",
        "passwordreset-emaildisabled": "इ विकि पर ई-मेल सुविधा अक्षम कर दिहल गईल बा।",
        "passwordreset-username": "प्रयोगकर्ता नाम",
        "passwordreset-domain": "डोमेन:",
-       "passwordreset-capture": "परिणामस्वरूप बनल ई-मेल देखब?",
-       "passwordreset-capture-help": "अगर रउआ इ चेकबॉक्स पर टिक करत बानी त ई-मेल (अस्थायी गुप्तशब्द के साथ) रउआ के दिखावल जाई आ सदस्य के भेजल भी जाई।",
        "passwordreset-email": "ई-मेल पता:",
        "passwordreset-emailtitle": "{{SITENAME}} पर खाता विवरण",
        "passwordreset-emailtext-ip": "केहु (शायद रउए, $1 आइ॰पी पता से) {{SITENAME}} ($4) पर आपन {{PLURAL:$3|गुप्तशब्द}} के रीसेट करे के अनुरोध कईले बानी। इ ई-मेल पता से निम्न {{PLURAL:$3|खाता जुड़ल बा}}:\n\n$2\n\n{{PLURAL:$3|इ}} अस्थायी गुप्तशब्द {{PLURAL:$5|एक दिन|$5 दिन}} के बाद काम ना करी। रउआ खाता में प्रवेश करके एगो नया गुप्तशब्द अभी चुन लेवे के चाहीं। यदि इ अनुरोध केहु अउर कइले बा, या फिर रउआ आपन मूल गुप्तशब्द याद आ गईल बा, अउर आप {{PLURAL:$3|आपन}} गुप्तशब्द नइखी बदले के चाहत त, रउआ इ संदेश के अनदेखा कर के आपन पुरानका गुप्तशब्द के प्रयोग जारी रख सकत बानी।",
        "tooltip-p-logo": "मुख्य पन्ना पर जाईं",
        "tooltip-n-mainpage": "मुख्य पन्ना पर जाईं",
        "tooltip-n-mainpage-description": "मुख्य पन्ना पर जाईं",
-       "tooltip-n-portal": "पà¥\8dरà¥\8bà¤\9cà¥\87à¤\95à¥\8dà¤\9f à¤\95à¥\80 à¤¬à¤¾à¤°à¥\87 à¤®à¥\87à¤\81, रउआँ का कर सकत बानी, कौनों चीज कहाँ खोजब",
+       "tooltip-n-portal": "पà¥\8dरà¥\8bà¤\9cà¥\87à¤\95à¥\8dà¤\9f à¤\95à¥\80 à¤¬à¤¾à¤°à¥\87 à¤®à¥\87à¤\82, रउआँ का कर सकत बानी, कौनों चीज कहाँ खोजब",
        "tooltip-n-currentevents": "वर्तमान के घटना पर पृष्ठभूमी जानकारी खोजीं",
        "tooltip-n-recentchanges": "विकि पर तुरंत भइल बदलाव के लिस्ट",
        "tooltip-n-randompage": "बेतरतीब पन्ना लोड करीं",
index dbf7814..b4f0ad4 100644 (file)
        "views": "দৃষ্টিকোণ",
        "toolbox": "সরঞ্জাম",
        "tool-link-userrights": "{{GENDER:$1|ব্যবহারকারী}} দল পরিবর্তন করুন",
+       "tool-link-userrights-readonly": "{{GENDER:$1|ব্যবহারকারী}} দল দেখুন",
        "tool-link-emailuser": "এই {{GENDER:$1|ব্যবহারকারী}}কে ইমেইল পাঠান",
        "userpage": "ব্যাবহারকারীর পাতা দেখুন",
        "projectpage": "মেটা-পাতা দেখুন",
        "nosuchaction": "এমন কোন কাজ নেই",
        "nosuchactiontext": "এই উআরএল এ নির্ধারিত কাজটি অবৈধ।\nআপনি হয়তো একটি ভুল লিঙ্ক দিয়েছেন অথবা ইউআরএল লিখতে ভুল করেছেন।\nএটি এমনও নির্দেশ করে যে {{SITENAME}} সাইটে ব্যবহৃত সফটওয়্যারটিতে একটি ত্রুটি রয়েছে।",
        "nosuchspecialpage": "এমন কোন বিশেষ পাতা নেই",
-       "nospecialpagetext": "<strong>আপনি একটি অবৈধ বিশেষ পাতা অনুরোধ করেছেন।</strong>\n\n[[Special:SpecialPages|{{int:specialpages}}]]-এ বৈধ বিশেষ পাতাগুলির একটি তালিকা পাবেন।",
+       "nospecialpagetext": "<strong>আপনি একটি অবৈধ বিশেষ পাতা অনুরোধ করেছেন।</strong>\n\n[[Special:SpecialPages|{{int:specialpages}}]]-এ বৈধ বিশেষ পাতাগুলির একটি তালিকা পাবেন।",
        "error": "ত্রুটি",
        "databaseerror": "ডাটাবেস ত্রুটি",
        "databaseerror-text": "ডাটাবেজ অনুসন্ধান ত্রুটি।\nএটি সফটওয়্যারের একটি ত্রুটি হতে পারে।",
        "diff-multi-sameuser": "(একই ব্যবহারকারী দ্বারা সম্পাদিত {{PLURAL:$1|একটি মধ্যবর্তী সংশোধন|$1টি মধ্যবর্তী সংশোধন}} দেখানো হচ্ছে না)",
        "diff-multi-otherusers": "({{PLURAL:$2|একজন|$2 জন}} ব্যবহারকারী দ্বারা সম্পাদিত {{PLURAL:$1|একটি|$1টি}} মধ্যবর্তী সংশোধন দেখানো হচ্ছে না)",
        "diff-multi-manyusers": "($2 জন {{PLURAL:$2|ব্যবহারাকারীর}} সম্পাদিত {{PLURAL:$1|একটি সাম্প্রতিক সংস্করণ|$1 টি সাম্প্রতিক সংস্করণ}} প্রদর্শিত হচ্ছে না)",
-       "difference-missing-revision": "$1 পার্থক্যের {{PLURAL:$2|একটি সংস্করণ|$2টি সংস্করণসমূহ}} খুজে পাওয়া যাচ্ছে না।\n\nসাধারণত মুছে ফেলা হয়েছে এমন পাতার মেয়াদ উত্তীর্ণ ইতিহাস পাতার লিংক ওপেন করার কারণে এটি হতে পারে। \n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} অপসারণ লগে] বিস্তারিত তথ্য জানা যাবে।",
+       "difference-missing-revision": "$1 পার্থক্যের {{PLURAL:$2|একটি সংস্করণ|$2টি সংস্করণ}} খুঁজে পাওয়া যাচ্ছে না।\n\nসাধারণত মুছে ফেলা হয়েছে এমন পাতার মেয়াদ উত্তীর্ণ ইতিহাস পাতার লিংক খোলার কারণে এটি হতে পারে। \n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} অপসারণ লগে] বিস্তারিত তথ্য জানা যাবে।",
        "searchresults": "অনুসন্ধানের ফলাফল",
        "searchresults-title": "\"$1\" অনুসন্ধানের ফলাফল",
        "titlematches": "নিবন্ধের শিরোনাম মিলেছে",
        "search-external": "বহিঃস্থ অনুসন্ধান",
        "searchdisabled": "{{SITENAME}} অনুসন্ধান এখন নিষ্ক্রিয় আছে। আপনি গুগলের মাধ্যমে অনুসন্ধান চালাতে পারেন। লক্ষ্য করুন যে {{SITENAME}}-এর বিষয়বস্তুর উপর গুগলের ইন্ডেক্সগুলি হালনাগাদ না-ও করা থাকতে পারে।",
        "search-error": "অনুসন্ধানের সময় একটি ত্রুটি হয়েছে: $1",
+       "search-warning": "অনুসন্ধানের সময় একটি সতর্কীকরণ দেখিয়েছে: $1",
        "preferences": "আমার পছন্দ",
        "mypreferences": "পছন্দসমূহ",
        "prefs-edits": "সম্পাদনা সংখ্যা:",
        "prefs-help-recentchangescount": "এতে সাম্প্রতিক পরিবর্তনসমূহ, পাতার ইতিহাস এবং লগ অন্তর্ভুক্ত।",
        "prefs-help-watchlist-token2": "এটি আপনার নজরতালিকার ওয়েব ফিডের গোপন চাবি। যে কেউ যিনি এটা জানেন তিনি আপনার নজরতালিকা পড়তে সক্ষম হবেন, তাই এটি প্রকাশ করবেন না। [[Special:ResetTokens|আপনার এটি পুনরায় সেট করার প্রয়োজন হলে এখানে ক্লিক করুন]]।",
        "savedprefs": "আপনার পছন্দগুলো সংরক্ষণ করা হয়েছে।",
-       "savedrights": "{{GENDER:$1|$1}}-à¦\8fর à¦¬à§\8dযবহারà¦\95ারà§\80 à¦\85ধিà¦\95ার সংরক্ষিত হয়েছে।",
+       "savedrights": "{{GENDER:$1|$1}}-à¦\8fর à¦¬à§\8dযবহারà¦\95ারà§\80 à¦¦à¦² সংরক্ষিত হয়েছে।",
        "timezonelegend": "সময়স্থান:",
        "localtime": "স্থানীয় সময়:",
        "timezoneuseserverdefault": "উইকির পূর্বনির্ধারিত সময় ব্যবহার করো ($1)",
        "prefswarning-warning": "আপানি পছন্দসমূহ পাতায় পাতায় পরিবর্তন করেছেন যেগুলো সংরক্ষণ করা হয়নি। আপনি যদি এই পাতাটি ছেড়ে যাওয়ার আগে \"$1\" ক্লিক না করেন তবে পছন্দসমূহ হালনাগাদ হবে না।",
        "prefs-tabs-navigation-hint": "সাহায্য: আপনি ডান এবং বাম অ্যারো বাটন ব্যবহার করে বিভিন্ন ট্যাবগুলোতে যেতে পারবেন।",
        "userrights": "ব্যবহারকারী অধিকার ব্যবস্থাপনা",
-       "userrights-lookup-user": "বà§\8dযবহারà¦\95ারà§\80 à¦¦à¦² à¦¬à§\8dযবসà§\8dথাপনা করুন",
+       "userrights-lookup-user": "à¦\8fà¦\95à¦\9cন à¦¬à§\8dযবহারà¦\95ারà§\80 à¦¨à¦¿à¦°à§\8dবাà¦\9aন করুন",
        "userrights-user-editname": "ব্যবহারকারীর নাম লিখুন:",
-       "editusergroup": "{{GENDER:$1|ব্যবহারকারীর}} দল সম্পাদনা করো",
+       "editusergroup": "ব্যবহারকারী দল লোড করুন",
        "editinguser": "<strong>[[User:$1|$1]]</strong> $2 {{GENDER:$1|ব্যবহারকারীর}} জন্য ব্যবহারকারী অধিকার পরিবর্তন করছেন",
        "userrights-editusergroup": "ব্যবহারকারীর দল সম্পাদনা করো",
        "saveusergroups": "{{GENDER:$1|ব্যবহারকারীর}} দল সংরক্ষণ করো",
        "grant-basic": "মৌলিক অধিকার",
        "grant-viewdeleted": "অপসারিত ফাইল ও পাতাগুলি দেখুন",
        "grant-viewmywatchlist": "আপনার নজরতালিকা দেখুন",
+       "grant-viewrestrictedlogs": "লগের সীমাবদ্ধ ভুক্তিগুলি দেখুন",
        "newuserlogpage": "ব্যবহারকারী সৃষ্টির লগ",
        "newuserlogpagetext": "এটি নতুন ব্যবহারকারী সৃষ্টির লগ",
        "rightslog": "ব্যবহারকারীর অধিকার লগ",
        "action-upload_by_url": "কোন ইউআরএল থেকে ফাইলটি আপলোড করো",
        "action-writeapi": "রাইট এপিআই ব্যবহার করুন",
        "action-delete": "পাতাটি মুছে ফেলো",
-       "action-deleterevision": "à¦\8fà¦\87 à¦¸à¦\82শà§\8bধনà¦\9fি à¦®à§\81à¦\9bà§\87 à¦«à§\87লার",
-       "action-deletedhistory": "পাতার à¦®à§\81à¦\9bà§\87 à¦«à§\87লা à¦\87তিহাস à¦¦à§\87à¦\96াà¦\93",
+       "action-deleterevision": "সংশোধনটি মুছে ফেলার",
+       "action-deletedhistory": "পাতার à¦\85পসারিত à¦\87তিহাস à¦¦à§\87à¦\96ার",
        "action-browsearchive": "অপসারিত পাতায় অনুসন্ধান করুন",
-       "action-undelete": "পাতাà¦\9fি à¦ªà§\81নরà§\81দà§\8dধার à¦\95রà§\8b",
-       "action-suppressrevision": "লà§\81à¦\95ানà§\8b à¦¸à¦\82সà§\8dà¦\95রণà¦\97à§\81লà§\8b à¦ªà¦°à§\8dযালà§\8bà¦\9aনা à¦\8fবà¦\82 à¦ªà§\81নà¦\83সà§\8dথাপন à¦\95রà§\81ন",
+       "action-undelete": "পাতাà¦\9fি à¦ªà§\81নরà§\81দà§\8dধার à¦\95রার",
+       "action-suppressrevision": "লà§\81à¦\95ানà§\8b à¦¸à¦\82সà§\8dà¦\95রণà¦\97à§\81লà§\8b à¦ªà¦°à§\8dযালà§\8bà¦\9aনা à¦\8fবà¦\82 à¦ªà§\81নà¦\83সà§\8dথাপন à¦\95রার",
        "action-suppressionlog": "এই ব্যক্তিগত লগ দেখার",
        "action-block": "এই ব্যবহারকারীকে সম্পাদনা করতে বাঁধা দেয়ার",
        "action-protect": "এই পাতার সুরক্ষার মাত্রা পরিবর্তন করার",
        "backend-fail-store": "\"$2\"-এ \"$1\" ফাইলটি সংরক্ষন করা সম্ভব নয়।",
        "backend-fail-copy": "\"$1\" ফাইলটি \"$2\"-তে অনুলিপি করা সম্ভব নয়।",
        "backend-fail-move": "\"$2\"-এ \"$1\" ফাইলটি স্থানান্তর করা সম্ভব নয়।",
-       "backend-fail-opentemp": "à¦\85সà§\8dথায়à§\80 à¦«à¦¾à¦\87লà¦\9fি à¦\93পà§\87ন à¦\95রা à¦¯à¦¾à¦\9aà§\8dà¦\9bে না।",
+       "backend-fail-opentemp": "à¦\85সà§\8dথায়à§\80 à¦«à¦¾à¦\87লà¦\9fি à¦\96à§\8bলা à¦¯à¦¾à¦¬ে না।",
        "backend-fail-writetemp": "অস্থায়ী ফাইলটিতে লেখা যাচ্ছে না।",
        "backend-fail-closetemp": "অস্থায়ী ফাইলটি বন্ধ করা যাচ্ছে না।",
-       "backend-fail-read": "$1 ফাইলটি ওপেন করা যাচ্ছে না।",
+       "backend-fail-read": "\"$1\" ফাইলটি পড়া যাচ্ছে না।",
        "backend-fail-create": "$1 ফাইলটি তৈরী করা যাচ্ছে না।",
        "backend-fail-maxsize": "\"$1\" ফাইলে লেখা যাচ্ছে না কারণ এটি {{PLURAL:$2|এক বাইট|$2 বাইট}} থেকে বড়।",
        "backend-fail-readonly": "\"$1\" স্টোরেজ ব্যাকএন্ড থেকে বর্তমানে শুধু-পঠনে রয়েছে। প্রদত্ত কারণ: <em>$2</em>",
        "apisandbox-alert-field": "এই ক্ষেত্রের মান বৈধ নয়।",
        "apisandbox-continue": "অব্যাহত",
        "apisandbox-continue-clear": "পরিস্কার",
+       "apisandbox-param-limit": "সর্বোচ্চ সীমা ব্যবহার করতে <kbd>max</kbd> লিখুন।",
+       "apisandbox-multivalue-all-namespaces": "$1 (সব নামস্থান)",
        "apisandbox-multivalue-all-values": "$1 (সব মান)",
        "booksources": "বইয়ের উৎস",
        "booksources-search-legend": "বইয়ের উৎসের জন্য অনুসন্ধান করা হোক",
        "booksources-text": "নতুন ও পুরাতন ব্যবহৃত বই বিক্রি করে, এমন কতগুলি সাইটের সংযোগের তালিকা নিচে দেওয়া হল, যে সাইটগুলিতে আপনার অনুসন্ধানকৃত বইগুলির উপর আরও তথ্য থাকতে পারে:",
        "booksources-invalid-isbn": "উল্লেখিত ISBN সঠিক নয়; অনুগ্রহ করে মূল উৎস থেকে আবার পরীক্ষা করুন।",
        "magiclink-tracking-rfc": "আরএফসি জাদু সংযোগ ব্যবহার করা পাতা",
+       "magiclink-tracking-rfc-desc": "এই পাতাটি RFC যাদু সংযোগ ব্যবহার করে। কিভাবে মাইগ্রেট করবেন জানতে [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] দেখুন।",
        "magiclink-tracking-pmid": "পিএমআইডি জাদু সংযোগ ব্যবহার করা পাতা",
+       "magiclink-tracking-pmid-desc": "এই পাতাটি পিএমআইডি যাদু সংযোগ ব্যবহার করে। কিভাবে মাইগ্রেট করবেন জানতে [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] দেখুন।",
        "magiclink-tracking-isbn": "আইএসবিএন জাদু সংযোগ ব্যবহার করা পাতা",
+       "magiclink-tracking-isbn-desc": "এই পাতাটি আইএসবিএন যাদু সংযোগ ব্যবহার করে। কিভাবে মাইগ্রেট করবেন জানতে [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] দেখুন।",
        "specialloguserlabel": "সম্পাদক:",
        "speciallogtitlelabel": "লক্ষ্য (শিরোনাম বা {{ns:user}}:ব্যবহারকারীর জন্য ব্যবহারকারী নাম):",
        "log": "লগগুলি",
        "allpagesbadtitle": "প্রদত্ত পাতার শিরোনামটি অবৈধ ছিল অথবা এটিতে কোন আন্তঃভাষা বা আন্তঃউইকি উপসর্গ ছিল। এটিতে এক বা একাধিক ক্যারেক্টার থাকতে পারে যা শিরোনামে ব্যবহার করা সম্ভব নয়।",
        "allpages-bad-ns": "{{SITENAME}}-এ \"$1\" নামের কোন নামস্থান নেই।",
        "allpages-hide-redirects": "পুনর্নির্দেশনাগুলো লুকাও",
-       "cachedspecial-viewing-cached-ttl": "à¦\86পনার à¦\93পà§\87ন à¦\95রা à¦ªà¦¾à¦¤à¦¾à¦\9fি à¦\95à§\8dযাশ à¦¥à§\87à¦\95à§\87 à¦ªà§\8dরদরà§\8dশিত à¦¹à¦\9aà§\8dà¦\9bà§\87, à¦\8fà¦\9fি $1 দিনের পুরানো হতে পারে।",
-       "cachedspecial-viewing-cached-ts": "à¦\86পনার à¦\93পà§\87ন à¦\95রা à¦ªà¦¾à¦¤à¦¾à¦\9fি à¦\95à§\8dযাশ à¦¥à§\87à¦\95à§\87 à¦ªà§\8dরদরà§\8dশিত à¦¹à¦\9aà§\8dà¦\9bà§\87, à¦\8fà¦\9fি à¦¸à¦®à§\8dপà§\82রà§\8dণ à¦¨à¦¤à§\81ন হতে পারে।",
+       "cachedspecial-viewing-cached-ttl": "à¦\86পনি à¦\8fà¦\87 à¦ªà§\83ষà§\8dঠায় à¦\8fà¦\95à¦\9fি à¦\95à§\8dযাশà§\87 à¦¸à¦\82সà§\8dà¦\95রণ à¦¦à§\87à¦\96à¦\9bà§\87ন, à¦¯à¦¾ $1 দিনের পুরানো হতে পারে।",
+       "cachedspecial-viewing-cached-ts": "à¦\86পনি à¦\8fà¦\87 à¦ªà§\83ষà§\8dঠায় à¦\8fà¦\95à¦\9fি à¦\95à§\8dযাশà§\87 à¦¸à¦\82সà§\8dà¦\95রণ à¦¦à§\87à¦\96à¦\9bà§\87ন, à¦¯à¦¾ à¦¸à¦®à§\8dপà§\82রà§\8dণরà§\82পà§\87 à¦¹à¦¾à¦²à¦¨à¦¾à¦\97াদà¦\95à§\83ত à¦¨à¦¾à¦\93 হতে পারে।",
        "cachedspecial-refresh-now": "সাম্প্রতিকগুলো প্রদর্শন করো।",
        "categories": "বিষয়শ্রেণীসমূহ",
        "categories-submit": "দেখাও",
        "activeusers-intro": "এটি ব্যবহারকারী তালিকা যাদের $1 {{PLURAL:$1|দিনে|দিনে}} যেকোন কর্মকান্ড রয়েছে।",
        "activeusers-count": "গত {{PLURAL:$3|কালে|$3 দিনে}} সর্বমোট {{PLURAL:$1|কর্মের}} সংখ্যা $1টি",
        "activeusers-from": "ব্যবহারকারী দেখাও যাদের নাম এই অক্ষর দিয়ে শুরু:",
+       "activeusers-groups": "এই দলভুক্ত ব্যবহারকারী দেখান:",
        "activeusers-noresult": "কোনো ব্যবহারকারী পাওয়া যায়নি।",
        "activeusers-submit": "সক্রিয় ব্যবহারকারী প্রদর্শন করুন",
        "listgrouprights": "দলগত ব্যবহারকারী অধিকার",
        "emailuserfooter": "এই ইমেইলটি {{SITENAME}} সাইটের \"{{int:emailuser}}\" সুবিধা ব্যবহার করে $1-এর পক্ষ থেকে {{GENDER:$2|$2}}-এর নিকট {{GENDER:$1|পাঠানো হয়েছে}}।",
        "usermessage-summary": "বাদবাকি সিস্টেম বার্তা",
        "usermessage-editor": "সিস্টেম ম্যাসেঞ্জার",
+       "usermessage-template": "MediaWiki:ব্যবহারকারী বার্তা",
        "watchlist": "নজর তালিকা",
        "mywatchlist": "নজর তালিকা",
        "watchlistfor2": "$1 ($2)-এর জন্য",
        "pageinfo-header-restrictions": "পাতা সুরক্ষা",
        "pageinfo-header-properties": "পাতা বৈশিষ্টসমূহ",
        "pageinfo-display-title": "শিরনাম প্রদর্শন",
-       "pageinfo-default-sort": "ডিফলà§\8dà¦\9f à¦¸à¦°à§\8dà¦\9f à¦\95ি",
+       "pageinfo-default-sort": "পà§\82রà§\8dবনিরà§\8dধারিত à¦¬à¦¾à¦\9bাà¦\87য়à§\87র à¦\9aাবি",
        "pageinfo-length": "পাতার দৈর্ঘ্য (বাইটে)",
        "pageinfo-article-id": "পাতার আইডি",
        "pageinfo-language": "পাতার তথ্যের ভাষা",
        "hijri-calendar-m11": "জ্বিলকদ",
        "hijri-calendar-m12": "জ্বিলহজ্জ",
        "hebrew-calendar-m1": "তিশরেই",
+       "hebrew-calendar-m2": "হেশভান",
+       "hebrew-calendar-m3": "কিসলেভ",
+       "hebrew-calendar-m4": "তেভেত",
+       "hebrew-calendar-m5": "শেভাত",
+       "hebrew-calendar-m6": "আদার",
+       "hebrew-calendar-m6a": "আদার ১",
+       "hebrew-calendar-m6b": "আদার ২",
+       "hebrew-calendar-m7": "নিসান",
+       "hebrew-calendar-m8": "ইয়্যার",
+       "hebrew-calendar-m9": "সিভান",
        "hebrew-calendar-m10": "তামুয",
        "hebrew-calendar-m11": "আভ",
        "hebrew-calendar-m12": "এলুল",
+       "hebrew-calendar-m1-gen": "তিশরি",
+       "hebrew-calendar-m2-gen": "হেশভান",
+       "hebrew-calendar-m3-gen": "কিসলেভ",
+       "hebrew-calendar-m4-gen": "তেভেত",
+       "hebrew-calendar-m5-gen": "শেভাত",
+       "hebrew-calendar-m6-gen": "আদার",
+       "hebrew-calendar-m6a-gen": "আদার ১",
+       "hebrew-calendar-m6b-gen": "আদার ২",
        "hebrew-calendar-m7-gen": "নিসান",
+       "hebrew-calendar-m8-gen": "ইয়্যার",
+       "hebrew-calendar-m9-gen": "সিভান",
+       "hebrew-calendar-m10-gen": "তামুয",
+       "hebrew-calendar-m11-gen": "আভ",
+       "hebrew-calendar-m12-gen": "এলুল",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|আলাপ]])",
        "timezone-utc": "ইউটিসি",
        "timezone-local": "স্থানীয়",
        "htmlform-user-not-exists": "<strong>$1</strong>-এর অস্তিত্ব নেই।",
        "htmlform-user-not-valid": "<strong>$1</strong> একটি বৈধ ব্যবহারকারীর নাম নয়।",
        "logentry-delete-delete": "$1 কর্তৃক $3 পাতাটি অপসারিত হয়েছে",
+       "logentry-delete-delete_redir": "$1 পুনর্লিখনের দ্বারা $3 পুনর্নির্দেশ {{GENDER:$2|অপসারণ করেছেন}}",
        "logentry-delete-restore": "$1 কর্তৃক $3 পাতাটি {{GENDER:$2|ফিরিয়ে আনা}} হয়েছে",
        "logentry-delete-event": "$1 {{PLURAL:$5|একটি লগ ইভেন্টের|$5 লগ ইভেন্টসমূহের}} দৃশ্যমানতা {{GENDER:$2|পরিবর্তন}} করেছেন $3: $4",
        "logentry-delete-revision": "$1 {{PLURAL:$5|একটি সংস্করণের|$5টি সংস্করণের}} দৃশ্যমানতা {{GENDER:$2|পরিবর্তন}} করেছেন $3: $4",
        "mw-widgets-dateinput-no-date": "কোন তারিখ নির্বাচন করা হয়নি",
        "mw-widgets-dateinput-placeholder-day": "বববব-মম-দদ",
        "mw-widgets-dateinput-placeholder-month": "বববব-মম",
+       "mw-widgets-mediasearch-input-placeholder": "মিডিয়ার জন্য অনুসন্ধান",
+       "mw-widgets-mediasearch-noresults": "কোনো ফলাফল পাওয়া যায়নি।",
        "mw-widgets-titleinput-description-new-page": "পাতা এখনো বিদ্যমান নয়",
        "mw-widgets-titleinput-description-redirect": "$1-এ পুনঃনির্দেশিত",
        "mw-widgets-categoryselector-add-category-placeholder": "একটি বিষয়শ্রেণী যোগ করুন...",
index 7fe1f34..08a7965 100644 (file)
        "history_short": "گزارش تاریخی",
        "printableversion": "نوسقئ پئلا ڤابیدٙئنی",
        "permalink": "لینک دایمی",
+       "print": "چاپ گرهڌن",
        "view": "ديئن",
        "view-foreign": "مئن $1 نه بوینین",
        "edit": "اصلاح",
+       "create": "راس كردن",
+       "create-local": "یأ توزی ڤولات نشيني إزافإ کونين",
        "editthispage": "اصلاح ای صفحه",
        "delete": "حذف",
        "protect": "حفاظت وحمایت",
        "toc": "محتواها",
        "showtoc": "نمایش",
        "hidetoc": "قایم",
+       "confirmable-yes": "هرإ",
+       "confirmable-no": "نأ",
        "thisisdeleted": "دیدن یا اعاده $1?",
        "viewdeleted": "دیدن$1?",
        "restorelink": "{{PLURAL:$1|پاک کردن یه اصلاح|$1 پاک کردن اصلاحات}}",
        "nstab-template": "قالب یا الگو",
        "nstab-category": "دسته",
        "mainpage-nstab": "سأرآسوٙنە",
+       "error": "خطا",
        "badtitle": "عنوان بد",
        "badtitletext": "عنوان درخواستی نامعتبر، خالی، یا عنوانی بین زبانی یا بین‌ویکی‌ای با پیوند نادرسته\nو ممکنه دارای یک یا چند کاراکتر بوه که در عنوان مربوط نوا زش استفاده کنین",
        "viewsource": "مشاهده منبع",
        "userlogin-yourname-ph": "نوم کاریاريتونأ بزنين",
        "yourpassword": "رمز:",
        "userlogin-yourpassword": "رازينإ گوڤأرتن",
+       "userlogin-remembermypassword": "مۈنإ مإن سامۈنإ ڤاڌار",
        "login": "اویدن به سیستم",
        "nav-login-createaccount": "اویدن به سیستم",
        "userlogin": "اویدن به سیستم / درست کردن حساب کاربری",
+       "userloginnocreate": "ڤامین اوڤیڌن",
        "logout": "رهدن زه سیستم",
        "userlogout": "رهدن زه سیستم",
+       "userlogin-noaccount": "یأ هساڤ کارياري دارين؟",
+       "userlogin-joinproject": "أندوم ديارگأ {{SITENAME}} ڤابۈین",
        "nologin": "آیا ایسا حساب کاربری ندارین? '''$1'''.",
        "nologinlink": "درست کردن یه حساب کاربری",
        "createaccount": "درست کردن حساب کاربری",
        "gotaccount": "آیا تقریبا یه حساب کاربری دارین? '''$1'''.",
        "gotaccountlink": "اویدن به",
+       "userlogin-helplink2": "هومياري کردن سي ڤامإن أڤوڌن",
        "loginsuccesstitle": "اویدن با بخت وتوفیق به سیستم",
        "loginsuccess": "''' ایسا اویدن به داخل سایت {{SITENAME}} بعنوان \"$1\".'''",
        "nosuchuser": "کاربری به ای نام وجود نداره \"$1\".\nحروف نام را چک کنین, یا [[Special:CreateAccount|درست کنین یه حساب کاربری تازه]].",
        "eauthentsent": "یه ایمیل سی تایید آدرس ایمیل به آدرس مورنظر ارسال وابید. قبل زه یو که ایمیل دیگری قابل ارسال به این آدرس بوه، وا دستورهایی که در آن ایمیل اویده را جهت تأیید ای مساله که ای آدرس مال ایسانه اجرا کنین.",
        "loginlanguagelabel": "زۈن:$1",
        "pt-login": "ڤامین اوڤیڌن",
+       "pt-login-button": "ڤامین اوڤیڌن",
        "pt-createaccount": "راسد کردن هساڤ کارياري",
        "pt-userlogout": "ز سامۈنإ درأڤوڌن",
        "retypenew": "تایپ دوباره رمز:",
+       "passwordreset": "ز نۉ داڌن رازينإ گوأرتن",
        "bold_sample": "متن گپ نما",
        "bold_tip": "متن گپ نما",
        "italic_sample": "متن شکسته",
        "blockedtext": " \"'''دسترسی نام کاربری یا نشانی اینترنتی ایسا بسته وابیده.'''\nای کار توسط $1 انجام شده‌است.\nدلیلی که گده اینه: $2''\n* آغاز قطع دسترسی: $8\n* زمان اتمام ای قطع دسترسی: $6\n* کاربری که قطع دسترسی‌اش در نظر بیده: $7\nایساترین با $1 یا یکی از [[{{MediaWiki:Grouppage-sysop}}|مدیران]] تماس بگیرین و در ای باره صحبت کنین.\nتوجه کنین که ایسا نترین زه امکان «ارسال پست الکترونیکی به ای کاربر» استفاده کنین مگر این که نشانی پست الکترونیکی معتبری در [[Special:Preferences|اولویتهای کاربری]]خود ثبت کرده بوین.\nنشانی IP ایسا $3 و شماره قطع دسترسی ایسا $5 است. لطفاً ای شماره‌ها را در همه کاوشهاتون ذکر کنین.\nایسا ترین با $1 یا یکی دیه زه [[{{MediaWiki:Grouppage-sysop}}|مدیران]] تماس بگیرین، تا در باره ای قطع دسترسی صحبت کنین.\nدقت کنین که سی ارسال پست الکترونیکی در ویکی، وا پست الکترونیکی خود را زه طریق صفحه[[Special:Preferences|تنظیمات]] فعال کرده بوین، و نیز، وا امکان استفاده زه ای ویژگی سی ایساقطع نبوه.\nنشانی اینترنتی الان ایسا $3 است و شماره قطع دسترسی $5 است.\nلطفاً ای شماره را در هر درخواستی که در ای مورد مطرح اکنین ذکر کنین",
        "loginreqlink": "ڤامین اوڤیڌن",
        "newarticle": "(تازه)",
-       "newarticletext": "ایسا لینکی را دنبال کردین و به صفحه‌ای رسیدین که هنی درست نوابیده.\nسی ایجاد صفحه، داخل مستطیل زیر شروع به تایپ کنین (سی اطلاعات بیشتر به [[{{ns:project}}:راهنما|صفحه راهنما] برین).\nایر اشتباهی ایچو اویدین دکمه «برگشت» مرورگرتو را بزنین.",
-       "noarticletext": " Ø§Ù\84اÙ\86 Ø§Û\8c ØµÙ\81Ø­Ù\87 Ù\85تÙ\86Û\8c Ù\86دارÙ\87Ø\8c Ø§Û\8cسا ØªØ±Û\8cÙ\86 [[Special:Search/{{PAGENAME}}عÙ\86Ù\88اÙ\86 Ø§Û\8c ØµÙ\81Ø­Ù\87 Ø±Ø§ Ø¯Ø± ØµÙ\81Ø­Ù\87â\80\8cÙ\87اÛ\8c Ø¯Û\8cگر Ø¬Ø³ØªØ¬Ù\88 Ú©Ù\86Û\8cÙ\86]] Û\8cا [{{fullurl:{{FULLPAGENAME}}|action=edit}} Ø§Û\8c ØµÙ\81Ø­Ù\87 Ø±Ø§ Ø§ØµÙ\84اح Ú©Ù\86Û\8cن].",
+       "newarticletext": "ايسا ز دين یأ هومپیڤأندي هڌين کإ نيڌس. سي رأڤأندياري بألگإ شورۈ کونين مإن اي جأڤإ دإڤۈني بنڤيسين(سي دونسدن بیشدر سإیل [$1]کونین).\nأر ايسا سي إشتڤاکاري ايچونين، دوگمإ رأهڌن ڤاپوشد نأ بپۈرنين.",
+       "noarticletext": " Ø§Ù\8aسإ Ø§Ù\8a Ø¨Ø£Ù\84گإ Ù\86إڤشدإÛ\8cÙ\8a Ù\86ارإØ\8c Ø§Ù\8aسا ØªØ¥Ø±Ù\8aÙ\86 [[Special:Search/{{PAGENAME}}داسÛ\88Ù\86 Ø§Ù\8a Ø¨Ø£Ù\84گإ Ù\86Ø¥ Ù\85Ø¥Ù\86 Ø¨Ø£Ù\84گإآ Ø¯Ù\8aأرÙ\8a Ù¾Û\8c Ø¬Û\88رÙ\8a Ú©Ù\88Ù\86Ù\8aÙ\86]] Û\8cا [{{fullurl:{{FULLPAGENAME}}|action=edit}} Ø§Ù\8a Ø¨Ø£Ù\84گإ Ù\86Ø£ Ú¤Ù\8aراÛ\8cشد Ú©Ù\88Ù\86Ù\8aن].",
        "previewnote": "'''ای فقط یه پیش نمایشه;\nتغییراتی که ایسا دادین هنی ضبط نوابیده!'''",
        "editing": "درحال اصلاح $1",
+       "creating": "راسد کردن $1",
        "editingsection": "درحال اصلاح $1 (قسمت)",
        "copyrightwarning": "لطفاً دقت کنین که درنظر گریده ابوه که همه شراکتهای ایسا  {{SITENAME}} تحت «$2» منتشر ابون ).\n\n\n(سی دیدن  جزئیات بیشتر به $1 برین\n\nایر نه خوین نوشته‌هاتو بی‌رحمانه اصلاح بوه و به دلخواه ارسال بوه، ایچو نفرستن.<br />\nدرضمن ایسادارین به ایما قول ادین که خودتو یونه نوشتین یا هونه زه یک منبع آزاد با مالکیت عمومی یا مثل هو ورداشتین. '''کارهای دارای کارهای دارای حق کپی رایت را بی‌اجازه نفرستین!'''',",
        "templatesused": "{{PLURAL:$1|چوٙأ|چوٙأیل}} ب کار گرهڌأ ڤابيڌإ مإن اي بألگأ:",
        "histfirst": "کهنه ترین",
        "histlast": "تازه ترین",
        "history-feed-item-nocomment": "$1 در $2",
+       "rev-delundel": "آلشد هال و بال ديإن",
        "history-title": "دڤارتإ دیئن ڤيرگار $1",
        "lineno": "سطر $1:",
        "compareselectedversions": "مقایسه نسخه‌های انتخاب‌ وابیده",
        "recentchanges-feed-description": "ردیابی آخرین تغییرات  ویکی در ای خورد",
        "recentchanges-label-newpage": "ای ويرايشت يه بلگه تازه راس كرده",
        "recentchanges-label-minor": "یو یه ويرايشت کوچيره",
+       "recentchanges-label-bot": "اي ڤيرایشد نأ یأ بوت أنجوم داڌإ",
        "recentchanges-label-unpatrolled": "ای ويرايشت هنی تيه واداشت نوابيه",
        "recentchanges-legend-heading": "<strong>میراث:</strong>",
        "rcnotefrom": "در زیر تغییرات زه تاریخ <b>$2</b> آمده‌اند (تا <b>$1</b> مورد نشو داده ابوه).",
        "movethispage": "جابجایی ای صفحه",
        "pager-older-n": "{{PLURAL:$1|گپسالتر 1|گپسالتر $1}}",
        "booksources": "منابع کتاب",
+       "booksources-search-legend": "پی جۈري سي سرچشمإیل کتاڤ",
        "booksources-search": "پی جۈري",
        "specialloguserlabel": "کاربر:",
        "speciallogtitlelabel": "عنوان:",
        "whatlinkshere-next": "{{PLURAL:$1|بعدی |مورد بعدی $1}}",
        "whatlinkshere-links": "← لینکها",
        "whatlinkshere-hideredirs": "$1 redirects",
+       "whatlinkshere-hidetrans": "ڤارو گونجایشدا $1",
        "whatlinkshere-hidelinks": "هوم پیڤأندا $1",
        "whatlinkshere-filters": "فيلترا",
        "blockip": "بستن کاربر",
        "thumbnail_error": "خطا سی درست کردن ناخن دانه: $1",
        "importlogpage": "داخل نمایه کردن",
        "tooltip-pt-userpage": "{{GENDER:|بألگأ کارياريتۈن}} بألگأ",
-       "tooltip-pt-mytalk": "صفحه صحبت مو",
+       "tooltip-pt-mytalk": "{{GENDER:|بألگأ چأک چنإ کارياري مو}}",
        "tooltip-pt-preferences": "{{GENDER:|ايسا}} أصل کاريا",
        "tooltip-pt-watchlist": "لیست صفحه‌هایی که ایسا تغییرات هونو  دنبال اکنین",
-       "tooltip-pt-mycontris": "لیست شراکتهای مو",
+       "tooltip-pt-mycontris": "یأ نومگأ ز هومياري {{GENDER:|ايسا}}",
        "tooltip-pt-login": "توصیه ابوه که به سیستم داخل بوین اما اجباری نه.",
        "tooltip-pt-logout": "رهدن زه سیستم",
        "tooltip-ca-talk": "صحبت درباره صفحه محتوا",
        "tooltip-t-recentchangeslinked": "تغییرات آخری  درصفحاتی که لینک شده اند به ای صفحه",
        "tooltip-feed-rss": "تغذیه آراس اس برای ای صفحه",
        "tooltip-feed-atom": "تغذیه کوچیک ترین جزء  ای صفحه",
-       "tooltip-t-contributions": "دیدن فهرست مشارکت کننده ها ومقاله دهنده های ای صفحه",
+       "tooltip-t-contributions": "ديإن نومگأآیي کإ {{GENDER:$1|اي کاریار}} هومياريسۈن کردإ",
        "tooltip-t-emailuser": "ارسال یه امیل به ای کاربر",
        "tooltip-t-upload": "آپلودکردن فایلها",
        "tooltip-t-specialpages": "فهرست همه صفحات مخصوص",
        "exif-orientation": "سرچشمأ",
        "exif-xresolution": "گپ نما کردن اوفقي",
        "exif-yresolution": "گپ نما کردن ز وارو",
+       "exif-datetime": "آلشد داین گات و مجال جانيا",
        "exif-make": "سازیار دیربین",
        "exif-model": "مودل ديربين",
        "exif-software": "نرم افزار ب کارگرهڌني",
        "tag-filter": "[[Special:سرديسا|سرديس]] فيلتر:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|سرديس|سرديسا}}]]: $2)",
        "logentry-delete-delete": "$1 بألگأ {{GENDER:$2|پاکسا ڤابيأ}} $3",
+       "logentry-move-move": "$1 {{GENDER:$2|جا ب جا کردإ}} بألگأ $3 نأ سي $4",
        "logentry-upload-upload": "$1 {{GENDER:$2|سوڤار کرده}} $3",
        "searchsuggest-search": "جستن {{SITENAME}}"
 }
index 8962400..dddc4f1 100644 (file)
        "passwordreset-emaildisabled": "Ne c'haller ket ober gant posteloù er wiki-mañ.",
        "passwordreset-username": "Anv implijer :",
        "passwordreset-domain": "Domani :",
-       "passwordreset-capture": "Gwelet ar postel ?",
-       "passwordreset-capture-help": "Ma askit al logell-mañ e vo diskouezet deoc'h ar postel (gant ar ger-tremen da c'hortoz) war un dro pa vo kaset d'an implijer.",
        "passwordreset-email": "Postel :",
        "passwordreset-emailtitle": "Titouroù kont war {{SITENAME}}",
        "passwordreset-emailtext-ip": "Unan bennak (c'hwi moarvat gant ar chomlec'h IP $1) en deus goulennet ma vefe degaset soñj dezhañ eus titouroù e gont evit {{SITENAME}} ($4). Emañ liammet {{PLURAL:$3|ar gont implijer|ar c'hontoù implijer}} da-heul gant ar chomlec'h postel-mañ :\n\n$2\n\nMont a raio da get {{PLURAL:$3|ar ger-tremen da c'hortoz|ar gerioù-tremen da c'hortoz}} a-benn {{PLURAL:$5|un devezh|$5 deiz}}.\nMat e vefe deoc'h kevreañ ha dibab ur ger-tremen nevez bremañ. Mard eo bet goulennet kement-se gant unan bennak all pe m'hoc'h eus soñj eus ho ker-tremen orin ha mar ne fell ket deoc'h e cheñch ken, na daolit ket evezh ouzh ar gemennadenn-mañ ha dalc'hit d'ober gant ho ker-tremen kozh.",
        "userrights-reason": "Abeg :",
        "userrights-no-interwiki": "N'oc'h ket aotreet da gemmañ ar gwirioù implijer war wikioù all.",
        "userrights-nodatabase": "N'eus ket eus an diaz titouroù $1 pe n'eo ket lec'hel.",
-       "userrights-nologin": "Ret eo deoc'h [[Special:UserLogin|bezañ enrollet]] gant ur gont merour a-benn reiñ gwirioù implijer.",
-       "userrights-notallowed": "N'oc'h ket aotreet da ouzhpennañ na da lemel gwirioù an implijerien.",
        "userrights-changeable-col": "Ar strolladoù a c'hallit cheñch",
        "userrights-unchangeable-col": "Ar strolladoù n'hallit ket cheñch",
        "userrights-conflict": "Bec'h zo abalamour da gemmoù e gwirioù an implijerien. Adwelit an traoù, mar plij, ha kadarnait ho kemmoù.",
-       "userrights-removed-self": "Lamet ho peus ho kwirioù deoc'h-c'hwi. Dre se ne c'hallit ket ken mont d'ar bajenn-mañ.",
        "group": "Strollad :",
        "group-user": "Implijerien",
        "group-autoconfirmed": "Implijerien bet kadarnaet ent emgefre",
        "right-siteadmin": "Prennañ ha dibrennañ ar bank-titouroù",
        "right-override-export-depth": "Ezporzhiañ ar pajennoù en ur lakaat e-barzh ar pajennoù liammet betek un donder a 5 live",
        "right-sendemail": "Kas ur postel d'an implijerien all",
-       "right-passwordreset": "Gwelet ar posteloù assevel gerioù-tremen",
        "grant-group-email": "Kas ur postel",
        "grant-createaccount": "Krouiñ kontoù",
        "grant-createeditmovepage": "Krouiñ, aozañ ha dilec'hiañ pajennoù",
        "spam_reverting": "Distreiñ d'ar stumm diwezhañ hep liamm davet $1",
        "spam_blanking": "Diverkañ an holl stummoù enno liammoù davet $1",
        "spam_deleting": "An holl stummoù enno liammoù war-zu $1, o tiverkañ",
-       "simpleantispam-label": "Taol gwiriañ eneb-strob.\n<trong>Arabat<strong> merkañ tra pe dra amañ !",
+       "simpleantispam-label": "Taol gwiriañ eneb-strob.\n<strong>Arabat</strong> merkañ tra pe dra amañ !",
        "pageinfo-title": "Titouroù evit \"$1\"",
        "pageinfo-not-current": "Hon digarezit, ne c'haller ket reiñ an titouroù-mañ evit an adweloù kozh.",
        "pageinfo-header-basic": "Titouroù diazez",
index 10918da..2a3c356 100644 (file)
        "userpage-userdoesnotexist": "Korisnički račun \"<nowiki>$1</nowiki>\" nije registrovan.\nMolimo provjerite da li želite napraviti/izmijeniti ovu stranicu.",
        "userpage-userdoesnotexist-view": "Korisnički račun \"$1\" nije registrovan.",
        "blocked-notice-logextract": "Ovaj korisnik je trenutno blokiran.\nPosljednje stavke zapisnika blokiranja možete pogledati ispod:",
-       "clearyourcache": "'''Pažnja:''' Nakon što sačuvate izmjene, morate \"osvježiti\" keš memoriju vašeg pretraživača da bi ste vidjeli nova podešenja.'''\n*'''Firefox / Safari:''' držite ''Shift'' tipku i kliknite na ''Reload'' dugme ili pritisnite ''Ctrl-F5'' ili ''Ctrl-R'' (''⌘-R'' na Macu)\n*'''Google Chrome:''' pritisnite ''Ctrl-Shift-R'' (''⌘-Shift-R'' na Macu)\n*'''Internet Explorer:''' držite tipku ''Ctrl'' i kliknite na ''Refresh'' ili pritisnite ''Ctrl-F5''\n*'''Opera:''' očistite \"keš\" preko izbornika ''Tools → Preferences''",
+       "clearyourcache": "<strong>Napomena:</strong> Nakon što sačuvate izmjene, možda ćete morati osvježiti keš preglednika da biste vidjeli izmjene.\n* <strong>Firefox / Safari:</strong> Držite <em>Shift</em> i kliknite na <em>Reload</em> ili pritisnite <em>Ctrl-F5</em> ili <em>Ctrl-R</em> (<em>⌘-R</em> na Macu)\n* <strong>Google Chrome:</strong> Pritisnite <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> na Macu)\n* <strong>Internet Explorer:</strong> Držite <em>Ctrl</em> i kliknite na <em>Refresh</em> ili pritisnite <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Idite na <em>Menu → Settings</em> (<em>Opera → Preferences</em> na Macu) i zatim <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "'''Pažnja:''' Koristite dugme \"{{int:showpreview}}\" da testirate svoj novi CSS prije nego što sačuvate.",
        "userjsyoucanpreview": "'''Pažnja:''' Koristite dugme \"{{int:showpreview}}\" da testirate svoj novi JavaScript prije nego što sačuvate.",
        "usercsspreview": "'''Zapamtite ovo je samo izgled Vašeg CSS-a.'''\n'''Ovaj pregled još uvijek nije sačuvan!'''",
        "htmlform-user-not-exists": "<strong>$1</strong> ne postoji.",
        "htmlform-user-not-valid": "<strong>$1</strong> nije ispravno korisničko ime.",
        "logentry-delete-delete": "$1 {{GENDER:$2|obrisao|obrisala}} je stranicu $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|obrisao|obrisala}} je preusmjerenje $3 prepisivanjem",
        "logentry-delete-restore": "$1 {{GENDER:$2|vratio|vratila}} je stranicu $3",
        "logentry-delete-event": "$1 {{GENDER:$2|promijenio|promijenila}} je vidljivost {{PLURAL:$5|unosa|$5 unosa}} u zapisniku na $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|promijenio|promijenila}} je vidljivost {{PLURAL:$5|izmjene|$5 izmjene|$5 izmjena}} na stranici $3: $4",
index 2cce206..5f7ca63 100644 (file)
        "views": "Vistes",
        "toolbox": "Eines",
        "tool-link-userrights": "Canvia els grups de l'{{GENDER:$1|usuari|usuària}}",
+       "tool-link-userrights-readonly": "Mostra els grups d'{{GENDER:$1|usuari}}",
        "tool-link-emailuser": "Envia un missatge electrònic a l'{{GENDER:$1|usuari|usuària}}",
        "userpage": "Visualitza la pàgina d'usuari",
        "projectpage": "Visualitza la pàgina del projecte",
        "invalid-content-data": "Dades de contingut no vàlides",
        "content-not-allowed-here": "No és permès el contingut \"$1\" a la pàgina [[$2]]",
        "editwarning-warning": "Si sortiu d'aquesta pàgina, perdreu tots els canvis que hàgiu fet.\nSi teniu un compte d'usuari, podeu eliminar aquest avís en la secció «{{int:prefs-editing}}» de les vostres preferències.",
+       "editpage-invalidcontentmodel-title": "Model de contingut no permès",
+       "editpage-invalidcontentmodel-text": "El model de contingut «$1» no és permès.",
        "editpage-notsupportedcontentformat-title": "No s'admet el format del contingut",
        "editpage-notsupportedcontentformat-text": "No s'admet el format del contingut $1 pel model de contingut $2.",
        "content-model-wikitext": "wikitext",
        "mergehistory-fail-bad-timestamp": "La marca horària no és vàlida.",
        "mergehistory-fail-invalid-source": "La pàgina font no és vàlida.",
        "mergehistory-fail-invalid-dest": "La pàgina de destinació no és vàlida.",
+       "mergehistory-fail-no-change": "La fusió d'historial no ha fusionat cap revisió. Comproveu de nou la pàgina i els paràmetres temporals.",
        "mergehistory-fail-permission": "No hi ha permisos suficients per fusionar l'historial.",
        "mergehistory-fail-self-merge": "Les pàgines d'origen i de destinació no poden ser la mateixa",
        "mergehistory-fail-toobig": "No s'ha pogut fer la fusió de l'historial perquè es mourien més del límit de $1 {{PLURAL:$1|revisió|revisions}}.",
        "search-external": "Cerca externa",
        "searchdisabled": "La cerca dins el projecte {{SITENAME}} està inhabilitada. Mentrestant, podeu cercar a través de Google, però tingueu en compte que la seua base de dades no estarà actualitzada.",
        "search-error": "S'ha produït un error durant la recerca: $1",
+       "search-warning": "S'ha produït un avís en cercar: $1",
        "preferences": "Preferències",
        "mypreferences": "Preferències",
        "prefs-edits": "Nombre d'edicions:",
        "prefs-help-recentchangescount": "Inclou els canvis recents, els historials de pàgines i els registres.",
        "prefs-help-watchlist-token2": "Aquesta és la clau secreta pel canal de continguts de la vostra llista de seguiment.\nQualsevol que la conegui podria llegir la vostra llista de seguiment, així que no la compartiu.\n[[Special:ResetTokens|Cliqueu aquí si voleu restaurar-la]].",
        "savedprefs": "S’han desat les vostres preferències.",
-       "savedrights": "S'han desat els permisos d'usuari de {{GENDER:$1|$1}}.",
+       "savedrights": "S'han desat els grups d'usuari de {{GENDER:$1|$1}}.",
        "timezonelegend": "Fus horari:",
        "localtime": "Hora local:",
        "timezoneuseserverdefault": "Utilitza l'hora per defecte del wiki ($1)",
        "prefswarning-warning": "Heu fet canvis a les preferències que encara no s'han desat.\nSi abandoneu la pàgina sense fer clic a «$1», les preferències no s'actualitzaran.",
        "prefs-tabs-navigation-hint": "Consell: Podeu utilitzar les tecles de cursor de dreta i esquerra per a navegar entre les pestanyes.",
        "userrights": "Gestió dels permisos d’usuari",
-       "userrights-lookup-user": "Gestiona els grups d'usuari",
+       "userrights-lookup-user": "Seleccioneu un usuari",
        "userrights-user-editname": "Introduïu un nom d'usuari:",
-       "editusergroup": "Edita els grups d'{{GENDER:$1|usuari}}",
+       "editusergroup": "Carrega els grups d'usuari",
        "editinguser": "Modificació dels permisos de {{GENDER:$1|l'usuari|la usuària}} <strong>[[User:$1|$1]]</strong>$2",
        "userrights-editusergroup": "Edita els grups d'usuaris",
+       "userrights-viewusergroup": "Mostra els grups d'usuari",
        "saveusergroups": "Desa els grups d'{{GENDER:$1|usuari}}",
        "userrights-groupsmember": "Membre de:",
        "userrights-groupsmember-auto": "Membre implícit de:",
        "action-upload_by_url": "carregar aquest fitxer des d'una adreça URL",
        "action-writeapi": "fer servir l'API d'escriptura",
        "action-delete": "esborrar aquesta pàgina",
-       "action-deleterevision": "esborrar aquesta revisió",
-       "action-deletedhistory": "visualitzar l'historial esborrat d'aquesta pàgina",
+       "action-deleterevision": "suprimeix les revisions",
+       "action-deletelogentry": "suprimeix les entrades de registre",
+       "action-deletedhistory": "mostra l'historial esborrat d'una pàgina",
        "action-browsearchive": "cercar pàgines esborrades",
-       "action-undelete": "recuperar aquesta pàgina",
-       "action-suppressrevision": "revisar i recuperar aquesta revisió oculta",
+       "action-undelete": "restaura les pàgines",
+       "action-suppressrevision": "revisa i restaura les revisions ocultes",
        "action-suppressionlog": "visualitzar aquest registre privat",
        "action-block": "blocar aquest usuari perquè no pugui editar",
        "action-protect": "canviar els nivells de protecció d'aquesta pàgina",
        "action-userrights-interwiki": "modificar permisos d'usuari en altres wikis",
        "action-siteadmin": "bloquejar o desbloquejar la base de dades",
        "action-sendemail": "enviar missatges de correu",
+       "action-editmyoptions": "modifiqueu les vostres preferències",
        "action-editmywatchlist": "edita la llista de seguiment",
        "action-viewmywatchlist": "mostra la llista de seguiment",
        "action-viewmyprivateinfo": "mostra la informació personal",
        "fileexists-forbidden": "Ja hi existeix un fitxer amb aquest nom i no es pot sobreescriure.\nSi us plau, torneu enrere i carregueu aquest fitxer sota un altre nom. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Ja hi ha un fitxer amb aquest nom en el fons comú de fitxers.\nSi encara voleu pujar el fitxer, torneu enrere i pugeu-ne una còpia amb un altre nom. [[File:$1|thumb|center|$1]]",
        "fileexists-no-change": "La càrrega és un duplicat exacte de la versió actual de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "La càrrega és un duplicat exacte {{PLURAL:$2|d'una versió antiga|de versions antigues}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Aquest fitxer és un duplicat {{PLURAL:$1|del fitxer |dels següents fitxers:}}",
        "file-deleted-duplicate": "S'ha suprimit anteriorment un fitxer idèntic a aquest ([[:$1]]). Hauríeu de comprovar el registre de supressions del fitxer abans de tornar-lo a carregar.",
        "file-deleted-duplicate-notitle": "Un fitxer idèntic a aquest fitxer havia estat suprimit abans, i també el títol. Hauríeu de demanar a algú que pugui veure les dades suprimides del fitxer que revisi la situació abans de procedir a tornar a carregar-lo.",
        "filerevert-submit": "Reverteix",
        "filerevert-success": "S'ha revertit '''[[Media:$1|$1]]''' a la [$4 versió de $3, $2].",
        "filerevert-badversion": "No hi ha cap versió local anterior d'aquest fitxer amb la marca horària que es proporciona.",
+       "filerevert-identical": "La versió actual del fitxer ja és idèntica a la seleccionada.",
        "filedelete": "Suprimeix $1",
        "filedelete-legend": "Suprimeix el fitxer",
        "filedelete-intro": "Esteu eliminant el fitxer '''[[Media:$1|$1]]''' juntament amb el seu historial.",
        "apisandbox": "Pàgina de proves de l'API",
        "apisandbox-api-disabled": "L'API està desactivada en aquest lloc.",
        "apisandbox-intro": "Utilitzeu aquesta pàgina per experimentar amb l'<nowiki />'''API de web service de MediaWiki'''.\nVisiteu [https://www.mediawiki.org/wiki/API:Main_page la documentació de l'API] per a més informació sobre l'ús de l'API. Exemple: [https://www.mediawiki.org/wiki/API#A_simple_example recuperar el contingut d'una Pàgina Principal]. Seleccioneu una acció per veure més exemples.\n\nTingueu en compte que, encara que això és una pàgina de proves, les accions que feu en aquesta pàgina poden modificar la wiki.",
+       "apisandbox-fullscreen": "Expandeix el plafó",
+       "apisandbox-fullscreen-tooltip": "Expandeix el plafó de l'entorn de proves per tal que ocupi la finestra del navegador.",
        "apisandbox-unfullscreen": "Mostra la pàgina",
+       "apisandbox-unfullscreen-tooltip": "Redueix el plafó de l'entorn de proves per tal que els enllaços de navegació de MediaWiki siguin disponibles.",
        "apisandbox-submit": "Fes sol·licitud",
        "apisandbox-reset": "Neteja",
        "apisandbox-retry": "Torna a provar",
        "apisandbox-submit-invalid-fields-title": "Alguns camps no són vàlids",
        "apisandbox-results": "Resultats",
        "apisandbox-sending-request": "S'està enviant una sol·licitud API...",
+       "apisandbox-loading-results": "S'estan reben els resultats de l'API...",
        "apisandbox-request-url-label": "Sol·licita URL:",
        "apisandbox-request-time": "Temps de sol·licitud: {{PLURAL:$1|$1 ms}}",
        "apisandbox-continue": "Continua",
        "apisandbox-continue-clear": "Neteja",
+       "apisandbox-multivalue-all-namespaces": "$1 (tots els espais de noms)",
+       "apisandbox-multivalue-all-values": "$1 (tots els valors)",
        "booksources": "Obres de referència",
        "booksources-search-legend": "Cerca fonts de llibres",
        "booksources-isbn": "ISBN:",
        "activeusers-intro": "Aquí hi ha una llista d'usuaris que han tingut algun tipus d'activitat en {{PLURAL:$1|el darrer dia|els darrers $1 dies}}.",
        "activeusers-count": "$1 {{PLURAL:$1|acció|accions}} en {{PLURAL:$3|el darrer dia|els $3 darrers dies}}",
        "activeusers-from": "Mostra els usuaris començant per:",
+       "activeusers-excludegroups": "Exclou els usuaris que pertanyen als grups:",
        "activeusers-noresult": "No s'han trobat usuaris.",
        "activeusers-submit": "Mostra els usuaris actius",
        "listgrouprights": "Drets dels grups d'usuaris",
        "patrol-log-header": "Això és un registre de les revisions patrullades.",
        "log-show-hide-patrol": "$1 el registre de patrulla",
        "log-show-hide-tag": "$1 el registre d’etiquetes",
+       "confirm-markpatrolled-button": "D'acord",
        "deletedrevision": "S'ha eliminat la revisió antiga $1.",
        "filedeleteerror-short": "Error en suprimir el fitxer: $1",
        "filedeleteerror-long": "S'han produït errors en suprimir el fitxer:\n\n$1",
        "tags-actions-header": "Accions",
        "tags-active-yes": "Sí",
        "tags-active-no": "No",
-       "tags-source-extension": "Definit per una extensió",
+       "tags-source-extension": "Definit pel programari",
        "tags-source-manual": "Aplicat manualment per usuaris i bots",
        "tags-source-none": "Ja no està en ús",
        "tags-edit": "modifica",
        "tags-deactivate": "desactiva",
        "tags-hitcount": "$1 {{PLURAL:$1|canvi|canvis}}",
        "tags-manage-no-permission": "No teniu permisos per administrar etiquetes de canvi",
+       "tags-manage-blocked": "No podeu canviar etiquetes mentre esteu {{GENDER:$1|blocat|blocada}}.",
        "tags-create-heading": "Crea una nova etiqueta",
        "tags-create-explanation": "Per defecte, les etiquetes acabades de crear estaran disponibles per usuaris i bots",
        "tags-create-tag-name": "Nom de l'etiqueta:",
        "api-error-autoblocked": "S'ha blocat la vostra IP automàticament perquè la va utilitzar un usuari blocat.",
        "api-error-badaccess-groups": "No teniu permís per a carregar fitxers en aquest wiki.",
        "api-error-badtoken": "Error intern: argument incorrecte.",
+       "api-error-blocked": "Heu estat blocat per poder editar.",
        "api-error-copyuploaddisabled": "Les càrregues via URL estan desactivades en aquest servidor.",
        "api-error-duplicate": "Ja hi ha {{PLURAL:$1|un altre fitxer|altres fitxers}} en aquest lloc web amb el mateix contingut.",
        "api-error-duplicate-archive": "Aquí ja hi ha hagut {{PLURAL:$1|un altre fitxer|altres fitxers}} amb el mateix contingut, i {{PLURAL:$1|va ser esborrat|varen ser esborrats}}.",
        "mw-widgets-dateinput-no-date": "No s'ha seleccionat cap data",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Cerca de fitxers multimèdia",
+       "mw-widgets-mediasearch-noresults": "No s'han trobat resultats.",
        "mw-widgets-titleinput-description-new-page": "la pàgina no existeix encara",
        "mw-widgets-titleinput-description-redirect": "redirigeix a $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Afegeix una categoria...",
        "sessionmanager-tie": "No es poden combinar diferents tipus de sol·licituds d'autenticació: $1.",
        "sessionprovider-generic": "$1 sessions",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sessions basades en galetes",
        "sessionprovider-nocookies": "Pot ser que les galetes estiguin inhabilitades. Assegureu-vos que teniu les galetes habilitades i inicieu de nou.",
        "randomrootpage": "Pàgina arrel aleatòria",
        "log-action-filter-block": "Tipus de blocatge:",
+       "log-action-filter-contentmodel": "Tipus de modificació del model de contingut:",
        "log-action-filter-delete": "Tipus de supressió:",
        "log-action-filter-import": "Tipus d'importació:",
        "log-action-filter-managetags": "Tipus d'acció de gestió d'etiquetes:",
        "log-action-filter-block-block": "Bloca",
        "log-action-filter-block-reblock": "Bloca la modificació",
        "log-action-filter-block-unblock": "Desbloca",
+       "log-action-filter-contentmodel-change": "Canvi del model de contingut",
        "log-action-filter-delete-delete": "Supressió de pàgines",
+       "log-action-filter-delete-delete_redir": "Sobreescriptura de la redirecció",
        "log-action-filter-delete-restore": "Restauració de pàgines",
        "log-action-filter-delete-event": "Registre de supressió",
        "log-action-filter-delete-revision": "Supressió de revisions",
        "log-action-filter-managetags-create": "Creació de l'etiqueta",
        "log-action-filter-managetags-delete": "Supressió de l'etiqueta",
        "log-action-filter-managetags-activate": "Activació de l'etiqueta",
+       "log-action-filter-managetags-deactivate": "Desactivació d'etiquetes",
+       "log-action-filter-newusers-create2": "Creació per usuari registrat",
        "log-action-filter-newusers-autocreate": "Creació automàtica",
        "log-action-filter-patrol-patrol": "Patrullatge manual",
        "log-action-filter-patrol-autopatrol": "Patrullatge automàtic",
        "log-action-filter-protect-protect": "Protecció",
        "log-action-filter-protect-modify": "Modificació de la protecció",
        "log-action-filter-protect-unprotect": "Desprotecció",
+       "log-action-filter-protect-move_prot": "Protecció de reanomenaments",
        "log-action-filter-rights-rights": "Canvi manual",
        "log-action-filter-rights-autopromote": "Canvi automàtic",
        "log-action-filter-suppress-event": "Supressió de registres",
        "authmanager-authn-autocreate-failed": "Ha fallat la creació automàtica d'un compte local: $1",
        "authmanager-create-disabled": "S'ha inhabilitat la creació de comptes.",
        "authmanager-create-from-login": "Per crear un compte, ompliu els camps de sota.",
+       "authmanager-authplugin-setpass-failed-title": "El canvi de contrasenya ha fallat",
        "authmanager-authplugin-setpass-bad-domain": "Domini invàlid.",
+       "authmanager-userdoesnotexist": "El compte d'usuari «$1» no està registrat.",
        "authmanager-retype-help": "Contrasenya de nou per confirmar",
        "authmanager-email-label": "Correu electrònic",
        "authmanager-email-help": "Adreça electrònica",
        "authmanager-provider-password": "Autenticació basada en contrasenya",
        "authmanager-provider-password-domain": "Autenticació basada en contrasenya i en domini",
        "authmanager-provider-temporarypassword": "Contrasenya temporal",
+       "authprovider-confirmlink-request-label": "Comptes que caldrien enllaçar-se",
+       "authprovider-confirmlink-success-line": "$1: s'ha enllaçat satisfactòriament.",
        "authprovider-resetpass-skip-label": "Omet",
+       "authprovider-resetpass-skip-help": "Omet el restabliment de contrasenya.",
        "specialpage-securitylevel-not-allowed-title": "No permès",
        "authpage-cannot-login": "No s'ha pogut iniciar la sessió.",
        "authpage-cannot-login-continue": "No es pot continuar amb l'inicio de sessió. Probablement la vostra sessió ha expirat.",
        "cannotauth-not-allowed": "No teniu permisos per utilitzar la pàgina",
        "changecredentials": "Canvi de dades credencials",
        "changecredentials-submit": "Canvia les dades credencials",
+       "removecredentials": "Suprimeix les credencials",
+       "removecredentials-submit": "Suprimeix les credencials",
+       "removecredentials-success": "S'ha suprimit les vostres credencials.",
        "credentialsform-provider": "Tipus de dades credencials:",
        "credentialsform-account": "Nom del compte:",
        "cannotlink-no-provider-title": "No hi ha cap compte enllaçable",
        "linkaccounts-submit": "Enllaça els comptes",
        "unlinkaccounts": "Desenllaça els comptes",
        "unlinkaccounts-success": "El compte s'ha desenllaçat.",
-       "authenticationdatachange-ignored": "No s'ha gestionat el canvi de dades d'autenticació. Potser no s'ha configurat cap proveïdor?"
+       "authenticationdatachange-ignored": "No s'ha gestionat el canvi de dades d'autenticació. Potser no s'ha configurat cap proveïdor?",
+       "restrictionsfield-badip": "Adreça o interval d'IP no vàlid: $1",
+       "restrictionsfield-label": "Intervals d'IP permesos:",
+       "pageid": "ID de pàgina $1"
 }
index d9bc4a6..d94cfbe 100644 (file)
        "search-external": "Externí hledání",
        "searchdisabled": "<p>Omlouváme se. Plnotextové vyhledávání je dočasně nedostupné. Zatím můžete zkusit vyhledávání Googlem; je ale možné, že jeho výsledky nemusí být aktuální.</p>",
        "search-error": "Při hledání došlo k chybě: $1",
+       "search-warning": "Při hledání došlo k varování: $1",
        "preferences": "Nastavení",
        "mypreferences": "Nastavení",
        "prefs-edits": "Počet editací:",
        "userrights-user-editname": "Zadejte uživatelské jméno:",
        "editusergroup": "Načíst uživatelské skupiny",
        "editinguser": "Úprava práv {{GENDER:$1|uživatele|uživatelky}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Prohlížení práv {{GENDER:$1|uživatele|uživatelky}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Upravit uživatelské skupiny",
+       "userrights-viewusergroup": "Zobrazit uživatelské skupiny",
        "saveusergroups": "Uložit {{GENDER:$1|uživatelské}} skupiny",
        "userrights-groupsmember": "{{GENDER:$2|Člen|Členka}} {{PLURAL:$1|skupiny|skupin}}:",
        "userrights-groupsmember-auto": "Automaticky {{GENDER:$2|člen|členka}} {{PLURAL:$1|skupiny|skupin}}:",
        "action-upload_by_url": "nahrát tento soubor z URL adresy",
        "action-writeapi": "používat API pro zápis",
        "action-delete": "smazat tuto stránku",
-       "action-deleterevision": "smazat tuto revizi",
-       "action-deletedhistory": "zobrazit historii smazaných revizí této stránky",
+       "action-deleterevision": "mazat revize",
+       "action-deletelogentry": "mazat protokolovací záznamy",
+       "action-deletedhistory": "prohlížet si smazanou historii stránky",
+       "action-deletedtext": "prohlížet si smazané texty revizí",
        "action-browsearchive": "hledat smazané stránky",
-       "action-undelete": "obnovit tuto stránku",
-       "action-suppressrevision": "zkontrolovat a obnovit tuto skrytou revizi",
+       "action-undelete": "obnovovat stránky",
+       "action-suppressrevision": "prohlížet si a obnovovat skryté revize",
        "action-suppressionlog": "prohlédnout si tento skrytý protokolovací záznam",
        "action-block": "znemožnit tomuto uživateli editování",
        "action-protect": "změnit úrovně ochrany této stránky",
        "action-userrights-interwiki": "upravovat práva uživatelů na jiných wiki",
        "action-siteadmin": "zamykat nebo odemykat databázi",
        "action-sendemail": "posílat e-maily",
+       "action-editmyoptions": "měnit svá uživatelská nastavení",
        "action-editmywatchlist": "upravovat vlastní seznam sledovaných stránek",
        "action-viewmywatchlist": "prohlížet vlastní seznam sledovaných stránek",
        "action-viewmyprivateinfo": "prohlížet si své soukromé údaje",
        "apisandbox-continue-clear": "Vymazat",
        "apisandbox-continue-help": "{{int:apisandbox-continue}} bude [https://www.mediawiki.org/wiki/API:Query#Continuing_queries pokračovat] v posledním požadavku; {{int:apisandbox-continue-clear}} vymaže parametry související s pokračováním.",
        "apisandbox-param-limit": "Pro použití maximálního limitu zadejte <kbd>max</kbd>.",
+       "apisandbox-multivalue-all-namespaces": "$1 (všechny jmenné prostory)",
+       "apisandbox-multivalue-all-values": "$1 (všechny hodnoty)",
        "booksources": "Zdroje knih",
        "booksources-search-legend": "Vyhledat knižní zdroje",
        "booksources-search": "Hledat",
        "emailccsubject": "Kopie Vaší zprávy pro uživatele $1: $2",
        "emailsent": "E-mail odeslán",
        "emailsenttext": "Váš e-mail byl odeslán.",
-       "emailuserfooter": "Tento e-mail byl odeslán z {{grammar:2sg|{{SITENAME}}}} pomocí funkce „{{int:emailuser}}“; {{GENDER:$1|odeslal ho uživatel|odeslala ho uživatelka}} $1 {{GENDER:$2|uživateli|uživatelce}} $2.",
+       "emailuserfooter": "Tento e-mail byl odeslán z {{grammar:2sg|{{SITENAME}}}} pomocí funkce „{{int:emailuser}}“; {{GENDER:$1|odeslal ho uživatel|odeslala ho uživatelka}} $1 {{GENDER:$2|uživateli|uživatelce}} $2. Váš e-mail bude odeslán přímo {{GENDER:$1|původnímu odesílateli, čímž mu|původní odesílatelce, čímž jí}} prozradíte svou e-mailovou adresu.",
        "usermessage-summary": "Doručena zpráva od systému.",
        "usermessage-editor": "Systémový poslíček",
        "watchlist": "Sledované stránky",
        "wlshowhidemine": "moje editace",
        "wlshowhidecategorization": "kategorizaci stránek",
        "watchlist-options": "Možnosti sledovaných stránek",
+       "watchlist-mark-all-visited": "Jste si {{GENDER:|jist|jista|jisti}}, že chcete odznačit neprohlédnuté změny sledovaných stránek tím, že všechny stránky označíte jako navštívené?",
        "watching": "Přidávám na seznam sledovaných stránek…",
        "unwatching": "Odebírám ze seznamu sledovaných stránek…",
        "watcherrortext": "Při změně sledování stránky „$1“ došlo k chybě.",
        "mw-widgets-dateinput-no-date": "Nevybráno žádné datum",
        "mw-widgets-dateinput-placeholder-day": "RRRR-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "RRRR-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Hledat média",
+       "mw-widgets-mediasearch-noresults": "Nebyly nalezeny žádné výsledky.",
        "mw-widgets-titleinput-description-new-page": "stránka zatím neexistuje",
        "mw-widgets-titleinput-description-redirect": "přesměrování na $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Přidat kategorii…",
        "sessionmanager-tie": "Nelze kombinovat několik typů autentizace požadavků: $1.",
        "sessionprovider-generic": "relace pomocí $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "relace pomocí cookies",
        "usercssispublic": "Uvědomte si prosím, že podstránky s CSS by neměly obsahovat tajné údaje, protože jsou viditelné ostatním uživatelům.",
        "restrictionsfield-badip": "Neplatná IP adresa nebo rozsah: $1",
        "restrictionsfield-label": "Povolené rozsahy IP adres:",
-       "restrictionsfield-help": "Jedna IP adresa nebo CIDR rozsah na řádek. Všechno povolíte pomocí<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Jedna IP adresa nebo CIDR rozsah na řádek. Všechno povolíte pomocí<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "Stránka s ID $1"
 }
index 215cdea..4b25593 100644 (file)
        "search-external": "Externe Suche",
        "searchdisabled": "Die {{SITENAME}}-Suche ist deaktiviert. Du kannst unterdessen mit Google suchen. Bitte bedenke, dass der Suchindex von {{SITENAME}} veraltet sein kann.",
        "search-error": "Bei der Suche ist ein Fehler aufgetreten: $1",
+       "search-warning": "Beim Suchen ist eine Warnung aufgetreten: $1",
        "preferences": "Einstellungen",
        "mypreferences": "Einstellungen",
        "prefs-edits": "Anzahl der Bearbeitungen:",
        "userrights-user-editname": "Benutzername:",
        "editusergroup": "Benutzergruppen laden",
        "editinguser": "Ändere Benutzerrechte {{GENDER:$1|des Benutzers|der Benutzerin}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Benutzerrechte {{GENDER:$1|des Benutzers|der Benutzerin}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Benutzer-Gruppenzugehörigkeit bearbeiten",
+       "userrights-viewusergroup": "Benutzergruppen ansehen",
        "saveusergroups": "{{GENDER:$1|Gruppenzugehörigkeit}} ändern",
        "userrights-groupsmember": "Mitglied von:",
        "userrights-groupsmember-auto": "Automatisch Mitglied von:",
        "action-writeapi": "die API mit Schreibzugriffen zu verwenden",
        "action-delete": "Seiten zu löschen",
        "action-deleterevision": "Versionen zu löschen",
+       "action-deletelogentry": "Logbucheinträge zu löschen",
        "action-deletedhistory": "die Liste der gelöschten Versionen zu sehen",
+       "action-deletedtext": "gelöschten Versionstext anzusehen",
        "action-browsearchive": "nach gelöschten Seiten zu suchen",
-       "action-undelete": "die Seite wiederherzustellen",
-       "action-suppressrevision": "die versteckte Version einzusehen und wiederherzustellen",
+       "action-undelete": "Seiten wiederherzustellen",
+       "action-suppressrevision": "versteckte Versionen einzusehen und wiederherzustellen",
        "action-suppressionlog": "das private Logbuch einzusehen",
        "action-block": "den Benutzer zu sperren",
        "action-protect": "den Schutzstatus von Seiten zu ändern",
        "action-userrights-interwiki": "die Rechte von Benutzern in anderen Wikis zu ändern",
        "action-siteadmin": "die Datenbank zu sperren oder freizugeben",
        "action-sendemail": "E-Mails zu senden",
+       "action-editmyoptions": "deine Einstellungen zu bearbeiten",
        "action-editmywatchlist": "deine Beobachtungsliste zu bearbeiten",
        "action-viewmywatchlist": "deine Beobachtungsliste anzusehen",
        "action-viewmyprivateinfo": "deine privaten Informationen einzusehen",
        "emailccsubject": "Kopie deiner Nachricht an $1: $2",
        "emailsent": "E-Mail verschickt",
        "emailsenttext": "Deine E-Mail wurde verschickt.",
-       "emailuserfooter": "Diese E-Mail wurde von „$1“ an „{{GENDER:$2|$2}}“ durch die Funktion „{{int:emailuser}}“ bei {{SITENAME}} {{GENDER:$1|gesendet}}.",
+       "emailuserfooter": "Diese E-Mail wurde von „$1“ an „{{GENDER:$2|$2}}“ durch die Funktion „{{int:emailuser}}“ bei {{SITENAME}} {{GENDER:$1|gesendet}}. {{GENDER:$2|Deine}} E-Mail wird direkt an {{GENDER:$1|den Originalabsender|die Originalabsenderin}} gesendet mit der Preisgabe {{GENDER:$2|deiner}} E-Mail-Adresse an {{GENDER:$1|ihn|sie}}.",
        "usermessage-summary": "Systemnachricht gespeichert.",
        "usermessage-editor": "System-Messenger",
        "usermessage-template": "MediaWiki:Benutzernachricht",
        "wlshowhidemine": "Meine Bearbeitungen",
        "wlshowhidecategorization": "Seitenkategorisierung",
        "watchlist-options": "Anzeigeoptionen",
+       "watchlist-mark-all-visited": "Bist du sicher, dass du ungesehene Änderungen auf deiner Beobachtungsliste durch das Markieren aller Seiten als besucht zurücksetzen möchtest?",
        "watching": "Beobachten …",
        "unwatching": "Nicht mehr beobachten …",
        "watcherrortext": "Beim Ändern der Beobachtungslisteneinstellungen für „$1“ ist ein Fehler aufgetreten.",
        "mw-widgets-dateinput-no-date": "Kein Datum ausgewählt",
        "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
        "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Nach Medien suchen",
+       "mw-widgets-mediasearch-noresults": "Keine Ergebnisse gefunden.",
        "mw-widgets-titleinput-description-new-page": "Seite ist noch nicht vorhanden",
        "mw-widgets-titleinput-description-redirect": "Weiterleitung nach $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Eine Kategorie hinzufügen …",
        "usercssispublic": "Bitte beachten: CSS-Unterseiten sollten keine vertraulichen Daten enthalten, da sie von anderen Benutzern eingesehen werden können.",
        "restrictionsfield-badip": "Ungültige IP-Adresse oder ungültiger IP-Adressbereich: $1",
        "restrictionsfield-label": "Erlaubte IP-Adressbereiche:",
-       "restrictionsfield-help": "Eine IP-Adresse oder ein CIDR-Bereich pro Zeile. Um alles zu aktivieren, verwende<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Eine IP-Adresse oder ein CIDR-Bereich pro Zeile. Um alles zu aktivieren, verwende<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "revid": "v$1",
+       "pageid": "Seitenkennung $1"
 }
index 6137af4..35a9ebd 100644 (file)
@@ -85,7 +85,7 @@
        "tuesday": "Sêşeme",
        "wednesday": "Çarşeme",
        "thursday": "Pancşeme",
-       "friday": "Yene",
+       "friday": "Êne",
        "saturday": "Şeme",
        "sun": "Krê",
        "mon": "Dış",
        "categoryviewer-pagedlinks": "($1) ($2)",
        "about": "Heqa cı de",
        "article": "Pela zerreki",
-       "newwindow": "(pençereyê newey de beno a)",
-       "cancel": "Bıtekelne",
+       "newwindow": "(Teqaya newi de abena)",
+       "cancel": "Peyd kı",
        "moredotdotdot": "Vêşi...",
        "morenotlisted": "Na lista qay kemi ya.",
        "mypage": "Pele",
        "mytalk": "Mesac",
-       "anontalk": "Werênayış",
+       "anontalk": "Vaten",
        "navigation": "Pusula",
        "and": "&#32;u",
        "qbfind": "Bıvêne",
        "history_short": "Tarix",
        "updatedmarker": "cıkewtena mına peyêne ra dıme biyo rocane",
        "printableversion": "Asayışê çapkerdışi",
-       "permalink": "Gıreyo daimi",
+       "permalink": "Gıreyo bêpeyni",
        "print": "Çap ke",
        "view": "Bıvêne",
        "view-foreign": "$1 de bıvêne",
        "unprotectthispage": "Starkerdışe ena peler bıvurne",
        "newpage": "Pela newiye",
        "talkpage": "Ena pele sero werêne",
-       "talkpagelinktext": "werênayış",
-       "specialpage": "Pella xısusi",
+       "talkpagelinktext": "Mesac",
+       "specialpage": "Pelê hısusiy",
        "personaltools": "Hacetê şexsiy",
        "articlepage": "Pera zerreki bıvin",
-       "talk": "Behs",
+       "talk": "Vaten",
        "views": "Asayışi",
        "toolbox": "Haceti",
        "tool-link-userrights": "Grubanê {{GENDER:$1|karberi}} bıvırnë",
        "viewhelppage": "Pera peşti bıvin",
        "categorypage": "Pela kategoriya bıasne",
        "viewtalkpage": "Werênayışi bıvêne",
-       "otherlanguages": "Zederna zıwani",
+       "otherlanguages": "Zıwananê binan dı",
        "redirectedfrom": "($1 ra kırışı yê)",
        "redirectpagesub": "Pela berdışi",
        "redirectto": "Beno hetê:",
-       "lastmodifiedat": "Per roca $1, sehat $2 de biya anewe.",
+       "lastmodifiedat": "Tewr peyên ena pele $1 dı $2 dı vuriyaya.",
        "viewcount": "Ena pele {{PLURAL:$1|rae|$1 rey}} vêniya.",
        "protectedpage": "Pela pawıtiye",
        "jumpto": "Şo be:",
-       "jumptonavigation": "Pusula",
+       "jumptonavigation": "Navigasyon",
        "jumptosearch": "cı geyre",
        "view-pool-error": "Qaytê qısuri mekerên, serverê ma enıka zêde bar gırewto xo ser.\nHedê xo ra zêde karberi kenê ke seyrê na pele bıkerê.\nŞıma rê zehmet, tenê vınderên, heta ke reyna kenê ke ena pele kewê.\n\n$1",
        "generic-pool-error": "Üzgünüz, şu an sunucular aşırı yüklendi.\nÇok fazla kullanıcı bu sayfayı görüntülemeye çalışıyor.\nLütfen bu sayfaya  tekrar erişmeyi denemeden önce biraz bekleyin.",
        "aboutpage": "Project:Heqa",
        "copyright": "Zerrekacı $1 bındı not biya.",
        "copyrightpage": "{{ns:project}}:Heqa telifi",
-       "currentevents": "Hediseyê rocaneyi",
-       "currentevents-url": "Project:Hediseyê rocaneyi",
+       "currentevents": "Hediseyê rocaney",
+       "currentevents-url": "Project:Hediseyê rocaney",
        "disclaimers": "Redê mesuliyeti",
-       "disclaimerpage": "Project:Reddê mesuliyetê bıngey",
+       "disclaimerpage": "Project:Redê mesulêtê pêroyi",
        "edithelp": "Peştdariya vurnayışi",
        "helppage-top-gethelp": "Peşti",
        "mainpage": "Pela Seri",
        "mainpage-description": "Pela seri",
        "policy-url": "Project:Terzê hereketi",
-       "portal": "Portalê cemaeti",
-       "portal-url": "Project:Portalë Å\9fëlıgi",
-       "privacy": "Politikaya nımıteyiye",
+       "portal": "Portalé cemati",
+       "portal-url": "Project:Portalé cemati",
+       "privacy": "Politikay nımıtışi",
        "privacypage": "Project:Xısusiyetê nımıtışi",
        "badaccess": "Xeta mısadey",
        "badaccess-group0": "Heqa şıma çıniya, karo ke şıma waşt, bıkerê.",
        "editlink": "bıvurne",
        "viewsourcelink": "çımey bıvêne",
        "editsectionhint": "Leteyo ke bıvuriyo: $1",
-       "toc": "Sernameyê meselan",
+       "toc": "Tedeestey",
        "showtoc": "bımocne",
        "hidetoc": "bınımne",
        "collapsible-collapse": "Teng kı",
        "nstab-main": "Pele",
        "nstab-user": "Pela karberi",
        "nstab-media": "Pela medya",
-       "nstab-special": "Pella xısusi",
+       "nstab-special": "Pela xısusiye",
        "nstab-project": "Pela proceyi",
        "nstab-image": "Dosya",
        "nstab-mediawiki": "Mesac",
        "nstab-template": "Şablon",
        "nstab-help": "Pela peşti",
-       "nstab-category": "Kategoriye",
-       "mainpage-nstab": "Pera esas",
+       "nstab-category": "Kategori",
+       "mainpage-nstab": "Pela seri",
        "nosuchaction": "Fealiyeto wınasi çıniyo",
        "nosuchactiontext": "URL ra kar qebul nêbı.\nŞıma belka URL şaş nuşt, ya zi gıreyi şaş ra ameyi.\nKeyepelê {{SITENAME}} eşkeno xeta eşkera bıkero.",
-       "nosuchspecialpage": "Pela xasa wınasiye çıniya",
+       "nosuchspecialpage": "Pela hısusiya wınasiyên çıniya.",
        "nospecialpagetext": "<strong>To yew pela xasa nêvêrdiye waşte.</strong>\n\nSeba lista pelanê xasanê vêrdeyan reca kena: [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Xeta",
        "databaseerror": "Ğetay ardoği",
        "databaseerror-error": "Xeta: $1",
        "laggedslavemode": "Diqet: Pel de newe vıraşteyi belka çini .",
        "readonly": "database kılit biyo",
-       "enterlockreason": "Database kılit biyo",
+       "enterlockreason": "Kılitkerdışi rê tarixê abiyayışê kılit ra piya yew sebeb bınusê.",
        "readonlytext": "Qey pawıtış ri yew sebeb vace. Texmini yew tarix vace şıma key pawıtış wedarneni:  $1",
        "missing-article": "Banqa, pela be nameyê \"$1\" $2 ke gani bıbo, nêdiye.\n\nEna belki seba yew vurnayışo kıhan ya zi tarixê gıreyê yew pele esteriya.\n\nEke wına niyo, belki ''software'' de yew xeta esta.\nKerem kerên, naye be nameyê ''URL''yi yew [[Special:ListUsers/sysop|karber]]i ra vacên.",
        "missingarticle-rev": "(rewizyon#: $1)",
        "perfcached": "Datay cı ver hazır biye. No semedê ra nıkayin niyo! tewr zaf {{PLURAL:$1|netice|$1 netice}} debêno de",
        "perfcachedts": "Cêr de malumatê nımıteyi esti, demdê newe kerdışo peyın: $1. Tewr zaf {{PLURAL:$4|netice|$4 neticey cı}} debyayo de",
        "querypage-no-updates": "Rocanebiyayışê na pele nıka cadayiyê.\nDayiyi tiya nıka newe nêbenê.",
-       "viewsource": "Çemi bıvin",
+       "viewsource": "Çımey bıvêne",
        "viewsource-title": "Cı geyrayışê $1'i bıvin",
        "actionthrottled": "Kerden peysnaya",
        "actionthrottledtext": "Riyê tedbirê anti-spami ra,  wextê do kılmek de şıma nê fealiyeti nêşkenê zaf zêde bıkerê, şıma ki no hedi viyarna ra.\nÇend deqey ra tepeya reyna bıcerrebnên.",
        "virus-unknownscanner": "antiviruso ke nêzanyeno:",
        "logouttext": "'''Şıma hesabra newke vicyay.'''\n\nWexta ke verhafızayê cıgerayoxê şıma pak beno no benate de taye peli de hesabe şıma akerde aseno.",
        "cannotlogoutnow-title": "Enewke ronıştışo nêracneyêno",
-       "welcomeuser": "Ğeyr amey, $1!",
+       "welcomeuser": "Heyr 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",
-       "userlogin-yourname-ph": "Nameyê xoyê karberi cı kewe",
+       "userlogin-yourname-ph": "Namey xoyê karberi cı kewe",
        "createacct-another-username-ph": "Nameyê karberi cı kewe",
        "yourpassword": "Parola",
        "userlogin-yourpassword": "Parola",
-       "userlogin-yourpassword-ph": "Parolaya xo cıkewe",
+       "userlogin-yourpassword-ph": "Parolay xo cı kewe",
        "createacct-yourpassword-ph": "Parola cıkewe",
        "yourpasswordagain": "Parola reyna bınusne:",
        "createacct-yourpasswordagain": "Parola tesdiq ke",
        "passwordreset-text-many": "{{PLURAL:$1|Qande parola yana e-posta reset kerdışi cayanra taynın pırkeri.}}",
        "passwordreset-disabled": "Parola reset kerdış ena viki sera qefılneyayo.",
        "passwordreset-emaildisabled": "Na wikid hısusiyeté e-posta dewera vıcyayé",
-       "passwordreset-username": "Nameyê karberi:",
+       "passwordreset-username": "Namey karberi:",
        "passwordreset-domain": "Domain:",
        "passwordreset-email": "Adresa e-postey:",
        "passwordreset-emailtitle": "Hesab timarê {{SITENAME}}",
        "bold_tip": "Metno qalın",
        "italic_sample": "Metno çewt",
        "italic_tip": "Metno çewt",
-       "link_sample": "Sernameyê gırey",
-       "link_tip": "Gıreyê miyani",
+       "link_sample": "Serekê gıri",
+       "link_tip": "Gırey Żerri",
        "extlink_sample": "http://www.misal.com sernameyê gırey",
        "extlink_tip": "Gırey teberi (xo vira mekerên http:// prefix)",
        "headline_sample": "metnê sernamey",
        "summary": "Xulasa:",
        "subject": "Mewzu:",
        "minoredit": "No yew vurnayışo werdiyo",
-       "watchthis": "Na pele seyr ke",
-       "savearticle": "Peller qeyd kı",
-       "savechanges": "Vuryayışa qeyd kerê",
+       "watchthis": "Bewni ena per",
+       "savearticle": "Pele qeyd kı",
+       "savechanges": "Vurnayışan qeyd kı",
        "publishpage": "Perer bıhesırne",
        "publishchanges": "Vurnayışa vıla ke",
        "preview": "Verqayt",
-       "showpreview": "Var asani bıvinê",
-       "showdiff": "Vurriyayışa bıasne",
+       "showpreview": "Verasayışi bımocne",
+       "showdiff": "Vurnayışan bımocne",
        "anoneditwarning": "<strong>İqaz:</strong> Şıma be hesabê xo nêkewtê cı. \nAdresê şımayê IP tarixê vırnayışê na pele de do qeyd bo. Eke şıma <strong>[$1 cıkewê]</strong> ya zi <strong>[$2 hesab vırazê]</strong>, vurnayışê şıma be zewbina kare ra nameyê şıma rê bar beno.",
        "anonpreviewwarning": "\"Şıma be hesabê xo nêkewtê cı. Eke qeyd kerê, adresê şımaê IP tarixê vırnayışê na pele de do qeyd bo.\"",
        "missingsummary": "'''DİQET:''' Şıma jû xulasa nênuşte.\nEke şıma \"{{int:savearticle}}\" reyna bıtıknê, vırnayışê şıma bê xulasa qeyd beno.",
        "token_suffix_mismatch": "'''Vurnayişê şıma tepeya ameyo çunke qutiyê imla xerıbya.\nVurnayişê şıma qey nêxerepyayişê peli tepeya geyra a.\nEke şıma servisê proksi yo anonim şuxulneni sebebê ey noyo.'''",
        "edit_form_incomplete": "'''Qandê form dê vurnayışa tay wastera ma nêreşti; Vurnayışê ke şıma kerdê nêalızyayê, çım ra ravyarnê u fına bıcerbnê.'''",
        "editing": "$1 vuriyeno",
-       "creating": "$1 vıraziyeno",
+       "creating": "$1 vıraziyeno.",
        "editingsection": "Per da $1 de şımaye kenê ke leti bıvurnê",
        "editingcomment": "$1 vuryeno (qısmo newe)",
        "editconflict": "Têverabiyayışê vurnayışi: $1",
        "templatesusedpreview": "{{PLURAL:$1|Sablon|Sabloni}}  ke na verqayt de xebetnayê:",
        "templatesusedsection": "{{PLURAL:$1|Template|Templateyan}}  ke na qısım de xebetniyenê:",
        "template-protected": "(kılit biyo)",
-       "template-semiprotected": "(nimey ena pele kılit biya)",
+       "template-semiprotected": "(nêmê ena pele kılit biyo)",
        "hiddencategories": "Ena per de {{PLURAL:$1|1 kategoriyo nımıte|$1 kategoriyê nımıtey}} muhtewa benê:",
        "edittools": "<div id=\"specialcharss\" class=\"toccolours specialchars\" style=\"margin-top:.5em; padding: .3em .5em; font-size: 100%; color:#aaa; text-align:left;\" title=\"{{int:bw-edittools-tooltip}}\">\n<p class=\"specialbasic\" id=\"Standard\">\n'''{{int:bw-edittools-lead-in}}''' \n<charinsert>Á á É é Í í Ó ó Ú ú Ý ý</charinsert> –\n<charinsert>À à È è Ì ì Ò ò Ù ù </charinsert> –\n<charinsert> â Ê ê Î î Ô ô Û û </charinsert> –\n<charinsert>Ä ä Ë ë Ï ï Ö ö Ü ü Ÿ ÿ</charinsert> –\n<charinsert>Æ æ Ø ø Œ œ ẞ ß </charinsert> –\n<charinsert>Å å Ů ů </charinsert> –\n<charinsert>àã Ẽ ẽ ɛ̃ Ĩ ĩ Ñ ñ Õ õ ɔ̃ Ũ ũ </charinsert> –\n<charinsert>Рð Þ þ </charinsert> –\n<charinsert>Ç ç Ģ ģ Ķ ķ Ļ ļ Ņ ņ Ŗ ŗ Ş ş Ţ ţ </charinsert> –\n<charinsert>Ć ć Ĺ ĺ Ń ń Ŕ ŕ Ś ś Ý ý Ź ź </charinsert> –\n<charinsert>Č č Ď ď Ľ ľ Ň ň Ř ř Š š Ť ť Ž ž </charinsert> –\n<charinsert>Ǎ ǎ Ě ě Ǐ ǐ Ǒ ǒ Ǔ ǔ </charinsert> –\n<charinsert>Ā ā Ē ē Ī ī Ō ō Ū ū </charinsert> –\n<charinsert>ǖ ǘ ǚ ǜ </charinsert> –\n<charinsert>Ĉ ĉ Ĝ ĝ Ĥ ĥ Ĵ ĵ Ŝ ŝ Ŵ ŵ Ŷ ŷ </charinsert> –\n<charinsert>Ă ă Ğ ğ Ŭ ŭ </charinsert> –\n<charinsert>Ċ ċ Ė ė Ġ ġ Għ għ İ ı Ż ż </charinsert> –\n<charinsert>Ą ą Ę ę Į į Ų ų </charinsert> –\n<charinsert>Ő ő Ű ű </charinsert> –\n<charinsert>Đ đ Ħ ħ Ł ł Ŀ ŀ </charinsert> –\n<charinsert>Ɖ ɖ Ɛ ɛ Ƒ ƒ Ɣ ɣ Ŋ ŋ Ɔ ɔ Ʋ ʋ </charinsert> -\n<charinsert>Ə ə </charinsert> –\n<charinsert>– — ’</charinsert> –\n<charinsert>~ | ° ¹ ² ³ ⅛ ¼ ⅓ ⅜ ½ ⅝ ¾ ⅔ ⅞ € $ ¥ £ † × ← → ↔ ↑ ± ≠ © ® ™ ‰ «+» ‹+› „+“ „+” ‚+‘ ¡ ¿ …</charinsert> –\n<charinsert>&amp;nbsp; &nbsp; [[Category:+]] #REDIRECT[[+]] {{msg-mw|+|notext=1}} &#33;!FUZZY!! ~~~~  &lt;nowiki>+</nowiki></charinsert>\n<charinsert>ڈ ڑ ٹ </charinsert>\n<charinsert>ټ څ ځ ډ ړ ږ ښ ڼ ؤ ي ې ۍ ئ </charinsert>\n<charinsert>{{{+}}} {{+}} {{subst:+}} <noinclude>+</noinclude></charinsert>\n<charinsert>&lt;!--&nbsp;+&nbsp;--> &lt;br&nbsp;/></charinsert>\n</p></div>",
        "edittools-upload": "-",
        "nohistory": "Verê vurnayışanê na pele çıniyo.",
        "currentrev": "Çımraviyarnayışo rocane",
        "currentrev-asof": "$1 ra tepya mewcud weziyeta pela",
-       "revisionasof": "Verziyonê roca $1ine",
+       "revisionasof": "Çımraviyarnayışê $1",
        "revision-info": "Vurnayışo ke $1 de terefê {{GENDER:$6|$2}}$7 ra biyo",
        "previousrevision": "← Çımraviyarnayışo kıhanêr",
        "nextrevision": "Rewizyono newên →",
        "lineno": "Xeta $1:",
        "compareselectedversions": "Rewizyonanê weçineyan pêver ke",
        "showhideselectedversions": "Revizyonanê weçinıtan bımocne/bınımne",
-       "editundo": "peyser biya",
+       "editundo": "Peyser bıgêre",
        "diff-empty": "(Babetna niyo)",
        "diff-multi-sameuser": "(Terefê eyni karberi ra {{PLURAL:$1|yew revizyono miyanên nêmocno|$1 revizyonê miyanêni nêmocnê}})",
        "diff-multi-otherusers": "(Terefê {{PLURAL:$2|yew karberi|$2 karberan}} ra {{PLURAL:$1|yew revizyono miyanên nêmocno|$1 revizyonê miyanêni nêmocnê}})",
        "diff-multi-manyusers": "({{PLURAL:$1|jew timar kerdışo qıckeko|$1 timar kerdışo qıckeko}} timar kerdo, $2 {{PLURAL:$2|Karber|karberi}} memocne)",
        "difference-missing-revision": "Ferqê {{PLURAL:$2|Yew rewizyonê|$2 rewizyonê}} {{PLURAL:$2|dı|dı}} ($1) sero çıniyo.\n\nNo normal de werênayış dê pelanê besterneyan dı ena xırabin asena.\nDetayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.",
-       "searchresults": "Neticeyê geyrayışi",
+       "searchresults": "Peyniyê cıgeyrayışi",
        "searchresults-title": "Qandê \"$1\" neticeyê geyrayışi",
        "titlematches": "Tekê (zewcê) sernameyê pele",
        "textmatches": "Tekê (zewcê) nuştey pele",
        "next-page": "Pela peyên",
        "prevn-title": "$1o verên  {{PLURAL:$1|netice|neticeyan}}",
        "nextn-title": "$1o ke yeno {{PLURAL:$1|netice|neticey}}",
-       "shown-title": "Herg per sero $1 {{PLURAL:$1|netici|netica}} bıasne",
+       "shown-title": "Her pele sero $1 {{PLURAL:$1|netici|netica}} bımocne",
        "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) bıvênên",
        "searchmenu-exists": "''Ena 'Wikipediya de ser \"[[:$1]]\" yew pel esto'''",
        "searchmenu-new": "<strong>Na wiki de pela \"[[:$1]]\" vıraze!</strong> {{PLURAL:$2|0=|Sewbina pela ke şıma geyrayê cı aye bıvênê.|Yew zi neticanê cıgeyrayışê xo bıvênê.}}",
-       "searchprofile-articles": "Perrê muhteway",
-       "searchprofile-images": "Zafınmedya",
+       "searchprofile-articles": "Pelê zerreki",
+       "searchprofile-images": "Multimedya",
        "searchprofile-everything": "Pêro çi",
        "searchprofile-advanced": "Herayen",
        "searchprofile-articles-tooltip": "$1 de cı geyre",
        "searchprofile-images-tooltip": "Dosya cı geyre",
-       "searchprofile-everything-tooltip": "Tedeesteyan hemine cı geyre (pelanê mınaqeşeyi zi tey)",
-       "searchprofile-advanced-tooltip": "Cayê nameyanê xısusiyan de cı geyre",
+       "searchprofile-everything-tooltip": "Tedeesteyan hemine cı geyre (pelanê werênayışi zi tey)",
+       "searchprofile-advanced-tooltip": "Cayê namanê xısusiyan de cı geyre",
        "search-result-size": "$1 ({{PLURAL:$2|1 çeku|$2 çekuy}})",
        "search-result-category-size": "{{PLURAL:$1|1 eza|$1 ezayan}} ({{PLURAL:$2|1 kategoriyê bini|$2 kategirayanê binan}}, {{PLURAL:$3|1 dosya|$3 dosyayan}})",
-       "search-redirect": "($1 ra kırışiyè)",
+       "search-redirect": "($1 ra kırışya ya)",
        "search-section": "(qısmê $1)",
        "search-category": "(kategori $1)",
        "search-file-match": "(zerreyê dosya yewbini gêno)",
        "datedefault": "Tercih çıniyo",
        "prefs-labs": "Xacetê labs",
        "prefs-user-pages": "Pelê karberi",
-       "prefs-personal": "Pela karberi",
+       "prefs-personal": "Profilê karberi",
        "prefs-rc": "Vurriyayışê peyêni",
        "prefs-watchlist": "Lista seyrkerdışi",
        "prefs-editwatchlist": "Lista seyrkerdışi bıvurne",
        "prefs-reset-intro": "ena pele de şıma tercihanê xo şenê bıçarnê be tercihanê keyepelê ke verê coy eyar biy.\nNa game tepeya nêerziyena.",
        "prefs-emailconfirm-label": "Tesdiqiya E-posta:",
        "youremail": "E-Mail (mecbur niyo) *:",
-       "username": "{{GENDER:$1|Nameyê karberi}}:",
+       "username": "{{GENDER:$1|Namey karberi}}:",
        "prefs-memberingroups": "{{GENDER:$2|Ezayê}} {{PLURAL:$1|grube|gruban}}:",
        "prefs-memberingroups-type": "$1",
        "prefs-registration": "Wextê qeydbiyayışi",
        "grouppage-autoconfirmed": "{{ns:project}}:Karberê ke otomatikmen biyê araşt",
        "grouppage-bot": "{{ns:project}}:Boti",
        "grouppage-sysop": "{{ns:project}}:İdarekeri",
-       "grouppage-bureaucrat": "{{ns:project}}:Buroqrati",
+       "grouppage-bureaucrat": "{{ns:project}}:Burokrati",
        "grouppage-suppress": "{{ns:project}}:Teftişkar",
        "right-read": "Pera bıwané",
        "right-edit": "Pele bıvurne",
        "right-bot": "Zey yew kardê otomotiki kar bıvin",
        "right-nominornewtalk": "Pelanê werênayışan rê vurnayışê qıckeki çıniyê, qutiya mesacanê newiyan bıgurene",
        "right-apihighlimits": "Persanê API de sinoranê berzêran bıgurene",
-       "right-writeapi": "İstıfadey APIyê nuştey",
+       "right-writeapi": "Xebtnayışê API nusnayışi",
        "right-delete": "Pele bestere",
        "right-bigdelete": "Pelanê be tarixanê dergan bestere",
        "right-deletelogentry": "Qeydanê cıkewtışanê xısusiyan bestere û peyser biya",
        "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": "Vurnayışê peyêni",
        "recentchanges-legend": "Tercihê vurnayışanê peyênan",
-       "recentchanges-summary": "Wiki sero vurriyayışê peyêni asenê.",
+       "recentchanges-summary": "Wiki sero vurnayışê peyêni asenê.",
        "recentchanges-noresult": "Goreyê kriteranê kıfşkerdeyan ra qet yew vurnayış nêvêniya.",
        "recentchanges-feed-description": "Ena feed dı vurnayişanê tewr peniyan teqip bık.",
-       "recentchanges-label-newpage": "Enê vurnayışi ra yu pera newi vıraziya ya",
+       "recentchanges-label-newpage": "Enê vurnayışi yew pela newiye vıraşta.",
        "recentchanges-label-minor": "No yew vurnayışo werdiyo",
        "recentchanges-label-bot": "Eno vurnayış terefê yew boti ra vıraziyo",
        "recentchanges-label-unpatrolled": "Eno vurnayış hewna dewriya nêbiyo",
        "recentchanges-submit": "Bımocne",
        "rcnotefrom": "Cêr de <strong>$2</strong> ra nata {{PLURAL:$5|vurnayışiyê}} asenê (tewr vêşi <strong>$1</strong> asenê) <strong>$3, $4</strong>",
        "rclistfrom": "$3 $2 ra tepiya vurnayışanê neweyan bımocne",
-       "rcshowhideminor": "Vurriyayışanê werdiya $1",
+       "rcshowhideminor": "Vurnayışê werdiy $1",
        "rcshowhideminor-show": "Bımocne",
        "rcshowhideminor-hide": "Bınımne",
        "rcshowhidebots": "botan $1",
        "rcshowhideliu": "karberanê qeydina $1",
        "rcshowhideliu-show": "Bımocne",
        "rcshowhideliu-hide": "Bınımne",
-       "rcshowhideanons": "karberanê bênameyan $1",
+       "rcshowhideanons": "karberê bênamey $1",
        "rcshowhideanons-show": "Bımocne",
        "rcshowhideanons-hide": "Bınımne",
        "rcshowhidepatr": "$1 vurnayışê ke dewriya geyrayê",
        "rcshowhidepatr-show": "Bımocne",
        "rcshowhidepatr-hide": "Bınımne",
-       "rcshowhidemine": "vurnayışanê mı $1",
+       "rcshowhidemine": "vurnayışê mı $1",
        "rcshowhidemine-show": "Bımocne",
        "rcshowhidemine-hide": "Bınımne",
        "rcshowhidecategorization": "kategorizasyonê pele $1",
        "rc_categories": "Kategoriyan rêz kı ( \"|“ ya ciya yo):",
        "rc_categories_any": "Weçinayiyan ra her yew",
        "rc-change-size": "$1",
-       "rc-change-size-new": "Vurnayışa dıma $1 {{PLURAL:$1|bayt|bayt}}",
+       "rc-change-size-new": "$1 {{PLURAL:$1|bayt|bayti}} ra dıma vurnayış",
        "newsectionsummary": "/* $1 */ qısımo newe",
        "rc-enhanced-expand": "Detaya bıvin (JavaScript lazımo)",
        "rc-enhanced-hide": "Melumat bınımne",
        "recentchangeslinked-toolbox": "Vurnayışê elaqeyıni",
        "recentchangeslinked-title": "Heqa \"$1\" de vurnayışi",
        "recentchangeslinked-summary": "Lista cêrêne, pela bêlikerdiye rê (ya zi karberanê kategoriya bêlikerdiye rê) pelanê gırêdayoğan de lista de vurnayışê peyênana.\n[[Special:Watchlist|Lista şımaya seyrkedışi de]] peli be nuşteyo '''qolınd''' bêli kerdê.",
-       "recentchangeslinked-page": "Nameyê pele:",
+       "recentchangeslinked-page": "Namey perrer:",
        "recentchangeslinked-to": "Heruna pela ke yena dayene, vurnayışanê pelanê ke daye ra gırêdayiyê inan bımocne",
        "recentchanges-page-added-to-category": "[[:$1]] kerd kategoriye miyan",
        "recentchanges-page-removed-from-category": "[[:$1]] kategoriye ra vet",
        "imagelinks": "Gurenayışê dosya",
        "linkstoimage": "Ena {{PLURAL:$1|pela|$1 pela}} gıreye ena dosya:",
        "linkstoimage-more": "$1 ra ziyed {{PLURAL:$1|pel|pel}} re gırey dano.\nlisteya ke ha ver a têna na {{PLURAL:$1|dosyaya ewwili|dosyaya $1 ewwili}} mocnena.\n[[Special:WhatLinksHere/$2|pêroyê liste]] mevcud o.",
-       "nolinkstoimage": "Pelanê ser ena dosyayê link biyê çin o.",
+       "nolinkstoimage": "Ena dosya rê gırê dayen ju per çıni ya.",
        "morelinkstoimage": "[[Special:WhatLinksHere/$1|Linkanê zafyerî]] ena pele ra link biyo bivîne.",
        "linkstoimage-redirect": "$1 (Dosya raçarnayış) $2",
        "duplicatesoffile": "a {{PLURAL:$1|dosya|$1 dosya}}, kopyayê na dosyayi ([[Special:FileDuplicateSearch/$2|teferruati]]):",
        "unusedtemplateswlh": "linkanê binî",
        "randompage": "Pela raştameyiye",
        "randompage-nopages": "Na {{PLURAL:$2|heruna namey|heruna nameyan}} de nê peli çıniyê: $1.",
-       "randomincategory": "Kategori ra raşt amıyayi perr",
+       "randomincategory": "Ena kategoriye dı pela raştameye",
        "randomincategory-invalidcategory": "\"$1\" yew nameyê kategoriya vêrdiye niyo.",
        "randomincategory-nopages": "Kategori da [[:Category:$1|$1]] de qet  per çıniya.",
-       "randomincategory-category": "Kategoriye:",
+       "randomincategory-category": "Kategori:",
        "randomincategory-legend": "Kategori ra raştamayi per",
        "randomincategory-submit": "Şo",
        "randomredirect": "Serçarnayışo rastameye",
        "lonelypagestext": "Ena pelî link nibiyê ya zi pelanê binî {{SITENAME}} de transclude biy.",
        "uncategorizedpages": "Pelê ke kategorize nêbiyê",
        "uncategorizedcategories": "Kategoriyê ke kategorize nêbiyê",
-       "uncategorizedimages": "Dosye yê  bêkategori",
+       "uncategorizedimages": "Dosyeyê kategorinêbiyay.",
        "uncategorizedtemplates": "Şablonê ke bêkategoriyê",
-       "unusedcategories": "Kategoriyê ke nêgureniyê",
+       "unusedcategories": "Kategoriyê ke nêguriyay",
        "unusedimages": "Dosyeyê ke nêguriyenê",
        "wantedcategories": "Kategoriyê ke waziyayê",
        "wantedpages": "Pelê ke waziyayê",
        "mostimages": "Dosyayan ke tewr zaf link estê.",
        "mostinterwikis": "Pelan ke tewr zaf interwiki biyê.",
        "mostrevisions": "Pelan ke tewr zaf revizyonî biyê.",
-       "prefixindex": "Veroleya peley pêro",
+       "prefixindex": "Verbenda pelli heme",
        "prefixindex-namespace": "Peleyê Veroleyıni ($1 cay nami)",
        "prefixindex-submit": "Bımocne",
        "prefixindex-strip": "Listeya réz bıyayışi",
-       "shortpages": "Perrê kılmeki",
-       "longpages": "Perrê  dergeki",
+       "shortpages": "Pelê kılmi",
+       "longpages": "Pelê dergi",
        "deadendpages": "Perrê kı perranê binan rê grey c çıni yo",
        "deadendpagestext": "Ena pelan ke {{SITENAME}} de zerrî ey de link çini yo.",
-       "protectedpages": "Pelê pawıteyi",
+       "protectedpages": "Pellê kı pawıyayeyè",
        "protectedpages-indef": "têna pawıteyê bêmuddeti",
        "protectedpages-summary": "Listeya ena peler newke pawıtiya.Sername de  ena lista rê pawıte vıraştışi rê [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]] bıvinê.",
        "protectedpages-cascade": "Kilit biyaye ke teyna cascadiye",
        "listusers-desc": "Kemeyen rézed ratn",
        "usereditcount": "$1 {{PLURAL:$1|vurnayîş|vurnayîşî}}",
        "usercreated": "$2 de $1 {{GENDER:$3|viraziya}}",
-       "newpages": "Perrê newey",
+       "newpages": "Pelê newey",
        "newpages-submit": "Bımocne",
        "newpages-username": "Nameyê karberi:",
-       "ancientpages": "Perrê kı rewnayo kı nêvuriya yê",
+       "ancientpages": "Tewr pelê kıhani",
        "move": "Bıkırışe",
        "movethispage": "Ena pele bıkırışe",
        "unusedimagestext": "Enê dosyey estê, feqet zerrey yew pele de wedardey niyê.\nXo vira mekerê ke, sıteyê webiê bini şenê direkt ebe URLi yew dosya ra gırê bê, u wına şenê verba gurênayışo feal de tiya hewna lista bê.",
        "apisandbox-sending-request": "API waştış rışêno...",
        "apisandbox-request-url-label": "URL waştış:",
        "apisandbox-request-time": "Demê waştışi: {{PLURAL:$1|$1 ms}}",
-       "booksources": "Çıme kıtabi",
+       "booksources": "Çımey kıtabi",
        "booksources-search-legend": "Seba çımeyanê kıtaban cı geyre",
        "booksources-isbn": "ISBN:",
        "booksources-search": "Cı geyre",
        "speciallogtitlelabel": "Meqsed (sername ya zi {{ns:user}}:karberi rê nameyê karberi):",
        "log": "Qeydi",
        "logeventslist-submit": "Bımocne",
-       "all-logs-page": "Qeydê umumi pêro",
+       "all-logs-page": "Rocekê degme pêron",
        "alllogstext": "qey {{SITENAME}}i mocnayişê heme rocaneyani.\ntipa rocaneyi, nameyê karberi (herfa pil u qıci re hessas a), ya zi peli (reyna hessasiyê herfa pil u qıciyi) bıweçine u esayiş qıc kerê.",
        "logempty": "Qeydan dı malumato unasin çıni yo.",
        "log-title-wildcard": "Sernameyê ke be nê nuşteyi ra destkenê pê, cıgeyre",
        "checkbox-all": "Pêro",
        "checkbox-none": "Temam",
        "checkbox-invert": "Rageyre",
-       "allpages": "Peli pêro",
+       "allpages": "Pêro peli",
        "nextpage": "Pela badê cû ($1)",
        "prevpage": "Pela verêne ($1)",
        "allpagesfrom": "Pera liste kerdışi bıasne:",
        "removedwatchtext": "Ena pela \"[[:$1]]\" biya wedariya [[Special:Watchlist|listeyê seyr-kerdışi şıma]].",
        "removedwatchtext-short": "Pera $1`i listeya seyran de şıma ra wedari yê",
        "watch": "Seyr ke",
-       "watchthispage": "Na pele seyr ke",
+       "watchthispage": "Bewni ena per",
        "unwatch": "Teqib meke",
        "unwatchthispage": "temaşa kerdışê peli vındarn.",
        "notanarticle": "mebhesê peli niyo",
        "delete-warning-toobig": "no pel wayirê tarixê vurnayiş ê derg o, $1 {{PLURAL:$1|revizyonê|revizyonê}} seri de.\nhewn a kerdışê ıney {{SITENAME}} şuxul bıne gırano;\nbı diqqet dewam kerê.",
        "deleteprotected": "Şıma nêşenê ena perer esternê,  çıkı per starya ya.",
        "rollback": "vurnayişan tepiya bıger",
-       "rollbacklink": "peyser biya",
+       "rollbacklink": "peyser biyare",
        "rollbacklinkcount": "$1 {{PLURAL:$1|vurnayış|vurnayışi}} peyd gıroti",
        "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|vurnayış|vuranyışi}} tewr peyd gırot",
        "rollbackfailed": "Peyserardış nêbi",
        "rollback-success": "vurnayişê no kesi $1 tepiya geriyayo u hetê no\n$2 kesi ra cıwa ver o ke revizyon biyo no revizyon tepiya anciyayo.",
        "sessionfailure-title": "Seans xeripiya",
        "sessionfailure": "cıkewtışê hesabê şıma de yew problem aseno;\nno kar semedê dızdiyê hesabi ibtal biyo.\nkerem kerê \"tepiya\" şiyerê u pel o ke şıma tera ameyî u o pel newe ra bar kerê , newe ra tesel/cereb kerê.",
+       "changecontentmodel": "Modelê zerrekê pele bıvurne",
        "changecontentmodel-title-label": "Sernameyê pele",
        "changecontentmodel-model-label": "Modelê zerrekiyo newe",
        "changecontentmodel-reason-label": "Sebeb:",
        "undelete-error-long": "hewn a kerdışê na dosyayi wexta tepiya geriyenê xeta vıraziya:\n\n$1",
        "undelete-show-file-confirm": "\"<nowiki>$1</nowiki>\" şıma emin î dosyaya revizyonê no $2 $3 tarixi bıvini?",
        "undelete-show-file-submit": "Eya",
-       "namespace": "Heruna nameyi:",
+       "namespace": "Heruna namey",
        "invert": "Weçinayışi dimlaşt ke",
        "tooltip-invert": "nameyo ke nışan biyo (u nameyo elekeyın zi nışanyyayo se) vurnayışan  zerrekan nımtışi re ena dore tesdiqi nışan kerê",
-       "namespace_association": "Heruna nameyanê elaqedaran",
+       "namespace_association": "Heruna namanê elaqedaran",
        "tooltip-namespace_association": "Herunda canemiya elekeyın nışan kerdışi sero qıse kerdışi yana zerre dekerdışi rê ena dora tesdiqi nışan kerê",
        "blanknamespace": "(Ser)",
        "contributions": "İştırakê {{GENDER:$1|karber}}i",
        "sp-contributions-deleted": "iştırakê {{GENDER:$1|karberi}} esterdi",
        "sp-contributions-uploads": "Barkerdışi",
        "sp-contributions-logs": "qeydi",
-       "sp-contributions-talk": "werênayış",
+       "sp-contributions-talk": "vaten",
        "sp-contributions-userrights": "idareyê heqanê karberan",
        "sp-contributions-blocked-notice": "verniyê no/na karber/e geriyayo/a\nqê referansi qeydê vernigrewtışi cêr de eşkera biyo:",
        "sp-contributions-blocked-notice-anon": "Eno adresê IPi bloke biyo.\nCıkewtışo tewr peyêno ke bloke biyo, cêr seba referansi belikerdeyo:",
        "sp-contributions-hideminor": "Vurriyayışanê werdiyan bınımne",
        "sp-contributions-submit": "Cı geyre",
        "whatlinkshere": "Linkê tedeestey",
-       "whatlinkshere-title": "Per da \"$1\" rê perê ke gre danê",
+       "whatlinkshere-title": "Wesiqe da \"$1\" rê gıre dayen perri",
        "whatlinkshere-page": "Pele:",
        "linkshere": "Ena peleyan grey biya '''[[:$1]]''':",
        "nolinkshere": "Per da '''[[:$1]]''' rê pera ke gıre dana çıniya.",
        "tooltip-pt-login": "Mayê şıma ronıştış akerdışi rê dawet keme; labelê ronıştış mecburi niyo",
        "tooltip-pt-logout": "Bıveciye",
        "tooltip-pt-createaccount": "Şıma rê tewsiyey ma xorê jew hesab akerê. Fına zi hesab akerdış mecburi niyo.",
-       "tooltip-ca-talk": "Heqa zerrekê pele de werênayış",
+       "tooltip-ca-talk": "Heqa zerreki vaten",
        "tooltip-ca-edit": "Ena pele bıvurne",
-       "tooltip-ca-addsection": "Zu bınnusteya newi ak",
+       "tooltip-ca-addsection": "Bınleteyo newe akerê",
        "tooltip-ca-viewsource": "Ena pele kılit biya.\nŞıma şenê çımeyê aye bıvênê",
        "tooltip-ca-history": "Versiyonê verênê ena pele",
        "tooltip-ca-protect": "Ena pele bışevekne",
        "tooltip-t-emailuser": "Ena karber ri yew email bırış",
        "tooltip-t-upload": "Dosyeyan bar ke",
        "tooltip-t-specialpages": "Yew lista pelanê xasanê pêroyinan",
-       "tooltip-t-print": "Hewl versiyona ploğnayışa na perer",
+       "tooltip-t-print": "Versiyonê peleyo ke şeno çap bıkeriyo.",
        "tooltip-t-permalink": "Gırêyo daimi be ena versiyonê pele",
        "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 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-image": "Pela dosya bıvêne",
        "tooltip-ca-nstab-mediawiki": "Mesacê sistemi bımocne",
        "tooltip-ca-nstab-template": "Şabloni bıvêne",
        "tooltip-ca-nstab-help": "Pela peşti bıvêne",
        "tooltip-minoredit": "Nay vırnayışa werdi nışan bıkeré",
        "tooltip-save": "Vurnayışanê xo qeyd ke",
        "tooltip-publish": "Vurnayışê xo vıla kı",
-       "tooltip-preview": "Vuryayışané xo çım ra ravyarné. Verdé qeyd kerdışi eneri bıkarné!",
+       "tooltip-preview": "Vurnayışanê xo çım ra bıviyarnê. Qeydkerdış ra ver bıgurê cı!",
        "tooltip-diff": "Metni sero vurnayışan mocneno",
        "tooltip-compareselectedversions": "Ena per de ferqê rewziyonan de dı weçinaya bıvinê",
        "tooltip-watch": "Ena pele lista xoya seyrkerdışi ke",
        "tooltip-rollback": "\"Peyser biya\" be yew tık pela iştıraqanê peyênan peyser ano",
        "tooltip-undo": "\"Undo\" ena vurnayışê newi iptal kena u vurnayışê verni a kena.\nTı eşkeno yew sebeb bınus.",
        "tooltip-preferences-save": "Terciha qeyd ke",
-       "tooltip-summary": "Yew xulasaya kilm binuse",
+       "tooltip-summary": "Xulasa kılmek bınuse",
        "interlanguage-link-title": "$1 - $2",
        "common.css": "/************************************************\n * COMMON CSS\n *\n * Any CSS placed in this page will be used on \n * all skins, please think carefully about if it\n * belongs here (and not in one of the skin CSS\n * pages) before adding it. Thanks.\n ************************************************/\n\n/* <table class=\"highlighthovertable\"> */\ntable.highlighthovertable tr:hover,\ntable.highlighthovertable tr:hover td,\ntable.mw-ext-translate-groupstatistics tr:hover,\ntable.mw-ext-translate-groupstatistics tr:hover td {\n background-color: white;\n}\n\n\n/* Babel wrapper layout. */\n/* XXX: This is either redundant or should be in-core */\n/* @noflip */table.mw-babel-wrapper {\n\twidth:        238px;\n\tfloat:        right;\n\tclear:        right;\n\tmargin:       1em;\n\tborder-style: solid;\n\tborder-width: 1px;\n\tborder-color: #99B3FF;\n}\n\n/* Babel box layout. */\n/* @noflip */div.mw-babel-box {\n\tfloat:  left;\n\tclear:  left;\n\tmargin: 1px;\n}\n\ndiv.mw-babel-box table {\n\twidth: 238px;\n}\n\ndiv.mw-babel-box table th {\n\twidth:       238px;\n\twidth:       45px;\n\theight:      45px;\n\tfont-size:   14pt;\n\tfont-family: monospace;\n}\n\ndiv.mw-babel-box table td {\n\tfont-size:   8pt;\n\tpadding:     4pt;\n\tline-height: 1.25em;\n}\n\n/* Babel box colours. */\ndiv.mw-babel-box-0 {\n\tborder: solid #B7B7B7 1px;\n}\n\ndiv.mw-babel-box-1 {\n\tborder: solid #C0C8FF 1px;\n}\n\ndiv.mw-babel-box-2 {\n\tborder: solid #77E0E8 1px;\n}\n\ndiv.mw-babel-box-3 {\n\tborder: solid #99B3FF 1px;\n}\n\ndiv.mw-babel-box-4 {\n\tborder: solid #CCCC00 1px;\n}\n\ndiv.mw-babel-box-5 {\n\tborder: solid #F99C99 1px;\n}\n\ndiv.mw-babel-box-N {\n\tborder: solid #6EF7A7 1px;\n}\n\ndiv.mw-babel-box-0 table th {\n\tbackground-color: #B7B7B7;\n}\n\ndiv.mw-babel-box-1 table th {\n\tbackground-color: #C0C8FF;\n}\n\ndiv.mw-babel-box-2 table th {\n\tbackground-color: #77E0E8;\n}\n\ndiv.mw-babel-box-3 table th {\n\tbackground-color: #99B3FF;\n}\n\ndiv.mw-babel-box-4 table th {\n\tbackground-color: #CCCC00;\n}\n\ndiv.mw-babel-box-5 table th {\n\tbackground-color: #F99C99;\n}\n\ndiv.mw-babel-box-N table th{\n\tbackground-color: #6EF7A7;\n}\n\ndiv.mw-babel-box-0 table {\n\tbackground-color: #E8E8E8;\n}\n\ndiv.mw-babel-box-1 table {\n\tbackground-color: #F0F8FF;\n}\n\ndiv.mw-babel-box-2 table {\n\tbackground-color: #D0F8FF;\n}\n\ndiv.mw-babel-box-3 table {\n\tbackground-color: #E0E8FF;\n}\n\ndiv.mw-babel-box-4 table {\n\tbackground-color: #FFFF99;\n}\n\ndiv.mw-babel-box-5 table {\n\tbackground-color: #F9CBC9;\n}\n\ndiv.mw-babel-box-N table {\n\tbackground-color: #C5FCDC;\n}\n\n.babel-box td.babel-footer {\n\ttext-align: center;\n}\n\n/* Styling for portals. */\ndiv.table {\n    display:        table;\n    vertical-align: top;\n    width:          100%;\n}\n\ndiv.table-row {\n    display:        table-row;\n    vertical-align: top;\n}\n\ndiv.table-cell {\n    display:        table-cell;\n    vertical-align: top;\n}\n\nbody.ns-100 table.mw-babel-wrapper {\n    border:           solid 1px #bbbbbb;\n    background-color: #f0f0f0;\n    margin-left:      1em;\n}\n\n.graytext {\n    color: #aaa;\n}\n\n/* On [[Special:RecentChanges]] and [[Special:Watchlist]] make the new pages symbol bold green and the minor edit symbol gray. */\n.newpage {\n    color:       green;\n    font-weight: bold\n}\n\n.minoredit,\n.minor {\n    color: gray;\n}\n\n/* Monospace diffs, this makes more sense since diffs show what would be seen in the edit box. */\n/* Note: Anno 2012 many browsers don't use monospace in the textarea anymore by default, notably Chrome and Safari don't (unless the user overrides this in the preferences) */\n.diff-context,\n.diff-deletedline,\n.diff-addedline {\n    font-family: monospace, \"Courier New\";\n/* Just guess does the stupid wikidiff2 extensions add extra whitespace around..... */\n    white-space: -moz-pre-wrap;\n    white-space: pre-wrap;\n}\n \n.diffchange {\n    border: 1px dotted rgb( 170, 170, 170 );\n}\n\n/* It is unclear what the following CSS does, please add comments if you can clarify. */\n/* The box which is 400px high and if its content is longer, it gets the scrollbar */\n.scrollme {\n    overflow: scroll;\n    width:    100%;\n    height:   400px;\n}\n\n/* Standard Navigationsleisten, aka box hiding thingy from .de.  Documentation at [[Wikipedia:NavFrame]]. */\ndiv.Boxmerge, div.NavFrame { margin: 0; padding: 4px; border-collapse: collapse;}\ndiv.Boxmerge div.NavFrame { border-style: none; border-style: hidden; }\ndiv.NavFrame + div.NavFrame { border-top-style: none; border-top-style: hidden; }\ndiv.NavFrame div.NavHead { height: 1.6em; position:relative; }\ndiv.NavEnd { margin: 0; padding: 0; line-height: 1px; clear: both; }\na.NavToggle { position: absolute; top: 0; right: 5px; }\n.note-flaggedrevs * a.NavToggle { right: 12px; } /* For [[Template:Flagged Revs]] */\n\n/* Template:Languages */\n.bw-languages {\n    border:          1px solid #aaaaaa;\n    padding:         0.2em;\n    border-collapse: collapse;\n    line-height:     1.2;\n    font-size:       95%;\n    margin:          1px 1px;\n}\n.bw-languages-title {\n    width:        180px;\n    border:       1px solid #aaaaaa;\n    background:   #EEF3E2;\n    padding:      0.5em;\n    font-weight:  bold;\n}\n.bw-languages-links { padding:0.5em; background:#F6F9ED; }\n\n/* Senseless in this project */\n#editpage-copywarn { display: none; }\n\n/* Hide warnings about bad links on MediaWiki:Common.css */\n.page-MediaWiki_Common_css .mw-translate-messagechecks { display: none; }\n\n/*******************\n** Faciliate RTL translation\n*******************/\n/* @noflip */\n#bodyContent .arabic a {\n\tpadding-right:0;\n\tbackground:none;\n}\n\n.vatop tr, tr.vatop, .vatop td, .vatop th {\n vertical-align: top;\n}\n\n.bw-languages {\n direction: ltr;\n}\n\n/* prevent wrapping of lines in LQT TOC if not necessary */\ntable.lqt_toc {\n\twidth: auto;\n}\n\n/* [[m:MediaZilla:35337]] */\n@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx) {\n        #p-logo a {\n                background-image: url(\"//translatewiki.net/images/thumb/7/7c/Translatewiki-logo-bare.svg/152px-Translatewiki-logo-bare.svg.png\") !important;\n                background-size: auto 135px;\n        }\n}\n@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {\n        #p-logo a {\n                background-image: url(\"//translatewiki.net/images/thumb/7/7c/Translatewiki-logo-bare.svg/202px-Translatewiki-logo-bare.svg.png\") !important;\n                background-size: auto 135px;\n        }\n}\n\n/* qqq visibility, [[Thread:Support/Suggestion: Add this CSS to MediaWiki:Common.css]] */\n \n.mw-sp-translate-edit-info .mw-content-ltr {\n  background-position:left center;\n  padding-left:45px;\n}\nfieldset.mw-sp-translate-edit-info .mw-centent-rtl {\n  background-position:right center;\n  padding-right:45px;\n}\n\n/* Semantic MediaWiki - make special properties easier to identify */\n\n.smwbuiltin a,\n.smwbuiltin a.new {\n\tcolor: #FF8000;\n}\n\n/* Recentchangestext toggle link */\n.white-link a {\n    color: #fff;\n}",
        "common.js": "/* Any JavaScript here will be loaded for all users on every page load. */",
        "pageinfo-protect-cascading": "Sıtarkerdeyi tiya cı ra yenê war",
        "pageinfo-protect-cascading-yes": "Eya",
        "pageinfo-protect-cascading-from": "Sıtarkerdey cı ra yenê war",
-       "pageinfo-category-info": "Şınasiya kategoriye",
+       "pageinfo-category-info": "Melumatê kategori",
        "pageinfo-category-pages": "Amarê pelan",
        "pageinfo-category-subcats": "Amarê bınkategoriyan",
        "pageinfo-category-files": "Amarê dosyeyan",
        "widthheight": "$1 - $2",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|pele|peli}}",
        "file-info": "ebatê dosyayi: $1, MIME tip: $2",
-       "file-info-size": "$1 × $2 pixelan, ebatê dosya: $3, MIME type: $4",
+       "file-info-size": "$1 × $2 pikselan, ebatê dosya: $3, MIME tipê cı: $4",
        "file-info-size-pages": "$1 × $2 pikse, dergeya dosyay: $3, MIME tipiya cı: $4, $5 {{PLURAL:$5|pela|pela}}",
        "file-nohires": "Deha berz agozney cı çıniyo",
        "svg-long-desc": "Dosyay SVG, zek vanê $1 × $2 piksela, ebatê dosya: $3",
        "svg-long-desc-animated": "SVG dosya, nominalin $1 × $2 piksela, ebatê dosya: $3",
        "svg-long-error": "Nêmeqbul dosyaya SVG'i: $1",
        "show-big-image": "Dosyaya oricinale",
-       "show-big-image-preview": "Verqaytê dergiya: $1.",
+       "show-big-image-preview": "Vervênayışê ebatê : $1.",
        "show-big-image-other": "Zewmi{{PLURAL:$2|Vılêşnayış|Vılêşnayışê}}: $1.",
        "show-big-image-size": "$1 × $2 piksel",
        "file-info-gif-looped": "viyariye biyo",
        "exif-primarychromaticities": "Kromaticitiyê eveli",
        "exif-ycbcrcoefficients": "Cayê rengi yê transformasyon metriksê koefişinti",
        "exif-referenceblackwhite": "Çiftyê siya u sipe değerê referansi",
-       "exif-datetime": "Zeman u tarixê vurnayişê dosyayi",
+       "exif-datetime": "Zeman u tarixê vurnayışê dosya",
        "exif-imagedescription": "Serê resimi",
        "exif-make": "Vıraştoğê kamera",
        "exif-model": "Modelê kamera",
-       "exif-software": "Software ke hebitiyeno",
+       "exif-software": "Karenaye nusnerek",
        "exif-artist": "Nuştoğ",
        "exif-copyright": "Wahirê copyrighti",
        "exif-exifversion": "Versiyonê Exif",
        "exif-usercomment": "Mışewreyê karberi",
        "exif-relatedsoundfile": "Derhekê dosya yê vengi",
        "exif-datetimeoriginal": "Demê afernayışê dayeyo sıfteyıni",
-       "exif-datetimedigitized": "Zeman û tarixê dicitalkerdışi",
+       "exif-datetimedigitized": "Dema  dijital kerdışi",
        "exif-subsectime": "ZemanTarix saniyeyibini",
        "exif-subsectimeoriginal": "ZemanTarixOricinal saniyeyibini",
        "exif-subsectimedigitized": "ZemanTarixDicital saniyeyibini",
        "exif-writer": "Nuştekar",
        "exif-languagecode": "Zıwan",
        "exif-iimversion": "Verqaydê IIM",
-       "exif-iimcategory": "Kategoriye",
+       "exif-iimcategory": "Kategori",
        "exif-iimsupplementalcategory": "Kategoriyê temamkerdışi",
        "exif-datetimeexpires": "No peyra mekarênê",
        "exif-datetimereleased": "Bıroşe",
        "version": "Versiyon",
        "version-extensions": "Ekstensiyonî ke ronaye",
        "version-skins": "Bar kerde bejni",
-       "version-specialpages": "Pellê xısusiy",
+       "version-specialpages": "Pelê hısusiy",
        "version-parserhooks": "Çengelê Parserî",
        "version-variables": "Vurnayeyî",
        "version-antispam": "Spam vındarnayış",
        "fileduplicatesearch-result-1": "Dosyayê ''$1î'' de hem-kopya çini yo.",
        "fileduplicatesearch-result-n": "Dosyayê ''$1î'' de {{PLURAL:$2|1 hem-kopya|$2 hem-kopyayî'}} esto.",
        "fileduplicatesearch-noresults": "Ebe namey \"$1\" ra dosya nêdiyayê.",
-       "specialpages": "Pellê xısusiy",
+       "specialpages": "Pelê hısusiy",
        "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-login": "Dekew / hesab vıraz",
        "specialpages-group-changes": "Vurnayışê peyêni û qeydi",
        "specialpages-group-media": "Raporê medya û barkerdışi",
-       "specialpages-group-users": "Karberi û heqi",
+       "specialpages-group-users": "Karberi u heqê inan",
        "specialpages-group-highuse": "Peleyê ke vêşi karênê",
        "specialpages-group-pages": "Listeyê pelan",
        "specialpages-group-pagetools": "Haletê pelan",
        "tags-create-reason": "Sebeb:",
        "tags-create-submit": "Vıraze",
        "tags-edit-reason": "Sebeb:",
-       "comparepages": "Perra pêver kı",
+       "comparepages": "Pelan têversanê",
        "compare-page1": "Pele 1",
        "compare-page2": "Pele 2",
        "compare-rev1": "Revizyonê 1i",
        "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|bayt|bayti}}",
        "limitreport-expansiondepth": "Tewr veşi herayina dergbiyayışi",
        "limitreport-expensivefunctioncount": "Amoriya fonksiyonde vay agozni",
-       "expandtemplates": "Å\9fablona hera kı",
+       "expandtemplates": "Å\9eablonan hera kı",
        "expand_templates_intro": "Na pela xususi metın geno u şablonê ke tedeyê reyna reyna hêra keno.\nU hem zi nê fonksiyonan hêra keno\n<nowiki>{{</nowiki>#language:…}}</code>, u zey nê parametreyan\n<nowiki>{{</nowiki>CURRENTDAY}}</code>\nEneri Medya wiki sera xo keno.",
        "expand_templates_title": "Sernameyê weziyeti, misal qandê {{FULLPAGENAME}}.:",
        "expand_templates_input": "sernameyê cıkewtışi:",
        "mw-widgets-dateinput-placeholder-day": "SSSS-AA-RR",
        "mw-widgets-dateinput-placeholder-month": "SSSS-AA",
        "mw-widgets-titleinput-description-redirect": "berd be $1",
-       "randomrootpage": "Raştamaye perra çımey",
+       "randomrootpage": "Pela raştmameya rıçıkıne",
        "log-action-filter-newusers": "Babetê hesabvıraştışi:",
-       "changecredentials": "Malumatanê karberi bıvurnê"
+       "changecredentials": "Malumatanê karberi bıvurnê",
+       "removecredentials": "Kamiye wedarne",
+       "removecredentials-submit": "Kamiyer wedarne"
 }
index 2685328..b6e1b18 100644 (file)
        "category-file-count-limited": "निम्न {{PLURAL:$1|फाइल|$1 फाइलहरू}} यै श्रेणीमी रया छ ।",
        "listingcontinuesabbrev": "निरन्तरता...",
        "index-category": "क्रमाङ्कित पानाहरू",
-       "noindex-category": "à¤\95à¥\8dरमाà¤\99à¥\8dà¤\95न à¤¨à¤\97रà¥\80याà¤\95ा à¤ªà¤¾à¤¨à¤¾à¤¹à¤°à¥\82",
+       "noindex-category": "à¤\85नà¥\81à¤\95à¥\8dरमित à¤¨à¤\85रियाऽ à¤ªà¤¨à¥\8dनाà¤\85न",
        "broken-file-category": "टुटेको फाइल लिङ्कहरूसितको पाना",
        "about": "बारेमी",
        "article": "सामाग्री पानो",
        "passwordreset": "पासवर्ड पूर्वनिर्धारित गर",
        "passwordreset-username": "प्रयोगकर्ता-नाम:",
        "passwordreset-domain": "डोमेन",
-       "passwordreset-capture": "निस्कने इमेलको नमुना हेर्ने ?",
        "passwordreset-email": "इमेल ठेगाना:",
        "passwordreset-emailtitle": "{{SITENAME}}मा खाता विवरण",
        "passwordreset-emailelement": "प्रयोगकर्ताको नाम: \n$1\n\nअस्थाई पासवर्ड: \n$2",
        "searchprofile-advanced-tooltip": "अनुकुल नेमस्पेसमा खोज्या",
        "search-result-size": "$1 ({{PLURAL:$2|1 आँखर|$2 आँखर}})",
        "search-result-category-size": "{{PLURAL:$1|एक सदस्य|$1 सदस्यहरू}} ({{PLURAL:$2|1 उपश्रेणी|$2  उपश्रेणीहरू}}, {{PLURAL:$3|एउटा फाइल|$3 फाइलहरू}})",
-       "search-redirect": "(जान्या $1)",
+       "search-redirect": "($1 बठेइ पुन:निर्देशित)",
        "search-section": "(खण्ड $1)",
        "search-category": "(श्रेणी $1)",
        "search-file-match": "(भेटिईया फाइल सामाग्री)",
        "userrights-changeable-col": "तमले परिवर्तन गद्द सक्दया समूहअन",
        "userrights-unchangeable-col": "तमीले परिवर्तन गद्द नसक्ने समूहहरू",
        "userrights-conflict": "प्रयोगकर्ताको अधिकार परिवर्तनमी मतभेद भयो ! कृपया तमरो परिवर्तन पुनरावलोकन तथा पुष्टि गर ।",
-       "userrights-removed-self": "तमले सफलतापूर्वक आफनो अधिकारहरूलाई मेटाया । त्यै कारण तम आब यो पानो हेद्द नाइसक्दा ।",
        "group": "समूह:",
        "group-user": "प्रयोगकर्ताहरू",
        "group-autoconfirmed": "स्वत स्थापित प्रयोगकर्ताहरू",
        "logentry-newusers-create": "प्रयोगकर्ता खाता $1 {{GENDER:$2|खोलियो}}",
        "logentry-upload-upload": "$1 ले $3 {{GENDER:$2|अपलोड अरेका छन्}}",
        "feedback-bugornote": "यदि तमी कुनै प्राविधिक समस्यालाई विस्तारले सम्झाउन तयार छौ भण्या कृपया [$1 बग राख]।\nयदि हैन, भण्या तमी तल दियाको सरल फारमको प्रयोग गद्दसक्द्याहौ । तमरो टिप्पणी, तमरो प्रयोगकर्ता नाम र तमरो ब्राउजरको नाम सहित \"[$3 $2]\" पानामी जोडिन्याछ ।",
-       "searchsuggest-search": "खोज:",
+       "searchsuggest-search": "{{SITENAME}} खोजऽ",
        "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 9aa0f46..865aaee 100644 (file)
        "searchdisabled": "{{SITENAME}} search is disabled.\nYou can search via Google in the meantime.\nNote that their indexes of {{SITENAME}} content may be out of date.",
        "googlesearch": "<form method=\"get\" action=\"//www.google.com/search\" id=\"googlesearch\">\n\t<input type=\"hidden\" name=\"domains\" value=\"{{SERVER}}\" />\n\t<input type=\"hidden\" name=\"num\" value=\"50\" />\n\t<input type=\"hidden\" name=\"ie\" value=\"$2\" />\n\t<input type=\"hidden\" name=\"oe\" value=\"$2\" />\n\n\t<input type=\"text\" name=\"q\" size=\"31\" maxlength=\"255\" value=\"$1\" />\n\t<input type=\"submit\" name=\"btnG\" value=\"$3\" />\n  <div>\n\t<input type=\"radio\" name=\"sitesearch\" id=\"gwiki\" value=\"{{SERVER}}\" checked=\"checked\" /><label for=\"gwiki\">{{SITENAME}}</label>\n\t<input type=\"radio\" name=\"sitesearch\" id=\"gWWW\" value=\"\" /><label for=\"gWWW\">WWW</label>\n  </div>\n</form>",
        "search-error": "An error has occurred while searching: $1",
+       "search-warning": "A warning has occured while searching: $1",
        "opensearch-desc": "{{SITENAME}} ({{CONTENTLANGUAGE}})",
        "preferences": "Preferences",
        "preferences-summary": "",
        "userrights-user-editname": "Enter a username:",
        "editusergroup": "Load user groups",
        "editinguser": "Changing user rights of {{GENDER:$1|user}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Viewing user rights of {{GENDER:$1|user}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Edit user groups",
+       "userrights-viewusergroup": "View user groups",
        "saveusergroups": "Save {{GENDER:$1|user}} groups",
        "userrights-groupsmember": "Member of:",
        "userrights-groupsmember-auto": "Implicit member of:",
        "action-upload_by_url": "upload this file from a URL",
        "action-writeapi": "use the write API",
        "action-delete": "delete this page",
-       "action-deleterevision": "delete this revision",
-       "action-deletedhistory": "view this page's deleted history",
+       "action-deleterevision": "delete revisions",
+       "action-deletelogentry": "delete log entries",
+       "action-deletedhistory": "view a page's deleted history",
+       "action-deletedtext": "view deleted revision text",
        "action-browsearchive": "search deleted pages",
-       "action-undelete": "undelete this page",
-       "action-suppressrevision": "review and restore this hidden revision",
+       "action-undelete": "undelete pages",
+       "action-suppressrevision": "review and restore hidden revisions",
        "action-suppressionlog": "view this private log",
        "action-block": "block this user from editing",
        "action-protect": "change protection levels for this page",
        "action-userrights-interwiki": "edit user rights of users on other wikis",
        "action-siteadmin": "lock or unlock the database",
        "action-sendemail": "send emails",
+       "action-editmyoptions": "edit your preferences",
        "action-editmywatchlist": "edit your watchlist",
        "action-viewmywatchlist": "view your watchlist",
        "action-viewmyprivateinfo": "view your private information",
        "listgrants-summary": "The following is a list of grants with their associated access to user rights. Users can authorize applications to use their account, but with limited permissions based on the grants the user gave to the application. An application acting on behalf of a user cannot actually use rights that the user does not have however.\nThere may be [[{{MediaWiki:Listgrouprights-helppage}}|additional information]] about individual rights.",
        "listgrants-grant": "Grant",
        "listgrants-rights": "Rights",
+       "listgrants-grant-display": "$1 <code>($2)</code>",
        "trackingcategories": "Tracking categories",
        "trackingcategories-summary": "This page lists tracking categories which are automatically populated by the MediaWiki software. Their names can be changed by altering the relevant system messages in the {{ns:8}} namespace.",
        "trackingcategories-msg": "Tracking category",
        "emailccsubject": "Copy of your message to $1: $2",
        "emailsent": "Email sent",
        "emailsenttext": "Your email message has been sent.",
-       "emailuserfooter": "This email was {{GENDER:$1|sent}} by $1 to {{GENDER:$2|$2}} by the \"{{int:emailuser}}\" function at {{SITENAME}}.",
+       "emailuserfooter": "This email was {{GENDER:$1|sent}} by $1 to {{GENDER:$2|$2}} by the \"{{int:emailuser}}\" function at {{SITENAME}}. {{GENDER:$2|Your}} email will be sent directly to the {{GENDER:$1|original sender}}, revealing {{GENDER:$2|your}} email address to {{GENDER:$1|them}}.",
        "usermessage-summary": "Leaving system message.",
        "usermessage-editor": "System messenger",
        "usermessage-template": "MediaWiki:UserMessage",
        "wlshowhidemine": "my edits",
        "wlshowhidecategorization": "page categorization",
        "watchlist-options": "Watchlist options",
+       "watchlist-mark-all-visited": "Are you sure you want to reset unseen watchlist changes by marking all pages as visited?",
        "watching": "Watching...",
        "unwatching": "Unwatching...",
        "watcherrortext": "An error occurred while changing your watchlist settings for \"$1\".",
        "mw-widgets-dateinput-no-date": "No date selected",
        "mw-widgets-dateinput-placeholder-day": "YYYY-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "YYYY-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Search for media",
+       "mw-widgets-mediasearch-noresults": "No results found.",
        "mw-widgets-titleinput-description-new-page": "page does not exist yet",
        "mw-widgets-titleinput-description-redirect": "redirect to $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Add a category...",
        "usercssispublic": "Please note: CSS subpages should not contain confidential data as they are viewable by other users.",
        "restrictionsfield-badip": "Invalid IP address or range: $1",
        "restrictionsfield-label": "Allowed IP ranges:",
-       "restrictionsfield-help": "One IP address or CIDR range per line. To enable everything, use<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "One IP address or CIDR range per line. To enable everything, use<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "revid": "revision $1",
+       "pageid": "page ID $1"
 }
index 31f0641..68d3155 100644 (file)
        "passwordreset-emaildisabled": "Retpoŝtaj funkcioj estas malfunkciigitaj en tiu ĉi vikio.",
        "passwordreset-username": "Uzantnomo:",
        "passwordreset-domain": "Domajno:",
-       "passwordreset-capture": "Vidi la rezultan retpoŝton?",
-       "passwordreset-capture-help": "Se vi markis ĉi tiun skatoleton, la retpoŝto (kun provizora pasvorto) estos montrita al vi kaj estos sendita al la uzanto.",
        "passwordreset-email": "Retpoŝtadreso:",
        "passwordreset-emailtitle": "Kontaj detaloj en {{SITENAME}}",
        "passwordreset-emailtext-ip": "Iu (verŝajne vi, de IP-adreso $1) petis restarigon de via pasvorto por {{SITENAME}} ($4). La {{PLURAL:$3|jena uzanto-konto estas asociita|jenaj uzanto-kontoj estas asociitaj}} kun ĉi tiu retpoŝtadreso:\n\n$2\n\nĈi {{PLURAL:$3|tiu provizora pasvorto|tiuj provizoraj pasvortoj}} findatiĝos {{PLURAL:$5|unu tagon|$5 tagojn}}.\nVi ensalutu kaj elektu novan pasvorton nun. Se iu alia petis ĉi tion,\naŭ se vi memoris vian originalan pasvorton, kaj vi ne plu volas\nŝanĝi ĝin, vi povas ignori ĉi tiun mesaĝon kaj plu uzi vian \nmalnovan pasvorton.",
        "passwordreset-emailelement": "Salutnomo: \n$1\n\nProvizora pasvorto: \n$2",
        "passwordreset-emailsentemail": "Se tiu ĉu retpoŝta adreso estas kunligita kun via konto, tiam al ĉi tiu adreso estos sendita retpoŝto por renovigi pasvorton.",
        "passwordreset-emailsentusername": "Se estas retpoŝta adreso, kiu estas asociita kun tiu uzantnomo, tiam ni sendos retpoŝtan mesaĝon pri reagordado de la pasvorto.",
-       "passwordreset-emailsent-capture2": "La {{PLURAL:$1|retpoŝto|retpoŝtoj}} por pasvorta restarigo estis {{PLURAL:$1|sendita|senditaj}}. La {{PLURAL:$1|salutnomo kaj pasvorto|listo de salutnomoj kaj pasvortoj}} estas montrita ĉi tie.",
-       "passwordreset-emailerror-capture2": "Retpoŝtado al la {{GENDER:$2|uzanto}} malsukcesis: $1 La {{PLURAL:$3|uzantonomo kaj pasvorto|listo de uzantonomoj kaj pasvortoj}} estas montrita ĉi tie.",
        "passwordreset-nocaller": "Vokanto devas esti provizita",
        "passwordreset-nosuchcaller": "Vokanto ne ekzistas: $1",
        "passwordreset-ignored": "La pasvorta reensignado ne estis pritraktita. Eble neniu provizanto estis formita?",
        "userrights-reason": "Kialo:",
        "userrights-no-interwiki": "Vi ne rajtas redakti uzanto-rajtojn en aliaj vikioj.",
        "userrights-nodatabase": "Datumbazo $1 ne ekzistas aŭ ne estas loka.",
-       "userrights-nologin": "Vi devas [[Special:UserLogin|ensaluti]] per administranto-konto por doni uzanto-rajtojn.",
-       "userrights-notallowed": "Via konto ne rajtas doni aŭ forigi uzanto-rajtojn.",
        "userrights-changeable-col": "Grupoj kiujn vi povas ŝanĝi",
        "userrights-unchangeable-col": "Grupoj kiujn vi ne povas ŝanĝi",
        "userrights-conflict": "Konflikto ĉe la ŝanĝo de uzantorajtoj! Bonvolu kontroli kaj konfirmi viajn ŝanĝojn.",
-       "userrights-removed-self": "Vi nuligis viajn proprajn rajtojn, do vi ne plu rajtas aliri ĉi tiun paĝon.",
        "group": "Grupo:",
        "group-user": "Uzantoj",
        "group-autoconfirmed": "Aŭtomate konfirmitaj uzantoj",
        "right-siteadmin": "Ŝlosi kaj malŝlosi la datumbazon",
        "right-override-export-depth": "Eksporti paĝojn inkluzivante ligitajn paĝojn ĝis profundeco de 5",
        "right-sendemail": "Sendi retpoŝton al aliaj uzantoj",
-       "right-passwordreset": "Vidi retpoŝtojn de pasvorta restarado.",
        "right-managechangetags": "Kreado kaj (mal)aktivgo de [[Special:Tags|etikedoj]]",
        "right-applychangetags": "Aldoni [[Special:Tags|etikedojn]] al propraj ŝanĝoj",
        "right-changetags": "Aldoni kaj forigi arbitrajn [[Special:Tags|etikedojn]] ĉe unuopaj revizioj kaj protokoleroj",
        "recentchanges-label-newpage": "Ĉi tiu redakto kreis novan paĝon",
        "recentchanges-label-minor": "Ĉi tiu estas eta redakto",
        "recentchanges-label-bot": "Ĉi tiu redakto estis farita per roboto.",
-       "recentchanges-label-unpatrolled": "Ĉi tiu redakto ne jam estis patrolata.",
+       "recentchanges-label-unpatrolled": "Ĉi tiu redakto ankoraŭ ne estis patrolita.",
        "recentchanges-label-plusminus": "La paĝa grandeco ŝanĝiĝis je ĉi tiu nombro de bitokoj",
        "recentchanges-legend-heading": "<strong>Klarigo:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (vidu ankaŭ [[Special:NewPages|liston de novaj paĝoj]])",
        "usercssispublic": "Bonvolu noti: subpaĝoj en CSS ne enhavu konfidenciajn datumojn ĉar ili estas videblaj por aliaj uzantoj.",
        "restrictionsfield-badip": "Malvalida IP-adreso de la intervalo: $1",
        "restrictionsfield-label": "Permesita IP-intervalo:",
-       "restrictionsfield-help": "Unu IP-adreso aŭ CIDR-intervalo per linio. Por permesigi ĉion, uzu<br><code>0.0.0.0/0</code><br><code>::/0</code>",
-       "edit-error-short": "Eraro: $1",
-       "edit-error-long": "Eraroj\n\n$1"
+       "restrictionsfield-help": "Unu IP-adreso aŭ CIDR-intervalo per linio. Por permesigi ĉion, uzu<br><code>0.0.0.0/0</code><br><code>::/0</code>"
 }
index 6b8740b..6120b5c 100644 (file)
        "prefswarning-warning": "Has hecho cambios en tus preferencias que todavía no se han guardado. Si sales de esta página sin pulsar en «$1» no se actualizarán las preferencias.",
        "prefs-tabs-navigation-hint": "Sugerencia: Puede utilizar las teclas de flecha izquierda y derecha para navegar entre las pestañas de la lista de pestañas.",
        "userrights": "Gestión de permisos de usuario",
-       "userrights-lookup-user": "Configurar grupos de usuarios",
+       "userrights-lookup-user": "Selecciona una cuenta de usuario",
        "userrights-user-editname": "Escribe un nombre de usuario:",
-       "editusergroup": "Modificar grupos {{GENDER:$1|del usuario|de la usuaria}}",
+       "editusergroup": "Cargar grupos de usuarios",
        "editinguser": "Cambio de los permisos {{GENDER:$1|del usuario|de la usuaria}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Modificar grupos {{GENDER:$1|del usuario| de la usuaria}}",
        "saveusergroups": "Guardar grupos {{GENDER:$1|del usuario|de la usuaria}}",
        "right-upload": "Subir archivos",
        "right-reupload": "Subir una nueva versión de un archivo existente",
        "right-reupload-own": "Subir una nueva versión de un archivo creado por uno mismo",
-       "right-reupload-shared": "Sobreescribir localmente archivos del repositorio multimedia",
+       "right-reupload-shared": "Sobrescribir localmente archivos presentes en el repositorio multimedia compartido",
        "right-upload_by_url": "Subir un archivo a traves de un URL",
        "right-purge": "Purgar la caché en el servidor sin tener que dar confirmación",
        "right-autoconfirmed": "No resultar afectado por los límites de frecuencia de edición para las IP",
        "action-upload_by_url": "subir este archivo desde una URL",
        "action-writeapi": "utilizar la API de escritura",
        "action-delete": "borrar esta página",
-       "action-deleterevision": "borrar esta revisión",
-       "action-deletedhistory": "ver el historial borrado de esta página",
+       "action-deleterevision": "eliminar revisiones",
+       "action-deletedhistory": "ver el historial de ediciones borradas de una página",
        "action-browsearchive": "buscar páginas borradas",
        "action-undelete": "recuperar esta página",
        "action-suppressrevision": "revisar y restaurar esta revisión oculta",
        "action-userrights-interwiki": "modificar los permisos de los usuarios en otros wikis",
        "action-siteadmin": "bloquear o desbloquear la base de datos",
        "action-sendemail": "enviar correos electrónicos",
+       "action-editmyoptions": "editar tus preferencias",
        "action-editmywatchlist": "editar tu lista de seguimiento",
        "action-viewmywatchlist": "ver tu lista de seguimiento",
        "action-viewmyprivateinfo": "ver tu información privada",
        "apisandbox-continue": "Continuar",
        "apisandbox-continue-clear": "Vaciar",
        "apisandbox-param-limit": "Escribe <kbd>max</kbd> para usar el límite máximo.",
+       "apisandbox-multivalue-all-namespaces": "$1 (Todos los espacios de nombres)",
        "booksources": "Fuentes de libros",
        "booksources-search-legend": "Buscar fuentes de libros",
        "booksources-search": "Buscar",
        "api-error-empty-file": "El archivo que enviaste estaba vacío.",
        "api-error-emptypage": "No se pueden crear páginas nuevas que estén vacías.",
        "api-error-fetchfileerror": "Error interno: Algo salió mal mientras se obtenía el archivo.",
-       "api-error-fileexists-forbidden": "Ya existe el archivo \"$1\" y no se puede sobreescribir.",
-       "api-error-fileexists-shared-forbidden": "Ya existe \"$1\" en el repositorio de archivos compartidos y no se puede sobreescribir.",
+       "api-error-fileexists-forbidden": "Ya existe un archivo con el nombre «$1» y no se puede sobrescribir.",
+       "api-error-fileexists-shared-forbidden": "Ya existe un archivo con el nombre «$1» en el repositorio de archivos compartido y no se puede sobrescribir.",
        "api-error-file-too-large": "El archivo que enviaste era demasiado grande.",
        "api-error-filename-tooshort": "El nombre de archivo es demasiado corto.",
        "api-error-filetype-banned": "Este tipo de archivo está prohibido.",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
        "mw-widgets-titleinput-description-new-page": "la página aún no existe",
        "mw-widgets-titleinput-description-redirect": "redirigir a $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Agregar una categoría...",
        "sessionmanager-tie": "No se pueden combinar múltiples tipos de autenticación de solicitudes: $1.",
        "sessionprovider-generic": "sesiones $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sesiones basadas en cookies",
        "log-action-filter-contentmodel-change": "Cambio de modelo de contenido",
        "log-action-filter-contentmodel-new": "Creación de página con modelo de contenidos no estándar",
        "log-action-filter-delete-delete": "Eliminación de páginas",
+       "log-action-filter-delete-delete_redir": "Sobrescritura de la redirección",
        "log-action-filter-delete-restore": "Restauración de páginas",
        "log-action-filter-delete-event": "Eliminación de registros",
        "log-action-filter-delete-revision": "Eliminación de revisión",
index 6699cc8..872451b 100644 (file)
        "tagline": "Allikas: {{SITENAME}}",
        "help": "Juhend",
        "search": "Otsing",
+       "search-ignored-headings": " #<!-- Jäta see rida muutmata kujule. --> <pre>\n# Pealkirjad, mida otsingus eiratakse.\n# Muudatused jõustuvad kohe, kui vastava pealkirjaga lehekülg on indekseeritud.\n# Saad teha tühimuudatuse, selleks et sundida lehekülg uuesti indekseerimisele.\n# Süntaks on järgmine:\n#   * Kõik alates märgist \"#\" kuni rea lõpuni on kommentaar.\n#   * Iga rida, mis ei ole tühi, on eiratava pealkirja täpne tõstutundlik kuju.\nViited\nVälislingid\nVaata ka\n #</pre> <!-- Jäta see rida muutmata kujule. -->",
        "searchbutton": "Otsi",
        "go": "Mine",
        "searcharticle": "Mine",
        "views": "vaatamisi",
        "toolbox": "Tööriistad",
        "tool-link-userrights": "Muuda {{GENDER:$1|kasutajarühmi}}",
+       "tool-link-userrights-readonly": "Vaata {{GENDER:$1|kasutajarühmi}}",
        "tool-link-emailuser": "Saada {{GENDER:$1|kasutajale}} e-kiri",
        "userpage": "Vaata kasutajalehekülge",
        "projectpage": "Vaata projektilehekülge",
        "mainpage-nstab": "Esileht",
        "nosuchaction": "Sellist toimingut pole.",
        "nosuchactiontext": "Viki ei tunne internetiaadressile vastavat tegevust.\nVõimalik, et sa sisestasid aadressi valesti või kasutasid vigast linki.\nSamuti ei ole välistatud, et tarkvaras, mida {{SITENAME}} kasutatab, on viga.",
-       "nosuchspecialpage": "Sellist erilehekülge pole.",
+       "nosuchspecialpage": "Sellist erilehekülge pole",
        "nospecialpagetext": "<strong>Viki ei tunne erilehekülge, mille poole pöördusid.</strong>\n\nKäibel olevad erileheküljed on loetletud leheküljel [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Viga",
        "databaseerror": "Andmebaasi viga",
        "virus-scanfailed": "skaneerimine ebaõnnestus (veakood $1)",
        "virus-unknownscanner": "tundmatu viirusetõrje:",
        "logouttext": "<strong>Oled nüüd välja loginud.</strong>\n\nPane tähele, et seni, kuni sa pole veebilehitseja puhvrit tühjendanud, võidakse mõni lehekülg endiselt kuvada nii nagu oleksid ikka sisse logitud.",
+       "cannotlogoutnow-title": "Praegu ei saa välja logida",
+       "cannotlogoutnow-text": "Väljalogimine pole võimalik, kui kasutad $1.",
        "welcomeuser": "Tere tulemast, $1!",
        "welcomecreation-msg": "Sinu konto on loodud.\nÄra unusta seada oma {{GRAMMAR:genitive|{{SITENAME}}}} [[Special:Preferences|eelistusi]].",
        "yourname": "Kasutajanimi:",
        "createacct-yourpasswordagain-ph": "Sisesta uuesti parool",
        "userlogin-remembermypassword": "Jää sisseloginuks",
        "userlogin-signwithsecure": "Kasuta turvalist ühendust",
+       "cannotlogin-title": "Ei saa sisse logida",
+       "cannotlogin-text": "Sisselogimine pole võimalik.",
+       "cannotloginnow-title": "Praegu ei saa sisse logida",
+       "cannotloginnow-text": "Sisselogimine pole võimalik, kui kasutad $1.",
+       "cannotcreateaccount-title": "Ei saa kontosid luua",
+       "cannotcreateaccount-text": "Kontode käsitsi loomine pole selles vikis lubatud.",
        "yourdomainname": "Sinu domeen:",
        "password-change-forbidden": "Selles vikis ei saa paroole muuta.",
        "externaldberror": "Esines autentimistõrge või sul pole õigust konto andmeid muuta.",
        "botpasswords-label-appid": "Roboti nimi:",
        "botpasswords-label-create": "Loo",
        "resetpass_forbidden": "Paroole ei saa muuta",
+       "resetpass_forbidden-reason": "Paroole ei saa muuta: $1",
        "resetpass-no-info": "Pead olema sisselogitud, et sellele lehele pääseda.",
        "resetpass-submit-loggedin": "Muuda parool",
        "resetpass-submit-cancel": "Loobu",
        "passwordreset-emailelement": "Kasutajanimi: \n$1\n\nAjutine parool: \n$2",
        "passwordreset-emailsentemail": "Kui oled sidunud konto selle e-posti aadressiga, siis saadetakse sulle parooli lähtestamise e-kiri.",
        "passwordreset-emailsentusername": "Parooli lähtestamise e-kiri saadetakse, kui olemas on kontoga seotud e-posti aadress.",
+       "passwordreset-ignored": "Parooli lähtestamine jäi rahuldamata. Võimalik, et ühtegi pakkujat polnud häälestatud.",
+       "passwordreset-invalidemail": "Vigane e-posti aadress",
+       "passwordreset-nodata": "Ära toomata jäid nii kasutajanimi kui ka e-posti aadress",
        "changeemail": "E-posti aadressi muutmine või eemaldamine",
        "changeemail-header": "Täida see vorm, et muuta oma e-posti aadress. Kui soovid, et konto poleks enam seotud ühegi e-posti aadressiga, siis jäta vormi esitamisel e-posti aadressi väli tühjaks.",
        "changeemail-no-info": "Otselingi kaudu sellele lehele jõudmiseks pead olema sisse loginud.",
        "prefs-watchlist-token": "Jälgimisloendi luba:",
        "prefs-misc": "Muu",
        "prefs-resetpass": "Muuda parool",
-       "prefs-changeemail": "Muuda e-posti aadressi või eemalda see",
+       "prefs-changeemail": "muuda e-posti aadressi või eemalda see",
        "prefs-setemail": "Määra e-posti aadress",
        "prefs-email": "E-posti sätted",
        "prefs-rendering": "Ilme",
        "prefs-namespaces": "Nimeruumid",
        "default": "vaikeväärtus",
        "prefs-files": "Failid",
-       "prefs-custom-css": "Kohandatud CSS",
-       "prefs-custom-js": "Kohandatud JavaScript",
+       "prefs-custom-css": "kohandatud CSS",
+       "prefs-custom-js": "kohandatud JavaScript",
        "prefs-common-css-js": "Kõigi kujunduste ühine CSS/JavaScript:",
        "prefs-reset-intro": "Sellel leheküljel saad oma eelistused lähtestada võrgukoha vaike-eelistusteks.\nToimingut ei saa hiljem tühistada.",
        "prefs-emailconfirm-label": "E-posti kinnitus:",
        "grant-editpage": "Olemasolevate lehekülgede redigeerimine",
        "grant-editprotected": "Kaitstud lehekülgede redigeerimine",
        "grant-highvolume": "Suuremahuline redigeerimine",
+       "grant-oversight": "Kasutajate peitmine ja redaktsioonide varjamine",
        "grant-patrol": "Lehekülgede muudatuste kontroll",
        "grant-protect": "Lehekülgede kaitsmine ja kaitse eemaldamine",
        "grant-rollback": "Lehekülgede muudatuste tühistamine",
        "action-upload_by_url": "seda faili internetiaadressilt üles laadida",
        "action-writeapi": "kirjutamise rakendusliidest kasutada",
        "action-delete": "seda lehekülge kustutada",
-       "action-deleterevision": "seda redaktsiooni kustutada",
-       "action-deletedhistory": "selle lehekülje kustutatud ajalugu vaadata",
+       "action-deleterevision": "redaktsioone kustutada",
+       "action-deletedhistory": "vaadata lehekülje kustutatud ajalugu",
        "action-browsearchive": "kustutatud lehekülgi otsida",
        "action-undelete": "lehekülgi taastada",
-       "action-suppressrevision": "seda peidetud redaktsiooni vaadata ega taastada",
+       "action-suppressrevision": "peidetud redaktsioone vaadata ega taastada",
        "action-suppressionlog": "seda eralogi vaadata",
        "action-block": "selle kasutaja redigeerimisõigust blokeerida",
        "action-protect": "selle lehekülje kaitsetasemeid muuta",
        "allpagesbadtitle": "Lehekülje pealkiri oli vigane või sisaldas teise viki või keele eesliidet.\nSee võib sisaldada üht või enamat märki, mida ei saa pealkirjades kasutada.",
        "allpages-bad-ns": "{{GRAMMAR:inessive|{{SITENAME}}}} ei ole nimeruumi \"$1\".",
        "allpages-hide-redirects": "Peida ümbersuunamised",
-       "cachedspecial-viewing-cached-ttl": "Vaata vahemälus olevat lehekülje versiooni, mis võib olla kuni $1 vana.",
+       "cachedspecial-viewing-cached-ttl": "Sa vaatad lehekülje vahemälus olevat versiooni, mis võib olla kuni $1 vana.",
        "cachedspecial-viewing-cached-ts": "Vaatad vahemälus olevat lehekülje versiooni, mis ei pruugi olla täiesti ajakohane.",
        "cachedspecial-refresh-now": "Vaata uusimat versiooni.",
        "categories": "Kategooriad",
        "activeusers-intro": "See on loetelu kasutajatest, kes on viimase $1 {{PLURAL:$1|päev|päeva}} jooksul midagi teinud.",
        "activeusers-count": "$1 {{PLURAL:$1|toiming|toimingut}} viimase {{PLURAL:$3|päeva|$3 päeva}} jooksul",
        "activeusers-from": "Näita kasutajaid alates:",
+       "activeusers-groups": "Kuva kasutajad, kes kuuluvad järgmistesse rühmadesse:",
+       "activeusers-excludegroups": "Ära kuva kasutajaid, kes kuuluvad järgmistesse rühmadesse:",
        "activeusers-noresult": "Kasutajaid ei leidunud.",
        "activeusers-submit": "Kuva aktiivsed kasutajad",
        "listgrouprights": "Kasutajarühma õigused",
        "emailccsubject": "Koopia sinu sõnumist kasutajale $1: $2",
        "emailsent": "E-kiri saadetud",
        "emailsenttext": "Sinu teade on e-kirjaga saadetud.",
-       "emailuserfooter": "Selle e-kirja saatis $1 {{GRAMMAR:elative|{{SITENAME}}}} kasutajale $2 toimingu \"{{int:emailuser}}\" abil.",
+       "emailuserfooter": "Selle e-kirja saatis $1 {{GRAMMAR:elative|{{SITENAME}}}} kasutajale $2 toimingu \"{{int:emailuser}}\" abil. Sinu kiri saadetakse otse algse kirja saatjale, mistõttu saab ta sinu e-posti aadressi teada.",
        "usermessage-summary": "Jätan süsteemiteate.",
        "usermessage-editor": "Süsteemiteadete edastaja",
        "watchlist": "Jälgimisloend",
        "mywatchlist": "Jälgimisloend",
-       "watchlistfor2": "Kasutaja $1 $2 jaoks",
+       "watchlistfor2": "Kasutaja $1 $2 jälgimisloend",
        "nowatchlist": "Sinu jälgimisloend on tühi.",
        "watchlistanontext": "Palun logi sisse, et oma jälgimisloendit näha või muuta.",
        "watchnologin": "Ei ole sisse logitud",
        "contributions-title": "Kasutaja $1 kaastöö",
        "mycontris": "Kaastöö",
        "anoncontribs": "Kaastöö",
-       "contribsub2": "Kasutaja {{GENDER:$3|$1}} ($2) jaoks",
+       "contribsub2": "Kasutaja {{GENDER:$3|$1}} ($2) kaastöö",
        "contributions-userdoesnotexist": "Kasutajakonto \"$1\" pole registreeritud.",
        "nocontribs": "Antud kriteeriumitele vastavaid muudatusi ei leitud.",
        "uctop": "(praegune)",
        "move-page": "Lehekülje \"$1\" teisaldamine",
        "move-page-legend": "Lehekülje teisaldamine",
        "movepagetext": "Allolevat vormi kasutades saad lehekülje ümber nimetada. Lehekülje ajalugu tõstetakse uue pealkirja alla automaatselt.\nPraeguse pealkirjaga leheküljest saab ümbersuunamislehekülg uuele leheküljele.\nSaad senisele pealkirjale viitavad ümbersuunamised automaatselt parandada.\nKui sa seda ei tee, kontrolli, et teisaldamise tõttu ei jää maha [[Special:DoubleRedirects|kahekordseid]] ega [[Special:BrokenRedirects|katkiseid ümbersuunamisi]].\nSinu kohus on hoolitseda selle eest, et kõik jääks toimima, nagu ette nähtud.\n\nPane tähele, et lehekülge <strong>ei teisaldata</strong> juhul, kui uue pealkirjaga lehekülg on juba olemas. Erandiks on juhud, kui viimane on redigeerimisajaloota ümbersuunamislehekülg.\nSee tähendab, et kogemata ei saa üle kirjutada juba olemasolevat lehekülge, kuid saab ebaõnnestunud ümbernimetamise tagasi pöörata.\n\n<strong>Märkus:</strong>\nTegu võib olla väga loetava lehekülje jaoks tõsise ja ootamatu muudatusega;\nenne jätkamist teadvusta palun tagajärgi.",
-       "movepagetext-noredirectfixer": "Allolevat vormi kasutades saad lehekülje ümber nimetada. Lehekülje ajalugu tõstetakse uue pealkirja alla automaatselt.\nPraeguse pealkirjaga leheküljest saab ümbersuunamislehekülg uuele leheküljele.\nKontrolli, et teisaldamise tõttu ei jää maha [[Special:DoubleRedirects|kahekordseid]] ega [[Special:BrokenRedirects|katkiseid ümbersuunamisi]].\nSinu kohus on hoolitseda selle eest, et kõik jääks toimima, nagu ette nähtud.\n\nPane tähele, et lehekülge <strong>ei teisaldata</strong> juhul, kui uue pealkirjaga lehekülg on juba olemas. Erandiks on juhud, kui olemasolev lehekülg on tühi või redigeerimisajaloota ümbersuunamislehekülg.\nSee tähendab, et kogemata ei saa üle kirjutada juba olemasolevat lehekülge, kuid saab ebaõnnestunud ümbernimetamise tagasi pöörata.\n\n<strong>Note:</strong>\nTegu võib olla väga loetava lehekülje jaoks tõsise ja ootamatu muudatusega;\nenne jätkamist teadvusta palun tagajärgi.",
+       "movepagetext-noredirectfixer": "Allolevat vormi kasutades saad lehekülje ümber nimetada. Lehekülje ajalugu tõstetakse uue pealkirja alla automaatselt.\nPraeguse pealkirjaga leheküljest saab ümbersuunamislehekülg uuele leheküljele.\nKontrolli, et teisaldamise tõttu ei jää maha [[Special:DoubleRedirects|kahekordseid]] ega [[Special:BrokenRedirects|katkiseid ümbersuunamisi]].\nSinu kohus on hoolitseda selle eest, et kõik jääks toimima, nagu ette nähtud.\n\nPane tähele, et lehekülge <strong>ei teisaldata</strong> juhul, kui uue pealkirjaga lehekülg on juba olemas. Erandiks on juhud, kui olemasolev lehekülg on tühi või redigeerimisajaloota ümbersuunamislehekülg.\nSee tähendab, et kogemata ei saa üle kirjutada juba olemasolevat lehekülge, kuid saab ebaõnnestunud ümbernimetamise tagasi pöörata.\n\n<strong>Hoiatus!</strong>\nTegu võib olla väga loetava lehekülje jaoks tõsise ja ootamatu muudatusega;\nenne jätkamist teadvusta palun tagajärgi.",
        "movepagetalktext": "Kui märgid selle ruudu, teisaldatakse arutelulehekülg automaatselt uue pealkirja alla. Seda välja arvatud juhul, kui uue pealkirja all on juba arutelulehekülg, mis pole tühi.\n\nSel juhul saad lehekülje soovi korral käsitsi teisaldada või liita.",
        "moveuserpage-warning": "'''Hoiatus:''' Oled teisaldamas kasutajalehekülge. Pane tähele, et teisaldatakse ainult lehekülg ja kasutajat '''ei''' nimetata ümber.",
        "movecategorypage-warning": "<strong>Hoiatus:</strong> Oled teisaldamas kategoorialehekülge. Pane palun tähele, et teisaldatakse vaid see lehekülg ja ühtegi vanas kategoorias sisalduvat lehekülge <em>ei</em> kategoriseerita ümber uude kategooriasse.",
        "watchlistedit-clear-removed": "{{PLURAL:$1|Üks pealkiri|$1 pealkirja}} eemaldati:",
        "watchlistedit-too-many": "Pealkirju on siin kuvamiseks liiga palju.",
        "watchlisttools-clear": "Tühjenda jälgimisloend",
-       "watchlisttools-view": "Näita vastavaid muudatusi",
-       "watchlisttools-edit": "Vaata ja redigeeri jälgimisloendit",
-       "watchlisttools-raw": "Muuda lähteteksti",
+       "watchlisttools-view": "näita vastavaid muudatusi",
+       "watchlisttools-edit": "vaata ja redigeeri jälgimisloendit",
+       "watchlisttools-raw": "redigeeri jälgimisloendi toorandmeid",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|arutelu]])",
        "duplicate-defaultsort": "'''Hoiatus:''' Järjestamisvõti \"$2\" tühistab eespool oleva järjestamisvõtme \"$1\".",
        "duplicate-displaytitle": "<strong>Hoiatus:</strong> Kuvatava pealkirjaga \"$2\" kirjutatakse üle varasem kuvatav pealkiri \"$1\".",
        "htmlform-user-not-exists": "Kasutajat <strong>$1</strong> pole olemas.",
        "htmlform-user-not-valid": "<strong>$1</strong> pole sobiv kasutajanimi.",
        "logentry-delete-delete": "$1 {{GENDER:$2|kustutas}} lehekülje $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|kustutas}} ülekirjutamise teel ümbersuunamise $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|taastas}} lehekülje $3",
        "logentry-delete-event": "$1 {{GENDER:$2|muutis}} leheküljel $3 {{PLURAL:$5|ühe|$5}} logisündmuse nähtavust: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|muutis}} leheküljel $3 {{PLURAL:$5|ühe|$5}} redaktsiooni nähtavust: $4",
        "mw-widgets-dateinput-placeholder-month": "AAAA-KK",
        "mw-widgets-titleinput-description-new-page": "lehekülge pole veel",
        "mw-widgets-titleinput-description-redirect": "ümbersuunamine leheküljele \"$1\"",
+       "mw-widgets-categoryselector-add-category-placeholder": "Lisa kategooria...",
+       "sessionprovider-generic": "klassi $1 seansse",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "küpsisepõhiseid seansse",
        "randomrootpage": "Juhuslik juurlehekülg",
        "log-action-filter-block": "Blokeeringu tüüp:",
+       "log-action-filter-contentmodel": "Sisumudeli muudatuse tüüp:",
        "log-action-filter-delete": "Kustutamise tüüp:",
+       "log-action-filter-import": "Impordi tüüp:",
+       "log-action-filter-managetags": "Märgiste haldamistegevuse tüüp:",
+       "log-action-filter-move": "Teisaldamise tüüp:",
+       "log-action-filter-newusers": "Konto loomise tüüp:",
+       "log-action-filter-patrol": "Kontrolli tüüp:",
+       "log-action-filter-protect": "Kaitsmise tüüp:",
+       "log-action-filter-rights": "Õiguste muudatuse tüüp:",
+       "log-action-filter-suppress": "Varjamise tüüp:",
+       "log-action-filter-upload": "Üleslaadimise tüüp:",
        "log-action-filter-all": "Kõik",
        "log-action-filter-block-block": "Blokeerimine",
        "log-action-filter-block-reblock": "Blokeeringu muutmine",
        "log-action-filter-block-unblock": "Blokeeringu tühistamine",
+       "log-action-filter-contentmodel-change": "Sisumudeli muudatus",
+       "log-action-filter-contentmodel-new": "Ebastandardse sisumudeliga lehekülje loomine",
        "log-action-filter-delete-delete": "Lehekülje kustutamine",
+       "log-action-filter-delete-delete_redir": "Ümbersuunamise ülekirjutamine",
        "log-action-filter-delete-restore": "Lehekülje taastamine",
        "log-action-filter-delete-event": "Logi kustutamine",
        "log-action-filter-delete-revision": "Redaktsiooni kustutamine",
+       "log-action-filter-import-interwiki": "Vikidevaheline import",
+       "log-action-filter-import-upload": "XML-faili üleslaadimisega import",
+       "log-action-filter-managetags-create": "Märgise koostamine",
+       "log-action-filter-managetags-delete": "Märgise kustutamine",
+       "log-action-filter-managetags-activate": "Märgise lubamine",
+       "log-action-filter-managetags-deactivate": "Märgise keelamine",
+       "log-action-filter-move-move": "Teisaldamine ümbersuunamise ülekirjutamiseta",
+       "log-action-filter-move-move_redir": "Teisaldamine ümbersuunamise ülekirjutamisega",
+       "log-action-filter-newusers-create": "Loonud anonüümne kasutaja",
+       "log-action-filter-newusers-create2": "Loonud registreeritud kasutaja",
+       "log-action-filter-newusers-autocreate": "Loodud automaatselt",
+       "log-action-filter-newusers-byemail": "Loodud e-kirjatsi saadetud parooliga",
+       "log-action-filter-patrol-patrol": "Kontrollitud käsitsi",
+       "log-action-filter-patrol-autopatrol": "Kontrollitud automaatselt",
+       "log-action-filter-protect-protect": "Kaitsmine",
+       "log-action-filter-protect-modify": "Kaitse muutmine",
+       "log-action-filter-protect-unprotect": "Kaitse eemaldamine",
+       "log-action-filter-protect-move_prot": "Kaitse teisaldamine",
+       "log-action-filter-rights-rights": "Muudetud käsitsi",
+       "log-action-filter-rights-autopromote": "Muudetud automaatselt",
+       "log-action-filter-suppress-event": "Logi varjamine",
+       "log-action-filter-suppress-revision": "Redaktsiooni varjamine",
+       "log-action-filter-suppress-delete": "Lehekülje varjamine",
+       "log-action-filter-suppress-block": "Kasutaja varjamine blokeerimise teel",
+       "log-action-filter-suppress-reblock": "Kasutaja varjamine taasblokeerimise teel",
+       "log-action-filter-upload-upload": "Uus üleslaadimine",
+       "log-action-filter-upload-overwrite": "Uuesti üleslaadimine",
        "authmanager-provider-password": "Paroolipõhine autentimine",
        "authmanager-provider-password-domain": "Parooli- ja domeenipõhine autentimine",
        "authmanager-provider-temporarypassword": "Ajutine parool",
index c45118c..22ec9c5 100644 (file)
@@ -26,7 +26,8 @@
                        "Xð",
                        "Asierog",
                        "Matma Rex",
-                       "Gorkaazk"
+                       "Gorkaazk",
+                       "Vriullop"
                ]
        },
        "tog-underline": "Azpimarratu loturak:",
        "enotif_body_intro_deleted": "{{SITENAME}}(e)ko $1 orrialdea {{GENDER:$2|ezabatu}} du $2 erabiltzaileak $PAGEEDITDATE datan, ikus $3.",
        "enotif_body_intro_created": "{{SITENAME}}(e)ko $1 orrialdea {{GENDER:$2|sortu}} du $2 erabiltzaileak $PAGEEDITDATE datan, ikus $3 oraingo bertsiorako.",
        "enotif_body_intro_moved": "{{SITENAME}}(e)ko $1 orrialdea {{GENDER:$2|mugitu}} du $2 erabiltzaileak $PAGEEDITDATE datan, ikus $3 oraingo bertsiorako.",
-       "enotif_body_intro_restored": "{{SITENAME}}(e)ko $1 orrialdea {{GENDER:$2|berrezarri}} du $2 erabiltzaileak $PAGEEDITDATE datan, ikus $3 oraingo bertsiorako.",
+       "enotif_body_intro_restored": "{{SITENAME}} guneko «$1» orria {{GENDER:$2|lehengoratu}} du $2 administratzaileak $PAGEEDITDATE datan. Oraingo bertsioa ikusteko, zoaz helbide honetara: $3.",
        "enotif_body_intro_changed": "{{SITENAME}}(e)ko $1 orrialdea {{GENDER:$2|aldatu}} du $2 erabiltzaileak $PAGEEDITDATE datan, ikus $3 oraingo bertsiorako.",
        "enotif_lastvisited": "Ikus «$1» zure azken bisitaz geroztik izandako aldaketa guztiak ikusteko.",
        "enotif_lastdiff": "Jo $1(e)ra aldaketa hau ikusteko.",
        "confirm-unwatch-button": "Ados",
        "confirm-unwatch-top": "Orrialde hau zure jarraipen-zerrendatik kendu?",
        "confirm-rollback-button": "Ados",
+       "word-separator": "&#32;",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "&larr; aurreko orrialdea",
        "imgmultipagenext": "hurrengo orrialdea &rarr;",
        "htmlform-user-not-exists": "<strong>$1</strong> ez da existitzen.",
        "htmlform-user-not-valid": "<strong>$1</strong> erabiltzaile izena ezin da erabili.",
        "logentry-delete-delete": "$1 {{GENDER:$2|wikilariak}} «$3» orria ezabatu du",
-       "logentry-delete-restore": "$1(e)k $3 orrialdea {{GENDER:$2|berrezarri}} du",
+       "logentry-delete-restore": "$1 administratzaileak «$3» orria {{GENDER:$2|lehengoratu}} du",
        "logentry-delete-event": "$1 wikilariak ikusgaitasuna {{{{GENDER:$2|}}|aldatu}} {{PLURAL:$5|dio erregistroko sarrera bati|die erregistroko $5 sarrerari}}, $3 orrian: $4",
        "logentry-delete-revision": "$1 erabiltzaileak {{PLURAL:$5|berrikuste baten|$5 berrikusteren}} ikusgaitasuna aldatu du «$3» orrian: $4",
        "logentry-suppress-delete": "$1 erabiltzaileak $3 orrialdea {{GENDER:$2|ezabatu}} du",
index 4bffbc9..3a773d1 100644 (file)
                ]
        },
        "tog-underline": "خط کشیدن زیر پیوندها:",
-       "tog-hideminor": "نهفتن تغییرات جزئی از فهرست تغییرات اخیر",
-       "tog-hidepatrolled": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú¯Ø´Øªâ\80\8cØ®Ù\88ردÙ\87 Ø§Ø² Ù\81Ù\87رست ØªØºÛ\8cÛ\8cرات Ø§Ø®Û\8cر",
-       "tog-newpageshidepatrolled": "نهفتن صفحه‌های گشت‌خورده از فهرست صفحه‌های تازه",
+       "tog-hideminor": "تغییرات جزئی از فهرست تغییرات اخیر پنهان شود",
+       "tog-hidepatrolled": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú¯Ø´Øªâ\80\8cØ®Ù\88ردÙ\87 Ø§Ø² Ù\81Ù\87رست ØªØºÛ\8cÛ\8cرات Ø§Ø®Û\8cر Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
+       "tog-newpageshidepatrolled": "صفحه‌های گشت‌خورده از فهرست صفحه‌های تازه پنهان شود",
        "tog-hidecategorization": "نهفتن رده‌بندی صفحه‌ها",
        "tog-extendwatchlist": "گسترش فهرست پیگیری‌ها برای نمایش همهٔ تغییرات، نه فقط جدیدترین‌ها",
-       "tog-usenewrc": "گروه‌بندی تغییرات بر پایهٔ صفحه‌ در تغییرات اخیر و فهرست پیگیری‌ها",
+       "tog-usenewrc": "در تغییرات اخیر و فهرست پیگیری‌ها تغییرات بر پایهٔ صفحه‌ گروه‌بندی شود",
        "tog-numberheadings": "شماره‌گذاری خودکار عنوان‌ها",
-       "tog-showtoolbar": "Ù\86Ù\85اÛ\8cØ´ Ù\86Ù\88ار Ø§Ø¨Ø²Ø§Ø± Ù\88Û\8cراÛ\8cØ´",
+       "tog-showtoolbar": "Ù\86Ù\88ار Ø§Ø¨Ø²Ø§Ø± Ù\88Û\8cراÛ\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¯Ø§Ø¯Ù\87 Ø´Ù\88د",
        "tog-editondblclick": "ویرایش صفحات با دو کلیک",
        "tog-editsectiononrightclick": "فعال کردن ویرایش بخش‌ها با کلیک راست روی عنوان بخش‌ها",
-       "tog-watchcreations": "اÙ\81زÙ\88دÙ\86 ØµÙ\81Ø­Ù\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ù\85Û\8câ\80\8cسازÙ\85 Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ø¨Ø§Ø±Ú¯Ø°Ø§Ø±Û\8c Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86",
-       "tog-watchdefault": "اÙ\81زÙ\88دÙ\86 ØµÙ\81Ø­Ù\87â\80\8cÙ\87ا Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ù\88Û\8cراÛ\8cØ´ Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86",
-       "tog-watchmoves": "اÙ\81زÙ\88دÙ\86 ØµÙ\81Ø­Ù\87â\80\8cÙ\87ا Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ù\85Ù\86تÙ\82Ù\84 Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86",
-       "tog-watchdeletion": "اÙ\81زÙ\88دÙ\86 ØµÙ\81حات Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ø­Ø°Ù\81 Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86",
-       "tog-watchuploads": "افزودن پرونده‌های جدید به فهرست پیگیری من",
+       "tog-watchcreations": "صÙ\81Ø­Ù\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ù\85Û\8câ\80\8cسازÙ\85 Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ø¨Ø§Ø±Ú¯Ø°Ø§Ø±Û\8c Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86 Ø§Ù\81زÙ\88دÙ\87 Ø´Ù\88د",
+       "tog-watchdefault": "صÙ\81Ø­Ù\87â\80\8cÙ\87ا Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ù\88Û\8cراÛ\8cØ´ Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86 Ø§Ù\81زÙ\88دÙ\87 Ø´Ù\88د",
+       "tog-watchmoves": "صÙ\81Ø­Ù\87â\80\8cÙ\87ا Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ù\85Ù\86تÙ\82Ù\84 Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86 Ø§Ù\81زÙ\88دÙ\87 Ø´Ù\88د",
+       "tog-watchdeletion": "صÙ\81Ø­Ù\87â\80\8cÙ\87ا Ù\88 Ù¾Ø±Ù\88Ù\86دÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ú©Ù\87 Ø­Ø°Ù\81 Ù\85Û\8câ\80\8cÚ©Ù\86Ù\85 Ø¨Ù\87 Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8c Ù\85Ù\86 Ø§Ù\81زÙ\88دÙ\87 Ø´Ù\88د",
+       "tog-watchuploads": "پرونده‌های جدیدی که بارگذاری می‌کنم به فهرست پیگیری من افزوده شود",
        "tog-watchrollback": "افزودن صفحاتی که واگردانی می‌کنم به فهرست پیگیری‌های من",
-       "tog-minordefault": "علامت زدن همهٔ ویرایش‌ها به عنوان «جزئی» به طور پیش‌فرض",
-       "tog-previewontop": "Ù\86Ù\85اÛ\8cØ´ Ø¯Ø§Ø¯Ù\86 Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ø¨Ø§Ù\84اÛ\8c Ø¬Ø¹Ø¨Ù\87Ù\94 Ù\88Û\8cراÛ\8cØ´",
-       "tog-previewonfirst": "Ù\86Ù\85اÛ\8cØ´ Ø¯Ø§Ø¯Ù\86 Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ù\87Ù\86گاÙ\85 Ø§Ù\88Ù\84Û\8cÙ\86 Ù\88Û\8cراÛ\8cØ´",
+       "tog-minordefault": "همهٔ ویرایش‌ها به طور پیش‌فرض به عنوان «جزئی» علامت زده شود",
+       "tog-previewontop": "Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ø¨Ø§Ù\84اÛ\8c Ø¬Ø¹Ø¨Ù\87Ù\94 Ù\88Û\8cراÛ\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¯Ø§Ø¯Ù\87 Ø´Ù\88د",
+       "tog-previewonfirst": "Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ù\87Ù\86گاÙ\85 Ø§Ù\88Ù\84Û\8cÙ\86 Ù\88Û\8cراÛ\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¯Ø§Ø¯Ù\87 Ø´Ù\88د",
        "tog-enotifwatchlistpages": "اگر صفحه یا پرونده‌ای از فهرست پی‌گیری‌هایم ویرایش شد به من ایمیلی فرستاده شود",
        "tog-enotifusertalkpages": "هنگامی که در صفحهٔ بحث کاربری‌ام تغییری صورت می‌گیرد به من ایمیلی فرستاده شود",
        "tog-enotifminoredits": "برای تغییرات جزئی در صفحه‌ها و پرونده‌ها هم به من ایمیلی فرستاده شود",
        "tog-enotifrevealaddr": "نشانی پست الکترونیکی من در ایمیل‌های اطلاع‌رسانی هویدا گردد",
        "tog-shownumberswatching": "نمایشِ شمار کاربران پی‌گیری کننده",
-       "tog-oldsig": "امضای کنونی:",
+       "tog-oldsig": "امضای کنونی شما:",
        "tog-fancysig": "امضا به صورت ویکی‌متن در نظر گرفته شود (بدون درج خودکار پیوند)",
-       "tog-uselivepreview": "استÙ\81ادÙ\87 Ø§Ø² Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ø²Ù\86دÙ\87",
+       "tog-uselivepreview": "از Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ø²Ù\86دÙ\87 Ø§Ø³ØªÙ\81ادÙ\87 Ø´Ù\88د",
        "tog-forceeditsummary": "هنگامی که خلاصهٔ ویرایش ننوشته‌ام به من اطلاع داده شود",
-       "tog-watchlisthideown": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ø®Ù\88دÙ\85 Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا",
-       "tog-watchlisthidebots": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ø±Ø¨Ø§Øªâ\80\8cÙ\87ا Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا",
-       "tog-watchlisthideminor": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ø¬Ø²Ø¦Û\8c Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا",
-       "tog-watchlisthideliu": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú©Ø§Ø±Ø¨Ø±Ø§Ù\86 Ù\88ارد Ø´Ø¯Ù\87 Ø¨Ù\87 Ø³Ø§Ù\85اÙ\86Ù\87 Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا",
-       "tog-watchlistreloadautomatically": "زمانی که یک پالایه تغییر کرد فهرست پیگیری به صورت خودکار به روز می‌شود (نیازمند جاوا اسکریپت)",
-       "tog-watchlisthideanons": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú©Ø§Ø±Ø¨Ø±Ø§Ù\86 Ù\86اشÙ\86اس Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا",
-       "tog-watchlisthidepatrolled": "Ù\86Ù\87Ù\81تÙ\86 Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú¯Ø´Øªâ\80\8cØ®Ù\88ردÙ\87 Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا",
+       "tog-watchlisthideown": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ø®Ù\88دÙ\85 Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
+       "tog-watchlisthidebots": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ø±Ø¨Ø§Øªâ\80\8cÙ\87ا Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
+       "tog-watchlisthideminor": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ø¬Ø²Ø¦Û\8c Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
+       "tog-watchlisthideliu": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú©Ø§Ø±Ø¨Ø±Ø§Ù\86 Ù\88ارد Ø´Ø¯Ù\87 Ø¨Ù\87 Ø³Ø§Ù\85اÙ\86Ù\87 Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
+       "tog-watchlistreloadautomatically": "زمانی که یک پالایه تغییر کرد فهرست پیگیری به صورت خودکار به روز شود (نیازمند جاوااسکریپت)",
+       "tog-watchlisthideanons": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú©Ø§Ø±Ø¨Ø±Ø§Ù\86 Ù\86اشÙ\86اس Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
+       "tog-watchlisthidepatrolled": "Ù\88Û\8cراÛ\8cØ´â\80\8cÙ\87اÛ\8c Ú¯Ø´Øªâ\80\8cØ®Ù\88ردÙ\87 Ø¯Ø± Ù\81Ù\87رست Ù¾Û\8câ\80\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87ا Ù¾Ù\86Ù\87اÙ\86 Ø´Ù\88د",
        "tog-watchlisthidecategorization": "نهفتن رده‌بندی صفحه‌ها",
        "tog-ccmeonemails": "رونوشتی از ایمیلی که به دیگران ارسال می‌کنم برای خودم هم فرستاده شود",
        "tog-diffonly": "محتوای صفحه، زیر تفاوت نمایش داده نشود",
-       "tog-showhiddencats": "نمایش رده‌های پنهان",
+       "tog-showhiddencats": "رده‌های پنهان نمایش داده شود",
        "tog-norollbackdiff": "بعد از واگردانی، تفاوت نشان داده نشود",
        "tog-useeditwarning": "زمان خروج از صفحهٔ ویرایش در صورت داشتن ویرایش‌های‌ ذخیره‌نشده به من هشدار داده شود",
        "tog-prefershttps": "هنگامی که به سامانه وارد شده‌ام، همواره از اتصال امن استفاده شود",
        "newwindow": "(در پنجرهٔ تازه باز می‌شود)",
        "cancel": "لغو",
        "moredotdotdot": "بیشتر...",
-       "morenotlisted": "این فهرست کامل نیست.",
+       "morenotlisted": "این فهرست ممکن است کامل نباشد.",
        "mypage": "صفحه",
        "mytalk": "بحث",
        "anontalk": "بحث",
        "views": "بازدیدها",
        "toolbox": "ابزارها",
        "tool-link-userrights": "تغییر گروه‌های {{GENDER:$1|کاربر}}",
+       "tool-link-userrights-readonly": "نمایش گروه‌های {{GENDER:$1|کاربر}}",
        "tool-link-emailuser": "فرستادن نامه به {{GENDER:$1|کاربر}}",
        "userpage": "نمایش صفحهٔ کاربر",
        "projectpage": "نمایش صفحهٔ پروژه",
        "createacct-yourpasswordagain-ph": "گذرواژه را وارد کنید برای بار دوم",
        "userlogin-remembermypassword": "من را واردشده نگه‌دار",
        "userlogin-signwithsecure": "از ورود امن استفاده کنید",
+       "cannotlogin-title": "ناتوان از ورود به سامانه",
+       "cannotlogin-text": "ورود به سامانه ممکن نیست.",
        "cannotloginnow-title": "الان امکان وررود به سامانه نیست",
        "cannotloginnow-text": "در زمان استفاده از $1 امکان ورود به سامانه وجود ندارد.",
+       "cannotcreateaccount-title": "ناتوان از ایجاد حساب",
+       "cannotcreateaccount-text": "در این ویکی ایجاد مستقیم حساب فعال نشده‌است.",
        "yourdomainname": "دامنهٔ شما:",
        "password-change-forbidden": "شما نمی‌توانید گذرواژه‌ها را در این ویکی تغییر دهید.",
        "externaldberror": "خطایی در ارتباط با پایگاه داده رخ داده است یا اینکه شما اجازهٔ به‌روزرسانی حساب خارجی خود را ندارید.",
        "eauthentsent": "یک ایمیل تأیید برای آدرس ایمیل به نشانی مورد نظر ارسال شد.\nقبل از اینکه ایمیل دیگری قابل ارسال به این آدرس باشد، باید دستورهایی که در آن ایمیل آمده است را جهت تأیید این مساله که این آدرس متعلق به شماست، اجرا کنید.",
        "throttled-mailpassword": "یک ایمیل بازنشانی گذرواژه در $1 {{PLURAL:$1|ساعت|ساعت}} گذشته فرستاده شده است.\nبرای جلوگیری از سوءاستفاده، هر $1 {{PLURAL:$1|ساعت|ساعت}} تنها یک ایمیل بازنشانی گذرواژه فرستاده می‌شود.",
        "mailerror": "خطا در ارسال ایمیل: $1",
-       "acct_creation_throttle_hit": "بازدیدکنندگان این ویکی که از نشانی آی‌پی شما استفاده می‌کنند در روز گذشته {{PLURAL:$1|یک حساب کاربری|$1 حساب کاربری}} ساخته‌اند، که بیشترین تعداد مجاز در آن بازهٔ زمانی است.\nبه همین خاطر، بازدیدکنندگانی که از این نشانی آی‌پی استفاده می‌کنند نمی‌توانند در حال حاضر حساب جدیدی بسازند.",
+       "acct_creation_throttle_hit": "بازدیدکنندگان این ویکی که از نشانی آی‌پی شما استفاده می‌کنند در $2 گذشته {{PLURAL:$1|یک حساب کاربری|$1 حساب کاربری}} ساخته‌اند، که بیشترین تعداد مجاز در آن بازهٔ زمانی است.\nبه همین خاطر، بازدیدکنندگانی که از این نشانی آی‌پی استفاده می‌کنند نمی‌توانند در حال حاضر حساب جدیدی بسازند.",
        "emailauthenticated": "نشانی ایمیل شما در $2 ساعت $3 تأیید شده است.",
        "emailnotauthenticated": "آدرس ایمیل شما هنوز تأیید نشده است.\nبرای هیچ‌یک از ویژگی‌های زیر، ایمیل ارسال نخواهد شد.",
        "noemailprefs": "برای راه‌اندازی این قابلیت‌ها یک آدرس ایمیل در ترجیحات خود مشخص کنید.",
        "botpasswords-label-delete": "حذف",
        "botpasswords-label-resetpassword": "بازگردانی گذرواژه",
        "botpasswords-label-grants": "اعطاهای اجرا شدنی:",
-       "botpasswords-help-grants": "Ù\87ر Ø§Ø¬Ø§Ø²Ù\87 Ø¨Ù\87 Ø­Ù\82Ù\88Ù\82 Ú©Ø§Ø±Ø¨Ø±Û\8c Ú©Ù\87 Û\8cÚ© Ø­Ø³Ø§Ø¨ Ú©Ø§Ø±Ø¨Ø±Û\8c Ø¯Ø§Ø±د. [[Special:ListGrants|table of grants]] را برای اطلاعات بیشتر مشاهده کنید.",
+       "botpasswords-help-grants": "Ù\87ر Ø§Ø¬Ø§Ø²Ù\87 Ø¨Ù\87 Ø±Ø¨Ø§Øª Ø§Ø¬Ø§Ø²Ù\87 Ø¯Ø³ØªØ±Ø³Û\8c Ø¨Ù\87 Ø§Ø®ØªÛ\8cاراتÛ\8c Ø±Ø§ Ú©Ù\87 Ø­Ø³Ø§Ø¨ Ø´Ù\85ا Ø¯Ø§Ø±Ø¯ Ù\85Û\8câ\80\8cدÙ\87د. Ù\81عاÙ\84 Ú©Ø±Ø¯Ù\86 Û\8cÚ© Ø§Ø¬Ø§Ø²Ù\87 Ø¯Ø± Ø§Û\8cÙ\86جا Ù\87Û\8cÚ\86 Ø¯Ø³ØªØ±Ø³Û\8c Ø¬Ø¯Û\8cدÛ\8c Ú©Ù\87 Ø­Ø³Ø§Ø¨ Ø´Ù\85ا Ù\87Ù\85Û\8cÙ\86Ú© Ø¯Ø§Ø±Ø§ Ù\86Û\8cست Ø±Ø§ Ø¨Ù\87 Ø¢Ù\86 Ù\86Ù\85Û\8câ\80\8cبخشد. [[Special:ListGrants|table of grants]] را برای اطلاعات بیشتر مشاهده کنید.",
        "botpasswords-label-grants-column": "اعطا شد",
        "botpasswords-bad-appid": "نام ربات \"$1\" معتبر نیست.",
        "botpasswords-insert-failed": "شکست در افزودن نام ربات «$1». در حال حاضر اضافه شده است؟",
        "botpasswords-updated-body": "گذرواژهٔ رباتی برای ربات «$1» و کاربر «$2» به‌روز شد.",
        "botpasswords-deleted-title": "گذرواژه ربات حذف شد",
        "botpasswords-deleted-body": "گذرواژهٔ رباتی برای ربات «$1» و کاربر «$2» حذف شد.",
-       "botpasswords-newpassword": "<strong>$2</strong> گذرواژهٔ جدید برای ورود با <strong>$1</strong> است. <em>لطفاً این را برای ارجاع در آینده ذخیره کنید.</em>",
+       "botpasswords-newpassword": "<strong>$2</strong> گذرواژهٔ جدید برای ورود با حساب <strong>$1</strong> است. <em>لطفاً آن را برای ارجاع در آینده ذخیره کنید.</em> <br> (برای ربات‌های قدیمی که نیاز به نام کاربری مطابق با حساب کاربری‌شان دارد، شما می‌توانید از <strong>$3</strong> به عنوان نام کاربری و از <strong>$4</strong> به عنوان گذرواژه استفاده کنید.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider موجود نیست.",
        "botpasswords-restriction-failed": "محدودیت‌های گذرواژهٔ ربات از این ورود جلوگیری می‌کند.",
        "botpasswords-invalid-name": "نام کاربری مشخص شده دارای جداکنندهٔ گذرواژهٔ رباتی نیست (\"$1\").",
        "passwordreset-emaildisabled": "قابلیت‌های ایمیل در این ویکی غیرفعال شده‌اند.",
        "passwordreset-username": "نام کاربری:",
        "passwordreset-domain": "دامنه:",
-       "passwordreset-capture": "ایمیل نهایی نشان داده شود؟",
-       "passwordreset-capture-help": "اگر این گزینه را علامت بزنید، ایمیل (حاوی گذرواژهٔ موقت) به شما نشان داده خواهد شد و برای کاربر نیز فرستاده خواهد شد.",
        "passwordreset-email": "نشانی ایمیل:",
        "passwordreset-emailtitle": "جزئیات حساب در {{SITENAME}}",
        "passwordreset-emailtext-ip": "یک نفر (احتمالاً شما، با نشانی آی‌پی $1) درخواست بازنشانی گذرواژه‌تان در {{SITENAME}} ($4) را کرده‌است. {{PLURAL:$3|حساب|حساب‌های}} کاربری زیر با این آدرس ایمیل مرتبط هستند:\n\n$2\n\n{{PLURAL:$3|این گذرواژهٔ موقت|این گذرواژه‌های موقت}} پس از {{PLURAL:$5|یک روز|$5 روز}} باطل خواهند شد.\nشما باید هم‌اکنون ثبت ورود کنید و گذرواژه‌ای جدید برگزینید. اگر فکر می‌کنید شخص دیگری این درخواست را داده است یا اگر گذرواژهٔ اصلی‌تان را به یاد آوردید و دیگر نمی‌خواهید آن را تغییر دهید، می‌توانید این پیغام را نادیده بگیرید و به استفاده از گذرواژهٔ قبلی‌تان ادامه دهید.",
        "passwordreset-emailelement": "نام کاربری: \n$1\n\nگذرواژهٔ موقت: \n$2",
        "passwordreset-emailsentemail": "اگر نشانی پست الکترونیکی که وارد کردید برای حساب کاربریتان ثبت شده باشد، یک نامهٔ بازنشانی گذرواژه به آن فرستاده می‌شود.",
        "passwordreset-emailsentusername": "اگر نشانی پست الکترونیکی مرتبطی موجود باشد، یک نامه برای بازنشانی گذرواژه به آن ارسال خواهد شد.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|نامهٔ|نامهٔ}} بازنشانی گذرواژه ارسال شد. در زير $1 نام کاربری و گذرواژه در حال نمایش است.",
-       "passwordreset-emailerror-capture2": "ارسال ناموفق نامه به $2: $1\nدر زير $3 نام کاربری و گذرواژه در حال نمایش است",
        "passwordreset-nocaller": "فراخواننده می‌بايست مشخص شده باشد",
        "passwordreset-nosuchcaller": "اين فراخواننده وجود ندارد:$1",
        "passwordreset-ignored": "به بازنشانی گذرواژه پرداخته نشد. آیا ممکن است که هيچ مهياکننده‌ای برای این کار تنظيم نشده باشد؟",
        "invalid-content-data": "داده محتوای نامعتبر",
        "content-not-allowed-here": "محتوای «$1» در صفحهٔ [[$2]] مجاز نیست",
        "editwarning-warning": "خروج از این صفحه ممکن است باعث شود که شما هر شانسی که به وجود آورده‌اید را از دست بدهید.\nاگر شما وارد سامانه شده‌اید، می‌توانید این هشدار را در بخش «{{int:prefs-editing}}» ترجیحاتتان غیرفعال کنید.",
+       "editpage-invalidcontentmodel-title": "مدل محتوای پشتیبانی نشده",
+       "editpage-invalidcontentmodel-text": "مدل محتوای «$1» پشتیبای نمی‌شود.",
        "editpage-notsupportedcontentformat-title": "فرمت محتوا پشتیبانی نشده",
        "editpage-notsupportedcontentformat-text": "فرمت محتوای $1 توسط مدل محتوای $2 پشتیبانی نشده‌است.",
        "content-model-wikitext": "ویکی‌متن",
        "searchprofile-advanced-tooltip": "جستجو در فضاهای نام دلخواه",
        "search-result-size": "$1 ({{PLURAL:$2|یک واژه|$2 واژه}})",
        "search-result-category-size": "{{PLURAL:$1|یک عضو|$1 عضو}} ({{PLURAL:$2|یک زیررده|$2 زیررده}}، {{PLURAL:$3|یک پرونده|$3 پرونده}})",
-       "search-redirect": "(تغییرمسیر $1)",
+       "search-redirect": "(تغییرمسیر از $1)",
        "search-section": "(بخش $1)",
        "search-category": "(رده  $1)",
        "search-file-match": "(تشابه محتوی پرونده)",
        "search-external": "جستجوی خارجی",
        "searchdisabled": "جستجو در {{SITENAME}} فعال نیست.\nموقتاً می‌توانید از جستجوی Google استفاده کنید.\nتوجه کنید که نتایج حاصل از جستجو با آن روش ممکن است به‌روز نباشند.",
        "search-error": "خطایی هنگام جست‌وجو رخ داده است: $1",
+       "search-warning": "هشداری هنگام جست‌وجو رخ داده است: $1",
        "preferences": "ترجیحات",
        "mypreferences": "ترجیحات",
        "prefs-edits": "تعداد ویرایش‌ها:",
        "prefs-rc": "تغییرات اخیر",
        "prefs-watchlist": "فهرست پی‌گیری‌ها",
        "prefs-editwatchlist": "ویرایش فهرست پی‌گیری‌ها",
-       "prefs-editwatchlist-label": "Ù\88Û\8cراÛ\8cØ´ Ù\87Ù\85Ù\87 Ù\81Ù\87رست Ù¾Û\8cÚ¯Û\8cرÛ\8câ\80\8cÙ\87اÛ\8cتاÙ\86:",
+       "prefs-editwatchlist-label": "ویرایش فهرست پیگیری‌هایتان:",
        "prefs-editwatchlist-edit": "دیدن و حذف عنوان‌ها از فهرست پیگیری‌هایتان",
        "prefs-editwatchlist-raw": "ویرایش فهرست خام پیگیری‌ها",
        "prefs-editwatchlist-clear": "پاک کردن فهرست پیگیری‌هایتان",
        "prefs-help-recentchangescount": "این گزینه شامل تغییرات اخیر، تاریخچهٔ صفحه‌ها و سیاهه‌ها می‌شود.",
        "prefs-help-watchlist-token2": "این کلید رمز خوراک وب فهرست پی‌گیری‌های شماست.\nهرکس آن را بداند می‌تواند فهرست پی‌گیری‌هایتان را بخواند، بنابراین آن را به اشتراک نگذارید. [[Special:ResetTokens|اگر لازم است آن را تغییر دهید اینجا را کلیک کنید]].",
        "savedprefs": "ترجیحات شما ذخیره شد.",
-       "savedrights": "دسترسی کاربری {{GENDER:$1|$1}} ذخیره شده‌است.",
+       "savedrights": "گروه‌های کاربری {{GENDER:$1|$1}} ذخیره شده‌است.",
        "timezonelegend": "منطقهٔ زمانی:",
        "localtime": "زمان محلی:",
        "timezoneuseserverdefault": "استفاده از پیش‌فرض ویکی ($1)",
        "timezoneregion-europe": "اروپا",
        "timezoneregion-indian": "اقیانوس هند",
        "timezoneregion-pacific": "اقیانوس آرام",
-       "allowemail": "اÙ\85کاÙ\86 Ø¯Ø±Û\8cاÙ\81ت Ø§Û\8cÙ\85Û\8cÙ\84 Ø§Ø² Ø¯Û\8cگر Ú©Ø§Ø±Ø¨Ø±Ø§Ù\86",
+       "allowemail": "درÛ\8cاÙ\81ت Ø§Û\8cÙ\85Û\8cÙ\84 Ø§Ø² Ø¯Û\8cگر Ú©Ø§Ø±Ø¨Ø±Ø§Ù\86 Ù\85Ù\85Ú©Ù\86 Ø¨Ø§Ø´Ø¯",
        "prefs-searchoptions": "جستجو",
        "prefs-namespaces": "فضاهای نام",
        "default": "پیش‌فرض",
        "prefswarning-warning": "تغییراتتان به ترجیحات هنوز ذحیره نشده است.\nاگر این صفحه بدون کلیک بر «$1» ترک کنید ترجیحاتتان ذخیره نخواهد شد.",
        "prefs-tabs-navigation-hint": "نکته: شما می توانید از کلیدهای جهت‌نمای چپ و راست برای حرکت بین زبانه‌ها در فهرست زبانه‌ها استفاده کنید.",
        "userrights": "مدیریت اختیارات کاربر",
-       "userrights-lookup-user": "مدیریت گروه‌های کاربری",
+       "userrights-lookup-user": "انتخاب یک کاربر",
        "userrights-user-editname": "یک نام کاربری وارد کنید:",
-       "editusergroup": "ویرایش گروه‌های {{GENDER:$1|کاربری}}",
+       "editusergroup": "بارگیری گروه‌های کاربر",
        "editinguser": "تغییر اختیارات کاربری کاربر {{GENDER:$1|کاربر}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "ویرایش گروه‌های کاربری",
        "saveusergroups": "ثبت گروه‌های {{GENDER:$1|کاربری}}",
        "userrights-reason": "دلیل:",
        "userrights-no-interwiki": "شما اجازهٔ تغییر اختیارات کاربران دیگر ویکی‌ها را ندارید.",
        "userrights-nodatabase": "پایگاه دادهٔ $1 وجود ندارد یا محلی نیست.",
-       "userrights-nologin": "شما باید با یک حساب کاربری مدیر [[Special:UserLogin|به سامانه وارد شوید]] تا بتوانید اختیارات کاربران را تعیین کنید.",
-       "userrights-notallowed": "شما مجوز برای افزودن یا حذف حقوق کاربر را ندارید.",
        "userrights-changeable-col": "گروه‌هایی که می‌توانید تغییر دهید",
        "userrights-unchangeable-col": "گروه‌هایی که نمی‌توانید تغییر دهید",
        "userrights-conflict": "تعارض دسترسی‌های کاربری! لطفاً بررسی کنید و تغییرات را تأیید کنید.",
-       "userrights-removed-self": "شما دسترسی‌های خود را واستاندید. به این ترتیب شما دیگر به این صفحه دسترسی ندارید.",
        "group": "گروه:",
        "group-user": "کاربران",
        "group-autoconfirmed": "کاربران تأییدشدهٔ خودکار",
        "right-siteadmin": "قفل‌کردن و بازکردن پایگاه داده‌ها",
        "right-override-export-depth": "برون‌بری صفحه‌ها شامل صفحه‌های پیوند شده تا عمق ۵",
        "right-sendemail": "ارسال ایمیل به دیگر کاربران",
-       "right-passwordreset": "مشاهدهٔ نامه‌های تنظیم مجدد گذرواژه",
        "right-managechangetags": "ایجاد و غیرفعال کردن [[Special:Tags|برچسب‌ها]]",
        "right-applychangetags": "تائید [[Special:Tags|برچسب]] بر روی تغییرات یک نفر",
        "right-changetags": "افزودن یا حذف [[Special:Tags|برچسب]] قراردادی بر روی نسخه یا سیاهه ورودی‌ها",
        "grant-basic": "دسترسی‌های اولیه",
        "grant-viewdeleted": "مشاهدهٔ پرونده و صفحات حذف شده",
        "grant-viewmywatchlist": "مشاهدۀ فهرست پیگیری‌هایتان",
+       "grant-viewrestrictedlogs": "دیدن سیاه‌های محدود شده",
        "newuserlogpage": "سیاههٔ ایجاد کاربر",
        "newuserlogpagetext": "این سیاهه‌ای از نام‌های کاربری تازه‌ساخته‌شده است.",
        "rightslog": "سیاههٔ اختیارات کاربر",
        "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شما باید از کسی که دسترسی مشاهدهٔ پرونده متوقف شده را دارد، درخواست کنید تا شرایط را قبل از بارگذاری مجدد بررسی کند.",
        "upload-dialog-disabled": "در اين ويکی، بارگذاری پرونده‌هااز طريق اين رابط کاربری غير فعال است",
        "upload-dialog-title": "بارگذاری پرونده",
        "upload-dialog-button-cancel": "لغو",
+       "upload-dialog-button-back": "بازگشت",
        "upload-dialog-button-done": "انجام شد",
        "upload-dialog-button-save": "ذخیره",
        "upload-dialog-button-upload": "بارگذاری",
        "filerevert-submit": "برو",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> به [$4 نسخهٔ مورخ $2 ساعت $3] واگردانده شد.",
        "filerevert-badversion": "نسخهٔ قدیمی‌تری از این پرونده وجود نداشت.",
+       "filerevert-identical": "نسخهٔ فعلی پرونده با نسخه‌ای که انتخاب کردید یکسان است.",
        "filedelete": "حذف $1",
        "filedelete-legend": "حذف پرونده",
        "filedelete-intro": "شما در حال حذف کردن پروندهٔ '''[[Media:$1|$1]]''' به همراه تمام تاریخچه‌اش هستید.",
        "apisandbox-results-fixtoken-fail": "خطا در دریافت توکن \"$1\"",
        "apisandbox-alert-page": "بخش‌ها در این صفحه معتبر نیستند.",
        "apisandbox-alert-field": "مقدار این بخش معتبر نیست.",
+       "apisandbox-continue": "ادامه",
+       "apisandbox-continue-clear": "پاک‌کردن",
+       "apisandbox-continue-help": "{{int:apisandbox-continue}} آخرین درخواست را [https://www.mediawiki.org/wiki/API:Query#Continuing_queries ادامه خواهد داد]؛ {{int:apisandbox-continue-clear}} پارامترهای مرتبط به ادامه دادن را پاک خواهد کرد.",
+       "apisandbox-param-limit": "برای استفاده از یک حداکثر، <kbd>max</kbd> را وارد کنید.",
+       "apisandbox-multivalue-all-namespaces": "$1 (تمام فضاهای نام)",
+       "apisandbox-multivalue-all-values": "$1 (تمام مقادیر)",
        "booksources": "منابع کتاب",
        "booksources-search-legend": "جستجوی منابع کتاب",
        "booksources-isbn": "شابک:",
        "booksources-search": "جستجو",
        "booksources-text": "در زیر فهرستی از پیوندها به وبگاه‌های دیگر آمده‌است که کتاب‌های نو و دست دوم می‌فروشند، و همچنین ممکن است اطلاعات بیشتری راجع به کتاب مورد نظر شما داشته باشند:",
        "booksources-invalid-isbn": "شابک داده شده مجاز به نظر نمی‌رسد؛ از جهت اشکالات هنگام کپی کردن از منبع اصلی بررسی کنید.",
+       "magiclink-tracking-rfc": "صفحه‌های حاوی پیوند جادویی آراف‌سی",
+       "magiclink-tracking-rfc-desc": "این صفحه حاوی پیوندهای جادویی آراف‌سی است. برای چگونگی مهاجرت [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] را ببینید.",
+       "magiclink-tracking-pmid": "صفحه‌های حاوی پیوند جادویی پی‌ام‌آی‌دی",
+       "magiclink-tracking-pmid-desc": "این صفحه حاوی پیوندهای جادویی پی‌ام‌آی‌دی است. برای چگونگی مهاجرت [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] را ببینید.",
+       "magiclink-tracking-isbn": "صفحه‌های حاوی پیوند جادویی آی‌اس‌بی‌ان",
+       "magiclink-tracking-isbn-desc": "این صفحه حاوی پیوندهای جادویی آی‌اس‌بی‌ان است. برای چگونگی مهاجرت [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] را ببینید.",
        "specialloguserlabel": "مجری:",
        "speciallogtitlelabel": "هدف (عنوان یا {{ns:user}}:نام کاربر برای کاربر):",
        "log": "سیاهه‌ها",
        "activeusers-intro": "در زیر فهرستی از کاربرانی را می‌بینید که در $1 {{PLURAL:$1|روز|روز}} گذشته فعالیتی داشته‌اند.",
        "activeusers-count": "$1 {{PLURAL:$1|فعالیت|فعالیت}} در {{PLURAL:$3|روز|$3 روز}} اخیر",
        "activeusers-from": "نمایش کاربران با آغاز از:",
+       "activeusers-groups": "نمایش کاربران متعلق به گروه:",
+       "activeusers-excludegroups": "در نظر نگرفتن کاربران متعلق به گروه:",
        "activeusers-noresult": "کاربری پیدا نشد.",
        "activeusers-submit": "نمایش کاربران فعال",
        "listgrouprights": "اختیارات گروه‌های کاربری",
        "modifiedarticleprotection": "وضعیت محافظت «[[$1]]» را تغییر داد",
        "unprotectedarticle": "صفحهٔ «[[$1]]» را از محافظت بیرون آورد",
        "movedarticleprotection": "تنظیمات محافظت را از «[[$2]]» به «[[$1]]» منتقل کرد",
+       "protectedarticle-comment": "«[[$1]]» را {{GENDER:$2|محافظت کرد}}",
+       "modifiedarticleprotection-comment": "سطح محاظفت «[[$1]]» را {{GENDER:$2|تغییر داد}}",
+       "unprotectedarticle-comment": "«[[$1]]» را از محافظت {{GENDER:$2|در آورد}}",
        "protect-title": "تغییر وضعیت محافظت «$1»",
        "protect-title-notallowed": "مشاهده سطح حفاظت  \" $1 \"",
        "prot_1movedto2": "[[$1]] به [[$2]] منتقل شد",
        "movelogpagetext": "در زیر فهرستی از انتقال صفحات آمده‌است.",
        "movesubpage": "{{PLURAL:$1|زیرصفحه|زیرصفحه‌ها}}",
        "movesubpagetext": "این صفحه $1 زیرصفحه دارد که در زیر نمایش {{PLURAL:$1|یافته‌است|یافته‌اند}}.",
+       "movesubpagetalktext": "صفحهٔ بحث مربوط دارای $1 زیرصفحه است که در زیر نمایش {{PLURAL:$1|یافته‌است|یافته‌اند}}.",
        "movenosubpage": "این صفحه هیچ زیرصفحه‌ای ندارد.",
        "movereason": "دلیل:",
        "revertmove": "واگردانی",
        "importlogpagetext": "درون‌ریزی صفحات به همراه تاریخچهٔ ویرایش آن‌ها از ویکی‌های دیگر.",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|نسخه|نسخه}} واردشده",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|نسخه|نسخه}} واردشده از $2",
-       "javascripttest": "آزمایش جاوا اسکریپت",
+       "javascripttest": "آزمایش جاوااسکریپت",
        "javascripttest-pagetext-unknownaction": "تابع ناشناختهٔ \"$1\".",
        "javascripttest-qunit-intro": "[$1 مستندات آزمایش] را در mediawiki.org ببینید.",
        "tooltip-pt-userpage": "صفحهٔ {{GENDER:|کاربری شما}}",
        "pageinfo-article-id": "شناسهٔ صفحه",
        "pageinfo-language": "زبان محتوای صفحه",
        "pageinfo-content-model": "ساختار محتوای صفحه",
+       "pageinfo-content-model-change": "تغییر",
        "pageinfo-robot-policy": "‌فهرست‌کردن توسط ربات‌ها",
        "pageinfo-robot-index": "مجاز",
        "pageinfo-robot-noindex": "نامجاز",
        "pageinfo-category-pages": "تعداد صفحات",
        "pageinfo-category-subcats": "تعداد زیررده‌ها",
        "pageinfo-category-files": "تعداد پرونده‌ها",
+       "pageinfo-user-id": "شناسه کاربر",
        "markaspatrolleddiff": "برچسب گشت بزن",
        "markaspatrolledtext": "به این صفحه برچسب گشت بزن",
        "markaspatrolledtext-file": "انتخاب این نسخهٔ پرونده به عنوان گشت خورده",
        "patrol-log-header": "این سیاهه‌ای از ویرایش‌های گشت‌خورده است.",
        "log-show-hide-patrol": "$1 سیاههٔ گشت‌زنی",
        "log-show-hide-tag": "$1 سیاهه برچسب",
+       "confirm-markpatrolled-button": "تأیید",
+       "confirm-markpatrolled-top": "نسخهٔ $3 از $2 علامت گشت بخورد؟",
        "deletedrevision": "$1 نسخهٔ حذف شدهٔ قدیمی",
        "filedeleteerror-short": "خطا در حذف پرونده: $1",
        "filedeleteerror-long": "در زمان حذف پرونده خطا رخ داد:\n\n$1",
        "newimages-showbots": "نمایش بارگذاری‌ها توسط ربات‌ها",
        "newimages-hidepatrolled": "مخفی کردن بارگذاری گشت‌زن‌ها",
        "noimages": "چیزی برای دیدن نیست.",
+       "gallery-slideshow-toggle": "فعال یا غیرفعال کردن تصاویر بندانگشتی",
        "ilsubmit": "جستجو",
        "bydate": "از روی تاریخ",
        "sp-newimages-showfrom": "نشان‌دادن تصویرهای جدید از $2، $1 به بعد",
        "tag-filter": "پالایش [[Special:Tags|برچسب‌ها]]:",
        "tag-filter-submit": "پالایه",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|برچسب|برچسب‌ها}}]]: $2)",
+       "tag-mw-contentmodelchange": "تغییر مدل محتوا",
+       "tag-mw-contentmodelchange-description": "ویرایش‌هایی که [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel مدل محتوای صفحه را تغییر می‌دهند]",
        "tags-title": "برچسب‌ها",
        "tags-intro": "این صفحه فهرستی‌است از برچسب‌هایی که نرم‌افزار با آن‌ها ویرایش‌ها را علامت‌گذری می‌کند، به همراه معانی آن‌ها.",
        "tags-tag": "نام برچسب",
        "tags-actions-header": "فعالیت‌ها",
        "tags-active-yes": "بله",
        "tags-active-no": "خیر",
-       "tags-source-extension": "تعرÛ\8cÙ\81â\80\8cشدÙ\87 Ø¨Ø± Ù¾Ø§Û\8cÙ\87 Ø§Ù\81زÙ\88Ù\86Ù\87",
+       "tags-source-extension": "تعرÛ\8cÙ\81â\80\8cشدÙ\87 ØªÙ\88سط Ù\86رÙ\85â\80\8cاÙ\81زار",
        "tags-source-manual": "اعمال شده به صورت دستی توسط ربات‌ها یا کاربرها",
        "tags-source-none": "دیگر استفاده نمی‌شود",
        "tags-edit": "ویرایش",
        "tags-delete": "حذف",
        "tags-activate": "فعال‌سازی",
-       "tags-deactivate": "غیرفعال کردن/إکار کةتن",
+       "tags-deactivate": "غیرفعال کردن",
        "tags-hitcount": "$1 {{PLURAL:$1|تغییر|تغییر}}",
        "tags-manage-no-permission": "شما اجازه مدیریت تغییر تگ‌ها را ندارید.",
-       "tags-manage-blocked": "امکان تغییر برچسب‌ها را در زمان بسته‌بودن ندارید",
+       "tags-manage-blocked": "امکان تغییر برچسب‌ها را در زمان بسته‌بودن {{GENDER:$1|ندارید}}",
        "tags-create-heading": "ایجاد یک برچسب جدید",
        "tags-create-explanation": "به طور پیش‌فرض، تگ‌های تازه ایجاد شده برای استفاده کاربران و ربات‌ها در دسترس قرار می‌گیرند.",
        "tags-create-tag-name": "نام برچسب:",
        "tags-deactivate-not-allowed": "غیرفعال‌سازی تگ «$1» ممکن نیست.",
        "tags-deactivate-submit": "غیرفعال‌سازی",
        "tags-apply-no-permission": "دسترسی برای تغییر برچسب تغییراتتان را ندارید.",
-       "tags-apply-blocked": "در زمان بسته‌بودن امکان اعمال تغییراتتان بر روی برچسب‌ها را ندارید.",
+       "tags-apply-blocked": "در زمان بسته‌بودن امکان اعمال تغییراتتان بر روی برچسب‌ها را {{GENDER:$1|ندارید}}.",
        "tags-apply-not-allowed-one": "اجازهٔ تائید برچسب «$1» به صورت دستی وجود ندارد.",
        "tags-apply-not-allowed-multi": "اجازهٔ تائید {{PLURAL:$2|برچسب|برچسب}} به صورت دستی وجود ندارد:$1",
        "tags-update-no-permission": "شما اجازهٔ افزودن یا حذف برچسب از خود نسخه یا سیاهه را ندارید.",
-       "tags-update-blocked": "در زمان بسته بودن امکان حذف برچسب‌ها را ندارید.",
+       "tags-update-blocked": "در زمان بسته بودن امکان حذف برچسب‌ها را {{GENDER:$1|ندارید}}.",
        "tags-update-add-not-allowed-one": "اجازهٔ افزودن برچسب «$1» به صورت دستی وجود ندارد.",
        "tags-update-add-not-allowed-multi": "اجازهٔ افزودن {{PLURAL:$2|برچسب|برچسب}} به صورت دستی وجود ندارد:$1",
        "tags-update-remove-not-allowed-one": "اجازهٔ حذف برچسب «$1» به صورت دستی وجود ندارد.",
        "htmlform-cloner-create": "افزودن بیشتر",
        "htmlform-cloner-delete": "حذف",
        "htmlform-cloner-required": "حداقل یک مقدار مورد نیاز است.",
+       "htmlform-date-placeholder": "YYYY-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "YYYY-MM-DD HH:MM:SS",
+       "htmlform-date-invalid": "مقداری وارد کردید یک تاریخ معتبر نیست. لطفاً از قالب سال چهاررقمی، ماه دورقمی و روز دورقمی به شکل YYYY-MM-DD استفاده کنید.",
+       "htmlform-time-invalid": "مقداری وارد کردید یک زمان معتبر نیست. لطفاً از قالب ساعت دورقمی، دقیقه دورقمی و ثانیه دورقمی به شکل HH:MM:SS استفاده کنید.",
+       "htmlform-datetime-invalid": "مقداری وارد کردید یک تاریخ و ساعت معتبر نیست. لطفاً از قالب سال چهاررقمی، ماه دورقمی و روز دورقمی، ساعت دورقمی، دقیقه دورقمی و ثانیه دورقمی به شکل YYYY-MM-DD HH:MM:SS استفاده کنید.",
+       "htmlform-date-toolow": "مقداری که مشخص کردید پیش از زودترین تاریخ مجاز $1 است.",
+       "htmlform-date-toohigh": "مقداری که مشخص کردید پس از دیرترین تاریخ مجاز $1 است.",
+       "htmlform-time-toolow": "مقداری که مشخص کردید پیش از زودترین ساعت مجاز $1 است.",
+       "htmlform-time-toohigh": "مقداری که مشخص کردید پس از دیرترین ساعت مجاز $1 است.",
+       "htmlform-datetime-toolow": "مقداری که مشخص کردید پیش از زودترین تاریخ و ساعت مجاز $1 است.",
+       "htmlform-datetime-toohigh": "مقداری که مشخص کردید پس از دیرترین تاریخ و ساعت مجاز $1 است.",
        "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> معتبر نیست.",
        "logentry-delete-delete": "$1 صفحهٔ $3 را {{GENDER:$2|حذف کرد}}",
+       "logentry-delete-delete_redir": "$1 تغییرمسیر $3 را با رونویسی {{GENDER:$2|حذف کرد}}",
        "logentry-delete-restore": "$1 صفحهٔ $3 را {{GENDER:$2|احیا کرد}}",
        "logentry-delete-event": "$1 پیدایی {{PLURAL:$5|یک مورد سیاهه|$5 مورد سیاهه}} را در $3 {{GENDER:$2|تغییر داد}}: $4",
        "logentry-delete-revision": "$1 پیدایی {{PLURAL:$5|یک نسخه|$5 نسخه}} صفحه $3 را {{GENDER:$2|تغییر داد}}: $4",
        "feedback-thanks": "سپاس! بازخورد شما در صفحهٔ «[$1 $2]» ثبت شد.",
        "feedback-thanks-title": "با تشکر!",
        "feedback-useragent": "رابط کاربر:",
-       "searchsuggest-search": "جستجو",
+       "searchsuggest-search": "جستجو در {{SITENAME}}",
        "searchsuggest-containing": "صفحه‌های دربردارنده...",
        "api-error-autoblocked": "نشانی آی‌پی شما به صورت خودکار بسته شده‌است، چون توسط یک کاربر بسته‌شده استفاده می‌شد.",
        "api-error-badaccess-groups": "شما اجازهٔ بارگذاری پرونده‌ها را در این ویکی ندارید.",
        "expand_templates_generate_xml": "نمایش درخت تجزیهٔ XML",
        "expand_templates_generate_rawhtml": "نمایش اچ‌تی‌ام‌ال خام",
        "expand_templates_preview": "پیش‌نمایش",
-       "expand_templates_preview_fail_html": "<em>زÛ\8cرا {{SITENAME}} ØªØ§ Ø¨Ù\87 HTML Ø®Ø§Ù\85 Ù\81عاÙ\84 Ù\88 Û\8cÚ© Ø¯Ø³Øª Ø±Ù\81تÙ\86 Ø§Ø·Ù\84اعات Ù\86شست Ù\88جÙ\88د Ø¯Ø§Ø±Ø¯Ø\8c Ù¾Û\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¨Ù\87 Ø¹Ù\86Ù\88اÙ\86 Û\8cÚ© Ø§Ù\82داÙ\85 Ø§Ø­ØªÛ\8cاطÛ\8c Ø¯Ø± Ø¨Ø±Ø§Ø¨Ø± Ø­Ù\85Ù\84ات Ø¬Ø§Ù\88ا Ø§Ø³Ú©Ø±Û\8cپت Ù¾Ù\86Ù\87اÙ\86 Ø§Ø³Øª.</em>\n\n<strong>اگر Ø§Û\8cÙ\86 ØªÙ\84اش Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ù\85شرÙ\88ع Ø§Ø³ØªØ\8c Ù\84Ø·Ù\81ا Ø¯Ù\88بارÙ\87 Ø³Ø¹Û\8c Ú©Ù\86Û\8cد. Ø§Ú¯Ø± Ù\87Ù\86Ù\88ز Ú©Ø§Ø± Ù\86Ù\85Û\8c Ú©Ù\86د، سعی کنید [[Special:UserLogout|خروج از سیستم]] را کلیک نموده و دوباره وارد شوید، و از این‌ که مرورگر شما اجازه دریافت کوکی از این وب‌گاه را می‌دهد اطمینان حاصل کنید.",
-       "expand_templates_preview_fail_html_anon": "<em>زÛ\8cرا {{SITENAME}} ØªØ§ Ø¨Ù\87 HTML Ø®Ø§Ù\85 Ù\81عاÙ\84 Ù\88 Û\8cÚ© Ø¯Ø³Øª Ø±Ù\81تÙ\86 Ø§Ø·Ù\84اعات Ù\86شست Ù\88جÙ\88د Ø¯Ø§Ø±Ø¯Ø\8c Ù¾Û\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¨Ù\87 Ø¹Ù\86Ù\88اÙ\86 Û\8cÚ© Ø§Ù\82داÙ\85 Ø§Ø­ØªÛ\8cاطÛ\8c Ø¯Ø± Ø¨Ø±Ø§Ø¨Ø± Ø­Ù\85Ù\84ات Ø¬Ø§Ù\88ا Ø§Ø³Ú©Ø±Û\8cپت Ù¾Ù\86Ù\87اÙ\86 Ø§Ø³Øª.</em>\n\n<strong>اگر Ø§Û\8cÙ\86 ØªÙ\84اش Ù¾Û\8cØ´Ù\86Ù\85اÛ\8cØ´ Ù\85شرÙ\88ع Ø§Ø³ØªØ\8c Ù\84Ø·Ù\81ا Ø¯Ù\88بارÙ\87 Ø³Ø¹Û\8c Ú©Ù\86Û\8cد. Ø§Ú¯Ø± Ù\87Ù\86Ù\88ز Ú©Ø§Ø± Ù\86Ù\85Û\8c Ú©Ù\86دØ\8c Ø³Ø¹Û\8c Ú©Ù\86Û\8cد [[Special:UserLogout|خرÙ\88ج Ø§Ø² Ø³Û\8cستÙ\85]] Ø±Ø§ Ú©Ù\84Û\8cÚ© Ù\86Ù\85Ù\88دÙ\87 Ù\88 Ø¯Ù\88بارÙ\87 Ù\88ارد Ø´Ù\88Û\8cد.",
+       "expand_templates_preview_fail_html": "<em>بÙ\87 Ø¯Ù\84Û\8cÙ\84 Ø§Û\8cÙ\86 Ú©Ù\87 Ø¯Ø± {{SITENAME}} Ø§Ú\86â\80\8cتÛ\8câ\80\8cاÙ\85â\80\8cاÙ\84 Ø®Ø§Ù\85 Ù\81عاÙ\84 Ø§Ø³Øª Ù\88 Ø§Ø·Ù\84اعات Ù\86شست Ú©Ø§Ø±Ø¨Ø±Û\8c Ø§Ø² Ø¯Ø³Øª Ø±Ù\81تØ\8c Ù¾Û\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¨Ù\87 Ø¹Ù\86Ù\88اÙ\86 Û\8cÚ© Ø§Ù\82داÙ\85 Ø§Ø­ØªÛ\8cاطÛ\8c Ø¯Ø± Ø¨Ø±Ø§Ø¨Ø± Ø­Ù\85Ù\84ات Ø¬Ø§Ù\88ااسکرÛ\8cپت Ù¾Ù\86Ù\87اÙ\86 Ø§Ø³Øª.</em>\n\n<strong>اگر Ø§Û\8cÙ\86 ØªÙ\84اش Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ù\85شرÙ\88ع Ø§Ø³ØªØ\8c Ù\84Ø·Ù\81اÙ\8b Ø¯Ù\88بارÙ\87 Ø³Ø¹Û\8c Ú©Ù\86Û\8cد.</strong>\nاگر Ù\87Ù\86Ù\88ز Ú©Ø§Ø± Ù\86کرد، سعی کنید [[Special:UserLogout|خروج از سیستم]] را کلیک نموده و دوباره وارد شوید، و از این‌ که مرورگر شما اجازه دریافت کوکی از این وب‌گاه را می‌دهد اطمینان حاصل کنید.",
+       "expand_templates_preview_fail_html_anon": "<em>بÙ\87 Ø¯Ù\84Û\8cÙ\84 Ø§Û\8cÙ\86 Ú©Ù\87 Ø¯Ø± {{SITENAME}} Ø§Ú\86â\80\8cتÛ\8câ\80\8cاÙ\85â\80\8cاÙ\84 Ø®Ø§Ù\85 Ù\81عاÙ\84 Ø§Ø³Øª Ù\88 Ø§Ø·Ù\84اعات Ù\86شست Ú©Ø§Ø±Ø¨Ø±Û\8c Ø§Ø² Ø¯Ø³Øª Ø±Ù\81تØ\8c Ù¾Û\8cØ´ Ù\86Ù\85اÛ\8cØ´ Ø¨Ù\87 Ø¹Ù\86Ù\88اÙ\86 Û\8cÚ© Ø§Ù\82داÙ\85 Ø§Ø­ØªÛ\8cاطÛ\8c Ø¯Ø± Ø¨Ø±Ø§Ø¨Ø± Ø­Ù\85Ù\84ات Ø¬Ø§Ù\88ااسکرÛ\8cپت Ù¾Ù\86Ù\87اÙ\86 Ø§Ø³Øª.</em>\n\n<strong>اگر Ø§Û\8cÙ\86 ØªÙ\84اش Ù¾Û\8cØ´â\80\8cÙ\86Ù\85اÛ\8cØ´ Ù\85شرÙ\88ع Ø§Ø³ØªØ\8c Ù\84Ø·Ù\81ا [[Special:UserLogin|بÙ\87 Ø³Ø§Ù\85اÙ\86Ù\87 Ù\88ارد Ø´Ù\88Û\8cد]] Ù\88 Ø¯Ù\88بارÙ\87 ØªÙ\84اش Ú©Ù\86Û\8cد.</strong>",
        "expand_templates_input_missing": "شما نیازمندید که حداقل متن‌هایی را برای وارد کردن تهیه کنید.",
        "pagelanguage": "تغییر زبان صفحه",
        "pagelang-name": "صفحه",
        "special-characters-title-emdash": "خط فاسله کشیده",
        "special-characters-title-minus": "علامت منفی",
        "mw-widgets-dateinput-no-date": "هیچ داده‌ای انتخاب نشده",
+       "mw-widgets-mediasearch-input-placeholder": "جستجو برای رسانه‌ها",
+       "mw-widgets-mediasearch-noresults": "هیچ نتیجه‌ای پیدا نشد.",
        "mw-widgets-titleinput-description-new-page": "این صفحه هنوز وجود ندارد",
        "mw-widgets-titleinput-description-redirect": "تغییر مسیر به $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "در حال افزودن رده ...",
        "sessionmanager-tie": "نمی‌توان چندین نوع درخواست هویت‌سنجی را ترکیب کرد: $1.",
        "sessionprovider-generic": "$1 فصل",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "فصل‌های کوکی‌محور",
        "log-action-filter-block-block": "بستن",
        "log-action-filter-block-reblock": "تصحیح بلاک",
        "log-action-filter-block-unblock": "باز شدن",
-       "log-action-filter-contentmodel-change": "تغÛ\8cÛ\8cر Ù\86Ù\88ع محتوا",
+       "log-action-filter-contentmodel-change": "تغÛ\8cÛ\8cر Ù\85دÙ\84 محتوا",
        "log-action-filter-contentmodel-new": "ایجاد صفحه با contentmodel غیر استاندارد",
        "log-action-filter-delete-delete": "حذف صفحه",
+       "log-action-filter-delete-delete_redir": "رونویسی تغییرمسیر",
        "log-action-filter-delete-restore": "احیای صفحه",
        "log-action-filter-delete-event": "حذف سیاهه",
        "log-action-filter-delete-revision": "حذف ویرایش",
        "unlinkaccounts-success": "پیوند کاربری بدون پیوند شد.",
        "authenticationdatachange-ignored": "به تغيير اطلاعات احراز هويت پرداخته نشد. آیا ممکن است که هيچ مهيا کننده‌ای برای اين کار تنظيم نشده باشد؟",
        "userjsispublic": "لطفاً توجه کنید: زیرصفحه‌های جاوااسکریپت نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند.",
-       "usercssispublic": "لطفاً توجه کنید: زیرصفحه‌های سی‌اس‌اس نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند."
+       "usercssispublic": "لطفاً توجه کنید: زیرصفحه‌های سی‌اس‌اس نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند.",
+       "restrictionsfield-badip": "نشانی یا بازهٔ آی‌پی نامعتبر: $1",
+       "restrictionsfield-label": "بازه‌های آی‌پی مجاز:",
+       "restrictionsfield-help": "یک نشانی آی‌پی یا بازهٔ سی‌آی‌دی‌ار در هر خط وارد کنید. برای فعال کردن همه‌چیز، این مقدار را استفاده کنید: <code>0.0.0.0/0</code><br><code>::/0</code>"
 }
index 8cf7922..35806aa 100644 (file)
        "views": "Näkymät",
        "toolbox": "Työkalut",
        "tool-link-userrights": "Muokkaa {{GENDER:$1|käyttäjän}} ryhmiä",
+       "tool-link-userrights-readonly": "Katso {{GENDER:$1|käyttäjän}} ryhmiä",
        "tool-link-emailuser": "Lähetä sähköpostia tälle {{GENDER:$1|käyttäjälle}}",
        "userpage": "Näytä käyttäjäsivu",
        "projectpage": "Näytä projektisivu",
        "prefs-help-recentchangescount": "Tämä sisältää tuoreet muutokset, muutoshistoriat ja lokit.",
        "prefs-help-watchlist-token2": "Tämä on salainen avain tarkkailulistasi verkkosyötteeseen.\nKuka tahansa, joka tietää sen voi lukea tarkkailulistaasi, joten älä paljasta sitä.\n[[Special:ResetTokens|Napsauta tästä, jos sinun pitää uudistaa se]].",
        "savedprefs": "Asetuksesi on tallennettu.",
-       "savedrights": "Käyttäjän {{GENDER:$1|$1}} käyttöoikeudet on tallennettu.",
+       "savedrights": "Käyttäjän {{GENDER:$1|$1}} käyttäjäryhmät on tallennettu.",
        "timezonelegend": "Aikavyöhyke",
        "localtime": "Paikallinen aika",
        "timezoneuseserverdefault": "Käytä oletusta ($1)",
        "prefswarning-warning": "Olet tehnyt asetuksiisi muutoksia, joita ei ole vielä tallennettu.\nJos poistut sivulta klikkaamatta \"$1\", asetuksiasi ei päivitetä.",
        "prefs-tabs-navigation-hint": "Vihje: Voit käyttää vasenta ja oikeata nuolinäppäintä liikkumiseen välilehtien välillä.",
        "userrights": "Käyttöoikeuksien hallinta",
-       "userrights-lookup-user": "Hallinnoi käyttäjän ryhmiä",
+       "userrights-lookup-user": "Valitse käyttäjä",
        "userrights-user-editname": "Käyttäjätunnus:",
-       "editusergroup": "Muokkaa {{GENDER:$1|käyttäjän}} ryhmiä",
+       "editusergroup": "Lataa käyttäjäryhmät",
        "editinguser": "Muutetaan {{GENDER:$1|käyttäjän}} <strong>[[User:$1|$1]]</strong> $2 oikeuksia",
+       "viewinguserrights": "Näytetään {{GENDER:$1|käyttäjän}} <strong>[[User:$1|$1]]</strong> $2 käyttäjäryhmät",
        "userrights-editusergroup": "Muuta käyttäjän ryhmiä",
+       "userrights-viewusergroup": "Näytä käyttäjäryhmät",
        "saveusergroups": "Tallenna {{GENDER:$1|käyttäjän}} ryhmät",
        "userrights-groupsmember": "Jäsenenä ryhmissä:",
        "userrights-groupsmember-auto": "Automaattisesti jäsenenä ryhmissä:",
        "userrights-reason": "Syy:",
        "userrights-no-interwiki": "Sinulla ei ole oikeutta muokata käyttöoikeuksia muissa wikeissä.",
        "userrights-nodatabase": "Tietokantaa $1 ei ole tai se ei ole paikallinen.",
-       "userrights-nologin": "Sinun täytyy [[Special:UserLogin|kirjautua sisään]] ylläpitäjän tunnuksella, jotta voisit muuttaa käyttöoikeuksia.",
-       "userrights-notallowed": "Sinulla ei ole oikeutta lisätä tai poistaa käyttäjien oikeuksia.",
        "userrights-changeable-col": "Ryhmät, joita voit muuttaa",
        "userrights-unchangeable-col": "Ryhmät, joita et voi muuttaa",
        "userrights-conflict": "Päällekkäinen käyttöoikeuksien muutos! Tarkista tekemäsi muutokset ja vahvista ne.",
-       "userrights-removed-self": "Poistit omat oikeutesi. Tämän vuoksi sinulla ei enää ole oikeutta päästä tälle sivulle.",
        "group": "Ryhmä",
        "group-user": "käyttäjät",
        "group-autoconfirmed": "automaattisesti hyväksytyt käyttäjät",
        "action-upload_by_url": "tallentaa tätä tiedostoa URL-osoitteesta",
        "action-writeapi": "käyttää kirjoitus-APIa",
        "action-delete": "poistaa tätä sivua",
-       "action-deleterevision": "poistaa tätä versiota",
-       "action-deletedhistory": "tarkastella tämän sivun poistettua historiaa",
+       "action-deleterevision": "poistaa versioita",
+       "action-deletelogentry": "poistaa lokimerkintöjä",
+       "action-deletedhistory": "tarkastella sivun poistettua historiaa",
+       "action-deletedtext": "tarkastella poistetun version tekstiä",
        "action-browsearchive": "etsiä poistettuja sivuja",
-       "action-undelete": "palauttaa tätä poistettua sivua",
-       "action-suppressrevision": "tarkastella ja palauttaa tätä piilotettua versiota",
+       "action-undelete": "palauttaa poistettuja sivuja",
+       "action-suppressrevision": "tarkastella ja palauttaa piilotettuja versioita",
        "action-suppressionlog": "tarkastella tätä yksityislokia",
        "action-block": "estää tätä käyttäjää muokkaamasta",
        "action-protect": "muuttaa tämän sivun suojaustasoa",
        "action-userrights-interwiki": "muokata muiden wikien käyttäjien käyttöoikeuksia",
        "action-siteadmin": "lukita tai avata tietokantaa",
        "action-sendemail": "lähettää sähköpostia",
+       "action-editmyoptions": "muokata omia asetuksiasi",
        "action-editmywatchlist": "muokata omaa tarkkailulistaasi",
        "action-viewmywatchlist": "tarkastella tarkkailulistaasi",
        "action-viewmyprivateinfo": "katsoa omia yksityisiä tietojasi",
        "apisandbox-alert-field": "Tässä kentässä oleva arvo ei ole kelvollinen.",
        "apisandbox-continue": "Jatka",
        "apisandbox-continue-clear": "Tyhjennä",
+       "apisandbox-multivalue-all-namespaces": "$1 (Kaikki nimiavaruudet)",
+       "apisandbox-multivalue-all-values": "$1 (Kaikki arvot)",
        "booksources": "Kirjalähteet",
        "booksources-search-legend": "Etsi kirjalähteitä",
        "booksources-isbn": "ISBN",
        "activeusers-count": "$1 {{PLURAL:$1|toiminto|toimintoa}} viimeisen {{PLURAL:$3|päivän|$3 päivän}} aikana",
        "activeusers-from": "Näytä käyttäjät alkaen kohdasta:",
        "activeusers-groups": "Näytä käyttäjät, jotka kuuluvat ryhmiin:",
+       "activeusers-excludegroups": "Älä näytä käyttäjiä, jotka kuuluvat seuraaviin ryhmiin:",
        "activeusers-noresult": "Käyttäjiä ei löytynyt.",
        "activeusers-submit": "Hae aktiiviset käyttäjät",
        "listgrouprights": "Käyttäjäryhmien oikeudet",
        "emailccsubject": "Kopio lähettämästäsi viestistä osoitteeseen $1: $2",
        "emailsent": "Sähköposti lähetetty",
        "emailsenttext": "Sähköpostiviestisi on lähetetty.",
-       "emailuserfooter": "Tämän sähköpostin {{GENDER:$1|lähetti}} $1 vastaanottajalle {{GENDER:$2|$2}} käyttämällä ”{{int:emailuser}}” -toimintoa {{GRAMMAR:inessive|{{SITENAME}}}}.",
+       "emailuserfooter": "Tämän sähköpostin {{GENDER:$1|lähetti}} $1 vastaanottajalle {{GENDER:$2|$2}} käyttämällä ”{{int:emailuser}}” -toimintoa {{GRAMMAR:inessive|{{SITENAME}}}}. Sähköpostisi lähetetään suoraan {{GENDER:$1|alkuperäiselle lähettäjälle}}, paljastaen {{GENDER:$2|sinun}} sähköpostiosoitteesi {{GENDER:$1|hänelle}}.",
        "usermessage-summary": "Jätetään järjestelmäviesti.",
        "usermessage-editor": "Järjestelmäviestittäjä",
        "watchlist": "Tarkkailulista",
        "changecontentmodel-legend": "Muuta sisältömallia",
        "changecontentmodel-title-label": "Sivun otsikko",
        "changecontentmodel-model-label": "Uusi sisältömalli",
-       "changecontentmodel-reason-label": "Syy:",
+       "changecontentmodel-reason-label": "Syy",
        "changecontentmodel-submit": "Tee muutos",
        "changecontentmodel-success-title": "Sisältömallia on muutettu",
        "changecontentmodel-success-text": "Sisältötyyppiä kohteessa [[:$1]] on muutettu.",
        "undeletehistorynoadmin": "Tämä sivu on poistettu. \nSyy sivun poistamiseen näkyy alla olevassa yhteenvedossa, jossa on myös tiedot, ketkä olivat muokanneet tätä sivua ennen poistamista. \nNäiden poistettujen versioiden varsinainen tekstisisältö on vain ylläpitäjien luettavissa.",
        "undelete-revision": "Poistettu versio sivusta $1 (aikaleima $4 kello $5). Version tekijä: $3.",
        "undeleterevision-missing": "Virheellinen tai puuttuva versio. \nSinulla on kenties käytössä väärä linkki, tai sitten versio on saatettu palauttaa takaisin tai poistaa arkistosta.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Yhtä versiota|$1 versiota}} ei voitu palauttaa, koska {{PLURAL:$1|sen|niiden}} <code>rev_id</code> oli jo käytössä.",
        "undelete-nodiff": "Aikaisempaa versiota ei löytynyt.",
        "undeletebtn": "Palauta",
        "undeletelink": "näytä tai palauta",
        "tags-deactivate": "ota pois käytöstä",
        "tags-hitcount": "$1 {{PLURAL:$1|muutos|muutosta}}",
        "tags-manage-no-permission": "Sinulla ei ole oikeutta käsitellä merkkauksia.",
-       "tags-manage-blocked": "Et voi hallita muokkausmerkkauksia kun olet estettynä.",
+       "tags-manage-blocked": "Et voi hallita muokkausmerkkauksia kun {{GENDER:$1|olet}} estettynä.",
        "tags-create-heading": "Luo uusi merkkaus",
        "tags-create-explanation": "Oletuksena on, että uutena luodut merkkaukset tulevat käyttäjien ja bottien käyttöön.",
        "tags-create-tag-name": "Merkkauksen nimi:",
        "tags-deactivate-not-allowed": "Ei ole mahdollista poistaa käytöstä merkkausta \"$1\".",
        "tags-deactivate-submit": "Poista käytöstä",
        "tags-apply-no-permission": "Sinulla ei ole oikeutta käyttää merkkauksia muutostesi yhteydessä.",
-       "tags-apply-blocked": "Et voi asettaa merkkauksia muutostesi yhteyteen kun olet estettynä.",
+       "tags-apply-blocked": "Et voi asettaa merkkauksia muutostesi yhteyteen kun {{GENDER:$1|olet}} estettynä.",
        "tags-apply-not-allowed-one": "Merkkausta \"$1\" ei ole sallittua asettaa käsin.",
        "tags-apply-not-allowed-multi": "Seuraavia {{PLURAL:$2|merkkauksia}} ei ole sallittua asettaa käsin: $1",
        "tags-update-no-permission": "Sinulla ei ole oikeutta lisätä tai poistaa merkkauksia yksittäisissä sivuversioissa tai lokimerkinnöissä.",
-       "tags-update-blocked": "Et voi lisätä tai poistaa merkkauksia kun olet estettynä.",
+       "tags-update-blocked": "Et voi lisätä tai poistaa merkkauksia kun {{GENDER:$1|olet}} estettynä.",
        "tags-update-add-not-allowed-one": "Merkkausta \"$1\" ei ole sallittua asettaa käsin.",
        "tags-update-add-not-allowed-multi": "Seuraavia {{PLURAL:$2|merkkauksia}} ei ole sallittua asettaa käsin: $1",
        "tags-update-remove-not-allowed-one": "Merkkausta \"$1\" ei ole sallittua poistaa.",
        "htmlform-user-not-exists": "Käyttäjää <strong>$1</strong> ei ole olemassa.",
        "htmlform-user-not-valid": "<strong>$1</strong> ei ole kelvollinen käyttäjänimi.",
        "logentry-delete-delete": "$1 {{GENDER:$2|poisti}} sivun $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|poisti}} ohjaussivun $3 korvaamalla",
        "logentry-delete-restore": "$1 {{GENDER:$2|palautti}} sivun $3",
        "logentry-delete-event": "$1 {{GENDER:$2|muutti}} {{PLURAL:$5|lokitapahtuman|$5 lokitapahtuman}} näkyvyyttä kohteessa $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|muutti}} {{PLURAL:$5|version|$5 version}} näkyvyyttä sivulla $3: $4",
        "mw-widgets-dateinput-no-date": "Ei ole valittu mitään päivää",
        "mw-widgets-dateinput-placeholder-day": "VVVV-KK-PP",
        "mw-widgets-dateinput-placeholder-month": "VVVV-KK",
+       "mw-widgets-mediasearch-input-placeholder": "Etsi mediaa",
+       "mw-widgets-mediasearch-noresults": "Tuloksia ei löytynyt.",
        "mw-widgets-titleinput-description-new-page": "sivua ei ole olemassa vielä",
        "mw-widgets-titleinput-description-redirect": "ohjaus kohteeseen $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Lisää luokka...",
        "sessionmanager-tie": "!!FYZZ!!Cannot combine multiple request authentication types: $1.",
        "sessionprovider-generic": "$1 istuntoa",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "istuntoja, joissa on evästeet käytössä",
        "unlinkaccounts": "Poista tunnusten linkityksiä",
        "unlinkaccounts-success": "Tunnuksen linkitys poistettiin.",
        "authenticationdatachange-ignored": "Varmennustietojen muutosta ei käsitelty. Ehkä palveluntarjoajaa ei määritelty?",
+       "userjsispublic": "Huomio: JavaScript-alasivuilla ei tulisi olla luottamuksellisia tietoja, koska muut käyttäjät voivat nähdä ne.",
+       "usercssispublic": "Huomio: CSS-alasivuilla ei tulisi olla luottamuksellisia tietoja, koska muut käyttäjät voivat nähdä ne.",
        "restrictionsfield-badip": "Virheellinen IP-osoite tai alue: $1",
-       "restrictionsfield-label": "Sallitut IP-alueet:",
-       "edit-error-short": "$1",
-       "edit-error-long": "Virheet:\n\n$1"
+       "restrictionsfield-label": "Sallitut IP-alueet:"
 }
index 19113d8..eaef8b2 100644 (file)
@@ -12,7 +12,8 @@
                        "לערי ריינהארט",
                        "아라",
                        "Macofe",
-                       "Irus"
+                       "Irus",
+                       "Fitoschido"
                ]
        },
        "tog-underline": "Undirstrika leinki:",
        "passwordreset-emaildisabled": "Teldupost funksjónir eru óvirknar á hesi wiki.",
        "passwordreset-username": "Brúkaranavn:",
        "passwordreset-domain": "Umdømi (domain):",
-       "passwordreset-capture": "Sí tann endaliga t-postin?",
-       "passwordreset-capture-help": "Um tú setir kross við henda teigin, so verður t-posturin (við fyribils loyniorðinum) vístur fyri tær og verður harumframt sendur til brúkaran.",
        "passwordreset-email": "T-post adressur:",
        "passwordreset-emailtitle": "konto upplýsingar á {{SITENAME}}",
        "passwordreset-emailtext-ip": "Onkur (óiva tú, frá IP adressu $1) hevur biðið um nullstillan av tínum loyniorði til {{SITENAME}} ($4). Fylgjandi brúkara {{PLURAL:$3|konta er|kontur eru}}\nsettar í samband við hesa t-post adressu:\n\n$2\n\n{{PLURAL:$3|Hetta fyribils loyniorðið|Hesi fyribils loyniorðini}} ganga út um {{PLURAL:$5|ein dag|$5 dagar}}.\nTú eigur at rita inn og velja eitt nýtt loyniorð nú. Um onkur annar hevur gjørt hesa umbønina, ella um tú ert komin í tankar um títt uppruna loyniorð, og tú ikki longur ynskir at broyta tað, so kanst tú síggja burtur frá hesum boðum og halda fram at brúka títt gamla loyniorð.",
        "userinvalidcssjstitle": "'''Ávaring:''' Tað er onki skinn \"$1\".\nTilevnaðar .css og .js síður brúka heiti sum byrja við lítlum bókstavi, t.d.  {{ns:user}}:Foo/vector.css í mun til {{ns:user}}:Foo/Vector.css.",
        "updated": "(Dagført)",
        "note": "'''Viðmerking:'''",
-       "previewnote": "'''Minst til at hetta bara er ein forskoðan.'''\nTínar broytingar eru ikki goymdar enn!",
+       "previewnote": "<strong>Minst til at hetta bara er ein forskoðan.</strong>\nTínar broytingar eru ikki goymdar enn!",
        "continue-editing": "Far til økið har ið tú kanst gera rættingar",
        "previewconflict": "Henda forskoðanin vísir tekstin í erva soleiðis sum hann sær út, um tú velur at goyma.",
        "session_fail_preview": "'''Orsakað! Vit kundu ikki fullføra tínar broytingar, tí tínar sessións dáta eru horvin.'''\nVinarliga royn aftur.\nUm tað enn ikki virkar, royn so [[Special:UserLogout|rita út]] og rita so inn aftur.",
        "userrights-reason": "Orsøk:",
        "userrights-no-interwiki": "Tú hevur ikki loyvi til at rætta brúkara rættindi á øðrum wikium.",
        "userrights-nodatabase": "Dátugrunnurin $1 er ikki til ella er hann ikki lokalur.",
-       "userrights-nologin": "Tú mást [[Special:UserLogin|rita inn]] sum administrator fyri at kunna áseta brúkararættindi.",
-       "userrights-notallowed": "Tú hevur ikki loyvi til at geva ella taka burtur brúkara rættindi.",
        "userrights-changeable-col": "Bólkar sum tú kanst broyta",
        "userrights-unchangeable-col": "Bólkar, ið tú ikki kanst broyta",
        "userrights-conflict": "Ósamsvar viðvíkjandi broytingum í brúkararættindum! Vinarliga endurskoða og vátta tínar broytingar.",
-       "userrights-removed-self": "Tað eydnaðist tær at taka burtur tíni egnu rættindi. Tí kanst tú ikki longur fáa atgongd til hesa síðuna.",
        "group": "Bólkur:",
        "group-user": "Brúkarar",
        "group-autoconfirmed": "Sjálvvirkandi váttaðir brúkarar",
        "right-siteadmin": "Stong og læs upp dátugrunnin",
        "right-override-export-depth": "Útflyt síður, eisini slóðaðar síður upp til eina dýpd á 5",
        "right-sendemail": "Send t-post til aðrir brúkarar",
-       "right-passwordreset": "Sí teldupostar til nullstilling av loyniorði",
        "newuserlogpage": "Brúkara logg",
        "newuserlogpagetext": "Hetta er ein listi yvir seinast stovnaðu brúkarar.",
        "rightslog": "Rættindaloggur",
index 5423d90..7593a37 100644 (file)
        "subject-preview": "Aperçu du sujet :",
        "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.",
+       "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <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 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:<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.",
        "search-external": "Recherche externe",
        "searchdisabled": "La recherche sur {{SITENAME}} est désactivée. En attendant la réactivation, vous pouvez effectuer une recherche via Google. Attention, leur indexation du contenu de {{SITENAME}} peut ne pas être à jour.",
        "search-error": "Une erreur s'est produite en recherchant : $1",
+       "search-warning": "Un avertissement a été signalé lors de la recherche : $1",
        "preferences": "Préférences",
        "mypreferences": "Préférences",
        "prefs-edits": "Nombre de modifications :",
        "userrights-user-editname": "Entrez un nom d'utilisateur :",
        "editusergroup": "Charger des groupes d’utilisateurs",
        "editinguser": "Modification des droits de l’{{GENDER:$1|utilisateur|utilisatrice}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Affichage des droits utilisateur de {{GENDER:$1|l’utilisateur|l’utilisatrice}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Modifier les groupes de l'utilisateur",
+       "userrights-viewusergroup": "Afficher les groupes d'utilisateurs",
        "saveusergroups": "Enregistrer les groupes de l’{{GENDER:$1|utilisateur|utilisatrice}}",
        "userrights-groupsmember": "Membre de :",
        "userrights-groupsmember-auto": "Membre implicite de :",
        "action-upload_by_url": "importer ce fichier à partir d'une adresse URL",
        "action-writeapi": "utiliser l‘API d'écriture",
        "action-delete": "supprimer cette page",
-       "action-deleterevision": "supprimer cette version",
-       "action-deletedhistory": "voir l’historique supprimé de cette page",
+       "action-deleterevision": "supprimer les révisions",
+       "action-deletelogentry": "supprimer les entrées de la trace",
+       "action-deletedhistory": "voir l’historique supprimé d’une page",
+       "action-deletedtext": "Afficher le texte de la révision supprimée",
        "action-browsearchive": "rechercher des pages supprimées",
-       "action-undelete": "restaurer cette page",
-       "action-suppressrevision": "visionner et rétablir cette version supprimée",
+       "action-undelete": "restaurer des pages",
+       "action-suppressrevision": "visionner et rétablir des révisions supprimées",
        "action-suppressionlog": "voir ce journal privé",
        "action-block": "bloquer en écriture cet utilisateur",
        "action-protect": "modifier les niveaux de protection pour cette page",
        "action-userrights-interwiki": "modifier les droits des utilisateurs sur d'autres wikis",
        "action-siteadmin": "verrouiller ou déverrouiller la base de données",
        "action-sendemail": "envoyer des courriels",
+       "action-editmyoptions": "modifier vos préférences",
        "action-editmywatchlist": "modifier votre liste de suivi",
        "action-viewmywatchlist": "afficher votre liste de suivi",
        "action-viewmyprivateinfo": "voir vos informations personnelles",
        "uploaded-setting-event-handler-svg": "Positionner les attributs du gestionnaire d’événements n'est pas possbile, <code>&lt;$1 $2=\"$3\"&gt;</code> trouvé dans le fichier SVG téléchargé.",
        "uploaded-setting-href-svg": "L’utilisation de la balise « set » pour ajouter un attribut « href » à l’élément parent est interdite.",
        "uploaded-wrong-setting-svg": "L’utilisation de la balise « set » pour ajouter une cible distante/données/script à un attribut quelconque est interdite. <code>&lt;set to=\"$1\"&gt;</code> a été trouvé dans le fichier SVG téléchargé.",
-       "uploaded-setting-handler-svg": "Les SVG qui positionnent l’attribut « handler » avec distant/données/script sont interdits. <code>$1=\"$2\"</code> a été trouvé dans le fichier SVG téléchargé.",
+       "uploaded-setting-handler-svg": "Les SVG qui positionnent l’attribut « handler » avec distant/données/script sont bloqués. <code>$1=\"$2\"</code> a été trouvé dans le fichier SVG téléchargé.",
        "uploaded-remote-url-svg": "Les SVG qui positionnent un attribut de style avec une URL distante sont bloqués. <code>$1=\"$2\"</code> trouvé dans le fichier SVG téléchargé.",
        "uploaded-image-filter-svg": "Filtre d’image avec URL trouvé : <code>&lt;$1 $2=\"$3\"&gt;</code> dans le fichier SVG téléchargé.",
        "uploadscriptednamespace": "Ce fichier SVG contient un espace de noms '$1' non autorisé.",
        "emailccsubject": "Copie de votre message à $1 : $2",
        "emailsent": "Courriel envoyé",
        "emailsenttext": "Votre message a été envoyé par courriel.",
-       "emailuserfooter": "Ce courriel a été {{GENDER:$1|envoyé}} par « $1 » à « {{GENDER:$2|$2}} » par la fonction « {{int:emailuser}} » de {{SITENAME}}.",
+       "emailuserfooter": "Ce courriel a été {{GENDER:$1|envoyé}} par « $1 » à « {{GENDER:$2|$2}} » par la fonction « {{int:emailuser}} » de {{SITENAME}}. {{GENDER:$2|Votre}} courriel sera envoyé directement à l'{{GENDER:$1|émetteur initial}}, en {{GENDER:$1|lui}} mentionnant {{GENDER:$2|votre}} adresse courriel .",
        "usermessage-summary": "Laisser un message système.",
        "usermessage-editor": "Messager du système",
        "watchlist": "Liste de suivi",
        "wlshowhidemine": "mes modifications",
        "wlshowhidecategorization": "catégorisation de la page",
        "watchlist-options": "Options de la liste de suivi",
+       "watchlist-mark-all-visited": "Êtes-vous sûr de vouloir réinitialiser les modifications de la liste de suivi non vues en marquant toutes les pages comme visitées ?",
        "watching": "Suivi…",
        "unwatching": "Fin du suivi…",
        "watcherrortext": "Une erreur s'est produite lors de la modification des paramètres de votre liste de suivi pour « $1 ».",
        "feedback-external-bug-report-button": "Signaler un bogue technique",
        "feedback-dialog-title": "Soumettre un commentaire",
        "feedback-dialog-intro": "Vous pouvez utiliser le simple formulaire ci-dessous pour faire parvenir vos commentaires. Votre commentaire sera ajouté à la page « $1 », ainsi que votre nom d’utilisateur.",
-       "feedback-error1": "Erreur : Résultat de l'IPA non reconnu",
+       "feedback-error1": "Erreur : résultat de l'API non reconnu",
        "feedback-error2": "Erreur : la modification a échoué",
        "feedback-error3": "Erreur : aucune réponse de l'API",
        "feedback-error4": "Erreur : Impossible de publier sous le titre d’avis donné",
        "searchsuggest-search": "Rechercher sur {{SITENAME}}",
        "searchsuggest-containing": "contenant...",
        "api-error-autoblocked": "Votre adresse IP a été bloquée automatiquement, parce qu’elle a été utilisée par un utilisateur bloqué.",
-       "api-error-badaccess-groups": "Vous n'êtes pas autorisé à verser des fichiers sur ce wiki.",
+       "api-error-badaccess-groups": "Vous n'êtes pas autorisé à téléverser des fichiers sur ce wiki.",
        "api-error-badtoken": "Erreur interne : mauvais « jeton ».",
        "api-error-blocked": "Vous avez été bloqué en édition.",
        "api-error-copyuploaddisabled": "Les versements via URL sont désactivés sur ce serveur.",
        "limitreport-expansiondepth": "Plus grande profondeur d’expansion",
        "limitreport-expensivefunctioncount": "Nombre de fonctions d’analyse coûteuses",
        "expandtemplates": "Expansion des modèles",
-       "expand_templates_intro": "Cette page spéciale accepte un texte wiki source et permet de réaliser récursivement l’expansion des modèles qu’il contient.\nElle réalise aussi l’expansion des fonctions du parseur telles que\n<code><nowiki>{{</nowiki>#language:...}}</code> et des variables telles que\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code>.\nEn fait, elle réalise l'expansion de pratiquement tout ce qui est encadré par des doubles accolades.",
+       "expand_templates_intro": "Cette page spéciale accepte un texte wiki source et permet de réaliser récursivement l’expansion de tous les modèles qu’il contient.\nElle réalise aussi l’expansion des fonctions supportées d'analyse telles que\n<code><nowiki>{{</nowiki>#language:...}}</code> et des variables telles que\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code>.\nEn fait, elle réalise l'expansion de pratiquement tout ce qui est encadré par des doubles accolades.",
        "expand_templates_title": "Titre de la page, si le code utilise {{FULLPAGENAME}}, etc. :",
        "expand_templates_input": "Texte wiki source :",
        "expand_templates_output": "Texte wiki obtenu après expansion",
        "mw-widgets-dateinput-no-date": "Aucune date sélectionnée",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-JJ",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Rechercher des médias",
+       "mw-widgets-mediasearch-noresults": "Aucun résultat trouvé.",
        "mw-widgets-titleinput-description-new-page": "la page n’existe pas encore",
        "mw-widgets-titleinput-description-redirect": "redirection vers $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Ajouter une catégorie...",
        "usercssispublic": "Veuillez noter: les sous-pages CSS ne doivent pas contenir de données confidentielles parce qu'elles sont visibles des autres utilisateurs.",
        "restrictionsfield-badip": "Adresse IP ou plage non valide : $1",
        "restrictionsfield-label": "Plages IP autorisées :",
-       "restrictionsfield-help": "Une adresse IP ou une plage CIDR par ligne. Pour tout activer, utiliser <br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Une adresse IP ou une plage CIDR par ligne. Pour tout activer, utiliser <br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "ID de page $1"
 }
index 0dfda9e..e261808 100644 (file)
        "views": "Vistas",
        "toolbox": "Ferramentas",
        "tool-link-userrights": "Modificar os grupos {{GENDER:$1|do usuario|da usuaria}}",
+       "tool-link-userrights-readonly": "Ver os {{GENDER:$1|grupos de usuario}}",
        "tool-link-emailuser": "Enviar un correo electrónico {{GENDER:$1|ao usuario|á usuaria}}",
        "userpage": "Ver a páxina {{GENDER:{{BASEPAGENAME}}|do usuario|da usuaria}}",
        "projectpage": "Ver a páxina do proxecto",
        "revertmerge": "Desfacer a fusión",
        "mergelogpagetext": "A continuación hai unha lista coas fusións máis recentes do historial dunha páxina co doutra.",
        "history-title": "Historial de revisións de \"$1\"",
-       "difference-title": "Diferenzas entre revisións de \"$1\"",
+       "difference-title": "Diferenzas entre revisións de «$1»",
        "difference-title-multipage": "Diferenzas entre as páxinas \"$1\" e \"$2\"",
        "difference-multipage": "(Diferenzas entre páxinas)",
        "lineno": "Liña $1:",
        "search-external": "Procura externa",
        "searchdisabled": "As procuras en {{SITENAME}} están deshabilitadas por cuestións de rendemento.\nMentres tanto pode procurar usando o Google.\nNote que os seus índices do contido de {{SITENAME}} poden estar desactualizados.",
        "search-error": "Produciuse un erro durante a procura: $1",
+       "search-warning": "Houbo un aviso ó facer a buscaː $1",
        "preferences": "Preferencias",
        "mypreferences": "Preferencias",
        "prefs-edits": "Número de edicións:",
        "prefs-help-recentchangescount": "Isto inclúe os cambios recentes, os historiais e mais os rexistros.",
        "prefs-help-watchlist-token2": "Esta é a clave secreta da fonte de novas web para a súa lista de vixilancia.\nCalquera persoa que a saiba poderá ler a súa lista de vixilancia; non comparta esta clave.\n[[Special:ResetTokens|Prema aquí se necesita restablecela]].",
        "savedprefs": "Gardáronse as súas preferencias.",
-       "savedrights": "Gardáronse os dereitos de {{GENDER:$1|usuario|usuaria}} de $1.",
+       "savedrights": "Gardáronse os grupos de {{GENDER:$1|usuario|usuaria}} de $1.",
        "timezonelegend": "Fuso horario:",
        "localtime": "Hora local:",
        "timezoneuseserverdefault": "Usar a hora do servidor por defecto ($1)",
        "prefswarning-warning": "Fixo cambios nas súas preferencias que aínda non se gardaron.\nSe deixa esta páxina sen premer en \"$1\", non se actualizarán as súas preferencias.",
        "prefs-tabs-navigation-hint": "Consello: Pode empregar as frechas esquerda e dereita para navegar polas lapelas da lista.",
        "userrights": "Xestión dos dereitos de usuario",
-       "userrights-lookup-user": "Administrar os grupos do usuario",
+       "userrights-lookup-user": "Seleccionar un usuario",
        "userrights-user-editname": "Escriba un nome de usuario:",
-       "editusergroup": "Editar os grupos {{GENDER:$1|do usuario|da usuaria}}",
+       "editusergroup": "Cargar os grupos de usuario",
        "editinguser": "Mudando os dereitos {{GENDER:$1|do usuario|da usuaria}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Consultando os dereitos {{GENDER:$1|de usuario|de usuaria}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Editar os grupos do usuario",
+       "userrights-viewusergroup": "Consultar os grupos do usuario",
        "saveusergroups": "Gardar os grupos {{GENDER:$1|do usuario|da usuaria}}",
        "userrights-groupsmember": "Membro de:",
        "userrights-groupsmember-auto": "Membro implícito de:",
        "action-upload_by_url": "cargar este ficheiro desde un enderezo URL",
        "action-writeapi": "usar a escritura API",
        "action-delete": "borrar esta páxina",
-       "action-deleterevision": "borrar esta revisión",
-       "action-deletedhistory": "ver o historial borrado desta páxina",
+       "action-deleterevision": "borrar revisións",
+       "action-deletelogentry": "borrar entradas do rexistro",
+       "action-deletedhistory": "ver o historial borrado dunha páxina",
+       "action-deletedtext": "ver o texto dunha revisión borrada",
        "action-browsearchive": "procurar páxinas borradas",
-       "action-undelete": "restaurar esta páxina",
-       "action-suppressrevision": "revisar e restaurar esta revisión agochada",
+       "action-undelete": "restaurar páxinas",
+       "action-suppressrevision": "revisar e restaurar revisións agochadas",
        "action-suppressionlog": "ver este rexistro privado",
        "action-block": "bloquear o usuario fronte á edición",
        "action-protect": "cambiar o nivel de protección desta páxina",
        "action-userrights-interwiki": "editar os permisos de usuario dos usuarios doutros wikis",
        "action-siteadmin": "bloquear ou desbloquear a base de datos",
        "action-sendemail": "enviar correos electrónicos",
+       "action-editmyoptions": "Editar as súas preferencias",
        "action-editmywatchlist": "editar a súa lista de vixilancia",
        "action-viewmywatchlist": "ver a súa lista de vixilancia",
        "action-viewmyprivateinfo": "ver a súa información privada",
        "emailccsubject": "Copia da súa mensaxe para $1: $2",
        "emailsent": "Mensaxe enviada",
        "emailsenttext": "A súa mensaxe de correo electrónico foi enviada.",
-       "emailuserfooter": "$1 {{GENDER:$1|enviou}} este correo electrónico a {{GENDER:$2|$2}} mediante a función \"{{int:emailuser}}\" de {{SITENAME}}.",
+       "emailuserfooter": "$1 {{GENDER:$1|enviou}} este correo electrónico a {{GENDER:$2|$2}} mediante a función \"{{int:emailuser}}\" de {{SITENAME}}. {{GENDER:$2|A súa}} dirección de correo electrónico será enviada directamente {{GENDER:$1|ó|á}} remitente orixinal, {{GENDER:$1|revelándolle}} a {{GENDER:$2|súa}} dirección de correo.",
        "usermessage-summary": "Mensaxe deixada polo sistema.",
        "usermessage-editor": "Editor das mensaxes do sistema",
        "watchlist": "Lista de vixilancia",
        "mw-widgets-dateinput-no-date": "Non se seleccionou ningunha data",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Procurar ficheiros multimedia",
+       "mw-widgets-mediasearch-noresults": "Non se atopou ningún resultado.",
        "mw-widgets-titleinput-description-new-page": "a páxina aínda non existe",
        "mw-widgets-titleinput-description-redirect": "redirección cara a $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Engadir unha categoría...",
        "usercssispublic": "Lembre: As subpáxinas CSS non deberían conter datos confidenciais porque outros usuarios poden velos.",
        "restrictionsfield-badip": "Enderezo IP ou rango de IP non válido: $1",
        "restrictionsfield-label": "Rangos de IP permitidos:",
-       "restrictionsfield-help": "Un único enderezo IP ou rango CIDR por liña. Para habilitalos todos, utilice<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Un único enderezo IP ou rango CIDR por liña. Para habilitalos todos, utilice<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "identificador de páxina $1"
 }
index 458d0e5..30e2574 100644 (file)
@@ -29,7 +29,8 @@
                        "Nisargjhaveri",
                        "Matma Rex",
                        "Bhatakati aatma",
-                       "YmKavishwar"
+                       "YmKavishwar",
+                       "Kevin Kovadia"
                ]
        },
        "tog-underline": "કડીઓની નીચે લીટી (અંડરલાઇન):",
        "passwordreset-emaildisabled": "આ વિકિ પર ઇમેઇલ સગવડ બંધ છે.",
        "passwordreset-username": "સભ્ય નામ:",
        "passwordreset-domain": "ડોમેઈન:",
-       "passwordreset-capture": "પરિણામી ઈમેલ જોવો છે?",
-       "passwordreset-capture-help": "જો તમે આ ઓપ્શન સિલેક્ટ કરશો, તો તમને અને યુઝર ને ઈ મેલ (કામચલાઉ પાસવર્ડ સાથે) દેખાડવામાં આવશે.",
        "passwordreset-email": "ઇમેલ સરનામું:",
        "passwordreset-emailtitle": "{{SITENAME}} પર ખાતાની માહિતી",
        "passwordreset-emailtext-ip": "કોઈકે (કદાચ તમોએ , $1 IP એડ્રેસ થી) તમારી વેબસાઈટ {{SITENAME}}  ($4) નો પાસવર્ડ રિસેટ કરવાની રજૂઆત કરી છે. આ ઈમેઈલ એડ્રેસ સાથે {{PLURAL:$3|નું ખાતું|ના ખાતા}} જોડાયેલા છે.\n.\n.\n\n$2\n\n{{PLURAL:$3|આ કામચલાઉ પાસવર્ડ|આ બધા કામચલાઉ પાસવર્ડ}} {{PLURAL:$5|એક દિવસ|$5 દિવસો}} માં નષ્ટ થઇ જશે. તમારે અત્યારે જ ખાતું ખોલીને નવો પાસવર્ડ સેટ કરી લેવો જોઈએ .જો કોઈ બીજા એ આ રજૂઆત કરી હોય, અથવા જો તમને પોતાનો અસલ પાસવર્ડ યાદ હોય, અને તેને બદલવા નથી માગતા, તો આ સંદેશાને જતો કરીને પોતાના અસલ પાસવર્ડ ને વાપરી શકો છો.",
        "userrights-reason": "કારણ:",
        "userrights-no-interwiki": "અન્ય વિકિ પર અન્ય સભ્યો ના અધિકારો માં પરિવર્તન કરવાની તમને પરવાનગી નથી",
        "userrights-nodatabase": "માહિતીસંચ $1 અસ્તિત્વમાં નથી કે તે સ્થાનીય નથી.",
-       "userrights-nologin": "સભ્યના અધિકારો આપવા તમે પ્રબંધક તરીકે પ્રવેશ  [[Special:UserLogin|log in]] કરેલ હોવો જરૂરી છે.",
-       "userrights-notallowed": "તમને વપરાશકર્તા અધિકારો ઉમેરવા કે દૂર કરવાની પરવાનગી નથી.",
        "userrights-changeable-col": "તમે બદલી શકો તેવા જૂથ",
        "userrights-unchangeable-col": "તમે બદલી ન શકો તેવા જૂથ",
        "group": "સમુહ",
        "right-siteadmin": "માહિતી સંચયને ઉઘાડો અને વાસો.",
        "right-override-export-depth": "૫ સ્તર સુધી જોડાયેલ પાના સહીત પાના નિકાસ કરો",
        "right-sendemail": " અન્ય સભ્યોને ઈ-મેલ મોકલો",
-       "right-passwordreset": "પાસવર્ડ રીસેટ ઇ-મેઇલ્સ જુઓ",
        "newuserlogpage": "નવા બનેલા સભ્યોનો લૉગ",
        "newuserlogpagetext": "આ સભ્યોની રચનાનો લોગ છે.",
        "rightslog": "સભ્ય હક્ક માહિતિ પત્રક",
        "action-upload_by_url": "URL પરથી આ ફાઇલ ચઢાવો",
        "action-writeapi": "લેખન API વાપરો",
        "action-delete": "આ પાનું હટાવો",
-       "action-deleterevision": "આ પુનરાવર્તનારદ્દ કરો",
-       "action-deletedhistory": "àª\86 àªªàª¾àª¨àª¾àª¨àª¾ àª°àª¦à«\8dદà«\80àª\95રણનà«\8b àª\87તિહાસ બતાવો",
+       "action-deleterevision": "આ પુનરાવર્તન ને કાઢી નાખો",
+       "action-deletedhistory": "àª\86 àªªàª¾àª¨àª¾àª¨àª¾ àª°àª¦à«\8dદà«\80àª\95રણનà«\80 àªªà«\82રà«\8dવ-વિàª\97ત બતાવો",
        "action-browsearchive": "હટાવેલા પાનાં શોધો",
-       "action-undelete": "àª\86 àªªàª¾àª¨à«\81àª\82 àª«àª°à«\80 àªªà«\81નરà«\8dàª\9cà«\80વà«\80ત àª\95રà«\8b",
-       "action-suppressrevision": "સમà«\80àª\95à«\8dષા àª\95રà«\80 àª\86 àª\97à«\81પà«\8dત àªªà«\81નરાવરà«\8dતન પુન:સ્થાપિત કરો",
+       "action-undelete": "ડિલà«\80àª\9f àª¥àª¯àª¾ àªµàª\97રના àªªàª¾àª¨àª¾àª\82àª\93",
+       "action-suppressrevision": "સમà«\80àª\95à«\8dષા àª\95રà«\8b àª\85નà«\87 àª\97à«\81પà«\8dત àªªà«\81નરાવરà«\8dતનà«\8b àª¨à«\87 પુન:સ્થાપિત કરો",
        "action-suppressionlog": "આ અંગત યાદી જુઓ",
        "action-block": "આ સભ્ય દ્વારા થનાર ફેરફાર પ્રતિબંધીત કરો",
        "action-protect": "આ પાનાંનું પ્રતિબંધ સ્તર બદલો",
index 2610768..39ef2fd 100644 (file)
        "search-external": "חיפוש חיצוני",
        "searchdisabled": "חיפוש ב{{grammar:תחילית|{{SITENAME}}}} אינו מופעל כעת.\nבינתיים אפשר לחפש באמצעות גוגל.\nשימו לב שייתכן שהמידע של {{SITENAME}} שם אינו מעודכן.",
        "search-error": "אירעה שגיאה במהלך החיפוש: $1",
+       "search-warning": "אירעה אזהרה במהלך החיפוש: $1",
        "preferences": "העדפות",
        "mypreferences": "העדפות",
        "prefs-edits": "מספר העריכות:",
        "userrights-user-editname": "שם משתמש:",
        "editusergroup": "טעינת קבוצות המשתמש",
        "editinguser": "שינוי ההרשאות של {{GENDER:$1|המשתמש|המשתמשת}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "צפייה בהרשאות של {{GENDER:$1|המשתמש|המשתמשת}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "עריכת קבוצות משתמש",
+       "userrights-viewusergroup": "צפייה בקבוצות משתמש",
        "saveusergroups": "שמירת הקבוצות של ה{{GENDER:$1|משתמש|משתמשת}}",
        "userrights-groupsmember": "{{GENDER:$2|חבר|חברה}} ב{{PLURAL:$1|קבוצה|קבוצות}}:",
        "userrights-groupsmember-auto": "{{GENDER:$2|חבר|חברה}} אוטומטית ב{{PLURAL:$1|קבוצה|קבוצות}}:",
        "action-upload_by_url": "להעלות קובץ זה מכתובת URL",
        "action-writeapi": "להשתמש ב־API לשינוי דפים",
        "action-delete": "למחוק דף זה",
-       "action-deleterevision": "למחוק גרסה זו",
-       "action-deletedhistory": "לצפות בהיסטוריה המחוקה של דפים",
+       "action-deleterevision": "למחוק גרסאות",
+       "action-deletelogentry": "למחוק פריטי יומן",
+       "action-deletedhistory": "לצפות בהיסטוריה מחוקה של דף",
+       "action-deletedtext": "לצפות בטקסט של גרסה מחוקה",
        "action-browsearchive": "לחפש דפים מחוקים",
-       "action-undelete": "×\9cש×\97×\96ר ×\93×£ ×\96×\94",
-       "action-suppressrevision": "×\9c×\91×\93×\95ק ×\95×\9cש×\97×\96ר ×\92רס×\94 ×\9e×\95סתרת ×\96×\95",
+       "action-undelete": "×\9cש×\97×\96ר ×\93פ×\99×\9d",
+       "action-suppressrevision": "×\9cסק×\95ר ×\95×\9cש×\97×\96ר ×\92רס×\90×\95ת ×\9e×\95סתר×\95ת",
        "action-suppressionlog": "לצפות ביומן הפרטי הזה",
        "action-block": "לחסום משתמשים מעריכה",
        "action-protect": "לשנות את רמת ההגנה של דף זה",
        "action-userrights-interwiki": "לשנות הרשאות של משתמשים באתרי ויקי אחרים",
        "action-siteadmin": "לנעול או לבטל את הנעילה של בסיס הנתונים",
        "action-sendemail": "לשלוח דואר אלקטרוני למשתמשים",
+       "action-editmyoptions": "לערוך את ההעדפות {{GENDER:|שלך|שלך|שלכם}}",
        "action-editmywatchlist": "לערוך את רשימת המעקב {{GENDER:|שלך|שלך|שלכם}}",
        "action-viewmywatchlist": "לצפות ברשימת המעקב {{GENDER:|שלך|שלך|שלכם}}",
        "action-viewmyprivateinfo": "לצפות במידע הפרטי {{GENDER:|שלך|שלך|שלכם}}",
        "number_of_watching_users_pageview": "[{{PLURAL:$1|משתמש אחד עוקב|$1 משתמשים עוקבים}} אחרי הדף]",
        "rc_categories": "הגבלה לקטגוריות (מופרדות בתו \"|\"):",
        "rc_categories_any": "כל אחת מהנבחרות",
-       "rc-change-size-new": "{{PLURAL:$1|×\91×\99ת ×\90×\97×\93|$1 ×\91תים}} לאחר השינוי",
+       "rc-change-size-new": "{{PLURAL:$1|×\91×\99×\99×\98 ×\90×\97×\93|$1 ×\91×\99×\99×\98ים}} לאחר השינוי",
        "newsectionsummary": "/* $1 */ פסקה חדשה",
        "rc-enhanced-expand": "הצגת הפרטים",
        "rc-enhanced-hide": "הסתרת הפרטים",
        "emailccsubject": "העתק של הודעתך למשתמש $1: $2",
        "emailsent": "הדואר נשלח",
        "emailsenttext": "הודעת הדואר האלקטרוני שלך נשלחה.",
-       "emailuserfooter": "$1 {{GENDER:$1|שלח|שלחה}} את הדוא\"ל הזה ל{{GENDER:$2|משתמש|משתמשת}} $2 באמצעות התכונה \"{{int:emailuser}}\" באתר {{SITENAME}}.",
+       "emailuserfooter": "$1 {{GENDER:$1|שלח|שלחה}} את הדוא\"ל הזה ל{{GENDER:$2|משתמש|משתמשת}} $2 באמצעות התכונה \"{{int:emailuser}}\" באתר {{SITENAME}}. התשובה שלך תישלח ישירות {{GENDER:$1|לשולח המקורי|לשולחת המקורית}}, והיא תחשוף {{GENDER:$1|בפניו|בפניה}} את כתובת הדוא\"ל שלך.",
        "usermessage-summary": "השארת הודעת מערכת.",
        "usermessage-editor": "שולח הודעות המערכת",
        "watchlist": "רשימת המעקב",
        "wlshowhidemine": "עריכות שלי",
        "wlshowhidecategorization": "שינויים בקטגוריות",
        "watchlist-options": "אפשרויות ברשימת המעקב",
+       "watchlist-mark-all-visited": "האם ברצונך לאפס את כל השינויים שלא צפית בהם ברשימת המעקב ולסמן את כל הדפים כאילו נצפו?",
        "watching": "בהוספה לרשימת המעקב...",
        "unwatching": "בהסרה מרשימת המעקב...",
        "watcherrortext": "אירעה שגיאה בעת שינוי הגדרות רשימת המעקב של \"$1\".",
        "editcomment": "תקציר העריכה היה: <em>$1</em>.",
        "revertpage": "שוחזר מעריכות של [[Special:Contributions/$2|$2]] ([[User talk:$2|שיחה]]) לעריכה האחרונה של [[User:$1|$1]]",
        "revertpage-nouser": "שוחזר מעריכות של משתמש מוסתר לעריכה האחרונה של {{GENDER:$1|[[User:$1|$1]]}}",
-       "rollback-success": "שוחזר מעריכות של $1 לעריכה האחרונה של $2",
+       "rollback-success": "שוחזר מעריכות של $1 לעריכה האחרונה של $2.",
        "rollback-success-notify": "שוחזר מעריכות של $1 לעריכה האחרונה של $2. [$3 הצגת שינויים]",
        "sessionfailure-title": "בעיה בחיבור",
        "sessionfailure": "נראה שיש בעיה בחיבור שלך לאתר;\nפעולה זו בוטלה כאמצעי זהירות נגד התחזות לתקשורת ממחשבך.\nנא לחזור לדף הקודם, לטעון אותו מחדש ולנסות שוב.",
        "special-characters-title-emdash": "קו מפריד ארוך",
        "special-characters-title-minus": "מינוס",
        "mw-widgets-dateinput-no-date": "לא נבחר תאריך",
+       "mw-widgets-mediasearch-input-placeholder": "חיפוש מדיה",
+       "mw-widgets-mediasearch-noresults": "לא נמצאו תוצאות.",
        "mw-widgets-titleinput-description-new-page": "הדף עדיין לא קיים",
        "mw-widgets-titleinput-description-redirect": "הפניה ל{{GRAMMAR:תחילית|$1}}",
        "mw-widgets-categoryselector-add-category-placeholder": "הוספת קטגוריה...",
        "usercssispublic": "שימו לב: משתמשים אחרים יכולים לצפות בדפי ה־CSS שלכם, ולכן אין לכלול בהם מידע סודי.",
        "restrictionsfield-badip": "כתובת או טווח כתובות IP בלתי תקין: $1",
        "restrictionsfield-label": "טווחי כתובות IP מותרים:",
-       "restrictionsfield-help": "כתובת IP אחת או טווח CIDR אחד בשורה. כדי לאפשר את הכול, ניתן להשתמש ב:<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "כתובת IP אחת או טווח CIDR אחד בשורה. כדי לאפשר את הכול, ניתן להשתמש ב:<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "revid": "גרסה $1",
+       "pageid": "מזהה דף $1"
 }
index 42dd00e..231c40e 100644 (file)
@@ -74,7 +74,8 @@
                        "Upendradutt93",
                        "Nemo bis",
                        "Wassan.anmol",
-                       "Ziyaurr"
+                       "Ziyaurr",
+                       "NehalDaveND"
                ]
        },
        "tog-underline": "कड़ियाँ अधोरेखन:",
        "pagetitle-view-mainpage": "{{SITENAME}}",
        "backlinksubtitle": "← $1",
        "retrievedfrom": "\"$1\" से लिया गया",
-       "youhavenewmessages": "आपके लिए $1 हैं। ($2)",
-       "youhavenewmessagesfromusers": "आपके लिये {{PLURAL:$3|एक अन्य सदस्य|$3 अन्य सदस्यों}} के $1 हैं। ($2)",
+       "youhavenewmessages": "{{PLURAL:$3|आपके लिए}} $1 हैं। ($2)",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|आपके लिये}} {{PLURAL:$3|एक अन्य सदस्य|$3 अन्य सदस्यों}} के $1 हैं। ($2)",
        "youhavenewmessagesmanyusers": "आपके लिये $1 हैं। ($2)",
        "newmessageslinkplural": "{{PLURAL:$1|एक नया सन्देश|999=नये सन्देश}}",
        "newmessagesdifflinkplural": "{{PLURAL:$1|पिछला|999=पिछले}} बदलाव",
        "viewsourcetext": "आप इस पृष्ठ का स्रोत देख सकते हैं और उसकी नकल उतार सकते हैं:",
        "viewyourtext": "आप इस पृष्ठ में ''अपने सम्पादन'' का स्रोत देख सकते हैं और उसकी नकल उतार सकते हैं:",
        "protectedinterface": "यह पृष्ठ इस विकी के सॉफ़्टवेयर का इंटरफ़ेस पाठ देता है, और इसे गलत प्रयोग से बचाने के लिये सुरक्षित कर दिया गया है।\nसभी विकियों के लिए अनुवाद जोड़ने या बदलने के लिए कृपया मीडियाविकि के क्षेत्रीयकरण प्रकल्प [https://translatewiki.net/ translatewiki.net] का प्रयोग करें।",
-       "editinginterface": "<strong>à¤\9aà¥\87तावनà¥\80:</strong> à¤\86प à¤\8fà¤\95 à¤\90सà¥\87 à¤ªà¥\83षà¥\8dठ à¤\95à¥\8b à¤¬à¤¦à¤² à¤°à¤¹à¥\87 à¤¹à¥\88à¤\82 à¤\9cà¥\8b à¤¸à¥\89फ़à¥\8dà¤\9fवà¥\87यर à¤\95ा à¤\87à¤\82à¤\9fरफ़à¥\87स à¤ªà¤¾à¤  à¤ªà¥\8dरदान à¤\95रता à¤¹à¥\88।\nà¤\87स à¤ªà¥\83षà¥\8dठ à¤\95à¥\8b à¤¬à¤¦à¤²à¤¨à¥\87 à¤¸à¥\87 à¤\85नà¥\8dय à¤¸à¤¦à¤¸à¥\8dयà¥\8bà¤\82 à¤\95à¥\8b à¤ªà¥\8dरदरà¥\8dशित à¤\87à¤\82à¤\9fरफ़à¥\87स à¤\95à¥\80 à¤¶à¤\95à¥\8dलà¥\8bसà¥\82रत में बदलाव आएगा।",
+       "editinginterface": "<strong>à¤\9aà¥\87तावनà¥\80:</strong> à¤\86प à¤\8fà¤\95 à¤\90सà¥\87 à¤ªà¥\83षà¥\8dठ à¤\95à¥\8b à¤¬à¤¦à¤² à¤°à¤¹à¥\87 à¤¹à¥\88à¤\82 à¤\9cà¥\8b à¤¸à¥\89फ़à¥\8dà¤\9fवà¥\87यर à¤\95ा à¤\87à¤\82à¤\9fरफ़à¥\87स à¤ªà¤¾à¤  à¤ªà¥\8dरदान à¤\95रता à¤¹à¥\88।\nà¤\87स à¤ªà¥\83षà¥\8dठ à¤\95à¥\8b à¤¬à¤¦à¤²à¤¨à¥\87 à¤¸à¥\87 à¤\85नà¥\8dय à¤¸à¤¦à¤¸à¥\8dयà¥\8bà¤\82 à¤\95à¥\8b à¤ªà¥\8dरदरà¥\8dशित à¤\87à¤\82à¤\9fरफ़à¥\87स à¤\95à¥\80 à¤¸à¥\8dवरà¥\82प में बदलाव आएगा।",
        "translateinterface": "सभी विकियों के लिए अनुवाद जोड़ने या बदलने के लिए मीडियाविकि क्षेत्रीयकरण परियोजना [https://translatewiki.net/ translatewiki.net] का प्रयोग करें।",
        "cascadeprotected": "यह पृष्ठ सुरक्षित हैं, क्योंकि यह निम्नलिखित {{PLURAL:$1|पृष्ठ|पृष्ठों}} की सुरक्षा-सीढ़ी में समाविष्ट है:\n$2",
        "namespaceprotected": "आपको '''$1''' नामस्थान में समाविष्ट पृष्ठों को बदलने की अनुमति नहीं है।",
        "nextn-title": "{{PLURAL:$1|अगला|अगले}} $1 परिणाम",
        "shown-title": "हर पृष्ठ पर $1 {{PLURAL:$1|परिणाम}} दिखाएँ",
        "viewprevnext": "देखें ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "'''इस विकि पर \"[[:$1]]\" नाम का एक पृष्ठ है'''",
+       "searchmenu-exists": "<strong>इस विकि पर \"[[:$1]]\" नाम से एक पृष्ठ है।</strong> {{PLURAL:$2|0=|अन्य प्राप्त खोज परिणामों को भी देखें।}}",
        "searchmenu-new": "<strong>इस विकि पर \"[[:$1]]\" नाम का पृष्ठ बनाएँ!</strong>{{PLURAL:$2|0=|आपकी खोज से मिला पृष्ठ भी देखें।|खोज परिणाम भी देखें।}}",
        "searchprofile-articles": "सामग्री पृष्ठ",
        "searchprofile-images": "मल्टीमीडिया",
        "prefs-changeemail": "ई-मेल पता परिवर्तित करें",
        "prefs-setemail": "ई-मेल पता सेट करें",
        "prefs-email": "ई-मेल वरीयताएँ",
-       "prefs-rendering": "शà¤\95à¥\8dलà¥\8bसà¥\82रत",
+       "prefs-rendering": "सà¥\8dवरà¥\82प",
        "saveprefs": "संजोएँ",
        "restoreprefs": "वापिस मूल जमावों पर आ जाएँ (सभी भागों में)",
        "prefs-editing": "संपादन",
index 62a131b..077048b 100644 (file)
        "prefs-watchlist-edits-max": "Maksimalni broj: 1000",
        "prefs-watchlist-token": "Token popisa praćenja:",
        "prefs-misc": "Razno",
-       "prefs-resetpass": "Promijeni zaporku",
+       "prefs-resetpass": "promijeni zaporku",
        "prefs-changeemail": "promijeni adresu e-pošte",
        "prefs-setemail": "Postavite E-mail adresu",
        "prefs-email": "Mogućnosti e-maila",
        "rollback-success": "uklonjeno uređivanje {{GENDER:$1|suradnika|suradnice}} $1\nvraćeno na posljednju inačicu {{GENDER:$2|suradnika|suradnice}} $2.",
        "sessionfailure-title": "Prekid sesije",
        "sessionfailure": "Uočili smo problem s Vašom prijavom. Zadnja naredba nije izvršena kako bi se izbjegla zloupotreba. Molimo Vas da se u pregledniku vratite natrag na prethodnu stranicu, ponovno je učitate i zatim pokušate opet.",
-       "changecontentmodel": "Promijeni model sadržaja stranice",
+       "changecontentmodel": "Promjena modela sadržaja stranice",
        "changecontentmodel-legend": "Promijeni model sadržaja",
        "changecontentmodel-title-label": "Naziv stranice",
        "changecontentmodel-model-label": "Novi model sadržaja",
        "changecontentmodel-reason-label": "Razlog:",
+       "changecontentmodel-submit": "Promijeni",
        "changecontentmodel-success-title": "Sadržaj modela je promijenjen",
        "protectlogpage": "Evidencija zaštićivanja",
        "protectlogtext": "Ispod je evidencija zaštićivanja i uklanjanja zaštite pojedinih stranica.\nPogledajte [[Special:ProtectedPages|zaštićene stranice]] za popis trenutačno zaštićenih stranica.",
        "modifiedarticleprotection": "promijenjen stupanj zaštite za \"[[$1]]\"",
        "unprotectedarticle": "uklonjena zaštita članka \"[[$1]]\"",
        "movedarticleprotection": "premještene postavke zaštite s \"[[$2]]\" na \"[[$1]]\"",
+       "protectedarticle-comment": "{{GENDER:$2|$2 je zaštitio|$2 je zaštitila}} \"[[$1]]\"",
+       "modifiedarticleprotection-comment": "{{GENDER:$2|$2 je promijenio nivo zaštite|$2 je promijenila nivo zaštite}} za \"[[$1]]\"",
+       "unprotectedarticle-comment": "{{GENDER:$2|$2 je uklonio zaštitu|$2 je uklonila zaštitu}} za \"[[$1]]\"",
        "protect-title": "Zaštićujem \"$1\"",
        "protect-title-notallowed": "Vidi stupanj zaštite stranice \"$1\"",
        "prot_1movedto2": "$1 premješteno na $2",
        "tooltip-pt-userpage": "Moja suradnička stranica",
        "tooltip-pt-anonuserpage": "Suradnička stranica za IP adresu pod kojom uređujete",
        "tooltip-pt-mytalk": "Vaša stranica za razgovor",
-       "tooltip-pt-anontalk": "Razgovor o suradnicima s ove IP adrese",
+       "tooltip-pt-anontalk": "Razgovor o uređivanjima s ove IP adrese",
        "tooltip-pt-preferences": "Vaše postavke",
        "tooltip-pt-watchlist": "Popis stranica koje pratite.",
        "tooltip-pt-mycontris": "Popis Vaših doprinosa",
+       "tooltip-pt-anoncontribs": "Popis uređivanja učinjenih s ove IP adrese",
        "tooltip-pt-login": "Predlažemo Vam da se prijavite, međutim nije obvezno.",
        "tooltip-pt-logout": "Odjavi se",
        "tooltip-pt-createaccount": "Predlažemo Vam mogućnost stvaranja računa i prijave, iako to nije nužno.",
        "tooltip-watchlistedit-normal-submit": "Ukloni naslove",
        "tooltip-watchlistedit-raw-submit": "Osvježi popis praćenja",
        "tooltip-recreate": "Vrati stranicu unatoč tome što je obrisana",
-       "tooltip-upload": "Pokreni snimanje (''upload'')",
+       "tooltip-upload": "Pokreni postavljanje datoteke (''upload'')",
        "tooltip-rollback": "\"Ukloni\" uklanja uređivanja posljednjeg suradnika na ovoj stranici.",
        "tooltip-undo": "\"Ukloni ovu izmjenu\" uklanja ovu izmjenu i otvara okvir za uređivanje. Omogućava unošenje razloga u sažetak.",
        "tooltip-preferences-save": "Spremi postavke",
        "htmlform-time-invalid": "Unesena vrijednost nije prepoznati format vremena. Pokušajte koristiti format HH:MM:SS.",
        "htmlform-datetime-toohigh": "Uneseni datum i vrijeme su veći od $1",
        "logentry-delete-delete": "$1 je {{GENDER:$2|obrisao|obrisala}} stranicu $3",
+       "logentry-delete-delete_redir": "$1 je {{GENDER:$2|obrisao|obrisala}} preusmjeravanje $3 prepisivanjem",
        "logentry-delete-restore": "$1 je {{GENDER:$2|vratio|vratila}} stranicu $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|zapisa u evidenciji|$5 zapisa u evidenciji}} na $3: $4",
        "logentry-delete-revision": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|uređivanja|$5 uređivanja}} na stranici $3: $4",
index ea3fd1e..6b1e806 100644 (file)
        "cannotdelete": "Չհաջողվեց ջնջել «$1» էջը կամ ֆայլը։\nՀավանաբար այն արդեն ջնջվել է մեկ այլ մասնակցի կողմից։",
        "cannotdelete-title": "Հնարավոր չէ ջնջել $1 էջը",
        "delete-hook-aborted": "Խմբագրել չեղյալ է.\nԼրացուցիչ պարզաբանումներ չի դրվել.",
-       "no-null-revision": "Չի հաջողվել ստեղծել նոր զրոյական правку համար էջը \"$1\"",
+       "no-null-revision": "Չի հաջողվել ստեղծել նոր զրոյական խմբագրում էջի համար \"$1\"",
        "badtitle": "Անընդունելի անվանում",
        "badtitletext": "Հարցված էջի անվանումը անընդունելի է, դատարկ է կամ սխալ միջ-լեզվական կամ ինտերվիքի անվանում է։ Հնարավոր է, որ այն պարունակում է անթույլատրելի սիմվոլներ։",
        "title-invalid-empty": "Էջի հայցվող վերնագիրը դատարկ է կամ պարունակում է միայն անվանատարածքի անունը։",
        "watchlisttools-raw": "Խմբագրել հում հսկացանկը",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|քննարկում]])",
        "version": "ՄեդիաՎիքի տարբերակը",
-       "version-ext-license": "Ô¼Õ«Ö\81Õ¥Õ¶Õ¦Õ«Õ¡",
+       "version-ext-license": "Ô±Ö\80Õ¿Õ¸Õ¶Õ¡Õ£Õ«Ö\80",
        "version-ext-colheader-name": "Ընդլայնում",
        "version-ext-colheader-version": "Տարբերակ",
-       "version-ext-colheader-license": "Ô¼Õ«Ö\81Õ¥Õ¶Õ¦Õ«Õ¡",
-       "version-license-title": "Ô¼Õ«Ö\81Õ¥Õ¶Õ¦Õ«Õ¡ $1-ի համար",
+       "version-ext-colheader-license": "Ô±Ö\80Õ¿Õ¸Õ¶Õ¡Õ£Õ«Ö\80",
+       "version-license-title": "Ô±Ö\80Õ¿Õ¸Õ¶Õ¡Õ£Õ«Ö\80 $1-ի համար",
        "version-poweredby-credits": "Այս վիքին աշխատում է '''[https://www.mediawiki.org/ MediaWiki]'''֊ով, copyright © 2001-$1 $2։",
        "fileduplicatesearch": "Փնտրել կրկնօրինակ պատկերներ",
        "fileduplicatesearch-summary": "Փնտրել կրկնօրինակ պատկերներ՝ հեշ արժեքների հիման վրա",
        "special-characters-title-endash": "ո գծիկ (en dash)",
        "special-characters-title-emdash": "ա գծիկ (em dash)",
        "special-characters-title-minus": "հանածի նշան",
+       "mw-widgets-mediasearch-noresults": "Ոչինչ չի գտնվել",
        "authmanager-create-from-login": "Հաշիվ ստեղծելու համար, խնդրում ենք լրացնել ստորև դաշտերը"
 }
index baaaab2..face238 100644 (file)
        "search-external": "Recerca externe",
        "searchdisabled": "Le recerca in {{SITENAME}} es disactivate.\nTu pote cercar via Google in le interim.\nNota que lor indices del contento de {{SITENAME}} pote esser obsolete.",
        "search-error": "Un error ha occurrite durante le recerca: $1",
+       "search-warning": "Un aviso se ha producite durante le recerca: $1",
        "preferences": "Preferentias",
        "mypreferences": "Preferentias",
        "prefs-edits": "Numero de modificationes:",
        "userrights-user-editname": "Entra un nomine de usator:",
        "editusergroup": "Cargar gruppos de usator",
        "editinguser": "Cambia le derectos del {{GENDER:$1|usator}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Ecce le derectos del {{GENDER:$1|usator}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Modificar gruppos de usatores",
+       "userrights-viewusergroup": "Vider gruppos del usator",
        "saveusergroups": "Salveguardar gruppos de {{GENDER:$1|usator}}",
        "userrights-groupsmember": "Membro de:",
        "userrights-groupsmember-auto": "Membro implicite de:",
        "action-upload_by_url": "incargar iste file ab un adresse URL",
        "action-writeapi": "usar le API pro modificar le wiki",
        "action-delete": "deler iste pagina",
-       "action-deleterevision": "deler iste version",
-       "action-deletedhistory": "vider le historia delite de iste pagina",
+       "action-deleterevision": "deler versiones",
+       "action-deletelogentry": "deler entratas de registro",
+       "action-deletedhistory": "vider le historia delite de un pagina",
+       "action-deletedtext": "vider le texto de un version delite",
        "action-browsearchive": "cercar in paginas delite",
-       "action-undelete": "restaurar iste pagina",
-       "action-suppressrevision": "revider e restaurar iste version celate",
+       "action-undelete": "restaurar paginas",
+       "action-suppressrevision": "revider e restaurar versiones celate",
        "action-suppressionlog": "vider iste registro private",
        "action-block": "blocar iste usator de facer modificationes",
        "action-protect": "cambiar le nivellos de protection pro iste pagina",
        "action-userrights-interwiki": "modificar le derectos de usatores in altere wikis",
        "action-siteadmin": "blocar e disblocar le base de datos",
        "action-sendemail": "inviar e-mail",
+       "action-editmyoptions": "modificar tu preferentias",
        "action-editmywatchlist": "modificar le proprie observatorio",
        "action-viewmywatchlist": "vider le proprie observatorio",
        "action-viewmyprivateinfo": "vider le proprie information private",
        "mw-widgets-dateinput-no-date": "Nulle data seligite",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Cercar multimedia",
+       "mw-widgets-mediasearch-noresults": "Nulle resultato trovate.",
        "mw-widgets-titleinput-description-new-page": "pagina non existe ancora",
        "mw-widgets-titleinput-description-redirect": "redirection a $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Adder un categoria…",
        "usercssispublic": "Nota ben: Subpaginas CSS non debe continer datos confidential perque altere usatores pote vider los.",
        "restrictionsfield-badip": "Adresse o intervallo IP non valide: $1",
        "restrictionsfield-label": "Intervallos IP permittite:",
-       "restrictionsfield-help": "Un adresse IP o intervallo CIDR per linea. Pro activar toto, usa<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Un adresse IP o intervallo CIDR per linea. Pro activar toto, usa<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "ID de pagina $1"
 }
index f47a949..998280b 100644 (file)
        "tagline": "Dari {{SITENAME}}",
        "help": "Bantuan",
        "search": "Pencarian",
+       "search-ignored-headings": "# <pre>\n# Judul yang akan diabaikan oleh pencarian.\n# Suntingan ini akan diterapkan setelah halaman dengan judul ini diindeks.\n# Anda bisa memaksakan pengindeksan kembali halaman ini dengan melakukan suntingan kosong (''null edit'')\n# Sintaksisnya adalah seperti berikut:\n#   * Semuanya dari karakter \"#\" ke akhir baris adalah sebuah komentar.\n#   * Setiap baris tak-kosong adalah judul tepat yang akan diabaikan.\nReferensi\nPranala luar\nLihat pula\n #</pre>",
        "searchbutton": "Cari",
        "go": "Tuju ke",
        "searcharticle": "Lanjut",
        "views": "Tampilan",
        "toolbox": "Perkakas",
        "tool-link-userrights": "Simpan kelompok {{GENDER:$1|pengguna}}",
+       "tool-link-userrights-readonly": "Lihat kelompok {{GENDER:$1|pengguna}}",
        "tool-link-emailuser": "Kirim surel ke {{GENDER:$1|pengguna}} ini",
        "userpage": "Lihat halaman pengguna",
        "projectpage": "Lihat halaman proyek",
        "eauthentsent": "Sebuah surel untuk konfirmasi telah dikirim ke alamat surel. Sebelum surel lainnya dikirim ke akun tersebut, Anda harus mengikuti instruksi di dalam surel tersebut, untuk melakukan konfirmasi bahwa alamat tersebut adalah benar kepunyaan Anda.",
        "throttled-mailpassword": "Suatu pengingat kata sandi telah dikirimkan dalam {{PLURAL:$1|$1 jam}} terakhir.\nUntuk menghindari penyalahgunaan, hanya satu kata sandi yang akan dikirimkan setiap {{PLURAL:$1|$1 jam}}.",
        "mailerror": "Kesalahan dalam mengirimkan surel: $1",
-       "acct_creation_throttle_hit": "Pengunjung wiki ini dengan alamat IP yang sama dengan Anda telah membuat {{PLURAL:$1|1 akun|$1 akun}} dalam satu hari terakhir, hingga jumlah maksimum yang diizinkan.\nKarenanya, pengunjung dengan alamat IP ini tidak dapat lagi membuat akun lain untuk sementara.",
+       "acct_creation_throttle_hit": "Pengunjung wiki ini dengan alamat IP yang sama dengan Anda telah membuat {{PLURAL:$1|1 akun|$1 akun}} dalam $2 terakhir, hingga jumlah maksimum yang diizinkan.\nKarenanya, pengunjung dengan alamat IP ini tidak dapat lagi membuat akun lain untuk sementara.",
        "emailauthenticated": "Alamat surel Anda telah dikonfirmasi pada $3, $2.",
        "emailnotauthenticated": "Alamat surel Anda belum dikonfirmasi.\nSebelum dikonfirmasi Anda tidak akan menerima surel dari fitur berikut.",
        "noemailprefs": "Anda harus memasukkan alamat surel di preferensi Anda untuk dapat menggunakan fitur-fitur ini.",
        "botpasswords-label-delete": "Hapus",
        "botpasswords-label-resetpassword": "Setel ulang kata sandi",
        "botpasswords-label-grants": "Akses yang dapat diberikan:",
-       "botpasswords-help-grants": "Tiap izin memberikan akses ke hak-hak pengguna yang telah dimiliki suatu akun pengguna. Lihat [[Special:ListGrants|tabel izin]] untuk informasi lebih lanjut.",
+       "botpasswords-help-grants": "Izin ke akses tertentu telah dimiliki oleh akun pengguna Anda. Mengaktifkan sebuah hak di sini tidak memberikan akses ke akses lain yang tidak dimiliki oleh akun pengguna Anda. Lihat [[Special:ListGrants|daftar hak akses]] untuk informasi selengkapnya.",
        "botpasswords-label-grants-column": "Izin diberikan",
        "botpasswords-bad-appid": "Nama bot \"$1\" tidak valid.",
        "botpasswords-insert-failed": "Gagal menambah nama bot \"$1\". Apakah sudah ditambahkan sebelum ini?",
        "botpasswords-updated-body": "Kata sandi bot \"$1\" dari pengguna \"$2\" berhasil diperbarui.",
        "botpasswords-deleted-title": "Kata sandi bot dihapus",
        "botpasswords-deleted-body": "Kata sandi bot \"$1\" dari pengguna \"$2\" telah dihapus.",
-       "botpasswords-newpassword": "Kata sandi baru untuk masuk log dengan '''$1''' adalah '''$2'''. ''Mohon simpan untuk referensi di kemudian hari.''",
+       "botpasswords-newpassword": "Kata sandi baru untuk masuk log dengan <strong>$1</strong> adalah <strong>$2</strong>. <em>Catatlah kata sandi ini untuk referensi ke depan.</em> <br> (Untuk bot lama yang memerlukan nama masuk log yang sama dengan nama pengguna, dapat menggunakan <strong>$3</strong> sebagai nama pengguna dan <strong>$4</strong> sebagai kata sandi.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider tidak tersedia.",
        "botpasswords-restriction-failed": "Batasan kata sandi menghalangi masuk log ini.",
        "botpasswords-invalid-name": "Nama pengguna yang diberikan tidak mengandung pemisah kata sandi bot (\"$1\").",
        "invalid-content-data": "Data konten tidak sah",
        "content-not-allowed-here": "Konten \"$1\" tidak diizinkan di halaman [[$2]]",
        "editwarning-warning": "Meninggalkan halaman ini dapat menyebabkan semua perubahan yang belum tersimpan hilang.\nJika Anda telah masuk log, Anda dapat mematikan peringatan ini lewat bagian \"Penyuntingan\" pada halaman preferensi Anda.",
+       "editpage-invalidcontentmodel-title": "Model konten tidak didukung",
+       "editpage-invalidcontentmodel-text": "Model konten \"$1\" tidak didukung.",
        "editpage-notsupportedcontentformat-title": "Format konten tidak didukung",
        "editpage-notsupportedcontentformat-text": "Format konten $1 tidak didukung oleh model konten $2.",
        "content-model-wikitext": "teks wiki",
        "content-model-css": "CSS",
        "content-json-empty-object": "Objek kosong",
        "content-json-empty-array": "Larik kosong",
+       "deprecated-self-close-category": "Halaman yang menggunakan tag HTML tertutup-sendiri tidak sah",
        "duplicate-args-warning": "<strong>Peringatan:</strong> [[:$1]] memanggil [[:$2]] dengan nilai lebih dari satu untuk parameter \"$3\". Hanya nilai terakhir yang tersedia yang akan digunakan.",
        "duplicate-args-category": "Halaman dengan argumen ganda di pemanggilan templat",
        "duplicate-args-category-desc": "Halaman ini berisi pemanggilan templat yang menggunakan argumen ganda, seperti <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> atau <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "search-external": "Pencarian eksternal",
        "searchdisabled": "Pencarian {{SITENAME}} sementara dimatikan.\nAnda dapat mencari melalui Google untuk sementara waktu.\nPerlu diingat bahwa indeks Google untuk konten {{SITENAME}} mungkin belum mencakup perubahan-perubahan terakhir.",
        "search-error": "Kesalahan terjadi saat mencari: $1",
+       "search-warning": "Peringatan terjadi ketika mencari: $1",
        "preferences": "Preferensi",
        "mypreferences": "Preferensi",
        "prefs-edits": "Jumlah suntingan:",
        "prefs-help-recentchangescount": "Opsi ini berlaku untuk perubahan terbaru, versi terdahulu halaman, dan log.",
        "prefs-help-watchlist-token2": "Ini adalah kunci rahasia (token) ke umpan web dari daftar pantauan Anda.\nSiapa saja yang tahu akan dapat melihat daftar pantauan Anda, jadi jangan dibagikan. Jika diperlukan\n[[Special:ResetTokens|Anda dapat mengatur ulang kunci tersebut]].",
        "savedprefs": "Preferensi Anda telah disimpan",
-       "savedrights": "Hak pengguna {{GENDER:$1|$1}} telah disimpan.",
+       "savedrights": "Kelompok hak pengguna {{GENDER:$1|$1}} telah disimpan.",
        "timezonelegend": "Zona waktu:",
        "localtime": "Waktu setempat:",
        "timezoneuseserverdefault": "Gunakan bawaan wiki ($1)",
        "prefswarning-warning": "Perubahan preferensi anda belum tersimpan. Apabila anda meninggalkan halaman ini tanpa men-klik \"$1\" preferensi anda tidak akan diperbarui.",
        "prefs-tabs-navigation-hint": "Tip: Anda dapat menggunakan tombol panah kiri dan kanan untuk bernavigasi antartab di dalam daftar tab.",
        "userrights": "Manajemen hak pengguna",
-       "userrights-lookup-user": "Mengatur kelompok pengguna",
+       "userrights-lookup-user": "Pilih seorang pengguna",
        "userrights-user-editname": "Masukkan nama pengguna:",
-       "editusergroup": "Sunting kelompok {{GENDER:$1|pengguna}}",
+       "editusergroup": "Muat kelompok pengguna",
        "editinguser": "Mengubah hak pengguna untuk {{GENDER:$1|pengguna}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Melihat hak pengguna dari {{GENDER:$1|pengguna}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Sunting kelompok pengguna",
+       "userrights-viewusergroup": "Lihat kelompok pengguna",
        "saveusergroups": "Simpan kelompok {{GENDER:$1|pengguna}}",
        "userrights-groupsmember": "Anggota dari:",
        "userrights-groupsmember-auto": "Anggota implisit dari:",
        "grant-group-high-volume": "Melakukan aktivitas yang amat banyak",
        "grant-group-customization": "Kustomisasi dan preferensi",
        "grant-group-administration": "Melakukan tindakan administratif",
+       "grant-group-private-information": "Akses data pribadi tentang Anda",
        "grant-group-other": "Aktivitas lain-lain",
        "grant-blockusers": "Blokir dan buka pemblokiran pengguna",
        "grant-createaccount": "Buat akun",
        "grant-highvolume": "Penyuntingan dengan volume tinggi",
        "grant-oversight": "Sembunyikan pengguna dan revisinya",
        "grant-patrol": "Tandai halaman terpatroli",
+       "grant-privateinfo": "Akses informasi pribadi",
        "grant-protect": "Melindungi dan membuka perlindungan halaman",
        "grant-rollback": "Membalikkan perubahan pada halaman",
        "grant-sendemail": "Mengirim surel kepada pengguna lain",
        "grant-basic": "Akses dasar",
        "grant-viewdeleted": "Melihat halaman dan berkas yang dihapus",
        "grant-viewmywatchlist": "Lihat daftar pantauan Anda",
+       "grant-viewrestrictedlogs": "Lihat entri log terbatas",
        "newuserlogpage": "Log pengguna baru",
        "newuserlogpagetext": "Di bawah ini adalah log pendaftaran pengguna baru",
        "rightslog": "Log perubahan hak akses",
        "action-upload_by_url": "memuatkan berkas ini dari sebuah alamat URL",
        "action-writeapi": "menggunakan API penulisan",
        "action-delete": "menghapus halaman ini",
-       "action-deleterevision": "menghapus revisi ini",
-       "action-deletedhistory": "melihat versi terdahulu halaman yang telah dihapus ini",
+       "action-deleterevision": "menghapus revisi",
+       "action-deletelogentry": "hapus entri log",
+       "action-deletedhistory": "melihat versi terdahulu halaman yang telah dihapus",
+       "action-deletedtext": "lihat teks revisi yang dihapus",
        "action-browsearchive": "mencari halaman-halaman yang telah dihapus",
-       "action-undelete": "membatalkan penghapusan halaman ini",
-       "action-suppressrevision": "meninjau dan mengembalikan revisi yang disembunyikan ini",
+       "action-undelete": "batalkan penghapusan halaman",
+       "action-suppressrevision": "tinjau dan kembalikan revisi yang disembunyikan",
        "action-suppressionlog": "melihat log privat ini",
        "action-block": "Blokir pengguna ini dari penyuntingan",
        "action-protect": "mengganti tingkat pelindungan halaman ini",
        "action-userrights-interwiki": "menyunting hak akses dari pengguna di wiki lain",
        "action-siteadmin": "mengunci atau membuka kunci basis data",
        "action-sendemail": "kirim surel",
+       "action-editmyoptions": "sunting preferensi Anda",
        "action-editmywatchlist": "sunting daftar pantauan Anda",
        "action-viewmywatchlist": "lihat daftar pantau Anda",
        "action-viewmyprivateinfo": "lihat informasi pribadi Anda",
        "action-applychangetags": "terapkan tag bersamaan dengan perubahan Anda",
        "action-changetags": "menambah dan menghapus tag semaunya pada revisi individu dan entri log",
        "action-deletechangetags": "hapus tag dari basis data",
+       "action-purge": "hapus singgahan halaman ini",
        "nchanges": "$1 {{PLURAL:$1|perubahan|perubahan}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|sejak kunjungan terakhir}}",
        "enhancedrc-history": "riwayat",
        "emailccsubject": "Salinan pesan Anda untuk $1: $2",
        "emailsent": "Surel terkirim",
        "emailsenttext": "Surel Anda telah dikirimkan.",
-       "emailuserfooter": "Email ini dikirimkan oleh $1 ke $2 dengan fungsi \"{{int:emailuser}}\" di {{SITENAME}}.",
+       "emailuserfooter": "Surel ini telah {{GENDER:$1|dikirim}} oleh $1 kepada {{GENDER:$2|$2}} dengan fungsi \"{{int:emailuser}}\" pada {{SITENAME}}. Surel {{GENDER:$2|Anda}} akan dikirim langsung kepada {{GENDER:$1|pengirim asal}}, dengan menampilkan alamat surel {{GENDER:$2|Anda}} kepada {{GENDER:$1|mereka}}.",
        "usermessage-summary": "Tinggalkan pesan sistem.",
        "usermessage-editor": "Penyampai pesan sistem",
        "usermessage-template": "MediaWiki:UserMessage",
        "modifiedarticleprotection": "mengubah tingkat pelindungan \"[[$1]]\"",
        "unprotectedarticle": "menghilangkan perlindungan dari \"[[$1]]\"",
        "movedarticleprotection": "memindahkan pengaturan proteksi dari \"[[$2]]\" ke \"[[$1]]\"",
+       "protectedarticle-comment": "{{GENDER:$2|Melindungi}} \"[[$1]]\"",
+       "modifiedarticleprotection-comment": "{{GENDER:$2|Mengubah tingkat perlindungan}} untuk \"[[$1]]\"",
+       "unprotectedarticle-comment": "{{GENDER:$2|Menghapus perlindungan}} dari \"[[$1]]\"",
        "protect-title": "Melindungi \"$1\"",
        "protect-title-notallowed": "Lihat tingkat perlindungan dari \"$1\"",
        "prot_1movedto2": "[[$1]] dipindahkan ke [[$2]]",
        "undeletehistorynoadmin": "Halaman ini telah dihapus.\nAlasan penghapusan diberikan pada ringkasan di bawah ini, berikut rincian pengguna yang telah melakukan penyuntingan pada halaman ini sebelum dihapus. Isi terakhir dari revisi yang telah dihapus ini hanya tersedia untuk pengurus.",
        "undelete-revision": "Revisi yang telah dihapus dari $1 (pada $5, $4) oleh $3:",
        "undeleterevision-missing": "Revisi salah atau tak ditemukan. Anda mungkin mengikuti pranala yang salah, atau revisi tersebut telah dipulihkan atau dibuang dari arsip.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Sebuah revisi|$1 revisi}} tidak dapat dipulihkan, karena <code>rev_id</code> {{PLURAL:$1|mereka|mereka}} sedang digunakan.",
        "undelete-nodiff": "Tidak ada revisi yang lebih lama.",
        "undeletebtn": "Kembalikan",
        "undeletelink": "lihat/kembalikan",
        "undeletedrevisions": "$1 {{PLURAL:$1|revisi|revisi}} telah dikembalikan",
        "undeletedrevisions-files": "$1 {{PLURAL:$1|revisi|revisi}} and $2 berkas dikembalikan",
        "undeletedfiles": "$1 {{PLURAL:$1|berkas|berkas}} dikembalikan",
-       "cannotundelete": "Pembatalan penghapusan gagal:\n$1",
+       "cannotundelete": "Beberapa pembatalan penghapusan gagal:\n$1",
        "undeletedpage": "'''$1 berhasil dikembalikan'''\n\nLihat [[Special:Log/delete|log penghapusan]] untuk data penghapusan dan pengembalian.",
        "undelete-header": "Lihat [[Special:Log/delete|log penghapusan]] untuk daftar halaman yang baru dihapus.",
        "undelete-search-title": "Cari halaman yang dihapus",
        "sp-contributions-newbies-sub": "Untuk pengguna baru",
        "sp-contributions-newbies-title": "Kontribusi pengguna baru",
        "sp-contributions-blocklog": "log pemblokiran",
-       "sp-contributions-suppresslog": "kontribusi pengguna yang disembunyikan",
-       "sp-contributions-deleted": "kontribusi pengguna yang dihapus",
+       "sp-contributions-suppresslog": "kontribusi {{GENDER:$1|pengguna}} yang disembunyikan",
+       "sp-contributions-deleted": "kontribusi {{GENDER:$1|pengguna}} yang dihapus",
        "sp-contributions-uploads": "unggahan",
        "sp-contributions-logs": "log",
        "sp-contributions-talk": "bicara",
        "sp-contributions-username": "Alamat IP atau nama pengguna:",
        "sp-contributions-toponly": "Tampilkan hanya revisi teratas",
        "sp-contributions-newonly": "Hanya tampilkan suntingan yang berupa pembuatan halaman",
+       "sp-contributions-hideminor": "Sembunyikan suntingan kecil",
        "sp-contributions-submit": "Cari",
        "whatlinkshere": "Pranala balik",
        "whatlinkshere-title": "Halaman yang memiliki pranala ke \"$1\"",
        "movelogpagetext": "Di bawah ini adalah log pemindahan halaman.",
        "movesubpage": "{{PLURAL:$1|Subhalaman|Subhalaman}}",
        "movesubpagetext": "Halaman ini memiliki $1 {{PLURAL:$1|subhalaman|subhalaman}} seperti ditampilkan berikut.",
+       "movesubpagetalktext": "Halaman pembicaraan terkait mempunyai $1 {{PLURAL:$1|subhalaman|subhalaman}} yang tampil di bawah ini.",
        "movenosubpage": "Halaman ini tak memiliki subhalaman.",
        "movereason": "Alasan:",
        "revertmove": "batalkan",
        "pageinfo-article-id": "ID Halaman",
        "pageinfo-language": "Bahasa isi halaman",
        "pageinfo-content-model": "Model isi halaman",
+       "pageinfo-content-model-change": "ubah",
        "pageinfo-robot-policy": "Pengindeksan oleh robot",
        "pageinfo-robot-index": "Diperbolehkan",
        "pageinfo-robot-noindex": "Tidak diperbolehkan",
        "pageinfo-category-pages": "Jumlah halaman",
        "pageinfo-category-subcats": "Jumlah subkategori",
        "pageinfo-category-files": "Jumlah berkas",
+       "pageinfo-user-id": "ID pengguna",
        "markaspatrolleddiff": "Tandai telah dipatroli",
        "markaspatrolledtext": "Tandai halaman ini telah dipatroli",
        "markaspatrolledtext-file": "Tandai versi berkas sebagai terpatroli",
        "patrol-log-header": "Ini adalah log revisi terpatroli.",
        "log-show-hide-patrol": "$1 log patroli",
        "log-show-hide-tag": "log tag $1",
+       "confirm-markpatrolled-button": "OK",
+       "confirm-markpatrolled-top": "Tandai revisi $3 dari $2 sebagai terperiksa?",
        "deletedrevision": "Revisi lama yang dihapus $1",
        "filedeleteerror-short": "Kesalahan waktu menghapus berkas: $1",
        "filedeleteerror-long": "Terjadi kesalahan sewaktu menghapus berkas:\n\n$1",
        "tags-actions-header": "Tindakan",
        "tags-active-yes": "Ya",
        "tags-active-no": "Tidak",
-       "tags-source-extension": "Ditetapkan oleh suatu ekstensi",
+       "tags-source-extension": "Ditetapkan oleh perangkat lunak",
        "tags-source-manual": "Digunakan secara manual oleh pengguna dan bot",
        "tags-source-none": "Tidak digunakan lagi",
        "tags-edit": "sunting",
        "tags-deactivate": "nonaktifkan",
        "tags-hitcount": "$1 {{PLURAL:$1|perubahan}}",
        "tags-manage-no-permission": "Anda tak memiliki hak akses untuk mengatur perubahan tag.",
-       "tags-manage-blocked": "Anda tidak dapat mengganti tag ketika sedang diblokir.",
+       "tags-manage-blocked": "Anda tidak dapat mengatur perubahan tag ketika {{GENDER:$1|Anda}} diblokir.",
        "tags-create-heading": "Buat sebuah tag baru",
        "tags-create-explanation": "Secara baku, tag yang baru dibuat akan tersedia untuk digunakan oleh pengguna dan bot.",
        "tags-create-tag-name": "Nama tag:",
        "tags-create-warnings-below": "Apakah Anda ingin melanjutkan pembuatan tanda ini?",
        "tags-delete-title": "Hapus tag",
        "tags-delete-explanation-initial": "Anda akan menghapus tag \"$1\" dari basisdata.",
+       "tags-delete-explanation-warning": "Tindakan ini <strong>tidak bisa dikembalikan</strong> dan <strong>tidak bisa dibatalkan</strong> oleh siapa pun termasuk pengurus basis data. Pastikan sebaik-baiknya bahwa inilah tag yang ingin Anda hapus.",
        "tags-delete-reason": "Alasan:",
+       "tags-delete-submit": "Hapus tag ini juga",
+       "tags-delete-not-allowed": "Tag yang diberikan oleh ekstensi tidak dapat dihapus kecuali ekstensi tersebut mengizinkan.",
+       "tags-delete-not-found": "Tag \"$1\" tidak ada.",
+       "tags-delete-too-many-uses": "Tag \"$1\" diterapkan ke $2 atau lebih {{PLURAL:$2|revisi|revisi}} yang artinya tag tersebut tidak dapat dihapus.",
+       "tags-delete-warnings-after-delete": "Tag \"$1\" dihapus, namun {{PLURAL:$2|peringatan|peringatan}} berikut menemukan:",
+       "tags-delete-no-permission": "Anda tak memiliki hak akses untuk menghapus perubahan tag.",
+       "tags-activate-title": "Aktifkan tag",
+       "tags-activate-question": "Anda akan mengaktifkan tag \"$1\".",
        "tags-activate-reason": "Alasan:",
+       "tags-activate-not-allowed": "Tag \"$1\" tidak mungkin dapat diaktifkan.",
+       "tags-activate-not-found": "Tag \"$1\" tidak ada.",
        "tags-activate-submit": "Aktifkan",
+       "tags-deactivate-title": "Nonaktifkan tag",
+       "tags-deactivate-question": "Anda akan menonaktifkan tag \"$1\".",
        "tags-deactivate-reason": "Alasan:",
+       "tags-deactivate-not-allowed": "Tag \"$1\" tidak mungkin dapat dinonaktifkan.",
        "tags-deactivate-submit": "Matikan",
-       "tags-apply-blocked": "Anda tidak dapat menerapkan perubahan tag dan perubahan lainnya ketika sedang diblokir.",
-       "tags-update-blocked": "Anda tidak dapat menambah atau menghapus tag ketika sedang diblokir.",
+       "tags-apply-no-permission": "Anda tidak memiliki izin untuk menerapkan perubahan tag bersama-sama dengan perubahan Anda.",
+       "tags-apply-blocked": "Anda tidak dapat menerapkan perubahan tag dengan perubahan Anda ketika {{GENDER:$1|Anda}} sedang diblokir.",
+       "tags-apply-not-allowed-one": "Tag \"$1\" tidak diizinkan untuk diterapkan secara manual.",
+       "tags-apply-not-allowed-multi": "{{PLURAL:$2|Tag|Tag}} berikut tidak diizinkan untuk diterapkan secara manual: $1",
+       "tags-update-no-permission": "Anda tidak memiliki izin untuk menambah atau menghapus perubahan tag dari revisi atau entri log individu.",
+       "tags-update-blocked": "Anda tidak dapat menambahkan atau menghapus perubahan tag ketika {{GENDER:$1|Anda}} sedang diblokir.",
+       "tags-edit-title": "Sunting tag",
+       "tags-edit-manage-link": "Kelola tag",
+       "tags-edit-revision-selected": "{{PLURAL:$1|Revisi terpilih|Revisi terpilih}} dari [[:$2]]:",
+       "tags-edit-logentry-selected": "{{PLURAL:$1|Log peristiwa terpilih|Log peristiwa terpilih}}:",
+       "tags-edit-revision-legend": "Tambah atau hapus tag dari {{PLURAL:$1|revisi ini|semua revisi $1}}",
+       "tags-edit-logentry-legend": "Tambah atau hapus tag dari {{PLURAL:$1|entri log ini|semua entri log $1}}",
        "tags-edit-existing-tags": "Tag yang ada:",
        "tags-edit-existing-tags-none": "<em>Tidak ada</em>",
        "tags-edit-new-tags": "Tag baru:",
        "htmlform-cloner-create": "Tambahkan lebih banyak",
        "htmlform-cloner-delete": "Hapus",
        "htmlform-cloner-required": "Paling sedikit satu nilai diperlukan.",
+       "htmlform-date-placeholder": "TTTT-BB-HH",
+       "htmlform-time-placeholder": "JJ:MM:DD",
+       "htmlform-datetime-placeholder": "TTTT-BB-HH JJ:MM:DD",
+       "htmlform-date-invalid": "Nilai yang diberikan tidak dikenali sebagai tanggal. Coba lagi menggunakan format TTTT-BB-HH.",
        "htmlform-title-badnamespace": "[[:$1]] tidak berada dalam ruang nama \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "\"$1\" bukan merupakan judul halaman yang dapat dibuat",
        "htmlform-title-not-exists": "$1 tidak ada.",
        "htmlform-user-not-exists": "<strong>$1</strong> tidak ada.",
        "htmlform-user-not-valid": "<strong>$1</strong> bukan merupakan nama pengguna sah.",
        "logentry-delete-delete": "$1 {{GENDER:$2|menghapus}} halaman $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|menghapus}} pengalihan $3 dengan penimpaan",
        "logentry-delete-restore": "$1 {{GENDER:$2|mengembalikan}} halaman $3",
        "logentry-delete-event": "$1 {{GENDER:$2|mengubah}} tampilan {{PLURAL:$5|$5 log peristiwa}} di $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|mengubah}} tampilan {{PLURAL:$5|$5  revisi}} di halaman $3: $4",
        "feedback-thanks": "Terima kasih! Umpan balik Anda telah dikirimkan ke halaman \"[$2 $1]\".",
        "feedback-thanks-title": "Terima kasih!",
        "feedback-useragent": "Agen pengguna:",
-       "searchsuggest-search": "Cari",
+       "searchsuggest-search": "Cari {{SITENAME}}",
        "searchsuggest-containing": "berisi...",
        "api-error-autoblocked": "Alamat IP Anda telah diblokir secara otomatis, karena sebelumnya digunakan oleh pengguna yang diblokir.",
        "api-error-badaccess-groups": "Anda tidak diizinkan mengunggah berkas ke wiki ini.",
        "mw-widgets-dateinput-no-date": "Tanggal tidak ada yang terpilih",
        "mw-widgets-dateinput-placeholder-day": "TTTT-BB-HH",
        "mw-widgets-dateinput-placeholder-month": "TTTT-BB",
+       "mw-widgets-mediasearch-input-placeholder": "Cari media",
+       "mw-widgets-mediasearch-noresults": "Tidak ada hasil ditemukan.",
        "mw-widgets-titleinput-description-new-page": "halaman belum ada",
        "mw-widgets-titleinput-description-redirect": "mengalihkan ke $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Tambah sebuah kategori...",
        "sessionmanager-tie": "Tidak dapat menggabungkan banyak jenis otentikasi permintaan: $1.",
        "sessionprovider-generic": "sesi $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sesi berdasarkan kuki",
        "log-action-filter-newusers": "Jenis pembuatan akun:",
        "log-action-filter-patrol": "Jenis patroli:",
        "log-action-filter-protect": "Jenis perlindungan:",
-       "log-action-filter-rights": "Jenis penggantian hak",
-       "log-action-filter-suppress": "Jenis penyembunyian",
+       "log-action-filter-rights": "Jenis penggantian hak akses:",
+       "log-action-filter-suppress": "Jenis penyembunyian:",
        "log-action-filter-upload": "Jenis pengunggahan:",
        "log-action-filter-all": "Semua",
        "log-action-filter-block-block": "Blokir",
        "log-action-filter-contentmodel-change": "Ubah Modelkonten",
        "log-action-filter-contentmodel-new": "Pembuatan halaman dengan Modelkonten yang tak baku",
        "log-action-filter-delete-delete": "Penghapusan halaman",
+       "log-action-filter-delete-delete_redir": "Mengalihkan pengalihan",
        "log-action-filter-delete-restore": "Pembatalan penghapusan halaman",
        "log-action-filter-delete-event": "Log penghapusan",
        "log-action-filter-delete-revision": "Penghapusan revisi",
        "authmanager-authn-autocreate-failed": "Pembuatan otomatis dari akun lokal gagal: $1",
        "authmanager-change-not-supported": "Kredensial yang diberikan tidak dapat diganti, karena tidak ada yang akan menggunakannya.",
        "authmanager-create-disabled": "Pembuatan akun dimatikan.",
-       "authmanager-create-from-login": "Untuk membuat akun Anda, silakan isi kolom di bawah.",
+       "authmanager-create-from-login": "Untuk membuat akun, silakan isi kolom di bawah.",
        "authmanager-create-not-in-progress": "Pembuatan akun tidak dilanjutkan atau data sesi telah hilang. Ulang kembali dari awal.",
        "authmanager-create-no-primary": "Kredensial yang diberikan tidak dapat digunakan untuk pembuatan akun.",
        "authmanager-link-no-primary": "Kredensial yang diberikan tidak dapat digunakan untuk menautkan akun.",
        "linkaccounts-success-text": "Akun telah ditautkan.",
        "linkaccounts-submit": "Tautkan akun",
        "unlinkaccounts": "Lepastautkan akun",
-       "unlinkaccounts-success": "Akun berikut telah dilepastautkan."
+       "unlinkaccounts-success": "Akun berikut telah dilepastautkan.",
+       "restrictionsfield-badip": "Alamat IP atau rentang IP tidak sah: $1",
+       "restrictionsfield-label": "Rentang IP yang diizinkan:",
+       "restrictionsfield-help": "Satu alamat IP atau rentang CIDR per baris. Untuk mengaktifkan semuanya, gunakan <br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "ID halaman $1"
 }
index 724e86f..dd6c345 100644 (file)
        "editusergroup": "Modifica gruppi utente",
        "editinguser": "Modifica in corso dei diritti dell'{{GENDER:$1|utente}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Modifica gruppi utente",
+       "userrights-viewusergroup": "Visualizza gruppi utente",
        "saveusergroups": "Salva gruppi {{GENDER:$1|utente}}",
        "userrights-groupsmember": "Appartiene {{PLURAL:$1|al gruppo|ai gruppi}}:",
        "userrights-groupsmember-auto": "Membro implicito di:",
        "action-upload_by_url": "caricare questo file da un indirizzo URL",
        "action-writeapi": "usare le API in scrittura",
        "action-delete": "cancellare questa pagina",
-       "action-deleterevision": "cancellare questa versione",
-       "action-deletedhistory": "visualizzare la cronologia cancellata di questa pagina",
+       "action-deleterevision": "cancellare versioni",
+       "action-deletedhistory": "visualizzare la cronologia cancellata di una pagina",
        "action-browsearchive": "cercare pagine cancellate",
-       "action-undelete": "recuperare questa pagina",
-       "action-suppressrevision": "rivedere e ripristinare le modifiche nascoste",
+       "action-undelete": "recuperare pagine",
+       "action-suppressrevision": "rivedere e ripristinare le versioni nascoste",
        "action-suppressionlog": "vedere questo registro privato",
        "action-block": "bloccare questo utente in scrittura",
        "action-protect": "modificare i livelli di protezione per questa pagina",
        "action-userrights": "modificare tutti i diritti degli utenti",
        "action-userrights-interwiki": "modificare i diritti degli utenti su altre wiki",
        "action-siteadmin": "bloccare e sbloccare il database",
-       "action-sendemail": "inviare e-mail",
+       "action-sendemail": "inviare email",
        "action-editmywatchlist": "modificare i propri osservati speciali",
        "action-viewmywatchlist": "vedere i propri osservati speciali",
        "action-viewmyprivateinfo": "vedere i propri dati personali",
        "emailccsubject": "Copia del messaggio inviato a $1: $2",
        "emailsent": "Messaggio inviato",
        "emailsenttext": "Il messaggio e-mail è stato inviato.",
-       "emailuserfooter": "Questa email è stata {{GENDER:$1|inviata}} da $1 a {{GENDER:$2|$2}} attraverso la funzione \"{{int:emailuser}}\" su {{SITENAME}}.",
+       "emailuserfooter": "Questa email è stata {{GENDER:$1|inviata}} da $1 a {{GENDER:$2|$2}} attraverso la funzione \"{{int:emailuser}}\" su {{SITENAME}}. La {{GENDER:$2|tua}} eventuale email di risposta sarà inviata direttamente al {{GENDER:$1|mittente originale}}, rivelando il  {{GENDER:$2|tuo}} indirizzo di posta elettronica a {{GENDER:$1|lui|lei}}.",
        "usermessage-summary": "Messaggio di sistema",
        "usermessage-editor": "Messaggero di sistema",
        "usermessage-template": "MediaWiki:MessaggioUtente",
        "mw-widgets-dateinput-no-date": "Nessuna data selezionata",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-GG",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Ricerca file multimediali",
+       "mw-widgets-mediasearch-noresults": "Nessun risultato trovato.",
        "mw-widgets-titleinput-description-new-page": "questa pagina non esiste ancora",
        "mw-widgets-titleinput-description-redirect": "reindirizzamento a $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Aggiungi una categoria...",
index 6148fb4..b1d73f6 100644 (file)
                        "Matma Rex",
                        "組曲師",
                        "Foresttttttt",
-                       "ネイ"
+                       "ネイ",
+                       "Mirinano",
+                       "Suchichi02",
+                       "にょきにょき"
                ]
        },
        "tog-underline": "リンクの下線:",
        "views": "表示",
        "toolbox": "ツール",
        "tool-link-userrights": "{{GENDER:$1|利用者}}グループの変更",
+       "tool-link-userrights-readonly": "{{GENDER:$1|利用者}}グループの表示",
        "tool-link-emailuser": "この{{GENDER:$1|利用者}}にメールを送信",
        "userpage": "利用者ページを表示",
        "projectpage": "プロジェクトのページを表示",
        "contentmodelediterror": "コンテンツモデルが <code>$1</code> であるため、この版を編集することができません。ページの現在のコンテンツモデルは <code>$2</code> です。",
        "recreate-moveddeleted-warn": "<strong>警告: 以前削除されたページを再作成しようとしています。</strong>\n\nこのページの編集を続行するのが適切かどうかご確認ください。\n参考までに、このページの削除と移動の記録を以下に示します:",
        "moveddeleted-notice": "このページは削除されています。\n参考のため、このページの削除と移動の記録を以下に表示します。",
-       "moveddeleted-notice-recent": "ごめんなさい、このページは最近に削除されました (過去24時間以内)。このページについての削除と移動の記録が、参考のため、以下にて提供されています。",
+       "moveddeleted-notice-recent": "申し訳ありません。このページは最近(24時間以内)に削除されました。参考のため、このページの削除と移動の記録を以下に表示します。",
        "log-fulllog": "完全な記録を閲覧",
        "edit-hook-aborted": "フックによって編集が破棄されました。\n理由は不明です。",
        "edit-gone-missing": "ページを更新できませんでした。\n既に削除されているようです。",
        "prefs-help-recentchangescount": "この設定は最近の更新、ページの履歴、および記録に適用されます。",
        "prefs-help-watchlist-token2": "これはあなたのウォッチリスト フィードの秘密のコードです。\nこのトークンを知っている人は誰でもあなたのウォッチリストを読めてしまうため、他の人に教えないでください。\n[[Special:ResetTokens|トークンを再設定する必要がある場合はここをクリックしてください]]。",
        "savedprefs": "個人設定を保存しました。",
-       "savedrights": "{{GENDER:$1|$1}}の利用者権限が保存されました。",
+       "savedrights": "{{GENDER:$1|$1}}の利用者グループが保存されました。",
        "timezonelegend": "タイムゾーン:",
        "localtime": "地域の時刻:",
        "timezoneuseserverdefault": "ウィキの既定を使用 ($1)",
        "prefswarning-warning": "個人設定にまだ保存されていない変更があります。\n「$1」をクリックせずに離れた場合、個人設定は更新されません。",
        "prefs-tabs-navigation-hint": "ヒント: ← キーと → キーで、タブ一覧内のタブ間を移動できます。",
        "userrights": "利用者権限を管理",
-       "userrights-lookup-user": "å\88©ç\94¨è\80\85ã\82°ã\83«ã\83¼ã\83\97ã\82\92管ç\90\86",
+       "userrights-lookup-user": "å\88©ç\94¨è\80\85ã\82\92é\81¸æ\8a\9e",
        "userrights-user-editname": "利用者名を入力:",
-       "editusergroup": "{{GENDER:$1|利用者}}グループを編集",
+       "editusergroup": "利用者グループの表示",
        "editinguser": "利用者<strong> [[User:$1|$1]]</strong> $2 の権限を変更",
+       "viewinguserrights": "{{GENDER:$1|利用者}} <strong>[[User:$1|$1]]</strong> $2 の利用者権限",
        "userrights-editusergroup": "利用者グループを編集",
+       "userrights-viewusergroup": "利用者グループ",
        "saveusergroups": "{{GENDER:$1|利用者}}グループを保存",
        "userrights-groupsmember": "所属グループ:",
        "userrights-groupsmember-auto": "自動的に付与される権限:",
        "right-reupload-own": "自身がアップロードした既存のファイルに上書き",
        "right-reupload-shared": "共有メディアリポジトリ上のファイルにローカルで上書き",
        "right-upload_by_url": "URL からファイルをアップロード",
-       "right-purge": "確認なしでサイトキャッシュを破棄",
+       "right-purge": "確認なしでサイト上のページ・キャッシュを破棄",
        "right-autoconfirmed": "IPベースの速度制限を受けない",
        "right-bot": "自動処理と認識させる",
        "right-nominornewtalk": "議論ページの細部の編集をした際に、新着メッセージとして通知しない",
        "action-upload_by_url": "URL からのこのファイルのアップロード",
        "action-writeapi": "書き込みAPIの使用",
        "action-delete": "このページの削除",
-       "action-deleterevision": "この版の削除",
-       "action-deletedhistory": "このページの削除履歴の閲覧",
+       "action-deleterevision": "版の削除",
+       "action-deletelogentry": "記録項目の削除",
+       "action-deletedhistory": "ページの削除履歴の閲覧",
        "action-browsearchive": "削除されたページの検索",
-       "action-undelete": "ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81®å¾©å\85\83",
+       "action-undelete": "ページの復元",
        "action-suppressrevision": "隠された版の確認と復元",
        "action-suppressionlog": "非公開記録の閲覧",
        "action-block": "この利用者の編集ブロック",
        "protect-cascadeon": "このページは現在、カスケード保護が有効になっている以下の{{PLURAL:$1|ページ|ページ群}}からトランスクルードのため読み込まれているので、保護されています。\nこのページの保護レベルの変更は、カスケード保護には影響しません。",
        "protect-default": "すべての利用者に許可",
        "protect-fallback": "「$1」権限を持つ利用者のみに許可",
-       "protect-level-autoconfirmed": "自動承認された利用者のみ許可",
-       "protect-level-sysop": "管理者のみ許可",
+       "protect-level-autoconfirmed": "自動承認された利用者のみ許可",
+       "protect-level-sysop": "管理者のみ許可",
        "protect-summary-desc": "[$1=$2] ($3)",
        "protect-summary-cascade": "カスケード",
        "protect-expiring": "$1(UTC)で自動的に解除",
        "patrol-log-header": "以下は巡回された版の記録です。",
        "log-show-hide-patrol": "巡回記録を$1",
        "log-show-hide-tag": "タグ記録を$1",
+       "confirm-markpatrolled-button": "OK",
        "confirm-markpatrolled-top": "ページ$2の$3の版を巡回済みにマークしますか?",
        "deletedrevision": "古い版 $1 を削除しました",
        "filedeleteerror-short": "ファイルの削除エラー: $1",
        "unit-pixel": "ピクセル",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "このページのキャッシュを破棄しますか?",
-       "confirm-purge-bottom": "ã\83\9aã\83¼ã\82¸ã\82\92ã\83\91ã\83¼ã\82¸ã\81\99ã\82\8bã\81¨ã\80\81ã\82­ã\83£ã\83\83ã\82·ã\83¥ã\81\8cç ´æ£\84ã\81\95ã\82\8cã\80\81å¼·å\88¶ç\9a\84ã\81«æ\9c\80æ\96°ç\89\88ã\81\8c表示ã\81\95ã\82\8cます。",
+       "confirm-purge-bottom": "ã\83\9aã\83¼ã\82¸ã\81®ã\83\91ã\83¼ã\82¸ã\81«ã\82\88ã\82\8aã\82­ã\83£ã\83\83ã\82·ã\83¥ã\82\92ç ´æ£\84ã\81\97ã\80\81å¼·å\88¶ç\9a\84ã\81«æ\9c\80æ\96°ç\89\88ã\82\92表示ã\81\97ます。",
        "confirm-watch-button": "OK",
        "confirm-watch-top": "このページをウォッチリストに追加しますか?",
        "confirm-unwatch-button": "OK",
        "htmlform-user-not-exists": "<strong>$1</strong>は存在しません。",
        "htmlform-user-not-valid": "<strong>$1</strong>は有効な利用者名ではありません。",
        "logentry-delete-delete": "$1 がページ「$3」を{{GENDER:$2|削除しました}}",
+       "logentry-delete-delete_redir": "$1 がリダイレクト「$3」を上書きにより{{GENDER:$2|削除しました}}",
        "logentry-delete-restore": "$1 がページ「$3」を{{GENDER:$2|復元しました}}",
        "logentry-delete-event": "$1 が $3 の{{PLURAL:$5|記録項目|記録項目$5件}}の閲覧レベルを{{GENDER:$2|変更しました}}: $4",
        "logentry-delete-revision": "$1 がページ「$3」の{{PLURAL:$5|版|$5件の版}}の閲覧レベルを{{GENDER:$2|変更しました}}: $4",
        "special-characters-title-emdash": "em ダッシュ",
        "special-characters-title-minus": "マイナス記号",
        "mw-widgets-dateinput-no-date": "選択されたデータ無し",
+       "mw-widgets-mediasearch-noresults": "見つかりませんでした。",
        "mw-widgets-titleinput-description-new-page": "ページは存在しません",
        "mw-widgets-titleinput-description-redirect": "$1 へのリダイレクト",
+       "mw-widgets-categoryselector-add-category-placeholder": "カテゴリを追加...",
        "sessionmanager-tie": "複数の要求の認証方法を組み合わせることはできません: $1。",
        "sessionprovider-generic": "$1 セッション",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "クッキーベースのセッション",
index c1c62c3..bd26006 100644 (file)
@@ -44,7 +44,7 @@
        "tog-enotifminoredits": "Uga kirimi aku layangtronik yèn ana besutan cilik ing kaca lan barkas",
        "tog-enotifrevealaddr": "Singkab alamat layangtronikku ing layang pawarta",
        "tog-shownumberswatching": "Tuduhaké cacah wong sing ngawasi",
-       "tog-oldsig": "Tandha tangan sing ana:",
+       "tog-oldsig": "Tandha tangan panjenengan sing ana:",
        "tog-fancysig": "Anggep tandha tangan minangka tulisan wiki (tanpa pranala otomatis)",
        "tog-uselivepreview": "Nganggo pratuduh langsung",
        "tog-forceeditsummary": "Élingna aku menawa kothak ringkesan suntingan isih kosong",
@@ -61,7 +61,7 @@
        "tog-showhiddencats": "Tuduhaké kategori sing didhelikaké",
        "tog-norollbackdiff": "Aja tuduhaké prabédan sawisé mbalèkaké.",
        "tog-useeditwarning": "Élingaké kula yèn kula ninggalaké suntingan sing durung kasimpen",
-       "tog-prefershttps": "Tansah nganggo sambungan aman nalika mlebu",
+       "tog-prefershttps": "Tansah anggoa sambungan sing aman nalika mlebu log",
        "underline-always": "Tansah",
        "underline-never": "Ora tau",
        "underline-default": "Baku kulit utawa pangluron",
        "talk": "Parembugan",
        "views": "Praèn",
        "toolbox": "Piranti",
+       "tool-link-userrights": "Owahi golongan {{GENDER:$1|naraguna}}",
+       "tool-link-userrights-readonly": "Deleng golongan {{GENDER:$1|naraguna}}",
+       "tool-link-emailuser": "Kirimi {{GENDER:$1|naraguna}} iki layangtronik",
        "userpage": "Deleng kaca panganggo",
        "projectpage": "Deleng kaca proyèk",
        "imagepage": "Deleng kaca barkas",
        "pool-queuefull": "Kempalan antrian kebak",
        "pool-errorunknown": "Kalepata ingkang mboten dipun mangertosi",
        "poolcounter-usage-error": "Cacad panganggo: $1",
-       "aboutsite": "Bab {{SITENAME}}",
-       "aboutpage": "Project:Bab",
+       "aboutsite": "Ngenani {{SITENAME}}",
+       "aboutpage": "Project:Ngenani",
        "copyright": "Kabèh isi kasedyakaké miturut $1.",
        "copyrightpage": "{{ns:project}}:Hak cipta",
        "currentevents": "Kadadian saiki",
        "portal": "Gapura paguyuban",
        "portal-url": "Project:Garupa paguyuban",
        "privacy": "Niti priangga",
-       "privacypage": "Project:Niti pripasi",
+       "privacypage": "Project:Niti priangga",
        "badaccess": "Aksès ora olèh",
        "badaccess-group0": "Panjenengan ora pareng nglakokaké tindhakan sing panjenengan gayuh.",
        "badaccess-groups": "Pratingkah panjenengan diwatesi tumrap panganggo ing {{PLURAL:$2|klompoké|klompoké}}: $1.",
        "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": "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",
+       "cascadeprotected": "Kaca iki wis direksa saka besutan amarga katransklusi ing {{PLURAL:$1|kaca, sing|kaca-kaca, sing}} kareksa mawa pilihan \"runut\" murub:\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.",
        "customjsprotected": "Sampéyan ora dililakaké nyunting kaca JavaScript iki amarga kaisi pangaturan pribadi saka panganggo liya.",
        "createacct-yourpasswordagain-ph": "Lebokaké manèh tembung wadiné",
        "userlogin-remembermypassword": "Gawé amrih aku panggah kalebu",
        "userlogin-signwithsecure": "Nganggo koneksi aman",
+       "cannotlogin-title": "Ora bisa mlebu log",
+       "cannotlogin-text": "Mokal mlebu log.",
        "cannotloginnow-title": "Ora bisa mlebu saiki",
        "cannotloginnow-text": "Mlebu ora mungkin menawa nganggo $1.",
+       "cannotcreateaccount-title": "Ora bisa gawé akun",
+       "cannotcreateaccount-text": "Gawé akun langsung ora bisa ing wiki iki.",
        "yourdomainname": "Dhomain panjenengan",
        "password-change-forbidden": "Sampéyan ora bisa ngganti tembung sandhi nèng wiki iki.",
        "externaldberror": "Ana kasalahan otèntikasi basis dhata èksternal utawa panjenengan ora pareng nglakoni pemutakhiran marang akun èksternal panjenengan.",
        "login": "Mlebu",
+       "login-security": "Vèrifikasi idhèntitas panjenengan",
        "nav-login-createaccount": "Log mlebu / nggawé rékening (akun)",
        "userlogin": "Mlebu log / gawé rékening (akun)",
        "userloginnocreate": "Mlebu",
        "passwordremindertext": "Ana wong (mbokmanawa panjenengan dhéwé, saka alamat IP $1) nyuwun supaya dikirimi tembung sandhi anyar kanggo {{SITENAME}} ($4). Tembung sandi sawetara kanggo panganggo \"$2\" wis digawé lan saiki \"$3\". Yèn panjenengan pancèn nggayuh iki, mangga énggal mlebu log lan ngganti tembung sandi saiki.\nTembung sandi sawetara mau bakal kadaluwarsa ing {{PLURAL:$5|sadina|$5 dina}}.\nYèn wong liya sing nglakoni panyuwunan iki, utawa panjenengan éling tembung sandi panjenengan, lan ora kepéngin ngowahi, panjenengan ora usah nggubris pesen iki lan bisa tetep nganggo tembung sandi lawas.",
        "noemail": "Ora ana alamat layang e-mail sing kacathet kanggo panganggo \"$1\".",
        "noemailcreate": "Panjenengan kudu maringi alamat e-mail sing absah",
-       "passwordsent": "Tembung sandhi anyar wis dikirim menyang alamat layang e-mail panjenengan sing wis didaftar kanggo \"$1\". Mangga mlebu log manèh sawisé nampa e-mail iku.",
+       "passwordsent": "Tembung sandi anyar wis dikirim menyang alamat layang èlèktronik tumrap \"$1\". \nMangga mlebu log manèh sawisé panjenengan nampa iku.",
        "blocked-mailpassword": "Alamat IP panjenengan diblokir saka panyuntingan, mulané panjenengan ora olèh nganggo fungsi pèngetan tembung sandhi kanggo ''mencegah penyalahgunaan''.",
        "eauthentsent": "Sawijining layang élèktronik (e-mail) kanggo ndhedhes (konfirmasi) wis dikirim menyang alamat layang élèktronik sampeyan. \n\nSadhurunge layang élèktronik liyane dikirim menyang akun kuwi, sampeyan kudu melu parentah ing layang kuwi, kanggo mastikne yen alamat layang kuwi bener-bener dhuweke sampeyan.",
        "throttled-mailpassword": "Layang kanggo mbalèkaké tembung sandhi wis dikirim sasuwené ing {{PLURAL:$1|jam|$1 jam}}.\nKanggo nyegah ananing tumindhak culika, namung sak layang kanggo mbalèkaké tembung sandhi sing bakal dikirim sasuwéné ing {{PLURAL:$1|jam|$1 jam}}.",
        "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 [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|rembug]]) wis digawé.",
+       "accountcreatedtext": "Akun naraguna [[{{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.",
        "loginlanguagelabel": "Basa: $1",
        "suspicious-userlogout": "Panjaluk panjenengan supaya metu ditolak amarga katoné panjlajah internt utawa proksi panyinggah.",
        "createacct-another-realname-tip": "Jeneng asli ora kudu dilebokake.\n\nYen sampeyan milih nglebokake jeneng asli, jeneng kuwi bakal dinggo ngwenehi atribusi kanggo karya-karyane.",
-       "pt-login": "Mlebu",
+       "pt-login": "Mlebu log",
        "pt-login-button": "Mlebu",
        "pt-login-continue-button": "Banjuraké mlebu",
        "pt-createaccount": "Gawé akun",
        "preview": "Pratuduh",
        "showpreview": "Deleng pratuduh",
        "showdiff": "Tuduhaké owahan",
-       "anoneditwarning": "<strong>Penget:</strong> Panjenengan boten mlebet log. Alamat IP Panjenengan badhe katingal dening publik manawi Panjenengan ngayahi ewah-ewahan. Manawi Panjenengan  <strong>[$1 mlebet log]</strong> utawai <strong>[$2 damel akun]</strong>, suntingan Panjenengan badhe kaatribusekaken dhumateng  nama pangangge Panjenengan, lan rupi-rupi  kauntungan sanesipun.",
-       "anonpreviewwarning": "''Sampéyan durung mlebu log. Nyimpen bakal nyathet alamat IP Sampéyan nèng riwayat sunting kaca iki.''",
+       "anoneditwarning": "<strong>Pènget:</strong> Panjenengan durung mlebu log. Alamat IP-né panjenengan bakal katon marang wong akèh manawa panjenengan mbesut. Manawa panjenengan <strong>[$1 mlebu log]</strong> utawa <strong>[$2 nggawé akun]</strong>, besutané panjenengan bakal dadi darbéné naragunané panjenengan lan uga ana kauntungan liya.",
+       "anonpreviewwarning": "<em>Panjenengan durung mlebu log. Yèn disimpen, alamat IP panjenengan bakal kacathet ing sujarah besutan kaca iki.</em>",
        "missingsummary": "'''Pènget:''' Panjenengan ora nglebokaké ringkesan panyuntingan. Menawa panjenengan mencèt tombol Simpen manèh, suntingan panjenengan bakal kasimpen tanpa ringkesan panyuntingan.",
        "selfredirect": "<strong>Pélik:</strong> Sampéyan ngalih kaca iki iya nyang kaca iki dhéwé.\nSampéyan mungkin salah wènèh tujuan kanggo alihan utawa salah mbesut kaca.\nYèn sampéyan ngeklik \"{{int:savearticle}}\" manèh, kaca alihan bakal digawé.",
        "missingcommenttext": "Mangga isi tanggapan ing ngisor iki.",
        "loginreqtitle": "Kudu mlebu",
        "loginreqlink": "mlebu",
        "loginreqpagetext": "Panjenengan kudu $1 kanggo bisa mirsani kaca liyané.",
-       "accmailtitle": "Tembung wadi wis kinirim",
-       "accmailtext": "Sawijining tembung sandi sembarang kanggo [[User talk:$1|$1]] wis dikirim menyang $2.\n\nTembung sandi kanggo panganggo anyar iki isa diganti ing kaca ''[[Special:ChangePassword|ganti tembung sandi]]'' sawisé mlebu log.",
+       "accmailtitle": "Tembung sandi wis kinirim",
+       "accmailtext": "Tembung sandi sembarang kanggo [[User talk:$1|$1]] wis dikirim menyang $2.\n\nTembung sandi iki bisa diganti ing kaca <em>[[Special:ChangePassword|salin tembung sandi]]</em> sawisé mlebu log.",
        "newarticle": "(Anyar)",
        "newarticletext": "Katonané panjenengan ngetutaké pranala artikel sing durung ana.\nManawa kersa manulis artikel iki, manggaa. (Mangga mirsani [$1 Pitulung] kanggo informasi sabanjuré).\nYèn ora sengaja tekan kéné, bisa ngeklik pencètan '''back''' waé ing panjlajah wèb panjenengan.",
        "anontalkpagetext": "---- ''Iki yaiku kaca dhiskusi sawijining panganggo anonim sing durung kagungan akun utawa ora nganggo akuné, dadi kita keeksa kudu nganggo alamat IP-né kanggo nepangi. Alamat IP kaya mengkéné iki bisa dienggo déning panganggo sing séjé-séjé. Yèn panjenengan pancèn panganggo anonim lan olèh komentar-komentar miring, mangga [[Special:CreateAccount|nggawé akun]] utawa [[Special:UserLogin|log mlebu]] supaya ora rancu karo panganggo anonim liyané ing mangsa ngarep.''",
        "previewnote": "<strong>Élinga yèn iki mung pratuduh.</strong>\nOwahanmu durung kasimpen!",
        "continue-editing": "Menyang pambesutan",
        "previewconflict": "Pratilik iki nuduhaké tèks ing bagian dhuwur kothak suntingan tèks kayadéné bakal katon yèn panjenengan bakal simpen.",
-       "session_fail_preview": "'''Nuwun sèwu, suntingan panjenengan ora bisa diolah amarga dhata sèsi kabusak.\nCoba kirim dhata manèh. Yèn tetep ora bisa, coba log metua lan mlebu log manèh.''''''Amerga wiki iki marengaké panggunan kodhe HTML mentah, mula pratilik didhelikaké minangka pancegahan marang serangan JavaScript.'''\n'''Menawa iki sawijining usaha panyuntingan sing sah, mangga dicoba manèh.\nYèn isih tetep ora kasil, cobanen metu log lan mlebu manèh.'''",
+       "session_fail_preview": "Ngapunten! Kita ora bisa mrosès besutan panjenengan amarga ilangé sèsi data.\n\nPanjenengan bokmanawa wis metu log. <strong>Mangga vèrifikasi manawa panjenengan isih mlebu log lan jajala manèh</strong>.\nManawa isih durung kena, jajala [[Special:UserLogout|metu log]] lan mlebu log manèh, banjur priksaa apa browser panjenengan ngidinaké kuki saka situs iki.",
        "session_fail_preview_html": "'''Nuwun sèwu! Kita ora bisa prosès suntingan panjenengan amerga data sési ilang.'''\n\n''Amerga wiki iki ngidinaké panrapan HTML mentah, pratayang didelikaké minangka penggakan marang serangan Javascript.''\n\n'''Yèn iki sawijining upaya suntingan sing absah, mangga dicoba manèh. Yèn isih tetep ora kasil, cobanen metu log utawa oncat lan mlebua manèh.'''",
        "token_suffix_mismatch": "'''Suntingan panjenengan ditulak amerga aplikasi klièn panjenengan ngowahi karakter tandha wewacan ing suntingan. Suntingan iku ditulak kanggo untuk menggak kaluputan ing tèks artikel. Prekara iki kadhangkala dumadi yèn panjenengan ngangem dines layanan proxy anonim adhedhasar situs wèb sing duwé masalah.'''",
        "edit_form_incomplete": "'''Sebagéyan pormulir suntingan ora tekan nèng sasana; cèk pindho yèn suntingan Sampéyan isih wutuh lan jajal manèh.'''",
        "readonlywarning": "'''PÈNGET: Basis data lagi dikunci amerga ana pangopènan, dadi saiki panjenengan ora bisa nyimpen kasil panyuntingan panjenengan. Panjenengan mbokmenawa prelu mindhahaké kasil panyuntingan panjenengan iki menyang panggonan liya kanggo disimpen bésuk.'''\n\nPangurus sing ngunci basis data mènèhi katrangan kaya mengkéné: $1",
        "protectedpagewarning": "'''PÈNGET:  Kaca iki wis dikunci dadi namung panganggo sing nduwé hak aksès pangurus baé sing bisa nyunting.'''\nEntri cathetan pungkasan disadiakake ing ngisor kanggo referensi:",
        "semiprotectedpagewarning": "'''Cathetan:''' Kaca iki lagi pinuju direksa, dadi namung panganggo kadaftar sing bisa nyunting.\nEntri cathetan pungkasan disadiakake ing ngisor kanggo referensi:",
-       "cascadeprotectedwarning": "'''PÈNGET:''' Kaca iki wis dikunci dadi namung panganggo mawa hak aksès pangurus waé sing bisa nyunting, amerga kalebu {{PLURAL:$1|kaca|kaca-kaca}} ing ngisor iki sing wis direksa mawa opsi 'pangreksan runtun' diaktifaké:",
+       "cascadeprotectedwarning": "<strong>Pènget:</strong> Kaca iki wis direksa saéngga mung naraguna kanthi hak pangurus waé sing bisa mbesut amarga kaca iki katransklusi ing {{PLURAL:$1|kaca|kaca-kaca}} sing kareksa runut ngisor iki:",
        "titleprotectedwarning": "'''Pènget: Kaca iki wis dikunci saéngga perlu [[Special:ListGroupRights|hak mligi]] kanggo gawéné.'''\nEntri cathetan pungkasan disadiakake ing ngisor kanggo referensi:",
        "templatesused": "{{PLURAL:$1|Cithakan|Cithakan}} sing dienggo ing kaca iki:",
        "templatesusedpreview": "{{PLURAL:$1|Cithakan|Cithakan-cithakan}} sing dienggo ing pratilik iki:",
        "edit-gone-missing": "Ora bisa nganyari kaca.\nKatoné kaca iki wis dibusak.",
        "edit-conflict": "Cengkah besutan",
        "edit-no-change": "Suntingan panjenengan dilirwakaké amerga panjenengan ora nglakoni pangowahan apa-apa ing tèks.",
+       "postedit-confirmation-created": "Kaca wis kagawé.",
        "postedit-confirmation-saved": "Besutan sampeyan wis kasimpen.",
        "edit-already-exists": "Ora bisa nggawé kaca anyar.\nAmerga wis ana.",
        "defaultmessagetext": "Tèks layang gawan",
        "searchprofile-advanced-tooltip": "Golèk ing jagat aran tinamtu",
        "search-result-size": "$1 ({{PLURAL:$2|1 tembung|$2 tembung}})",
        "search-result-category-size": "{{PLURAL:$1|1 anggota|$1 anggota}} ({{PLURAL:$2|1 subkatégori|$2 subkatégori}}, {{PLURAL:$3|1 berkas|$3 berkas}})",
-       "search-redirect": "(pangalihan $1)",
+       "search-redirect": "(alihan saka $1)",
        "search-section": "(pérangan $1)",
        "search-category": "(kategori $1)",
        "search-file-match": "(cocog karo isi barkas)",
        "action-createpage": "gawé kaca iki",
        "action-createtalk": "gawé kaca parembugan iki",
        "action-createaccount": "gawé akun panganggo iki",
-       "action-minoredit": "tandhani iki minangka besutan cilik",
+       "action-minoredit": "tandhani besutan iki yèn besutan cilik",
        "action-move": "alih kaca iki",
        "action-move-subpages": "mindahaké kaca iki, lan kabèh anak-kacané",
        "action-move-rootuserpages": "ngalih kaca panganggo oyod",
        "action-rollback": "gelis mbalèkaké suntingané panganggo pungkasan nèng sawijining saca",
        "action-import": "impor kaca iki saka wiki liya",
        "action-importupload": "impor kaca iki saka pamunggahan berkas",
-       "action-patrol": "nandhani suntingan panganggo liya minangka wis kapriksa",
-       "action-autopatrol": "nandhani suntingan panjenengan dhéwé minangka wis kapriksa",
-       "action-unwatchedpages": "pirsani dhaftar kaca-kaca sing ora kaawasi",
+       "action-patrol": "nandhani besutan wong liya yèn wis kapriksa",
+       "action-autopatrol": "nandhani besutan panjenengan dhéwé yèn wis kapriksa",
+       "action-unwatchedpages": "deleng pratélan kaca sing ingawasan",
        "action-mergehistory": "nggabungaké sajarah kaca iki",
        "action-userrights": "ngowahi kabèh hak panganggo",
        "action-userrights-interwiki": "ngowahi hak aksès saka panganggo ing wiki liya",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|cacahé sing ngawasi|cacahé sing ngawasi}}]",
        "rc_categories": "Watesana nganti kategori (dipisah karo \"|\")",
        "rc_categories_any": "Apa waé",
-       "rc-change-size-new": "$1 {{PLURAL:$1|bét|bét}} sabubaré diowah",
+       "rc-change-size-new": "$1 {{PLURAL:$1|bét|bét}} sawisé diowah",
        "newsectionsummary": "/* $1 */ pérangan anyar",
        "rc-enhanced-expand": "Tuduhaké princèn",
        "rc-enhanced-hide": "Dhelikaké princèn",
        "minlength1": "Jeneng berkas paling ora minimal kudu awujud saaksara.",
        "illegalfilename": "Jeneng berkas \"$1\" ngandhut aksara sing ora diparengaké ana sajroning irah-irahan kaca. Mangga owahana jeneng berkas iku lan cobanen  diunggahaké manèh.",
        "filename-toolong": "Jeneng berkas ora olèh luwih dawa saka 240 bita.",
-       "badfilename": "Berkas wis diowahi dados \"$1\".",
+       "badfilename": "Jeneng barkas wis diowah dadi \"$1\".",
        "filetype-mime-mismatch": "Èkstènsi berkas \".$1\" ora cocok karo jinis MIME sing kadètèk saka berkas ($2).",
        "filetype-badmime": "Berkas mawa tipe MIME \"$1\" ora pareng diunggahaké.",
        "filetype-bad-ie-mime": "Ora bisa ngunggahaké berkas iki amarga Internet Explorer ndhétèksi minangka \"$1\", sing ora diidinaké lan minangka tipe berkas sing nduwèni potènsi mbebayani.",
        "unusedtemplates": "Cithakan sing ora dienggo",
        "unusedtemplatestext": "Kaca iki ngamot kabèh kaca ing bilik jeneng {{ns:template}} sing ora dianggo ing kaca ngendi waé.\nPriksanen dhisik pranala-pranala menyang cithakan iki sadurungé mbusak.",
        "unusedtemplateswlh": "pranala liya-liyané",
-       "randompage": "Waton kaca",
+       "randompage": "Sembarang kaca",
        "randompage-nopages": "Ora ana kaca ing {{PLURAL:$2||}}bilik jeneng iki:$1.",
        "randomincategory": "Sembarang kaca ing kategori",
        "randomincategory-invalidcategory": "\"$1\" dudu jeneng kategori sing apik.",
        "confirmdeletetext": "Panjenengan bakal mbusak kaca utawa berkas iki minangka permanèn karo kabèh sajarahé saka basis data. Pastèkna dhisik menawa panjenengan pancèn nggayuh iki, ngerti kabèh akibat lan konsekwènsiné, lan apa sing bakal panjenengan tumindak iku cocog karo [[{{MediaWiki:Policy-url}}|kawicaksanan {{SITENAME}}]].",
        "actioncomplete": "Kasil diayahi",
        "actionfailed": "Tindakan gagal",
-       "deletedtext": "\"$1\" wis dibusak. \nDelenga $2 minangka rekamaning busak-busakan pungkasan.",
+       "deletedtext": "\"$1\" wis dibusak. \nDelenga $2 minangka cathetan ngenani sing pungkasan kabusak.",
        "dellogpage": "Log busak",
        "dellogpagetext": "Ing ngisor iki kapacak log pambusakan kaca sing anyar dhéwé.",
        "deletionlog": "log busak",
        "deletereasonotherlist": "Alesan liya",
        "deletereason-dropdown": "*Alesan pambusakan\n** Spam\n** Vandalisme\n** Nglanggar hak cipta\n** Disuwun sing nulis\n** Pangalihan rusak",
        "delete-edit-reasonlist": "Besut jalaraning pambusak",
-       "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-toobig": "Kaca iki darbé sujarah besutan sing dawa, punjul $1 {{PLURAL:$1|owahan}}.\nPambusak tumrap kaca sing kaya mangkono wis ora diidinaké nedya njagani murih ora ana 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": "Pulihaké besutan",
        "protect-legend": "Konfirmasi pangreksan",
        "protectcomment": "Alesan:",
        "protectexpiry": "Kadaluwarsa:",
-       "protect_expiry_invalid": "Wektu kadaluwarsa ora sah.",
+       "protect_expiry_invalid": "Wektu kadaluwarsa ora sah.",
        "protect_expiry_old": "Wektu kadaluwarsané kuwi ana ing jaman biyèn.",
        "protect-unchain-permissions": "Urubaké pilihan panjagan sabanjuré",
        "protect-text": "Ing kéné, sampéyan bisa ndeleng lan ngganti tataran kareksan tumrap kaca <strong>$1</strong>.",
        "protect-existing-expiry": "Wektu kadaluwarsa saiki: $3, $2",
        "protect-otherreason": "Alesan liya/tambahan:",
        "protect-otherreason-op": "Alesan liya",
-       "protect-dropdown": "*Alesan umum pangreksan\n** Vandalisme makaping-kaping\n** Spam makaping-kaping\n** Perang suntingan\n** Kaca kerep disunting",
+       "protect-dropdown": "*Alesan umum pangreksa\n** Vandalisme makaping-kaping\n** Spam makaping-kaping\n** Perang besutan ora prodhuktif\n** Kaca sing dhuwur trafiké",
        "protect-edit-reasonlist": "Mbesut jalaraning pangreksa",
        "protect-expiry-options": "1 jam:1 hour,1 dina:1 day,1 minggu:1 week,2 minggu:2 weeks,1 wulan:1 month,3 wulan:3 months,6 wulan:6 months,1 taun:1 year,tanpa wates:infinite",
        "restriction-type": "Pangreksan:",
        "whatlinkshere-prev": "{{PLURAL:$1|sadurungé|$1 sadurungé}}",
        "whatlinkshere-next": "{{PLURAL:$1|sabanjuré|$1 sabanjuré}}",
        "whatlinkshere-links": "← pranala",
-       "whatlinkshere-hideredirs": "$1 lih-lihan",
+       "whatlinkshere-hideredirs": "$1 alihan",
        "whatlinkshere-hidetrans": "$1 transklusi",
-       "whatlinkshere-hidelinks": "pranala-pranala $1",
+       "whatlinkshere-hidelinks": "$1 pranala",
        "whatlinkshere-hideimages": "$1 pranala berkas",
        "whatlinkshere-filters": "Panyaringan",
        "autoblockid": "Blokir otomatis #$1",
        "ipaddressorusername": "Alamat IP utawa jeneng panganggo",
        "ipbexpiry": "Kadaluwarsa",
        "ipbreason": "Alesan:",
-       "ipbreason-dropdown": "*Alesan umum mblokir panganggo\n** Mènèhi informasi palsu\n** Ngilangi isi kaca\n** Spam pranala menyang situs njaba\n** Nglebokaké tulisan ngawur ing kaca\n** Tumindak intimidasi/nglècèhaké\n** Nyalahgunakaké sawetara akun utawa rékening\n** Jeneng panganggo ora layak",
+       "ipbreason-dropdown": "*Alesan umum mblokir\n** Mènèhi informasi palsu\n** Mbusak isi kaca\n** Spam pranala menyang situs njaba\n** Nglebokaké tulisan ngawur ing kaca\n** Tumindak nglècèhaké\n** Ngujar-ujari sawenèh akun\n** Jeneng naraguna ora patut",
        "ipb-hardblock": "Alangi panganggo sing wis mlebu log nyunting saka alamat IP iki",
        "ipbcreateaccount": "Penggak nggawé akun utawa rékening",
        "ipbemailban": "Penggak panganggo ngirim layang e-mail",
        "movepagetext": "Formulir ing ngisor iki bakal ngowahi jeneng sawijining kaca, mindhah kabèh sajarahé menyang kaca sing anyar. Irah-irahan utawa judhul sing lawas bakal dadi kaca pangalihan menyang irah-irahan sing anyar. Pranala menyang kaca sing lawas ora bakal diowahi; dadi pastèkna dhisik mriksa pangalihan [[Special:DoubleRedirects|dobel]] utawa [[Special:BrokenRedirects|pangalihan sing rusak]] sawisé pamindhahan. Panjenengan sing tanggung jawab mastèkaké menawa kabèh pranala-pranala tetep nyambung ing kaca panujon kaya samesthiné.\n\nGatèkna yèn kaca iki '''ora''' bakal dipindhah yèn wis ana kaca liyané sing nganggo irah-irahan sing anyar, kejaba kaca iku kosong utawa ora nduwé sajarah panyuntingan. Dadi tegesé panjenengan bisa ngowahi jeneng kaca iku manèh kaya sedyakala menawa panjenengan luput, lan panjenengan ora bisa nimpani kaca sing wis ana.\n\n'''PÈNGET!'''\nPerkara iki bisa ngakibataké owah-owahan sing drastis lan ora kaduga kanggo kaca-kaca sing populèr;\npastekaké dhisik panjenengan ngerti konsekwènsi saka panggayuh panjenengan sadurungé dibanjuraké.",
        "movepagetext-noredirectfixer": "Formulir di bawah ini digunakan untuk mengubah nama suatu halaman dan memindahkan semua data sejarah ke nama baru.\nJudul yang lama akan menjadi halaman peralihan menuju judul yang baru.\nPastikan untuk memeriksa pengalihan [[Special:DoubleRedirects|ganda]] atau [[Special:BrokenRedirects|rusak]].\nAnda bertanggung jawab untuk memastikan bahwa pranala terus menyambung ke halaman yang seharusnya.\n\nPerhatikan bahwa halaman '''tidak''' akan dipindah apabila telah ada halaman yang menggunakan judul yang baru, kecuali bila halaman tersebut kosong atau merupakan halaman peralihan dan tidak mempunyai sejarah penyuntingan.\nIni berarti Anda dapat mengubah nama halaman kembali seperti semula apabila Anda membuat kesalahan, dan Anda tidak dapat menimpa halaman yang telah ada.\n\n'''Peringatan:'''\nHal ini dapat mengakibatkan perubahan yang tak terduga dan drastis bagi halaman yang populer;\nPastikan Anda mengerti konsekuensi dari perbuatan ini sebelum melanjutkan.",
        "movepagetalktext": "Menawa sampéyan nyénthang kothak iki, kaca parembugan sing magepokan bakal otomatis dilih nyang sesirah anyar, kajaba kaca parembugané sing dituju wis ana isiné.\n\nYèn mangkéné, sampéyan kudu ngalih utawa nggabung kaca-kaca iku kanthi manual.",
-       "moveuserpage-warning": "'''Pèngetan:''' Sampéyan arep mindhahaké kaca panganggo. Mangga cathet yèn namung kaca sing bakal dipindhahaké lan panganggo '''ora''' bakal diganti jenengé.",
+       "moveuserpage-warning": "<strong>Pènget:</strong> Panjenengan iki arep ngalih kaca naraguna. Mangga ngèlingi yèn mung kacané sing bakal dilih, déné naragunané <em>ora</em> bakal salin jeneng.",
        "movenologintext": "Panjenengan kudu dadi panganggo sing wis ndaftar lan wis [[Special:UserLogin|mlebu log]] kanggo mindhah kaca.",
        "movenotallowed": "Panjenengan ora pareng ngalihaké kaca.",
        "movenotallowedfile": "Panjenengan ora duwé hak kanggo mindhahaké berkas.",
        "movepagebtn": "Ngalih kaca",
        "pagemovedsub": "Kasil dilih",
        "movepage-moved": "<strong>\"$1\" wis dilih nyang \"$2\"</strong>",
-       "movepage-moved-redirect": "Kaca pengalihan wis kacipta.",
+       "movepage-moved-redirect": "Kaca alihan wis kagawé.",
        "movepage-moved-noredirect": "Kanggo gawé pengalihan wis ditahan.",
-       "articleexists": "Satunggalipun kaca kanthi asma punika sampun wonten, utawi asma ingkang panjenengan pendhet mboten leres. Sumangga nyobi asma sanèsipun.",
+       "articleexists": "Kaca mawa jeneng mangkono wis ana utawa jeneng sing kokpilih ora valid.\nMangga pilih jeneng liya.",
        "cantmove-titleprotected": "Panjenengan ora bisa mindhahaké kaca iki menyang lokasi iki, amerga irah-irahan tujuan lagi direksa; ora olèh digawé",
        "movetalk": "Lih kaca parembugan sing magepokan",
        "move-subpages": "Lih anak kaca (tekan $1)",
        "tooltip-t-emailuser": "Kirimna e-mail menyang panganggo iki",
        "tooltip-t-upload": "Unggah barkas",
        "tooltip-t-specialpages": "Pratélaning kabèh kaca mirunggan",
-       "tooltip-t-print": "Cara cithakan kaca iki",
+       "tooltip-t-print": "Vèrsi cithak kaca iki",
        "tooltip-t-permalink": "Pranala permanèn saka owahan iki",
        "tooltip-ca-nstab-main": "Deleng kaca isi",
        "tooltip-ca-nstab-user": "Deleng kaca panganggo",
        "feedback-subject": "Jejer:",
        "feedback-submit": "Kirim",
        "feedback-thanks": "Nuwun! Lebon saran Sampéyan wis dipasang nèng kacané \"[$2 $1]\".",
-       "searchsuggest-search": "Golèk",
+       "searchsuggest-search": "Golèk {{SITENAME}}",
        "searchsuggest-containing": "ngemu...",
        "api-error-badaccess-groups": "Sampéyan ora dililakaké ngunggah berkas nèng wiki iki.",
        "api-error-badtoken": "Kasalahan njero: Token èlèk.",
index 9275e71..5ff8194 100644 (file)
        "searchprofile-advanced-tooltip": "کسٹم نیم اسپیسا تلاش کورے",
        "search-result-size": "$1 ({{PLURAL:$2|1 لوظ|$2 الفاظ}})",
        "search-result-category-size": "{{PLURAL:$1|1 رُکن|$1 اراکین}} ({{PLURAL:$2|1 ذیلی زمرہ|$2 ذیلی زمرہ جات}}, {{PLURAL:$3|1 ملف|$3 ملفات}})",
-       "search-redirect": "(رجوع مکرر $1)",
+       "search-redirect": "(Redirect $1)",
        "search-section": "(حصہ $1)",
        "search-suggest": "تہ مطلب ھیہ تھے نو اوشوئے؟: $1",
        "search-interwiki-caption": "ملگیری منصوبہ",
        "whatlinkshere-prev": "{{PLURAL:$1|سابقہ|سابقہ $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|پروشٹیو|پروشٹیو$1}}",
        "whatlinkshere-links": "← لنکس",
-       "whatlinkshere-hideredirs": "$1 رجوع مکرر",
+       "whatlinkshere-hideredirs": "$1 Redirects",
        "whatlinkshere-hidetrans": "ٹرانسکلوژن $1",
        "whatlinkshere-hidelinks": "$1 لنکس",
        "whatlinkshere-hideimages": "ھوٹوان لنک $1",
index 28e84bc..f7ad3d9 100644 (file)
        "mypage": "Жеке бет",
        "mytalk": "Талқылау",
        "anontalk": "Талқылау",
-       "navigation": "Ð\91аÒ\93Ñ\8bÑ\82Ñ\82ау",
+       "navigation": "ШаÑ\80лау",
        "and": "&#32;және",
        "qbfind": "Табу",
        "qbbrowse": "Шолу",
        "passwordreset-emaildisabled": "E-mail мүмкіндігі бұл уикиде өшірілген.",
        "passwordreset-username": "Қатысушы аты:",
        "passwordreset-domain": "Домен:",
-       "passwordreset-capture": "Келген хатты қарау керек пе?",
-       "passwordreset-capture-help": "Егер Сіз берілген белгішені қондырсаңыз, қатысушыға жіберілетін уақытша құпия сөз жазылған хат көрсетіледі.",
        "passwordreset-email": "Е-поштаның мекен-жайы:",
        "passwordreset-emailtitle": "{{SITENAME}} тіркелгісі туралы анықтама",
        "passwordreset-emailtext-ip": "Әлде кім (мүмкін сіз болуыңыз, $1 IP адресінен) {{SITENAME}} сайтында ($4) құпия сөзді өзгертуге өтініш білдірді. Мына қатысушы {{PLURAL:$3|аккаунты|аккаунттары}} осы электронды почта қатысты:\n\n$2\n\n{{PLURAL:$3|Бұл уақытша құпия сөз|Бұл уақытша құпия сөздер}} {{PLURAL:$5|бір күнде|$5 күнде}}уақыты аяқталады.\nСіз кіруіңіз және жаңа құпия сөзді таңдауыңыз керек. Егер бұл өтінішті басқа біреу жасаса, немесе сіз  бұрынғы құпия сөзіңізді еске түсірсеңіз және құпия сөзді ауыстыруды қаламасаңыз, сіз бұл хабарламаны ескермей және бұрынғы құпия сөзді қолдана беруіңізге болады.",
        "passwordreset-emailtext-user": "$1 есімді қатысушы {{SITENAME}} сайтында ($4) құпия сөзді өзгертуге өтініш білдірді. Мына қатысушы {{PLURAL:$3|аккаунт|аккаунттар}} осы електронды почта қатысты:\n\n$2\n\n{{PLURAL:$3|Бұл уақытша құпия сөз|Бұл уақытша құпия сөздер}} {{PLURAL:$5|бір күнде|$5 күнде}}уақыты аяқталады.\nСіз кіруіңіз және жаңа құпия сөзді таңдауыңыз керек. Егер бұл өтінішті басқа біреу жасаса, немесе сіз  бұрынғы құпия сөзіңізді еске түсірсеңіз, және құпия сөзді ауыстыруды қаламасаңыз, сіз бұл хабарламаны ескермей және бұрыңғы құпия сөзді қолдана беруіңізге болады.",
        "passwordreset-emailelement": "Қатысушы есімі: \n$1\n\nУақытша құпия сөз: \n$2",
        "passwordreset-emailsentemail": "Бұл email мекенжайы тіркелгіңізге байланысқан, сол себепті құпия сөзді өзгерту электронды пошта арқылы жөнелтіледі.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|email has|emails have}}үшін құпия сөздің қалпына келтіру хабарламасы жіберілді. {{PLURAL:$1|username and password|list of usernames and passwords}} мында көрсетілген.",
-       "passwordreset-emailerror-capture2": "{{GENDER:$2|user}}-мен электронды поштамен хабарласу нәтижесіз қалды: $1 The {{PLURAL:$3|username and password|list of usernames and passwords}} мында көрсетілген.",
        "changeemail": "Е-пошта мекенжайын өзгерту немесе аластау",
        "changeemail-header": "Е-пошта мекен-жайының өзгертілуі",
        "changeemail-no-info": "Бұл бетке тікелей ену үшін жүйеге кіруіңіз керек.",
        "userrights-reason": "Себебі:",
        "userrights-no-interwiki": "Басқа уикилердегі қатысушы құқықтарын өңдеуге рұқсатыңыз жоқ.",
        "userrights-nodatabase": "$1 дерекқоры жоқ не жергілікті емес.",
-       "userrights-nologin": "Қатысушы құқықтарын тағайындау үшін әкімші тіркелгісімен [[Special:UserLogin|кіруіңіз]] жөн.",
-       "userrights-notallowed": "Сізге қатысушы құқықтарын қосуға немесе алып тастауға рұқсат берілмеген.",
        "userrights-changeable-col": "Өзгерте алатын топтар",
        "userrights-unchangeable-col": "Өзгерте алмайтын топтар",
        "userrights-conflict": "Қатысушы құқықтарының қақтығысы! Өзгертулеріңізді қайта қарап шығыңыз және құптаңыз.",
-       "userrights-removed-self": "Өзіңіздің құқықтарыңызды алып тастадыңыз.  Осылайша бұл бетке бұдан былай қатынай алмайсыз.",
        "group": "Топ:",
        "group-user": "Қатысушылар",
        "group-autoconfirmed": "Өздіктіқұпталған қатысушылар",
        "right-siteadmin": "Дерекқорды құлыптау және құлыптауын өшіру",
        "right-override-export-depth": "Тереңдігі 5-тен жоғары сілтенген бетттерді қамти беттерді экспорттау",
        "right-sendemail": "Басқа қатысушыларға е-пошта жіберу",
-       "right-passwordreset": "Өзгерген құпия сөз арқылы хабарламаларды шолу",
        "right-managechangetags": "[[Special:Tags|Тегтерді]] дерекқордан бастау және жою",
        "right-applychangetags": "[[Special:Tags|Тегтерді]] бір өзгерісімен қолдану",
        "right-changetags": "Кез келген [[Special:Tags|тегті]] жеке нұсқалардан және журнал жазбаларынан аластау және қосу",
        "trackingcategories-msg": "Санатты қадағалау",
        "trackingcategories-name": "Хабарлама атауы",
        "trackingcategories-desc": "Санаттарды қосу шарттары",
-       "restricted-displaytitle-ignored": "Еленбеген көретілетін атауларымен беттер",
+       "restricted-displaytitle-ignored": "Еленбеген көрcетілетін атауларымен беттер",
        "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> үлкенірек болады, сондықтан біраз үлгілер кеңейтілмейді.",
        "undeleterevisions": "$1 {{PLURAL:$1|нұсқа|нұсқа}} жойылды",
        "undeletehistory": "Егер бетті қалпына келтірсеңіз тарихындағы барлық түзетулер де қалпына келтіріледі. Егер жоюдан соң дәл солай атауымен жаңа бет басталса қалпына келтірілген түзетулер бұрынғы өңделу тарихында көрсетіледі.",
        "undeleterevdel": "Егер бұл үстіңгі бетте аяқталса, не файл түзетуі жарым-жартылай жойылған болса, жою болдырмауы орындалмайды.\nОсындай жағдайларда, ең жаңа жойылған түзетуін алып тастауыңыз не жасыруын болдырмауыңыз жөн.",
-       "undeletehistorynoadmin": "Бұл бет жойылған.\nЖою себебі алдындағы өңдеген қатысушылар егжей-тегжейлерімен бірге төмендегі қысқаша мазмұндамасында көрсетілген.\nМына жойылған түзетулерін көкейкесті мәтіні тек әкімшілерге жетімді.",
+       "undeletehistorynoadmin": "Бұл бет жойылған.\nЖойған себебі жою алдындағы өңдеген қатысушылар егжей-тегжейлерімен бірге төмендегі қысқаша мазмұндамасында көрсетілген. Осы жойылған нұсқалардың мәтіні тек әкімшілерге қатынаулы.",
        "undelete-revision": "$4,  $5  кезіндегі $3 жасаған $1 дегеннің жойылған түзетуі:",
        "undeleterevision-missing": "Жарамсыз не жоғалған түзету.\nСілтемеңіз жарамсыз, не түзету қалпына келтірілген, немесе мұрағаттан аласталған болуы мүмкін.",
        "undelete-nodiff": "Еш алдыңғы түзету табылмады.",
index 98b103f..8dc9246 100644 (file)
        "minoredit": "사소한 편집입니다",
        "watchthis": "이 문서 주시하기",
        "savearticle": "문서 저장",
-       "savechanges": "변경 사항 저장",
+       "savechanges": "변경사항 저장",
        "publishpage": "문서 게시",
-       "publishchanges": "변경 사항 게시",
+       "publishchanges": "변경사항 게시",
        "preview": "미리 보기",
        "showpreview": "미리 보기",
        "showdiff": "차이 보기",
        "search-external": "바깥 검색",
        "searchdisabled": "{{SITENAME}} 검색이 비활성화되어 있습니다.\n검색이 작동하지 않는 동안 Google을 통해 검색할 수 있습니다.\n검색 엔진의 내용은 최신이 아닐 수 있다는 점을 참고하세요.",
        "search-error": "검색하는 동안 오류가 발생했습니다: $1",
+       "search-warning": "검색하는 동안 경고가 발생했습니다: $1",
        "preferences": "환경 설정",
        "mypreferences": "환경 설정",
        "prefs-edits": "편집 수:",
        "editusergroup": "사용자 그룹 불러오기",
        "editinguser": "<strong>[[User:$1|$1]]</strong> $2 {{GENDER:$1|사용자}}의 권한 바꾸기",
        "userrights-editusergroup": "사용자 그룹 편집",
+       "userrights-viewusergroup": "사용자 그룹 보기",
        "saveusergroups": "{{GENDER:$1|사용자}} 권한 저장",
        "userrights-groupsmember": "현재 권한:",
        "userrights-groupsmember-auto": "자동으로 부여된 권한:",
        "grant-editprotected": "보호된 문서 편집하기",
        "grant-highvolume": "대용량 편집",
        "grant-oversight": "사용자 숨기기와 판 억제",
-       "grant-patrol": "페이지 변경 사항 점검",
+       "grant-patrol": "문서의 변경사항 점검",
        "grant-privateinfo": "개인 정보 접근",
        "grant-protect": "문서 보호 및 보호 해제",
        "grant-rollback": "문서의 바뀜을 되돌리기",
        "action-upload_by_url": "URL 주소를 통해 이 파일을 올리기",
        "action-writeapi": "API를 작성할",
        "action-delete": "이 문서 삭제하기",
-       "action-deleterevision": "이 판을 삭제",
-       "action-deletedhistory": "이 문서의 삭제된 기여의 역사 보기",
+       "action-deleterevision": "판을 삭제",
+       "action-deletedhistory": "문서의 삭제된 기여의 역사 보기",
        "action-browsearchive": "삭제된 문서 검색",
-       "action-undelete": "이 문서 되살리기",
+       "action-undelete": "문서 되살리기",
        "action-suppressrevision": "이 숨겨진 판을 검토하고 되살릴",
        "action-suppressionlog": "비공개 기록 보기",
        "action-block": "이 사용자가 편집하지 못하도록 차단",
        "htmlform-user-not-exists": "<strong>$1</strong> 문서는 존재하지 않습니다.",
        "htmlform-user-not-valid": "<strong>$1</strong>은 올바른 사용자 이름이 아닙니다.",
        "logentry-delete-delete": "$1님이 $3 문서를 {{GENDER:$2|삭제했습니다}}",
+       "logentry-delete-delete_redir": "$1님이 덮어쓰기를 통해 $3 문서를 {{GENDER:$2|삭제했습니다}}",
        "logentry-delete-restore": "$1님이 $3 문서를 {{GENDER:$2|되살렸습니다}}",
        "logentry-delete-event": "$1님이 $3의 {{PLURAL:$1|기록 $5개}}에 대해 보이기 설정을 {{GENDER:$2|바꾸었습니다}}: $4",
        "logentry-delete-revision": "$1님이 $3 문서의 {{PLURAL:$5|$5개 편집}}의 설정을 {{GENDER:$2|바꾸었습니다}}: $4",
        "special-characters-title-emdash": "em 대시",
        "special-characters-title-minus": "빼기 기호",
        "mw-widgets-dateinput-no-date": "선택된 날짜 없음",
+       "mw-widgets-mediasearch-input-placeholder": "미디어 검색",
+       "mw-widgets-mediasearch-noresults": "결과가 없습니다.",
        "mw-widgets-titleinput-description-new-page": "문서가 존재하지 않습니다",
        "mw-widgets-titleinput-description-redirect": "$1 문서로 넘겨주기",
        "mw-widgets-categoryselector-add-category-placeholder": "분류 추가...",
        "usercssispublic": "주목해 주십시오: CSS의 하위 문서들은 다른 사용자들이 볼 수 있기 때문에 기밀 데이터를 포함해서는 안 됩니다.",
        "restrictionsfield-badip": "유효하지 않은 IP 주소나 대역: $1",
        "restrictionsfield-label": "허용된 IP 대역:",
-       "restrictionsfield-help": "줄 단위의 하나의 IP 주소 또는 CIDR 대역입니다. 모든 곳에 적용하려면, 다음을 사용하세요<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "줄 단위의 하나의 IP 주소 또는 CIDR 대역입니다. 모든 곳에 적용하려면, 다음을 사용하세요<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "페이지 ID $1"
 }
index ede9c55..df813b8 100644 (file)
@@ -3,7 +3,8 @@
                "authors": [
                        "Jose77",
                        "Meno25",
-                       "Protostar"
+                       "Protostar",
+                       "Nemo bis"
                ]
        },
        "tog-underline": "Link ùndarlaintin",
        "versionrequired": "Yu nid MediaWiki Version $1",
        "versionrequiredtext": "Yu nid MediaWiki Versiòn $1 fòyuz dhis pej.\nLuk [[Special:Version|version page]].",
        "ok": "OK",
-       "pagetitle-view-mainpage": " \n{{SAITNEM}}",
+       "pagetitle-view-mainpage": "{{SITENAME}}",
        "retrievedfrom": "Dèn dòn ritriv am na\"$1\"",
        "youhavenewmessages": "{{PLURAL:$3|Yu gèt}} $1 ($2).",
        "editsection": "èdit",
index 887e84b..817902b 100644 (file)
        "editusergroup": "Benotzergruppe lueden",
        "editinguser": "Ännere vun de Rechter vum  {{GENDER:$1|Benotzer}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Benotzergruppen änneren",
+       "userrights-viewusergroup": "Benotzergruppe weisen",
        "saveusergroups": "{{GENDER:$1|Benotzer}}gruppe späicheren",
        "userrights-groupsmember": "Member vun:",
        "userrights-groupsmember-auto": "Implizit Member vun:",
        "action-upload_by_url": "Fichiere vun enger Internetadress (URL) eropzelueden",
        "action-writeapi": "d'API mat Schreifzougrëff ze benotzen",
        "action-delete": "dës Säit ze läschen",
-       "action-deleterevision": "dës Versioun ze läschen",
-       "action-deletedhistory": "d'Lëscht vun de geläschte Versiounen gesinn",
+       "action-deleterevision": "Versioune läschen",
+       "action-deletedhistory": "déi geläscht Versiounen vun enger Säit weisen",
        "action-browsearchive": "no geläschte Säiten ze sichen",
-       "action-undelete": "dës Säit ze restauréieren",
-       "action-suppressrevision": "déi verstoppt Versioun kucken a restauréieren",
+       "action-undelete": "Säite restauréieren",
+       "action-suppressrevision": "verstoppt Versiounen nokucken a restauréieren",
        "action-suppressionlog": "dës privat Lëscht ze kucken",
        "action-block": "dëse Benotzer fir Ännerungen ze spären",
        "action-protect": "de Protektiounsstatus vun dëser Säit änneren",
        "action-userrights-interwiki": "d'Rechter vu Benotzer vun anere Wikien z'änneren",
        "action-siteadmin": "d'Datebank ze spären oder d'Spär opzehiewen",
        "action-sendemail": "Maile schécken",
+       "action-editmyoptions": "ännert Är Astellungen",
        "action-editmywatchlist": "ännert Är Iwwerwaachungslëscht",
        "action-viewmywatchlist": "kuckt Är Iwwerwaachungslëscht",
        "action-viewmyprivateinfo": "Är privat Informatioune kucken",
        "emailccsubject": "Kopie vun denger Noriicht un $1: $2",
        "emailsent": "E-Mail geschéckt",
        "emailsenttext": "Är E-Mail gouf fortgeschéckt.",
-       "emailuserfooter": "Dës E-Mail gouf  {{GENDER:$1|vum}} $1  {{GENDER:$2|dem}} $2 geschéckt dobäi gouf d'Funktioun \"{{int:emailuser}}\" op {{SITENAME}} benotzt.",
+       "emailuserfooter": "Dës E-Mail gouf {{GENDER:$1|vum}} $1  {{GENDER:$2|dem}} $2 geschéckt dobäi gouf d'Funktioun \"{{int:emailuser}}\" op {{SITENAME}} benotzt. {{GENDER:$2|Är}} E-Mail gëtt direkt un den {{GENDER:$1|originalen Absender}} geschéckt do bäi gesäit  {{GENDER:$1|deen}} {{GENDER:$2|Är}} E-Mail-Adress.",
        "usermessage-summary": "Benoriichtegung hannerloossen.",
        "usermessage-editor": "Benoriichtegungs-System",
        "watchlist": "Iwwerwaachungslëscht",
        "mw-widgets-dateinput-no-date": "Keen Datum erausgesicht",
        "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
+       "mw-widgets-mediasearch-input-placeholder": "No Medie sichen",
+       "mw-widgets-mediasearch-noresults": "Näischt fonnt.",
        "mw-widgets-titleinput-description-new-page": "Säit gëtt et nach net",
        "mw-widgets-titleinput-description-redirect": "viruleeden op $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Eng Kategorie derbäisetzen...",
        "unlinkaccounts-success": "De Benotzerkont gouf getrennt.",
        "userjsispublic": "DEnkt drun: Op JavaScript-Ënnersäite solle keng vertraulech Informatioune stoe well se vun anere Benotzer kënne gesi ginn.",
        "restrictionsfield-badip": "Net valabel IP-Adress oder Beräich: $1",
-       "restrictionsfield-label": "Zougeloossen IP-Beräicher:"
+       "restrictionsfield-label": "Zougeloossen IP-Beräicher:",
+       "revid": "r$1"
 }
index f0441f0..ef2a562 100644 (file)
        "views": "Vìxite",
        "toolbox": "Arneixi",
        "tool-link-userrights": "Modiffica groppi {{GENDER:$1|utente}}",
+       "tool-link-userrights-readonly": "Vixualizza groppi {{GENDER:$1|utente}}",
        "tool-link-emailuser": "Manda un'e-mail a questo {{GENDER:$1|utente}}",
        "userpage": "Veddi a paggina utente",
        "projectpage": "Amia a paggina de serviççio",
        "botpasswords-label-delete": "Scassa",
        "botpasswords-label-resetpassword": "Reimposta a poula segretta",
        "botpasswords-label-grants": "Assegnaçioin applicabile:",
-       "botpasswords-help-grants": "Ogni assegnaçion a dà accesso a-i driti utente elencæ che un'utença a g'ha zà. Amia a [[Special:ListGrants|tabella d'e assegnaçioin]] pe de ulteioî informaçioin.",
+       "botpasswords-help-grants": "I assegnaçioin consentan l'accesso a di driti che a to utença a possede zà. Attivâ un'assegnaçion chì no fornisce l'accesso a arcun drito che a to utença atrimenti a no gh'aviæ. Amia a [[Special:ListGrants|tabella di assegnaçioin]] pe ciu informaçioin.",
        "botpasswords-label-grants-column": "Assegnaçioin",
        "botpasswords-bad-appid": "O nomme bot \"$1\" o no l'è vallido.",
        "botpasswords-insert-failed": "Imposcibile azonze o nomme bot \"$1\". O l'è za stæto azonto?",
        "newarticle": "(Nêuvo)",
        "newarticletext": "Sto colegaménto o corisponde a 'na pàgina ch'a no l'existe ancon.\n\nSe se vêu creâ a pàgina òua, se pêu comensâ a scrive into spàçio chì sotta.\n(amia e [$1 paggine d'agiûtto] pe ciû informaçioìn).\n\nSe t'ê intròu chì pe sballio,  sciacca '''Inderê''' into navegatô.",
        "anontalkpagetext": "----\n<em>Sta chì a l'è a paggina de discuscion de un utente anonnimo, ch'o no l'ha ancon creou un'utensa o comunque o no a doeuvia oua.</em> Pe identificâlo l'è quindi necessaio doeuviâ o nummero do so adresso IP. I adresci IP poeuan però ese condivixi da ciù utenti. Se t'ê un utente anonnimo e ti ritegni che i commenti inte sta pagina no se riferiscian a ti, [[Special:CreateAccount|crea una noeuva utensa]] o donque [[Special:UserLogin|intra con quella che ti g'hæ za]] pe evitâ de chì avanti de ese confuzo con di atri utenti anonnimi .",
-       "noarticletext": "Po-u momento a pagina çercâ a l'è vêua. Ti poeu [[Special:Search/{{PAGENAME}}|çercâ sto tittolo]] inti atre pagine do scito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} çercâ inti registri correlæ] oppû [{{fullurl:{{FULLPAGENAME}}|action=edit}} creâ questa pagina]</span>.",
+       "noarticletext": "Po-u momento a paggina çercâ a l'è voeua. Ti poeu [[Special:Search/{{PAGENAME}}|çercâ sto tittolo]] inti atre pagine do scito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} çercâ inti registri correlæ] oppû [{{fullurl:{{FULLPAGENAME}}|action=edit}} creâ questa paggina]</span>.",
        "noarticletext-nopermission": "Òua a pàgina çercâ a l'è vêua. L'è poscìbile [[Special:Search/{{PAGENAME}}|çercâ sto tìtolo]] inte di âtre pàgine do scîto o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} çercâ inti registri corelæ]</span>, ma no ti gh'hæ i outorizzaçioin pe creâ sta paggina.",
        "missing-revision": "La verscion #$1 da paggina \"{{FULLPAGENAME}}\" a no l'esiste.\n\nQuesto succede solitamente se inta stoia ti sciacchi un vegio ingancio a una paggina scassâ.\n\nI dettaggi peuan ese attrovæ into [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de scançellaçioin].",
        "userpage-userdoesnotexist": "L'utensa \"$1\" a no corisponde a un utente registròu.\nTi veu davei creâ o modificâ sta paggina?",
        "search-external": "Riçerca esterna",
        "searchdisabled": "A riçerca de {{SITENAME}} a no l'è attiva. Into fratempo ti peu çercâ in sce Google. \nNotta che i so indexi di contegnui de {{SITENAME}} porrieivan no ese aggiornæ.",
        "search-error": "S'è verificou 'n errô durante a riçerca: $1",
+       "search-warning": "Gh'è stæto in alerta durante a çerchia: $1",
        "preferences": "Preferençe",
        "mypreferences": "Preferençe",
        "prefs-edits": "Modiffiche effettuæ:",
        "prefs-help-recentchangescount": "Comprende i urtime modiffiche, paggine de stoie e registri.",
        "prefs-help-watchlist-token2": "Questa a l'è a ciave segretta pe-o feed web di to oservæ.\nChiunque a conosce saiâ in graddo de leze i to oservæ, quindi no stanni a condividdila. [[Special:ResetTokens|Clicca chi se ti g'hæ de besoeugno de rempostâla]].",
        "savedprefs": "E teu preferençe son stæte sarvæ.",
-       "savedrights": "I driti utente de {{GENDER:$1|$1}} son stæti sarvæ.",
+       "savedrights": "I groppi utente de {{GENDER:$1|$1}} son stæti sarvæ.",
        "timezonelegend": "Fuso oraio:",
        "localtime": "Oa locale:",
        "timezoneuseserverdefault": "Adeuvia l'oa predefinia do wiki ($1)",
        "prefswarning-warning": "T'hæ fæto de modiffiche a-e teu preferense che no son ancon stæte sarvæ.\nSe ti sciorti da sta paggina sensa sciaccâ \"$1\" e preferense no saian agiornæ.",
        "prefs-tabs-navigation-hint": "Suggeimento: ti peu deuviâ i pomelli co-a freccia scinistra e drita pe navegâ tra e schede inta lista de schede.",
        "userrights": "Manezzo di driti di utenti",
-       "userrights-lookup-user": "Gestisci i gruppi di utenti",
+       "userrights-lookup-user": "Seleçion-a un utente",
        "userrights-user-editname": "Scrivi o teu nomme utente:",
-       "editusergroup": "Modiffica groppi {{GENDER:$1|utente}}",
+       "editusergroup": "Carrega groppi utente",
        "editinguser": "Apreuvo a cangiâ i driti de l'{{GENDER:$1|utente}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Modiffica i gruppi di utenti",
        "saveusergroups": "Sarva groppi {{GENDER:$1|utente}}",
        "grant-basic": "Driti de base",
        "grant-viewdeleted": "Vedde i file e e pagine scassæ",
        "grant-viewmywatchlist": "Vedde i to öservæ speçiali",
+       "grant-viewrestrictedlogs": "Amia i valoî privæ do registro",
        "newuserlogpage": "Nêuvi utenti",
        "newuserlogpagetext": "Questo o l'è un registro de creaçioin di utençe.",
        "rightslog": "Diritti d'ûtente",
        "apisandbox-alert-field": "O valô de questo campo o no l'è vallido.",
        "apisandbox-continue": "Continnoa",
        "apisandbox-continue-clear": "Nettezza",
+       "apisandbox-param-limit": "Inseisci <kbd>max</kbd> pe doeuviâ o limmite mascimo.",
+       "apisandbox-multivalue-all-namespaces": "$1 (Tutti i namespace)",
+       "apisandbox-multivalue-all-values": "$1 (Tutti i valoî)",
        "booksources": "Fonte libraie",
        "booksources-search-legend": "Çerca e fonti",
        "booksources-isbn": "Codice ISBN:",
        "booksources-search": "Çerca",
        "booksources-text": "De sotta unn-a lista d'inganci a di ätri sciti che vendan libbri neuvi e vegi e che porrieivan avei ciu informaçioin in scî libbri che ti çerchi",
        "booksources-invalid-isbn": "O ISBN inserio pâ no ese vallido; controlla che no ghe segge stæto di ari into copiâlo da-a fonte originale.",
+       "magiclink-tracking-rfc": "Paggine ch'adoeuvian di inganci maggichi RFC",
+       "magiclink-tracking-rfc-desc": "Sta paggina a l'adoeuevia di inganci maggichi RFC. Amia [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] insce comme ezeguî a migraçion.",
+       "magiclink-tracking-pmid": "Paggine ch'adoeuvian di inganci maggichi PMID",
+       "magiclink-tracking-pmid-desc": "Sta paggina a l'adoeuvia dio inganci maggichi PMID. Amia [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] sciu comme exeguî a migraçion.",
        "specialloguserlabel": "Açion effettuâ da:",
        "speciallogtitlelabel": "Açion effettuâ sciu (tittolo da paggina ò {{ns:user}}:Nomme utente):",
        "log": "Log",
index 8c5fa41..369465c 100644 (file)
        "passwordreset-emaildisabled": "قابلیت‌های ایمیل در این ویکی غیرفعال شده‌اند.",
        "passwordreset-username": ":نۆم کاربەری",
        "passwordreset-domain": "دامنه:",
-       "passwordreset-capture": "ایمیل نهایی نشان داده شود؟",
-       "passwordreset-capture-help": "اگر این گزینه را علامت بزنید، ایمیل (حاوی گذرواژهٔ موقت) به شما نشان داده خواهد شد و برای کاربر نیز فرستاده خواهد شد.",
        "passwordreset-email": ":نیشانی ایمیل",
        "passwordreset-emailtitle": "جزئیات حساوو أڕ {{SITENAME}}",
        "passwordreset-emailtext-ip": "یک نفر (احتمالاً شما، با نشانی آی‌پی $1) درخواست بازنشانی گذرواژه‌تان در {{SITENAME}} ($4) را کرده‌است. {{PLURAL:$3|حساب|حساب‌های}} کاربری زیر با این آدرس ایمیل مرتبط هستند:\n\n$2\n\n{{PLURAL:$3|این گذرواژهٔ موقت|این گذرواژه‌های موقت}} پس از {{PLURAL:$5|یک روز|$5 روز}} باطل خواهند شد.\nشما باید هم‌اکنون ثبت ورود کنید و گذرواژه‌ای جدید برگزینید. اگر فکر می‌کنید شخص دیگری این درخواست را داده است یا اگر گذرواژهٔ اصلی‌تان را به یاد آوردید و دیگر نمی‌خواهید آن را تغییر دهید، می‌توانید این پیغام را نادیده بگیرید و به استفاده از گذرواژهٔ قبلی‌تان ادامه دهید.",
        "userrights-reason": ":دةلیل",
        "userrights-no-interwiki": "شما اجازهٔ تغییر اختیارات کاربران دیگر ویکی‌ها را ندارید.",
        "userrights-nodatabase": "پایگاه دادهٔ $1 وجود ندارد یا محلی نیست.",
-       "userrights-nologin": "شما باید با یک حساب کاربری مدیر [[Special:UserLogin|به سامانه وارد شوید]] تا بتوانید اختیارات کاربران را تعیین کنید.",
-       "userrights-notallowed": "شما مجوز برای افزودن یا حذف حقوق کاربر را ندارید.",
        "userrights-changeable-col": "گروه‌هایی که می‌توانید تغییر دهید",
        "userrights-unchangeable-col": "گروه‌هایی که نمی‌توانید تغییر دهید",
        "userrights-conflict": "تعارض دسترسی‌های کاربری! لطفاً بررسی کنید و تغییرات را تأیید کنید.",
-       "userrights-removed-self": "شما با موفقیت دسترسی‌های خود را واستاندید. به این ترتیب شما دیگر به این صفحه دسترسی ندارید.",
        "group": "گروه:",
        "group-user": "کاربۀر",
        "group-autoconfirmed": "کاربران تأییدشدهٔ خودکار",
        "right-siteadmin": "قفل‌کردن و بازکردن پایگاه داده‌ها",
        "right-override-export-depth": "برون‌بری صفحه‌ها شامل صفحه‌های پیوند شده تا عمق ۵",
        "right-sendemail": "ارسال ایمیل به دیگر کاربران",
-       "right-passwordreset": "مشاهدهٔ نامه‌های تنظیم مجدد گذرواژه",
        "right-managechangetags": "ایجاد و حذف [[Special:Tags|برچسب‌ها]] از پایگاه داده",
        "right-applychangetags": "تائید [[Special:Tags|برچسب]] بر روی تغییرات یک نفر",
        "right-changetags": "افزودن یا حذف [[Special:Tags|برچسب]] قراردادی بر روی نسخه یا سیاهه ورودی‌ها",
        "importlogpagetext": "درون‌ریزی صفحات به همراه تاریخچهٔ ویرایش آن‌ها از ویکی‌های دیگر.",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|نسخه|نسخه ها}} واردشده",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|نسخه|نسخه ها}} واردشده از $2",
-       "javascripttest": "آزمایش جاوا اسکریپت",
+       "javascripttest": "آزمایش جاوااسکریپت",
        "javascripttest-pagetext-unknownaction": "تابع ناشناختهٔ \"$1\".",
        "javascripttest-qunit-intro": "[$1 مستندات آزمایش] را در mediawiki.org ببینید.",
        "tooltip-pt-userpage": "وةڵگة کاربۀری هؤمۀ",
index d9b5e3a..12b6ad5 100644 (file)
        "action-upload_by_url": "įkelti šią rinkmeną iš URL adreso",
        "action-writeapi": "naudotis rašymo API",
        "action-delete": "ištrinti šį puslapį",
-       "action-deleterevision": "ištrinti šią reviziją",
-       "action-deletedhistory": "žiūrėti šio ištrinto puslapio istoriją",
+       "action-deleterevision": "ištrinti revizijas",
+       "action-deletelogentry": "trinti žurnalo įrašus",
+       "action-deletedhistory": "žiūrėti puslapio ištrintą istoriją",
        "action-browsearchive": "ieškoti ištrintų puslapių",
        "action-undelete": "atkurti šį puslapį",
        "action-suppressrevision": "peržiūrėti ir atkurti šią paslėptą versiją",
        "action-userrights-interwiki": "keisti naudotojų teises kitose wiki svetainėse",
        "action-siteadmin": "užrakinti ar atrakinti duomenų bazę",
        "action-sendemail": "siųsti e-mail laiškus",
+       "action-editmyoptions": "keisti savo nustatymus",
        "action-editmywatchlist": "redaguoti savo stebėjimų sąrašą",
        "action-viewmywatchlist": "rodyti savo stebėjimų sąrašą",
        "action-viewmyprivateinfo": "peržiūrėti jūsų privačią informaciją",
index 0b8c8c4..233b95f 100644 (file)
        "passwordreset-emaildisabled": "Šajā viki ir atspējotas e-pasta iespējas.",
        "passwordreset-username": "Lietotājvārds:",
        "passwordreset-domain": "Domēns:",
-       "passwordreset-capture": "Apskatīt izveidoto e-pastu?",
        "passwordreset-email": "E-pasta adrese:",
        "passwordreset-emailtitle": "Konta informācija {{SITENAME}}",
        "passwordreset-emailelement": "Lietotājvārds: \n$1\n\nPagaidu parole: \n$2",
        "userrights-reason": "Iemesls:",
        "userrights-no-interwiki": "Tev nav atļaujas izmainīt dalībnieku tiesības citos wiki.",
        "userrights-nodatabase": "Datubāze $1 neeksistē vai nav lokāla.",
-       "userrights-nologin": "Tev ir [[Special:UserLogin|jāieiet iekšā]] kā adminam, lai varētu izmainīt dalībnieku grupas.",
-       "userrights-notallowed": "Tev nav atļaujas pievienot vai noņemt dalībnieku tiesības.",
        "userrights-changeable-col": "Grupas, kuras tu vari izmainīt",
        "userrights-unchangeable-col": "Grupas, kuras tu nevari izmainīt",
        "group": "Grupa:",
        "right-userrights-interwiki": "Mainīt dalīnieku tiesības citās Vikipēdijās",
        "right-siteadmin": "Bloķēt un atbloķēt datubāzi",
        "right-sendemail": "Sūtīt e-pastu citiem dalībniekiem",
-       "right-passwordreset": "Apskatīt paroles atiestatīšanas e-pasta ziņojumus",
        "grant-group-email": "Sūtīt e-pastu",
        "grant-createaccount": "Izveidot kontu",
        "grant-editmywatchlist": "Labot uzraugāmo rakstu sarakstu",
        "htmlform-cloner-create": "Pievienot vairāk",
        "htmlform-cloner-delete": "Noņemt",
        "logentry-delete-delete": "$1 {{GENDER:$2|izdzēsa}} lapu $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|izdzēsa}} pāradresāciju $3 pārrakstot",
        "logentry-delete-restore": "$1 {{GENDER:$2|atjaunoja}} lapu $3",
        "revdelete-content-hid": "saturs slēpts",
        "revdelete-summary-hid": "labojuma kopsavilkums slēpts",
        "authmanager-realname-label": "Tavs īstais vārds",
        "authmanager-realname-help": "Dalībnieka īstais vārds",
        "authprovider-resetpass-skip-label": "Izlaist",
-       "specialpage-securitylevel-not-allowed-title": "Nav atļauts",
-       "edit-error-short": "Kļūda: $1",
-       "edit-error-long": "Kļūdas:\n\n$1"
+       "specialpage-securitylevel-not-allowed-title": "Nav atļauts"
 }
index a0634ba..5812742 100644 (file)
        "search-external": "Надворешно пребарување",
        "searchdisabled": "{{SITENAME}} пребарувањето е оневозможено.\nВо меѓувреме, можете да пребарувате преку Google.\nДа напоменеме дека нивното индексирање на {{SITENAME}} содржините може да биде застарено.",
        "search-error": "Се појави грешка при пребарувањето: $1",
+       "search-warning": "Се појави предупредување при пребарувањето: $1",
        "preferences": "Нагодувања",
        "mypreferences": "нагодувања",
        "prefs-edits": "Број на уредувања:",
        "userrights-user-editname": "Внесете корисничко име:",
        "editusergroup": "Вчитај кориснички групи",
        "editinguser": "Менување на правата на {{GENDER:$1|корисникот}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Поглед на правата на {{GENDER:$1|корисникот}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Уреди ги корисничките групи",
+       "userrights-viewusergroup": "Преглед на корисничките групи",
        "saveusergroups": "Зачувај ги {{GENDER:$1|корисничките}} групи",
        "userrights-groupsmember": "Член на:",
        "userrights-groupsmember-auto": "Подразбран член на:",
        "action-upload_by_url": "подигни ја податотекава од URL-адреса",
        "action-writeapi": "употребете запишување во извршникот",
        "action-delete": "избриши ја страницава",
-       "action-deleterevision": "избриши ја ревизијава",
-       "action-deletedhistory": "прегледај ја историјата на бришења за оваа страница",
+       "action-deleterevision": "бришење преработки",
+       "action-deletelogentry": "бришење на дневнички записи",
+       "action-deletedhistory": "преглед на историјата на бришења на оваа страница",
+       "action-deletedtext": "преглед на текст на избришани преработки",
        "action-browsearchive": "барање на избришани страници",
-       "action-undelete": "обнови Ñ\98а Ñ\81Ñ\82Ñ\80аниÑ\86ава",
-       "action-suppressrevision": "прегледај ја и обнови ја оваа скриена преработка",
+       "action-undelete": "обнова Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86и",
+       "action-suppressrevision": "преглед ја и обнова на скриени преработки",
        "action-suppressionlog": "преглед на овој li;en дневник",
        "action-block": "оневозможување на уредувањето на корисников",
        "action-protect": "измени го степенот на заштита на оваа страница",
        "action-userrights-interwiki": "уредување на кориснички права на корисници на други викија",
        "action-siteadmin": "заклучување или отклучување на базата на податоци",
        "action-sendemail": "испраќање на е-пошта",
+       "action-editmyoptions": "уредување на вашите нагодувања",
        "action-editmywatchlist": "уредување на мои набљудувани",
        "action-viewmywatchlist": "преглед на вашиот список на набљудувања",
        "action-viewmyprivateinfo": "преглед на вашите лични податоци",
        "emailccsubject": "Копија од вашата порака до $1: $2",
        "emailsent": "Писмото е испратено",
        "emailsenttext": "Писмото е испратено.",
-       "emailuserfooter": "$1 го испрати писмово на {{GENDER:$2|$2}} со помош на функцијата „{{int:emailuser}}“ на {{SITENAME}}.",
+       "emailuserfooter": "$1 го испрати писмово на {{GENDER:$2|$2}} со помош на функцијата „{{int:emailuser}}“ на {{SITENAME}}. {{GENDER:$2|Вашата}} е-пошта ќе му биде испратена право на {{GENDER:$1|изворниот испраќач}}, откривајќи {{GENDER:$1|му}} ја {{GENDER:$2|вашата}} адреса.",
        "usermessage-summary": "Оставете системска порака.",
        "usermessage-editor": "Системски гласник",
        "usermessage-template": "MediaWiki:КорисникПорака",
        "wlshowhidemine": "моите уредувања",
        "wlshowhidecategorization": "категоризација на страници",
        "watchlist-options": "Поставки за список на набљудувања",
+       "watchlist-mark-all-visited": "Дали сигурно сакате ги отстраниѕр невидените промени во набљудуваните, означувајќи ги сите страници како посетени?",
        "watching": "Набљудување...",
        "unwatching": "Отстранувам од набљудувани...",
        "watcherrortext": "Се појави грешка при менувањето на вашите нагодувања набљудуваните за „$1“.",
        "mw-widgets-dateinput-no-date": "Немате одбрано датум",
        "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
+       "mw-widgets-mediasearch-input-placeholder": "Пребарајте слики/снимки",
+       "mw-widgets-mediasearch-noresults": "Не пронајдов ништо.",
        "mw-widgets-titleinput-description-new-page": "страницата сè уште не постои",
        "mw-widgets-titleinput-description-redirect": "пренасочување кон $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Додај категорија...",
        "usercssispublic": "Напомена: потстраниците со CSS не треба да содржат дсоверливи податоци бидејќи истите се видливи и за други корисници.",
        "restrictionsfield-badip": "Неважечки IP-дијапазон на адреси: $1",
        "restrictionsfield-label": "Допуштени IP-опсези:",
-       "restrictionsfield-help": "Една IP-адреса или CIDR-опсег по ред. За да овозможите сè, користете<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Една IP-адреса или CIDR-опсег по ред. За да овозможите сè, користете<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "revid": "п$1",
+       "pageid": "назнака на страницата $1"
 }
index ddc46f0..1596eb6 100644 (file)
        "namespaces": "नामविश्वे",
        "variants": "चले(व्हेरियंट्स)",
        "navigation-heading": "दिक्चालन यादी",
-       "errorpagetitle": "à¤\9aà¥\82à¤\95",
+       "errorpagetitle": "तà¥\8dरà¥\81à¤\9fà¥\80",
        "returnto": "$1 कडे परत चला.",
        "tagline": "{{SITENAME}} कडून",
        "help": "साहाय्य",
        "passwordreset-emaildisabled": "या विकिवर विपत्र पाठविणे 'अशक्य' करण्यात आलेले आहे.",
        "passwordreset-username": "सदस्यनाव:",
        "passwordreset-domain": "डोमेन",
-       "passwordreset-capture": "ईमेल कशी असेल ते बघायचेय ?",
-       "passwordreset-capture-help": "या चौकटीत खूण केली तर, ईमेल (तात्पुरत्या परवलीच्या शब्दासह) दाखविण्यात व सदस्यास पाठविण्यात येईल.",
        "passwordreset-email": "विपत्र पत्ता",
        "passwordreset-emailtitle": "{{SITENAME}} वरील खात्याची माहिती",
        "passwordreset-emailtext-ip": "कुणीतरी (कदाचित तुम्ही, अंकपत्ता $1 वरुन) {{SITENAME}}($4) करिता नविन 'परवलीचा शब्द' पुनर्स्थापनेबद्दल विनंती केली आहे.\nखालील{{PLURAL:$3|सदस्यखाते}}या विपत्रपत्त्याशी निगडीत आहे: \n\"$2\"\n{{PLURAL:$3|हा तात्पुरता परवलीचा शब्द|हे तात्पुरते परवलीचे शब्द}}{{PLURAL:$5|एक दिवस|$5 दिवसात}} मुदतबाह्य होतील.आता आपण लॉग-ईन करून  नविन परवलीचा शब्द निवडा.जर ईतर कोणी ही विनंती केली असेल,किंवा जर आपणास परवलीच शब्द आठवला असेल तर,व जर आपण तो बदलु इच्छित नसाल तर आपण हा संदेश टाळा व आपला जुना परवलीचा शब्द वापरणे सुरू ठेवा.",
        "userrights-reason": "कारण:",
        "userrights-no-interwiki": "इतर विकींवरचे सदस्य अधिकार बदलण्याची परवानगी तुम्हाला नाही.",
        "userrights-nodatabase": "विदा $1 अस्तित्वात नाही अथवा स्थानिक नाही.",
-       "userrights-nologin": "सदस्य अधिकार देण्यासाठी तुम्ही प्रबंधक म्हणून [[Special:UserLogin|सनोंद प्रवेशित]] असणे आवश्यक आहे.",
-       "userrights-notallowed": "तुमच्या सदस्य खात्यास, सदस्य अधिकारांची निश्चिती करण्याची परवानगी नाही.",
        "userrights-changeable-col": "गट जे तुम्ही बदलू शकता",
        "userrights-unchangeable-col": "गट जे तुम्ही बदलू शकत नाही",
        "userrights-conflict": "बदलाबाबत सदस्य-हक्क विसंवाद !कृपया आपले बदल पुन्हा पुनरावलोकित व नक्की करा.",
-       "userrights-removed-self": "आपण आपले हक्क यशस्वीरित्या काढलेत.म्हणुन, या पानात आपण दाखल होऊ शकणार नाही.",
        "group": "गट:",
        "group-user": "सदस्य",
        "group-autoconfirmed": "स्वयंशाबीत सदस्य",
        "right-siteadmin": "माहितीसाठ्याला कुलूप लावा अथवा काढा",
        "right-override-export-depth": "जोडलेल्या पानांचा पाचव्या पातळीपर्यंत अंतर्भाव करुन पाने निर्यात करा",
        "right-sendemail": "इतर सदस्यांना विपत्रे पाठवा",
-       "right-passwordreset": "परवलीचा शब्द पुनर्स्थापित केल्याचे विपत्र पहा.",
        "right-managechangetags": "डाटाबेस मधून [[Special:Tags|खूणपताका]] तयार करा किंवा  वगळा",
        "right-applychangetags": "कोणाच्याही बदलास [[Special:Tags|खूणपताका]] जोडा",
        "right-changetags": "वैयक्तिक आवृत्त्यांना व नोंद प्रवेष्ट्यांना, आहेतुक(arbitrary) [[Special:Tags|खूणपताका]] जोडा अथवा हटवा",
        "listgrants": "अनुदाने",
        "listgrants-grant": "अनुदान",
        "listgrants-rights": "अधिकार",
-       "trackingcategories": "वरà¥\8dà¤\97 à¤¶à¥\8bधत à¤\86हà¥\8bत",
+       "trackingcategories": "माà¤\97à¥\8bवा à¤\98à¥\87णारà¥\87 à¤µà¤°à¥\8dà¤\97",
        "trackingcategories-summary": "या पानात ते रेखापथनातील वर्ग(tracking categories) आहेत, जे, मिडियाविकि संचेतनाद्वारे स्वयंचलितरित्या वसविण्यात (तयार करण्यात) आले आहेत. त्यांची नावे, {{ns:8}} नामविश्वातील संबंधित प्रणाली संदेशात फेरफार करुन, बदलविता येतात.",
+       "trackingcategories-msg": "मागोवा घेणारा वर्ग",
        "trackingcategories-name": "संदेश नाम",
        "trackingcategories-desc": "वर्ग अंतर्भूत करण्याचे निकष",
        "trackingcategories-nodesc": "वर्णन उपलब्ध नाही.",
        "randomrootpage": "अविशिष्ट मूळ पान",
        "log-action-filter-suppress-block": "रोधामार्फत सदस्य दाबणे",
        "changecredentials": "अधिकारपत्रे (क्रेडेंटियल्स)बदला",
-       "removecredentials": "अधिकारपत्रे (क्रेडेंटियल्स) हटवा",
-       "edit-error-short": "त्रुटी: $1",
-       "edit-error-long": "त्रुटी:$1"
+       "removecredentials": "अधिकारपत्रे (क्रेडेंटियल्स) हटवा"
 }
index 10bdd34..6cb3e84 100644 (file)
@@ -19,7 +19,7 @@
        "tog-hideminor": "Motlàtìs tepỉtzìn tlayèktlàlilistli ìpan welok tlapảtlalistli",
        "tog-hidepatrolled": "Motlàtìs tlapîpialli tlayèktlàlilistli ìpan welok tlapảtlalistli",
        "tog-newpageshidepatrolled": "Mokintlàtis tlapîpialtlaìxtlapaltìn ìwikpa ìtlapòpòwaltekpànal in yâyankuik tlaìxtlapaltìn",
-       "tog-extendwatchlist": "Mìxmảnas in tlapòpòwaltekpàntlachialli ìka mỏtas nochi in tlapảtlalistli, âmò in san okachi yankuik.",
+       "tog-extendwatchlist": "Mixmanaz in tlapopohualtecpantlachialli ica mottaz nochi in tlapatlaliztli, ahmo in zan ocachi yancuic.",
        "tog-usenewrc": "Molōloāzqueh in tlapatlaliztli in yancuīc tlapatlaliztli āmapan īhuān in tlachiyaliztli tlapōhualāmapan (monequi JavaScript)",
        "tog-showtoolbar": "Motlaīxtlatīz in tlachihchīhualōni pāntli",
        "tog-editondblclick": "Tiquimpatlāz in zāzanilli intlā ōme tiquimpachoa",
@@ -31,7 +31,7 @@
        "tog-previewontop": "Tiquittāz achtochīhualiztli achtopa tlapatlaliztli caxitl",
        "tog-previewonfirst": "Xiquitta achtochīhualiztli inic cē tlapatlalizpan",
        "tog-enotifwatchlistpages": "Notech moēhualtia cē maltzinteyōtl netitlaniztli ihcuāc mopatla zāzanilli in notlachiyaliz.",
-       "tog-enotifusertalkpages": "Nēchihtoa ihcuāc tlecpatla motēixnāmiquiliz",
+       "tog-enotifusertalkpages": "Ma annechihtoa ica ce tetitlaniliztli ihcuāc mopatla noteixnamiquiliz",
        "tog-enotifminoredits": "Notech moēhualtia cē maltzinteyōtl netitlaniztli nō ihcuāc mopatla tepitōn zāzanilli in notlachiyaliz.",
        "tog-enotifrevealaddr": "Ticnēxtīz mo e-mailcān āxcāncayōtechcopa āmatlacuilizpan",
        "tog-shownumberswatching": "Tiquinttāz tlatequitiltilīlli tlein tlachiyacateh",
@@ -48,9 +48,9 @@
        "tog-showhiddencats": "Mà monèxtìkàn in tlatlatìltìn tlaìxmatkàtlàlilòmë",
        "underline-always": "Mochipa",
        "underline-never": "Aīc",
-       "editfont-monospace": "Cencoyāhualiztli machiyōtlahtōliztli",
-       "editfont-sansserif": "Sans-serif machiyōtlahtōliztli",
-       "editfont-serif": "Serif machiyōtlahtōliztli",
+       "editfont-monospace": "Cencoyahualiztli machiyotlahtoliztli",
+       "editfont-sansserif": "Sans-serif machiyotlahtoliztli",
+       "editfont-serif": "Serif machiyotlahtoliztli",
        "sunday": "Īccemilhuitl",
        "monday": "Īcōmilhuitl",
        "tuesday": "Icyeyilhuitl",
        "sun": "Cemilhui",
        "mon": "Ōmilhui",
        "tue": "Ēyilhui",
-       "wed": "Nāuhilhui",
-       "thu": "Mācuililhui",
+       "wed": "Nahuilhui",
+       "thu": "Macuililhui",
        "fri": "6 ilhui",
        "sat": "7 ilhui",
        "january": "Icce metztli",
        "february": "Icome metztli",
-       "march": "3 Metz",
-       "april": "Ic nāuhtetl mētztli",
-       "may_long": "Ic mācuīlli mētztli",
-       "june": "Ic chicuacē mētztli",
-       "july": "7 Metz",
-       "august": "8 Metz",
-       "september": "9 Metz",
-       "october": "10 Metz",
+       "march": "Ic yetetl metztli",
+       "april": "Ic nauhtetl metztli",
+       "may_long": "Ic macuiltetl metztli",
+       "june": "Ic chicuacentetl metztli",
+       "july": "Ic chicontetl metztli",
+       "august": "Ic chicuetetl metztli",
+       "september": "Ic chiucnahtetl metztli",
+       "october": "Ic mahtlactetl metztli",
        "november": "11 Metz",
        "december": "12 Metz",
        "january-gen": "Ic cē mētztli",
        "pagecategories": "{{PLURAL:$1|Neneuhcayotl|Neneuhcayomeh}}",
        "category_header": "Tlahcuiloltin ipan neneuhcayotl \"$1\"",
        "subcategories": "Tlani-neneuhcayotl",
-       "category-media-header": "Media \"$1\" neneuhcāyōc",
-       "category-empty": "''Cah ahtlein inīn neneuhcāyōc.''",
+       "category-media-header": "Media \"$1\" ipan neneuhcayotl",
+       "category-empty": "''Ahtle oncah ipan neneuhcayotl.''",
        "hidden-categories": "{{PLURAL:$1|tlatlatilli neneuhcayotl|tlatlatiltin neneuhcayomeh}}",
        "hidden-category-category": "Tlatlàtìlkàtlaìxmatkàtlàlilòmë",
-       "category-subcat-count": "{{PLURAL:$2|Inin neneuhcayotl zan quipiya in tetoquilli tlani-neneuhcayotl.|Inn neneuhcayotl {{PLURAL:$1|quipiya intetoquilli tlani-neneuhcayotl|in tetoquiltin $1 tlani-neneuhcayomeh}}, itech tlacecempohualoni $2.}}",
-       "category-subcat-count-limited": "Inīn {{PLURAL:$1|neneuhcāyōtzintli cah|$1 neneuhcāyōtzintli cateh}} inīn neneuhcāyōc.",
+       "category-subcat-count": "{{PLURAL:$2|Inin neneuhcayotl zan quipiya in tetoquilli tlani-neneuhcayotl.|Inin neneuhcayotl {{PLURAL:$1|quipiya in tetoquilli tlani-neneuhcayotl|in tetoquiltin $1 tlani-neneuhcayomeh}}, itech tlacecempohualoni $2.}}",
+       "category-subcat-count-limited": "Inin {{PLURAL:$1|neneuhcayotl quipiya|$1 in tetoquilli tlani-neneuhcayotl|in tetoquiltin tlani-neneuhcayomeh}}.",
        "category-article-count": "{{PLURAL:$2|Inin neneuhcayotl zan quipiya in tetoquilli tlahcuilolli.|{{PLURAL:$1|In tetoquilli tlahcuilolli itech pohui|In tetoquiltin $1 tlahcuiloltin itech pohui}}, inin neneuhcayotl itech tlacecempohualoni ipan $2.}}",
        "category-article-count-limited": "Inīn {{PLURAL:$1|zāzanilli cah|$1 zāzanilli cateh}} inīn neneuhcāyōc.",
        "category-file-count": "{{PLURAL:$2|Inìn tlaìxmatkàyòtlàlilòtl san kipia|Inìn tlaìxmatkàyòtlalilòtl kimpia {{PLURAL:$1|inìn èwalli|inîke $1 èwaltìn}}, ìwikpa $2.}}",
        "category-file-count-limited": "{{PLURAL:$1|Inìn tlâkuilòlèwalli kä|Inîkë $1 tlâkuilòlèwaltìn katêkë}} ìpan inìn tlaìxmatkàtlàlilòtl.",
        "listingcontinuesabbrev": "niman",
-       "about": "Ītechcopa",
-       "article": "Tlâkuilòpilli",
-       "newwindow": "(Motlapoāz cē yancuīc tlanexillōtl)",
+       "about": "Itechcopa",
+       "article": "Tlahcuilolamatl",
+       "newwindow": "(Motlapoaz ce yancuic tlanexillotl)",
        "cancel": "Moxitiniz",
        "moredotdotdot": "Huehca ōmpa...",
        "mypage": "Noāmauh",
        "mytalk": "Teixnamiquiliztli",
-       "anontalk": "Tēixnāmiquiliztli",
-       "navigation": "Nēnemōhualiztli",
+       "anontalk": "Teixnamiquiliztli",
+       "navigation": "Panoliztli",
        "and": "&#32;ihuan",
-       "qbfind": "Xicahci",
+       "qbfind": "Tlatemoliztli",
        "qbbrowse": "Xitlatepotztoca",
        "qbedit": "Xicpatla",
        "qbpageoptions": "Inīn tlaīxtli",
        "faqpage": "Project:FAQ",
        "actions": "Āyiliztli",
        "namespaces": "Tocatlacauhtli",
-       "variants": "Nepāpan",
+       "variants": "Nepapan",
        "navigation-heading": "Nemiliztlahtolpohualamatl",
        "errorpagetitle": "Aiuhcāyōtl",
        "returnto": "Ximocuepa īhuīc $1.",
        "talkpagelinktext": "Teixnamiquiliztli",
        "specialpage": "Nònkuâkìskàtlaìxtlapalli",
        "personaltools": "In tlein nitēquitiltilia",
-       "articlepage": "Xiquitta in tlamantlaīxtli",
+       "articlepage": "Xiquitta tlahcuilolamatl",
        "talk": "Teixnamiquiliztli",
        "views": "Tlachiyaliztli",
        "toolbox": "Tequitihualoni",
        "userpage": "Xiquitta tlatequitiltilīlli zāzanilli",
        "projectpage": "Xiquitta tlachīhualiztli zāzanilli",
-       "imagepage": "Xiquitta in zāzanilli īāma",
-       "mediawikipage": "Xiquitta tlahcuilōltzin zāzanilli",
-       "templatepage": "Xiquitta neīxcuītīllaīxtli",
+       "imagepage": "Tiquittaz in tlahcuilolamatl itecpanaliztlapiyaliz",
+       "mediawikipage": "Xiquitta tetitlaniliztli itlahcuilolamauh",
+       "templatepage": "Xiquitta tlahcuilolamatl ineixcuitil",
        "viewhelppage": "Xiquitta tēpalēhuiliztli zāzanilli",
-       "categorypage": "Mà mỏta in tlaìxmatkàyòtlàlilòtlaìxtlapalli",
+       "categorypage": "Tiquittaz neneuhcayotl itlahcuilolamauh",
        "viewtalkpage": "Xiquitta tēixnāmiquiliztli zāzanilli",
-       "otherlanguages": "Occequīntīn tlahtōlcopa",
+       "otherlanguages": "Occequintin tlahtlahtolcopa",
        "redirectedfrom": "(Ōmotlacuep īhuīcpa $1)",
        "redirectpagesub": "Ōmotlacuep zāzanilli",
        "lastmodifiedat": "Inin tlahcuilolli omopatlac immanin $1, ipan $2.",
        "viewcount": "Inīn zāzanilli quintlapōhua {{PLURAL:$1|cē tlahpololiztli|$1 tlahpololiztli}}.",
        "protectedpage": "Ōmoquīxtix zāzanilli",
        "jumpto": "Īhuīcpa ticholōz:",
-       "jumptonavigation": "nēnemōhualiztli",
+       "jumptonavigation": "amapanoliztli",
        "jumptosearch": "Tlatemoliztli",
        "aboutsite": "Itechcopa {{SITENAME}}",
        "aboutpage": "Project:Itechcopa",
-       "copyright": "In tlahcuilōlli cah tlacēcencāhuani īpan $1 tel ahmo intlā īcuepca motēnēhua.",
+       "copyright": "In tlahcuilolpiyaliztli cah tlani tlacecencahuani $1 tel ahmo icuepca motenehua.",
        "copyrightpage": "{{ns:project}}:Tlachīhualōni ītlapiyaliz",
        "currentevents": "Axcancayotl",
        "currentevents-url": "Project:Axcancayotl",
        "site-atom-feed": "$1 Atom huelītiliztli",
        "page-rss-feed": "\"$1\" RSS huelītiliztli",
        "page-atom-feed": "\"$1\" RSS huelitiliztli",
-       "red-link-title": "$1 (ahmo oncah tlahcuilolli)",
-       "nstab-main": "Tlahcuilolli-amatl",
-       "nstab-user": "Tlatequitiltilīlli",
+       "red-link-title": "$1 (ahmo oncah tlahcuilolamatl)",
+       "nstab-main": "Tlahcuilolamatl",
+       "nstab-user": "Tequitiuhqui itlahcuilolamauh",
        "nstab-media": "Mēdiatl",
        "nstab-special": "Noncuahquizqui tlahcuilolli",
        "nstab-project": "Ìtlaìxtlapal in tlayẻkàntekitl",
        "filerenameerror": "Ahmō ōmohuelītic tlazaca \"$1\" īhuīc \"$2\".",
        "filedeleteerror": "Ahmō ōmohuelītic tlapoloa \"$1\".",
        "filenotfound": "Ahmō ōmohuelītic tlanāmiqui \"$1\".",
-       "cannotdelete": "Ahmō ōhuelītic mopoloa in zāzanilli \"$1\".\nHueli tlein āquin ōquipolo achtopa.",
-       "badtitle": "Ahcualli tōcāitl",
+       "cannotdelete": "Ahmo omopoloh in tlahcuilolamatl \"$1\".\nHueli tlein occe tequitiuhqui oquipoloh achtopa.",
+       "badtitle": "Ahcualli tocaitl",
        "badtitletext": "Zāzanilli ticnequi in ītōca cah ahcualli, ahtlein quipiya nozo ahcualtzonhuiliztli interwiki tōcāhuicpa.\nHueliz quimpiya tlahtōl tlein ahmo mohuelītih motequitiltia tōcāpan.",
-       "viewsource": "Xiquitta meyalli",
+       "viewsource": "Tiquittaz itzintiliz",
        "viewsource-title": "Xiquitta in $1 īmēyal",
        "actionthrottled": "Tlachīhualiztli ōmotzacuili",
        "viewsourcetext": "Tihuelīti tiquittaz auh ticcopīnaz inīn zāzanilli īmachiyōnecaquilizmēyal.",
        "virus-unknownscanner": "ahmatic antivirus:",
        "welcomeuser": "Ximopanōlti, $1!",
        "yourname": "Tequihuihcātōcāitl:",
-       "userlogin-yourname": "Tequihuihcātōcāitl",
+       "userlogin-yourname": "Tequitiuhcatocaitl",
        "userlogin-yourname-ph": "Xiquihcuilo motoca iuhqui tequitihuani",
        "yourpassword": "Motlahtōlichtacāyo",
        "userlogin-yourpassword": "Ichtacamachiyotl",
        "yourdomainname": "Moāxcāyō",
        "login": "Xicalaqui",
        "nav-login-createaccount": "Ximocalaqui / ximomachiyōmaca",
-       "userlogin": "Ximomachiyōmaca/Ximocalaqui",
+       "userlogin": "Ximomachiyomaca/Ximocalaqui",
        "userloginnocreate": "Ximocalaqui",
        "logout": "Xiquīza",
        "userlogout": "Xiquīza",
        "gotaccountlink": "Ximocalaqui",
        "createacct-email-ph": "xiquihcuilo mocorreo electrónico",
        "createaccountmail": "Ticnemītīz ahmo cemihcac zāzoichtacātlahtōlli nō in tiquēhualtīz in maltzinteyōtl monetitlanizyeyān",
-       "createaccountreason": "Tleīpampa:",
+       "createaccountreason": "Tleipampa:",
        "createacct-reason": "Tleīpampa",
        "createacct-submit": "Xicchīhua in motlapōhual",
        "badretype": "Ahneneuhqui motlahtōlichtacāyo.",
        "noname": "Ahmo ōtiquihto cualli tlatequitiltilīlli tōcāitl.",
        "loginsuccesstitle": "Ōticalac",
        "loginsuccess": "'''Ōticalac {{SITENAME}} quemeh \"$1\".'''",
-       "nosuchuser": "Ayāc tlatequitiltilīlli motōcāitīlo «$1».\nn tlatequitiltilīltōcāitl quimati in huēyimachiyōtlahtōliztli.\nXiquitta moyēquihcuilōl, ahnozo [[Special:CreateAccount|xicchīhua yancuīc cuentah]].",
+       "nosuchuser": "Ahmo oncah tequitiuhqui tlen quipiya tocaitl «$1».\nIn tequitiuhqueh intoca monequi pehua ic hueyimachiyotlahtoliztli.\nXiquitta moyequihcuilol, ahnozo [[Special:CreateAccount|xicchihua yancuic cuentah]].",
        "nosuchusershort": "Ayāc tlatequitiltilīlli motōcāitia \"$1\". Xiquitta in tlein ōtitlahcuiloh melāhuacā cah.\nXiquitta moyēquihcuilōl.",
        "nouserspecified": "Mohuīquilia tiquihtoa cualli tlatequitiltilīltōcāitl.",
        "wrongpassword": "Ahcualli motlahtōlichtacāyo.\nTimitztlātlauhtia xicchīhua occeppa.",
        "changepassword-success": "Moichtacātlahtōl ōmopatlac.",
        "resetpass_forbidden": "Tlahtōlichtacayōtl ahmo mohuelītih mopatlah",
        "resetpass-submit-loggedin": "Ticpatlāz motlahtōlichtacāyo",
-       "resetpass-submit-cancel": "Xiccāhua",
+       "resetpass-submit-cancel": "Xiccahua",
        "passwordreset-username": "Tequihuihcātōcāitl:",
-       "bold_sample": "Tlīltic tlahcuilōlli",
+       "bold_sample": "Tliltic tlahcuilolpiyaliz",
        "bold_tip": "Tlīltic tlahcuilōlli",
-       "italic_sample": "Cōliuhqui tlahcuilōliztli",
-       "italic_tip": "Cōliuhqui tlahcuilōliztli",
+       "italic_sample": "Nacacic tlahcuiloliztli",
+       "italic_tip": "Nacacic tlahcuiloliztli",
        "link_sample": "Tzonhuiliztli ītōcā",
        "link_tip": "Tlahtic tzonhuiliztli",
        "extlink_sample": "http://www.machiyōtl.com Tzonhuiliztōcāitl",
        "extlink_tip": "Calān tzonhuiliztli (xiquilnamiqui ticaquiāz in http://)",
        "headline_sample": "Cuātlahcuilōlli",
        "headline_tip": "Iuhcāyōtl 2 tōcāyōtl",
-       "image_sample": "Machiyōtl.jpg",
-       "media_sample": "Machiyōtl.ogg",
+       "image_sample": "Machiyotl.jpg",
+       "media_sample": "Machiyotl.ogg",
        "media_tip": "Mēdiahuīc tzonhuiliztli",
        "sig_tip": "Motōcā īca cāhuitl",
        "hr_tip": "Pāntli",
        "summary": "Mopatlaliz:",
-       "subject": "Ītechpa:",
-       "minoredit": "Ca tepitōn inīn tlapatlaliztli",
+       "subject": "Itechpa:",
+       "minoredit": "Ca tepiton inin tlapatlaliztli",
        "watchthis": "Tictlachiyaz inin tlahcuilolli",
        "savearticle": "Xicpiya tlahcuilolli",
        "preview": "Xiquitta achtochīhualiztli",
-       "showpreview": "Xiquitta achtochīhualiztli",
+       "showpreview": "Xiquitta achtochihualiztli",
        "showdiff": "Monextiz tlapatlaliztli",
        "missingcommenttext": "Timitztlātlauhtiah xitlanitlahcuiloa.",
        "summary-preview": "Tlahcuilōltōn achtochīhualiztli:",
        "blockedtitle": "Ōmotzacuili tlatequitiltilīlli",
        "blockednoreason": "ahmo cah īxtlamatiliztli",
        "whitelistedittext": "Tihuīquilia $1 ic ticpatla zāzaniltin.",
-       "nosuchsectiontitle": "In xeliuhcāyōtl ahmo ōquināmic",
+       "nosuchsectiontitle": "In xeliuhcayotl ahmo oquinamic",
        "loginreqtitle": "Ximocalaqui",
        "loginreqlink": "ximocalaqui",
        "loginreqpagetext": "Tihuīquilia $1 ic tiquintta occequīntīn zāzaniltin.",
        "accmailtitle": "Tlahtōlichtacāyōtl ōmoihuah.",
-       "accmailtext": "Ōquiyōcox zāzochtacātlahtōlli in [[User talk:$1|$1]] auh ōmoquitītlan īhuīc $2. Tihueliti ticpatlaz īpan ''[[Special:ChangePassword|Ticpatlaz in ]]'' in ōticalaco achtopa.",
+       "accmailtext": "Oquiyocox ce ichtacatlahtolli ipan [[User talk:$1|$1]] auh omoquititlan ihuic $2. Hueli ticpatlazquia ipan ''[[Special:ChangePassword|Ticpatlaz in ]]'' niman oticalaco achtopa.",
        "newarticle": "(Yancuic)",
        "newarticletext": "Ōtictocac cētiliztli cē zāzanilhuīc oc ahmo ia. Intlā quiēlēhuia quichīhua, xitlahcuiloa niman (nō xiquitta [$1 tēpalēhuiliztli zāzanilli] huehca ōmpa tlapatlaliztli). Intlā ahmo, yāuh achtopa zāzanilli.",
        "noarticletext": "In axcan, ahmo oncah tlahcuilolli ipan inin amatl.\nTihueliti [[Special:Search/{{PAGENAME}}|tictemoz inin tlahcuilolli itoca]] occequintin tlahcuilolli,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} xictemoa ipan in occe tlahcuilolmachiyotl],\nahnozo [{{fullurl:{{FULLPAGENAME}}|action=edit}} xichihua inin tlahcuilolli]</span>.",
        "editing": "Ticpatla $1",
        "creating": "Ticchīhua $1",
        "editingsection": "Ticpatlacah $1 (tlahtōltzintli)",
-       "editingcomment": "Ticpatlacah $1 (tlahtōltzintli)",
+       "editingcomment": "tlapatlaliztli itech $1 (yancuic xeliuhcayotl)",
        "editconflict": "Tlapatlaliztli yāōyōtōn: $1",
-       "yourtext": "Motlahcuilōl",
+       "yourtext": "Motlahcuilolpiyaliz",
        "yourdiff": "Ahneneuhquiliztli",
-       "copyrightwarning": "<small>Timitztlātlauhtiah xiquitta mochi mopatlaliz cana {{SITENAME}} tlatzayāna īpan $2 (huēhca ōmpa xiquitta $1). Āqueh tlācah quipatlazqueh in motlahcuilōl auh tlatzayāna occeppa; intlā ahmō ticnequi, zātēpan ahmō titlahcuilōz nicān. Nō mitzihtoah in ōtitlahcuiloh ahmō quipiya in copyright nozo in yōllōxoxouhqui tlahcuilōlli. '''¡AHMŌ XITĒQUITILTIA AHYŌLLŌXOXOUHQUI TLAHCUILŌLLI!'''</small>",
+       "copyrightwarning": "<small>Timitztlatlauhtiah ximomati mochi ipatlaliz ihuicpa {{SITENAME}} tiquittah iuhqui tetlanextililiztli tlanipa $2 (xiquitta $1 itech oc tlahtolmelahualiztli). Intlacamo tiquelehuia occequintin tlacah quipatlazqueh motlahcuilol ihuan quipanitlazazqueh, macamo xitlahcuiloz nican. No titechcemihtoa in tlein otiquihcuiloh ahmo quipiya in copyright ihuan itech pohui in yolloxoxouhqui tlahcuilolli. '''¡AHMO XICTEQUITILTIA AHYOLLOXOXOUHQUI TLAHCUILOLLI!'''</small>",
        "copyrightwarning2": "<small>Āqueh tlācah quipatlazqueh in motlahcuilōl auh tlatzayāna occeppa; intlā ahmō ticnequi, zātēpan ahmō titlahcuilōz nicān {{SITENAME}}. Nō mitzihtoah in ōtitlahcuiloh ahmō quipiya in copyright nozo in yōllōxoxouhqui tlahcuilōlli (huēhca ōmpa xiquitta $1). '''¡AHMŌ TIQUINTEQUITILTIA AHYŌLLŌXOXOUHQUI TLAHCUILŌLLI!'''</small>",
        "longpageerror": "'''Aiuhcāyōtl: In tlahcuilōlli tlein ōtiquihuah tlatamachīhua {{PLURAL:$1|cē kilobyte|$1 kilobyte}}, tzonēhua {{PLURAL:$2|cē kilobyte|$2 kilobyte}}. Ahmo mopiyāz.'''",
-       "templatesused": "{{PLURAL:$1|Nemachiòtl tlèn motekìuhtia|Nemachiòmë tlèn mokìntekìuhtiä}} ìpan inìn tlaìxtlapalli:",
-       "templatesusedpreview": "{{PLURAL:$1|Nemachiòtl tlèn motekìuhtia|Nemachiòmë tlèn mokìntekìuhtiä}} ìpan inìn achtochìwalistli:",
-       "templatesusedsection": "{{PLURAL:$1|Nemachiòtl tlèn motekìuhtia|Nemachiòmë tlèn mokìntekìuhtiä}} ìpan inìn tlaxélòlistli:",
+       "templatesused": "{{PLURAL:$1|Nemachiotl tlen motequiuhtia|Nemachiomeh tlen moquintequiuhtiah}} ipan inin tlahcuilolamatl:",
+       "templatesusedpreview": "{{PLURAL:$1|Nemachiotl tlen motequiuhtia|Nemachiomeh tlen moquintequiuhtiah}} ipan inin achtochihualiztli:",
+       "templatesusedsection": "{{PLURAL:$1|Nemachiotl tlen motequiuhtia|Nemachiomeh tlen moquintequiuhtiah}} ipan inin tlaxeloliztli:",
        "template-protected": "(ōmoquīxti)",
        "hiddencategories": "Inin tlahcuilolli pohui {{PLURAL:$1|1 tlatlalilli neneuhcayotl|$1 tlatlaliltin neneuhcayomeh}}:",
        "nocreatetext": "Inīn huiqui ōquitzacuili tlahuelītiliztli ic tlachīhua yancuīc zāzaniltin. Tichuelīti ticcuepa auh ticpatla cē zāzanilli, [[Special:UserLogin|xicalaqui nozo xicchīhua cē cuentah]].",
        "permissionserrors": "Tēmācāhualiztli aiuhcāyōtl",
        "permissionserrorstext": "Ahmo tihuelīti quichīhua inōn, inīn {{PLURAL:$1|īxtlamatilizpampa}}:",
        "permissionserrorstext-withaction": "Ahmo tiquihuelīti $2 inīn {{PLURAL:$1|īxtlamatilizpampa}}:",
-       "moveddeleted-notice": "Inīn zāzanilli ōmopolo.\nIn tlapololiztli īhuān in tlazacaliztli tlahcuilōlloh cah tlani.",
+       "moveddeleted-notice": "Inin tlahcuilolamatl omopoloh.\nIn tlapololiztli ihuan in tlazacaliztli tlahcuilolloh cah tlani.",
        "edit-gone-missing": "Ahmo huelīti yancuīya zāzanilli.\nHueliz ōmopolo.",
        "edit-conflict": "Tlapatlaliztli yāōyōtōn",
        "edit-already-exists": "Ahmo mohuelīti mochīhua yancuīc zāzanilli.\nYe ia.",
        "currentrev": "Āxcān tlapatlaliztli",
        "currentrev-asof": "Āxcān tlachiyaliztli īpan $1",
        "revisionasof": "Tlachiyaliztli ipan $1",
-       "revision-info": "Tlachiyaliztli īpan $1 īpal {{GENDER:$6|$2}}$7",
+       "revision-info": "Tlachicahualiztli ixquichca $1 ihuicpa {{GENDER:$6|$2}}$7",
        "previousrevision": "← Huehueh tlapatlaliztli",
-       "nextrevision": "Yancuīc tlapatlaliztli →",
-       "currentrevisionlink": "Āxcān tlapatlaliztli",
+       "nextrevision": "Yancuic tlapatlaliztli →",
+       "currentrevisionlink": "Axcan tlapatlaliztli",
        "cur": "axcan",
        "next": "niman",
        "last": "xocoyoc",
        "page_first": "achto",
        "page_last": "xōcoyōc",
        "history-fieldset-title": "Xitlatēmo īpan tlahtōllōtl",
-       "history-show-deleted": "Zan tlapolōlli",
+       "history-show-deleted": "Zan tlapololtin",
        "histfirst": "in achto",
        "histlast": "in tlatzaucticah",
        "historysize": "({{PLURAL:$1|1 byte|$1 byte}})",
        "rev-delundel": "tiquittāz/tictlātīz",
        "rev-showdeleted": "xicnēxti",
        "revisiondelete": "Tiquimpolōz/ahtiquimpolōz tlachiyaliztli",
-       "revdelete-show-file-submit": "Quēmah",
+       "revdelete-show-file-submit": "Quemah",
        "revdelete-hide-text": "In tlahtlachiyaliztli ītlahcuilōl",
        "revdelete-hide-image": "Tictlātīz tlahcuilōlli ītlapiyaliz",
-       "revdelete-radio-set": "Tlaīnāyalli",
-       "revdelete-radio-unset": "Ittalōni",
+       "revdelete-radio-set": "Tlahnayalli",
+       "revdelete-radio-unset": "Ittaloni",
        "revdelete-log": "Tleīpampa:",
        "revdel-restore": "Ticpatlāz tlattaliztli",
        "pagehist": "Tlaīxtli ītlahtōllo",
        "mergehistory-autocomment": "Ōmocēntili [[:$1]] īpan [[:$2]]",
        "mergehistory-comment": "Ōmocēntili [[:$1]] īpan [[:$2]]: $3",
        "mergehistory-reason": "Tleīpampa:",
-       "revertmerge": "Tiquīxipehuaz",
+       "revertmerge": "Ticahtletiliz in cetiliztli",
        "history-title": "«$1» ītlaceppahuiliztlahtōllo",
        "lineno": "Pantli $1:",
        "editundo": "Ticxitiniz",
        "viewprevnext": "Xiquintta ($1 {{int:pipe-separator}} $2) ($3).",
        "searchmenu-exists": "'''Ye ia zāzanilli ītōca \"[[$1]]\" inīn huiquipan'''",
        "searchmenu-new": "<strong>Ticchīhuāz in zāzanilli «[[:$1]]» inīn huiquipan.</strong> {{PLURAL:$2|0=|Nō xiquitta in tlanāmiquiliztli in mochīhualiztli.}}",
-       "searchprofile-articles": "Tlapiyaliztli zāzanilli",
+       "searchprofile-articles": "Itech tlahcuilolamatl",
        "searchprofile-images": "Nepapan media",
        "searchprofile-everything": "Mochi",
        "searchprofile-advanced": "Huehca ōmpa",
        "powersearch-togglenone": "Ahtlein",
        "search-external": "Tlatēmotiliztli calāmpa",
        "preferences": "Panitlatlālīlli",
-       "mypreferences": "Notlaēlēhuiliz",
+       "mypreferences": "Notlaelehuiliz",
        "prefs-edits": "Tlapatlaliztli tlapōhualli:",
        "prefs-skin": "Ēhuatl",
        "skin-preview": "Xiquitta quemeh yez",
        "prefs-rc": "Yancuic tlapatlaliztli",
        "prefs-watchlist": "Tlachiyaliztli",
        "prefs-watchlist-days": "Tōnaltin tiquinttāz tlachiyalizpan:",
-       "prefs-watchlist-edits": "Tlapatlaliztli tiquintta tlachiyalizpan:",
+       "prefs-watchlist-edits": "Oc cencah tlapohualli itech tlapatlaliztli tlen monextia ipan canahuac tlatecpanaliztli:",
        "prefs-misc": "Zāzo",
        "prefs-resetpass": "Ticpatlāz motlahtōlichtacāyo",
        "saveprefs": "Xicpiya",
        "prefs-editing": "Tlapatlaliztli",
        "rows": "Pāntli:",
-       "searchresultshead": "Tlatēmoliztli",
+       "searchresultshead": "Tlatemoliztli",
        "recentchangesdays": "Tōnaltin tiquinttāz yancuīc tlapatlalizpan:",
        "localtime": "Cāhuitl nicān:",
        "timezoneregion-africa": "Africa",
-       "timezoneregion-america": "Ixachitlān",
+       "timezoneregion-america": "America",
        "timezoneregion-antarctica": "Antártida",
        "timezoneregion-arctic": "Ártico",
        "timezoneregion-asia": "Asia",
-       "timezoneregion-atlantic": "Atlántico Ilhuicaātl",
+       "timezoneregion-atlantic": "Atlantico Ilhuicaatl",
        "timezoneregion-australia": "Australia",
        "timezoneregion-europe": "Europan",
-       "timezoneregion-indian": "Índico Ilhuicaātl",
-       "timezoneregion-pacific": "Pacífico Ilhuicaātl",
+       "timezoneregion-indian": "Indico Ilhuicaatl",
+       "timezoneregion-pacific": "Pacifico Ilhuicaatl",
        "prefs-searchoptions": "Titlatēmōz",
        "prefs-namespaces": "Tōcātlacāuhtli",
        "default": "ic default",
        "username": "{{GENDER:$1|Tequihuihcātōcāitl}}:",
        "prefs-memberingroups": "{{GENDER:$2|Tlacotōncayōtl}} in {{PLURAL:$1|tēolōlolli|tēolōloltin}}",
        "yourrealname": "Melāhuac motōcā:",
-       "yourlanguage": "Tlahtōlli:",
+       "yourlanguage": "Tlahtolli:",
        "yournick": "Motōcātlaliz:",
-       "badsiglength": "Motōcātlaliz cah ocachi huēyac.\nAhmo quihuīquilia quimpiya achi $1 {{PLURAL:$1|machiyōtlahtōliztli}}.",
+       "badsiglength": "Motocatlaliz cah huel hueyac.\nAhmo hueli quipiya achi $1 {{PLURAL:$1|machiyotlahtoliztli}}.",
        "gender-male": "Oquichtli",
        "gender-female": "Cihuātl",
        "email": "E-mail",
        "prefs-help-email-required": "Tihuīquilia quihcuiloa mo e-mailcān.",
        "prefs-signature": "Motōcā",
        "userrights-user-editname": "Xihcuiloa cē tlatequitiltilīltōcāitl:",
-       "editusergroup": "Tiquimpatlāz {{GENDER:$1|tlatequitiltilīlli}} īolōl",
+       "editusergroup": "Tiquimpatlaz {{GENDER:$1|tequitiuhqui}} itlacentlalilizhuan",
        "userrights-editusergroup": "Tiquimpatlāz tlatequitiltilīlli olōlli",
-       "saveusergroups": "Tiquimpiyāz {{GENDER:$1|tlatequitiltilīlli}} īolōl",
+       "saveusergroups": "Tiquimpiyaz {{GENDER:$1|tequitiuhqui}} itlacentlalilizhuan",
        "userrights-groupsmember": "Olōlco:",
        "userrights-reason": "Īxtlamatiliztli:",
        "userrights-no-interwiki": "Ahmo tihuelīti ticpatla tlatequitiltilīlli huelītiliztli occequīntīn huiquipan.",
-       "group": "Olōlli:",
+       "group": "Necentlaliliztli:",
        "group-user": "Tequihuihqueh",
        "group-bot": "Tepoztlācah",
        "group-sysop": "Tlahcuilōlpixqueh",
        "rightslog": "Tlatequitiltilīlli huelītiliztli tlahcuilōlloh",
        "action-read": "xāmapōhua inīn tlaīxtli",
        "action-edit": "xicpatla inīn tlaīxtli",
-       "action-createpage": "xicchīhua inīn āmatl",
-       "action-createtalk": "xicchīhuā inīn tēixnāmiquiliztli zāzaniltin",
+       "action-createpage": "xicchihua inin tlahcuilolamatl",
+       "action-createtalk": "xicchihua inin tlahcuilolamatl iteixnamiquiliz",
        "action-createaccount": "ticchīhuaz inīn tlatequitiltilīlli īcuentah",
        "action-move": "ticpatlāz inīn zāzanilli",
        "action-move-subpages": "tiquimpatlāz inīn zāzanilli īhuān zāzaniltōn",
        "enhancedrc-history": "Tlahtollotl",
        "recentchanges": "Yancuic tlapatlaliztli",
        "recentchanges-legend": "Yancuīc tlapatlaliztechcopa tlanequiliztli",
-       "recentchanges-summary": "Xiquinttāz in achi yancuīc ahmo occequīntīn tlapatlaliztli huiquipan inīn zāzanilpan.",
+       "recentchanges-summary": "Tictoquiliz itlapatlaliz oc yancuic inahuac huiqui inin tlahcuilolpan.",
        "recentchanges-label-newpage": "Inīn tlapatlaliztli ōquiyōcox cē yancuīc āmatl",
        "recentchanges-label-minor": " Inin tepiton tlapatlaliztli",
        "recentchanges-label-bot": "Inin tlapaltlaliztli oquichiuh ce robot",
-       "rclistfrom": "Xiquinttāz yancuīc tlapatlaliztli īhuīcpa $3 $2",
+       "rclistfrom": "Xiquittaz yancuic tlapatlaliztli ixquichca $3 ihuicpa $2",
        "rcshowhideminor": "$1 tlapatlalitzintli",
-       "rcshowhideminor-show": "Ticnēxtīz",
-       "rcshowhidebots": "$1 tepoztlācah",
+       "rcshowhideminor-show": "Xicnexti",
+       "rcshowhidebots": "$1 bots",
        "rcshowhidebots-show": "Xicnexti",
        "rcshowhidebots-hide": "Tiquihyānaz",
        "rcshowhideliu": "$1 tēmachiyōmacalli tlatequitiltilīltin",
-       "rcshowhideanons": "$1 ahtocatl tequiuhqui",
-       "rcshowhideanons-show": "Xicnēxti",
+       "rcshowhideanons": "$1 ahtocatl tequitiuhqui",
+       "rcshowhideanons-show": "Xicnexti",
        "rcshowhidepatr": "$1 tlapatlaliztli mochiyahua",
        "rcshowhidemine": "$1 notlahcuilol",
-       "rcshowhidemine-show": "Xicnēxti",
-       "rclinks": "Xiquintta xōcoyōc $1 tlapatlaliztli xōcoyōc $2 tōnalpan.<br />$3",
+       "rcshowhidemine-show": "Xicnexti",
+       "rclinks": "Xiquitta yancuic $1 tlapatlaliztli yancuic $2 tonalpan.<br />$3",
        "diff": "ahneneuhqui",
        "hist": "tlahtollotl",
        "hide": "Tiquintlātīz",
        "recentchangeslinked-feed": "Tlapatlaliztli tzonhuilizpan",
        "recentchangeslinked-toolbox": "Itloc itlapatlalizhuan",
        "recentchangeslinked-title": "Tlapatlaliztli \"$1\" itechcopa",
-       "recentchangeslinked-page": "Tlaīxtli ītōcā:",
+       "recentchangeslinked-page": "Tlahcuilolamatl itoca:",
        "upload": "Tlahcuilolquetzaliztli",
        "uploadbtn": "Tlahcuilōlquetza",
        "uploadnologin": "Ahmo ōtimocalac",
        "sourcefilename": "Mēyalihcuilōltōcāitl:",
        "sourceurl": "Mēyal-URL:",
        "destfilename": "Tōcāhuīc:",
-       "watchthisupload": "Tictlachiyāz inīn zāzanilli",
+       "watchthisupload": "Tictlachiyaz inin tecpanaliztlapiyaliztli",
        "upload-form-label-infoform-name": "Tōcāitl",
        "upload-form-label-usage-filename": "Ihcuilōlli ītōcā",
-       "upload_source_file": " (cē tlahcuilōlli mochīuhpōhualhuazco)",
+       "upload_source_file": "(ticpepenaz ce tlahcuilolli mochiuhpohualhuazco)",
        "listfiles_search_for": "Tlatēmōz mēdiatl tōcācopa:",
        "imgfile": "ihcuilōlli",
        "listfiles": "Mochīntīn īxiptli",
        "listfiles_name": "Tōcāitl",
        "listfiles_user": "Tequihuihqui",
-       "listfiles_size": "Octacayōtl (bytes)",
+       "listfiles_size": "Octacayotl (bytes)",
        "listfiles_count": "Cuepaliztli",
        "listfiles-latestversion-yes": "Quēmah",
        "listfiles-latestversion-no": "Ahmō",
        "filehist-deleteone": "xicpolo",
        "filehist-revert": "tlacuepāz",
        "filehist-current": "āxcān",
-       "filehist-datetime": "Tlapōhualpan/Cāhuitl",
-       "filehist-thumb": "Īxiptlahtōn",
-       "filehist-user": "Tequihuihqui",
-       "filehist-dimensions": "Octacayōtl",
+       "filehist-datetime": "Tonallapohualpan ihuan imman",
+       "filehist-thumb": "Ixiptlatontli",
+       "filehist-user": "Tequitiuhqui",
+       "filehist-dimensions": "Octacayotl",
        "filehist-comment": "TlahtoIcaquiliztiloni",
        "imagelinks": "In canin oquitlalihqueh",
        "linkstoimage": "Inīn {{PLURAL:$1|zāzanilli motzonhuilia|$1 zāzanilli motzonhuiliah}} inīn tlahcuilōlhuīc:",
        "nolinkstoimage": "Ahmo cateh zāzaniltin tlein tzonhuiliah inīn tlahcuilōlhuīc.",
        "morelinkstoimage": "Tiquinttāz [[Special:WhatLinksHere/$1|achi tzonhuiliztli]] inīn tlahcuilōlhuīc.",
-       "duplicatesoffile": "Inōn {{PLURAL:$1|tlahcuilōlli cah|$1 tlahcuilōlli cateh}} ōntiah inīn zāzanilli ([[Special:FileDuplicateSearch/$2|ocahci]]):",
-       "sharedupload": "Inīn $1 zāzanilli huelīti motequitiltia zāzocāmpa.",
+       "duplicatesoffile": "Inin {{PLURAL:$1|tlahcuilolli oppa ochihualoc|$1 tlahcuiloltin oppa ochihualoqueh}} ixquichca inin tlahcuilolli ([[Special:FileDuplicateSearch/$2|oc temachiztiliztli]]):",
+       "sharedupload": "Inin tlahcuilolli itech pohui $1 ihuan hueli motequitiltia ipan occe proyectos.",
        "uploadnewversion-linktext": "Ticquetzāz yancuīc tlahcuilōlli",
        "filerevert": "Ticcuepāz $1",
        "filerevert-legend": "Tlahcuilōlli tlacuepaliztli",
        "unusedtemplates": "Nemachiyōtīlli ahmotequitiltiah",
        "unusedtemplateswlh": "occequīntīn tzonhuiliztli",
        "randompage": "Cecen tlahcuilolli",
-       "randompage-nopages": "Ahmo oncah zāzanilli īpan inīn {{PLURAL:$2|tōcāitl}}: $1.",
+       "randompage-nopages": "Ahmo oncah tlahcuilolamameh ipan inin {{PLURAL:$2|tocatlacauhtli|tocatlacauhtin}}: $1.",
        "randomincategory-submit": "Yāuh",
        "randomredirect": "Zāzotlacuepaliztli",
        "statistics": "Tlapōhualiztli",
        "nbytes": "$1 {{PLURAL:$1|byte}}",
        "ncategories": "$1 {{PLURAL:$1|tlaìxmatkàyòtlàlilòtl|tlaìxmatkàyòtlàlilòme}}",
        "nlinks": "$1 {{PLURAL:$1|tzòwilistli|tzòwilistìn}}",
-       "nmembers": "$1 {{PLURAL:$1|tlahcuilolli|tlahcuiloltin}}",
+       "nmembers": "$1 {{PLURAL:$1|tlahcuilolamatl|tlahcuilolamameh}}",
        "nrevisions": "$1 {{PLURAL:$1|tlapiyaliztli}}",
        "nimagelinks": "Motekìuhtia ìpan $1 {{PLURAL:$1|tlaìxtlapalli|tlaìxtlapaltìn}}",
        "ntransclusions": "motekìuhtia ìpan $1 {{PLURAL:$1|tlaìxtlapalli|tlaìxtlapaltìn}}",
        "listusers": "Tlatequitiltilīlli",
        "newpages": "Yancuic tlahcuiloltin",
        "newpages-username": "Tlatequitiltilīltōcāitl:",
-       "ancientpages": "Huēhuehzāzanilli",
+       "ancientpages": "Huehcauh tlahcuilolamatl",
        "move": "Ticzacāz",
        "movethispage": "Ticzacāz inīn zāzanilli",
-       "pager-newer-n": "{{PLURAL:$1|1 yancuīc|$1 yancuīc}}",
-       "pager-older-n": "{{PLURAL:$1|1 huēhuetl|$1 huēhueh}}",
-       "booksources": "Āmoxmēyalli",
-       "booksources-search-legend": "Tiquīxtēmōz āmoxmēyalli",
-       "booksources-search": "Tiquīxtēmōz",
+       "pager-newer-n": "{{PLURAL:$1|1 yancuic|$1 yancuicqueh}}",
+       "pager-older-n": "{{PLURAL:$1|1 huehcauh|$1 huehcauhqueh}}",
+       "booksources": "Amoxtzintiliztli",
+       "booksources-search-legend": "Tiquixtemoz amoxtli itzintiliz",
+       "booksources-search": "Tlatemoliztli",
        "specialloguserlabel": "Tlatequitiltilīlli:",
        "speciallogtitlelabel": "Tōcāitl:",
        "log": "Tlahcuilōlloh",
-       "all-logs-page": "Mochīntīn tlācah īntlahcuilōlloh",
-       "allpages": "Mochīntīn zāzanilli",
+       "all-logs-page": "Mochintin nohuiyanyoh intlahcuilolhuan",
+       "allpages": "Mochintin tlahcuilolamatl",
        "nextpage": "Niman zāzanilli ($1)",
        "prevpage": "Achto zāzanilli ($1)",
-       "allarticles": "Mochīntīn tlahcuilōlli",
-       "allinnamespace": "Mochīntīn zāzanilli (īpan $1)",
-       "allpagessubmit": "Tiyāz",
+       "allarticles": "Mochintin tlahcuilolamameh",
+       "allinnamespace": "Mochintin tlahcuilolamameh (tocatlacauhtli $1)",
+       "allpagessubmit": "Tiyaz",
        "categories": "Neneuhcayotl",
        "categoriespagetext": "{{PLURAL:$1|Inìn tlaìxmatkàyòtlàlilòtl kimpia|Inîke tlaìxmatkàyòtlàlilòme kimpiâke}} tlaìxtlapaltìn noso medios.\nÂmò monèxtiâke nikàn in [[Special:UnusedCategories|tlaìxmatkàyòtlàlilòme tlèn âmò mokìntekitìltia]].\nNò mà mỏta in tlèn [[Special:WantedCategories|ìpan kineki tlaìxmatkàyòtlàlilòtl]].",
        "categoriesfrom": "Mà monèxtìkàn tlaìxmatkàtlàlilòmë tlèn pèwâkë ìka:",
-       "linksearch": "Calān tzonhuiliztli tlatemoliztli",
+       "linksearch": "Tlatemoliztli ihuic quiyahuac tzonhuiliztli",
        "linksearch-ns": "Tōcātzin:",
        "linksearch-ok": "Tictēmōz",
        "linksearch-line": "$1 tzonhuīlo īxquichca $2",
        "watching": "Tlachiyacah...",
        "unwatching": "Ahtlachiyacah...",
        "enotif_impersonal_salutation": "tlatequitiltilīlli īpan {{SITENAME}}",
-       "enotif_anon_editor": "ahtōcātlatequitiltilīlli $1",
+       "enotif_anon_editor": "ahtocatl tequitiuhqui $1",
        "enotif_body": "Māhuiztic $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nTlapatlani ītlahtōlpehuallo:  $PAGESUMMARY $PAGEMINOREDIT\n\nXicnotzāz in tlapatlani:\nīnetitlanizyeyān:$PAGEEDITOR_EMAIL\nīhuiqui:$PAGEEDITOR_WIKI\n\nAhmo occēppa mitztlamachiztīz intlā yancuīc tlapatlaliztli, zā mā tiquittaz inīn āmatl. Tihueliti ticcencahulīz in tēmachiztīlizpāmitl in mochintin motlachixāmatl in motlachiyaliz.\n\nIn {{SITENAME}} icnīuhtīliztica motēmachiztīliztlacentetilīz.\n\nIntlā ticnequi ticpatlaz in maltzinteyōtl monetitlanizyeyān, xiquihitta:\n{{canonicalurl:{{#special:Preferences}}}}\n\n\nIntlā ticnequi ticpatlaz in motlachiyaliz tlaēlēhuiliztli, xiquihitta:\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nIntlā ticnequi ticpolōz in āmatl ītech motlachiyaliz, xiquihitta:\n$UNWATCHURL\n\nMotlahtōlcaquiliztīlōni īhuān ocachi tēpalēhuiliztli, xiquihitta:\n$HELPPAGE",
        "created": "ōmochīuh",
        "changed": "ōmotlacuep",
        "rollback-success": "Ōmotlacuep $1 ītlahcuilōl; āxcān achto $2 ītlahcuilōl.",
        "changecontentmodel-title-label": "Tlaīxtōcāitl",
        "changecontentmodel-reason-label": "Tleīpampa:",
-       "protectlogpage": "Tlapiyaliztlīlmachiyōtīlli",
+       "protectlogpage": "Tlapiyaliztlilmachiyotilli",
        "protectedarticle": "ōmoquīxti \"[[$1]]\"",
        "unprotectedarticle": "ōahmoquīxtih «[[$1]]»",
        "prot_1movedto2": "[[$1]] ōmozacac īhuīc [[$2]]",
        "protectcomment": "Tleīpampa:",
        "protectexpiry": "Tlamiliztli:",
        "protect_expiry_invalid": "Ahcualli tlamiliztli cāhuitl.",
-       "protect-default": "Ticmācāhuaz mochintin in tlatequitiltilīltin",
+       "protect-default": "Macahualo ica mochintin in tequitiuhqueh",
        "protect-fallback": "Zan momācāhuazqueh tlatequitiltilīltin in tēmācāhualiztica «$1»",
-       "protect-level-autoconfirmed": "Zan momācāhuaz moneltilīlli tlatequitiltilīltin",
-       "protect-level-sysop": "Zan momācāhuaz tētlamahmacanimeh",
+       "protect-level-autoconfirmed": "Zan macahualo ica neneltililli tequitiuhqueh",
+       "protect-level-sysop": "Zan macahualo in tetlamahmacanimeh",
        "protect-expiring": "motlamīz $1 (UTC)",
-       "protect-expiry-options": "1 hora:1 hour,1 tōnalli:1 day,1 chicuēyilhuitl:1 week,2 chicuēyilhuitl:2 weeks,1 mētztli:1 month,3 mētztli:3 months,6 mētztli:6 months,1 xihuitl:1 year,mochipa:infinite",
-       "restriction-type": "Mācāhualiztli:",
+       "protect-expiry-options": "1 hora:1 hour,1 tonalli:1 day,1 chicueyilhuitl:1 week,2 chicueyilhuitl:2 weeks,1 metztli:1 month,3 metztli:3 months,6 metztli:6 months,1 xihuitl:1 year,mochipa:infinite",
+       "restriction-type": "Temacahualiztli:",
        "restriction-edit": "xicpatla",
        "restriction-move": "Ticzacāz",
        "restriction-create": "Ticchīhuāz",
        "viewdeletedpage": "Tiquinttāz zāzaniltin ōmopolōzqueh",
        "undelete-revision": "Tlapoloc $1 ītlachiyaliz (īpan $4, $5) īpal $3:",
        "undeletebtn": "Ahticpolōz",
-       "undeletelink": "xiquitta/xicmācuepa",
-       "undeleteviewlink": "tiquittāz",
+       "undeletelink": "tlattaliztli/tlacuepaliztli",
+       "undeleteviewlink": "tiquittaz",
        "undeletecomment": "Tleīpampa:",
        "undelete-search-box": "Tiquintlatēmōz zāzaniltin ōmopolōz",
        "undelete-search-prefix": "Tiquittāz zāzaniltin mopēhua īca:",
        "undelete-show-file-submit": "Quemah",
        "namespace": "Tocatlacauhtli:",
        "invert": "Tlacuepaz motlahtol",
-       "blanknamespace": "(Huēyi)",
+       "blanknamespace": "(Tlayacatic)",
        "contributions": "In {{GENDER:$1|tlatequitiltilīlli}} ītlahcuilōl",
        "contributions-title": "Tlatequitiltilīlli $1 ītlahcuilōl",
        "mycontris": "Notlahcuilol",
        "contribsub2": "$1 ($2)",
-       "uctop": "(āxcān tlapatlaliztli)",
+       "uctop": "(axcan tlapatlaliztli)",
        "month": "Īhuīcpa mētztli (auh achtopa):",
        "year": "Xiuhhuīcpa (auh achtopa):",
        "sp-contributions-newbies": "Tiquinttāz zan yancuīc tlatequitiltilīlli īntlapatlaliz",
        "sp-contributions-newbies-sub": "Ic yancuīc",
        "sp-contributions-newbies-title": "Yancuīc tlatequitiltilīlli ītlahcuilōl",
        "sp-contributions-blocklog": "Tlatzacuiliztli tlahcuilōlloh",
-       "sp-contributions-uploads": "tlahcuilōlquetzaliztli",
-       "sp-contributions-talk": "zānīlli",
+       "sp-contributions-uploads": "tlahcuilolquetzaliztli",
+       "sp-contributions-talk": "teixnamiquiliztli",
        "sp-contributions-search": "Tiquintlatēmōz tlapatlaliztli",
        "sp-contributions-username": "IP nozo tlatequitiltilīlli ītōcā:",
-       "sp-contributions-submit": "Tlatēmōz",
+       "sp-contributions-submit": "Tlatemoliztli",
        "whatlinkshere": "In tlein quitzonhuilia nican",
        "whatlinkshere-title": "Zāzaniltin quitzonhuiliah $1",
-       "whatlinkshere-page": "Zāzanilli:",
+       "whatlinkshere-page": "Tlahcuilolamatl:",
        "linkshere": "Inīn zāzaniltin quitzonhuiliah '''[[:$1]]''' īhuīc:",
        "nolinkshere": "Ahtle quitzonhuilia '''[[:$1]]''' īhuīc.",
        "isredirect": "ōmotlacuep zāzanilli",
        "isimage": "īxiptlahtli tzonhuiliztli",
        "whatlinkshere-prev": "{{PLURAL:$1|achtopa|$1 achtopa}}",
        "whatlinkshere-next": "{{PLURAL:$1|niman|$1 niman}}",
-       "whatlinkshere-links": "← tzòwilistìn",
+       "whatlinkshere-links": "← tzohuiliztin",
        "whatlinkshere-hideredirs": "$1 tlacuepaliztli",
        "whatlinkshere-hidelinks": "$1 tzonhuiliztli",
        "whatlinkshere-hideimages": "$1 tlahcuilōltzonhuīliztli",
        "blocklist-reason": "Tleīpampa",
        "ipblocklist-submit": "Tlatēmōz",
        "infiniteblock": "ahtlamic",
-       "expiringblock": "tlami īpan $1 īpan $2",
+       "expiringblock": "tlami ipan $1 ipan $2",
        "anononlyblock": "zan ahtōcā",
        "blocklink": "tictzacuiliz",
        "unblocklink": "ahtiquitzacuilīz",
        "contribslink": "tlapatlaliztli",
        "blocklogpage": "Tlatequitiltilīlli ōmotzacuili",
        "move-page": "Ticzacāz $1",
-       "move-page-legend": "Ticzacāz zāzanilli",
+       "move-page-legend": "Tictocapatlaliz inin tlahcuilolamatl",
        "movepagetext": "Nicān mohcuiloa quemeh ticzacāz cē zāzanilli auh mochi in ītlahcuillōloh īhuīc occē yancuīc ītōca.\nHuēhuehtōcāitl yez tlacuepaliztli yancuīc tōcāhuīc.\nTzonhuiliztli huēhuehzāzanilhuīc ahmo mopatlāz.\nXiquitta ic māca xicchīhua [[Special:DoubleRedirects|ōntlacuepaliztli]] ahnozo [[Special:BrokenRedirects|tzomoc]].\nTitzonhuilizpiyāz.\n\nXicmati in zāzanilli ahmo mozacāz intlā ye ia cē zāzanilli tōcātica, zan cah iztāc zāzanilli ahnozo tlacuepaliztli īca ahmo tlahcuilōlloh.\nQuihtōznequi tihuelītīz ticuepāz cē zāzanilli īhuīc ītlācatōca intlā ahcuallōtl ticchīhuāz, tēl ahmo tihuelītīz occeppa tihcuilōz īpan zāzanilli tlein ia.\n\n'''¡XICPŌHUA!'''\nHueliz cah inīn huēyi tlapatlaliztli. Timitztlātlauhtia ticmatīz cuallōtl auh ahcuallōtl achtopa ticzacāz.",
        "movenotallowed": "Ahmo tihuelīti tiquinzaca zāzaniltin.",
-       "newtitle": "Yancuīc tōcāhuīc",
-       "move-watch": "Tictlachiyāz inīn zāzanilli",
+       "newtitle": "Yancuic tocaitl",
+       "move-watch": "Tictlachiyaz tlahcuilolamatl itzintiliz ihuan itlatzaccan",
        "movepagebtn": "Ticzacāz zāzanilli",
        "pagemovedsub": "Cualli ōmozacac",
        "movepage-moved": "'''\"$1\" ōmotlacuep īhuīc \"$2\".'''",
-       "movetalk": "Xiczaca yehhuātl īzānīllaīx",
+       "movetalk": "tictocapatlaliz in tlahcuilolamatl iixiptla",
        "movepage-page-moved": "Zāzanilli $1 ōmozacac īhuīc $2.",
        "movepage-page-unmoved": "Ahmo huelīti $1 mozaca īhuīc $2.",
        "movelogpage": "Tlazacaliztli tlahcuilōlloh",
        "immobile-source-namespace": "Ahmo huelīti mozaca zāzanilli tōcātzimpan \"$1\"",
        "immobile-target-namespace": "Ahmo huelīti mozaca zāzanilli tōcātzinhuīc \"$1\"",
        "immobile-source-page": "Ahmo huelīti mozacāz zāzanilli.",
-       "move-leave-redirect": "Ticcāhuāz cē tlacuepaliztli",
+       "move-leave-redirect": "Ma ticcahua ce tlatzonhuiliztli",
        "export": "Tiquinnamacāz zāzaniltin",
        "export-submit": "Ticnamacāz",
        "export-addcattext": "Mà tlatlaìxtlapalwilo ìwikpa tlatlaìxmatkàyòtlàlilòpa:",
        "export-download": "Ticpiyāz quemeh tlahcuilōlli",
        "export-templates": "Tiquimpiyāz nemachiyōtīlli",
        "allmessages": "Mochīntīn Huiquimedia tlahcuilōltzintli",
-       "allmessagesname": "Tōcāitl",
-       "allmessagescurrent": "Āxcān tlahcuilōlli",
+       "allmessagesname": "Tocaitl",
+       "allmessagescurrent": "Tlahcuilolpiyaliztli itech axcan",
        "allmessages-filter-all": "Mochi",
-       "allmessages-language": "Tlâtòlli:",
-       "allmessages-filter-submit": "Yāuh",
+       "allmessages-language": "Tlahtolli:",
+       "allmessages-filter-submit": "Tiyaz",
        "thumbnail-more": "Tiquihuēyiyāz",
        "thumbnail_error": "Aiuhcāyōtl ihcuāc mochīhuaya tepitōntli: $1",
        "import": "Tiquincōhuāz zāzaniltin",
        "importbadinterwiki": "Ahcualli interhuiqui tzonhuiliztli",
        "import-upload": "Tiquinquetzāz XML tlahcuilōlli",
        "importlogpage": "Tiquincōhuāz tlahcuilōlloh",
-       "tooltip-pt-userpage": "{{GENDER:|Motlatequitiltilīlzāzanil}}",
-       "tooltip-pt-mytalk": "{{GENDER:|Motēīxnāmiquiliztli}}",
+       "tooltip-pt-userpage": "{{GENDER:|Motequitiuhcatlahcuilolamauh}}",
+       "tooltip-pt-mytalk": "{{GENDER:|Moteixnamiquiliz}}",
        "tooltip-pt-preferences": "{{GENDER:|Motlaēlēhuiliz}}",
        "tooltip-pt-watchlist": "Zāzaniltin tiquintlachiya ic tlapatlaliztli",
-       "tooltip-pt-mycontris": "{{GENDER:|Motlahcuilōl}}",
+       "tooltip-pt-mycontris": "{{GENDER:|Motlahcuilol}}",
        "tooltip-pt-login": "Tihueliti timocalaqui, tel ahmo tihuiquilia.",
-       "tooltip-pt-logout": "Tiquīzāz",
+       "tooltip-pt-logout": "Tiquizaz",
        "tooltip-ca-talk": "Iteixnamiquiliz itechpa inin tlahcuilolli",
        "tooltip-ca-edit": "Ticpatlaz inin tlahcuilolli",
-       "tooltip-ca-addsection": "Tictzintīz yancuic xeliuhcāyōtl.",
+       "tooltip-ca-addsection": "Ticpehualiz ce yancuic xeliuhcayotl.",
        "tooltip-ca-viewsource": "Inīn zāzanilli ōmoquīxti. Tihuelīti tiquitta ītlahtōlcaquiliztilōni.",
        "tooltip-ca-history": "Achtopa āxcān zāzanilli īhuān in tlatequitiltilīlli ōquinchīuhqueh",
        "tooltip-ca-protect": "Ticquīxtiāz inīn zāzanilli",
        "tooltip-ca-delete": "Ticpolōz inīn zāzanilli",
        "tooltip-ca-undelete": "Ahticpolōz inīn zāzanilli",
-       "tooltip-ca-move": "Ticzacāz inīn zāzanilli",
+       "tooltip-ca-move": "Ticzacaz inin tlahcuilolamatl",
        "tooltip-ca-watch": "Ticcentiliz inin tlahtolli motecpanaliz",
        "tooltip-ca-unwatch": "Ahtictlachiyāz inīn zāzanilli",
        "tooltip-search": "Tlatemoliztli ipan {{SITENAME}}",
-       "tooltip-search-go": "Tiyaz ihuicpa tlahcuilolli ica inin huel melahuac tocaitl intla oncah",
+       "tooltip-search-go": "Tiyaz ihuicpa tlahcuilolamatl ica inin huel melahuac tocaitl intla oncah",
        "tooltip-search-fulltext": "Tictemoz inin tlahcuilolli ipan amatl",
        "tooltip-p-logo": "Tiquittaz in yacatlahcuilolli",
        "tooltip-n-mainpage": "Tiquittaz in yacatlahcuilolli",
        "tooltip-t-specialpages": "Intlahtoltecpanaliz mochtin in noncuahquizquitlahcuiloltin",
        "tooltip-t-print": "Tepoztlahcuilolli",
        "tooltip-ca-nstab-main": "Tiquittaz tlein quipiya in tlahcuilolli",
-       "tooltip-ca-nstab-user": "Xiquitta tlatequitiltilīlli īzāzanil",
+       "tooltip-ca-nstab-user": "Xiquitta tequitiuhqui itlahcuilolamauh",
        "tooltip-ca-nstab-special": "Inīn nōncuahquīzqui āmatl, auh ahmohuelitizpatla",
        "tooltip-ca-nstab-project": "Xiquitta in tlatequipanōllaīxtli",
        "tooltip-ca-nstab-image": "Xiquittāz īxipzāzanilli",
        "spam_reverting": "Mocuepacah īhuīc xōcoyōc tlapatlaliztli ahmo tzonhuilizca īhuīc $1",
        "spam_blanking": "Mochi tlapatlaliztli quimpiyah tzonhuiliztli īhuīc $1, iztāctiliacah",
        "pageinfo-firstuser": "Tlaīxchīuhqui",
-       "pageinfo-toolboxlink": "Tlaīxtlahtōlmelāhualiztli",
+       "pageinfo-toolboxlink": "Tlahtolamatl itlahtolmelahualiz",
        "pageinfo-contentpage-yes": "Quēmah",
        "pageinfo-protect-cascading-yes": "Quēmah",
        "previousdiff": "← Achtopa",
-       "nextdiff": "Oc ye cencah yancuīc tlapatlaliztli →",
+       "nextdiff": "Oc ye cencah yancuic tlapatlaliztli →",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:|zāzanilli|zāzanilli}}",
-       "file-info-size": "$1 × $2 pixel; zāzanilli octacayōtl: $3; machiyōtl MIME: $4",
+       "file-info-size": "$1 × $2 pixel; tlaixiptlayotl octacayotl: $3; machiyotl MIME: $4",
        "file-nohires": "Ahmo ia achi cualli ahmo occē īxiptli.",
        "show-big-image": "Tzintiliztlahcuilolli",
        "show-big-image-size": "$1 × $2 pixels",
        "exif-isospeedratings": "ISO iciuhquiliztli tlapōhualcāyōtl",
        "exif-flash": "Flax",
        "exif-flashenergy": "Flax chicāhualiztli",
-       "exif-gpslatituderef": "Mictlāmpa ahnozo huitztlāmpa āncāyōtl",
-       "exif-gpslatitude": "Āncāyōtl",
-       "exif-gpslongituderef": "Tlāpcopa ahnozo cihuātlāmpa huehtlatzīncāyōtl",
+       "exif-gpslatituderef": "Mictlampa nozo huitztlampa ancayotl",
+       "exif-gpslatitude": "Ancayotl",
+       "exif-gpslongituderef": "Tlapcopa nozo cihuatlampa huehtlatzincayotl",
        "exif-gpslongitude": "Huehtlatzīncāyōtl",
        "exif-gpsaltitude": "Huehcapancayōtl",
        "exif-gpstimestamp": "GPS cāhuitl (atomic tepozcāhuitl)",
        "monthsall": "(mochīntīn)",
        "confirmemail": "Ticchicāhuāz e-mail",
        "confirmemail_needlogin": "Tihuīquilia $1 ic ticchicāhua mo e-mail.",
-       "confirmemail_success": "Mocorreo ōmotlahtōlneltilih\nNiman tihuelīti [[Special:UserLogin|timocalaqui]] auh ticpactiāz huiquitica.",
+       "confirmemail_success": "Mocorreo omotlahtolneltilih\nNiman tihueliti [[Special:UserLogin|timocalaquiz]] auh ticpactiaz huiquitica.",
        "confirmemail_loggedin": "Mo e-mailcān ōmochicāuh.",
        "confirmemail_subject": "e-mailcān {{SITENAME}} ītlachicāhualiz",
-       "scarytranscludetoolong": "[In URL achi huel huēiyac ca]",
+       "scarytranscludetoolong": "[In URL huel hueyac]",
        "recreate": "Ticchīhuāz occeppa",
        "confirm_purge_button": "Cualli",
        "imgmultipageprev": "← achto zāzanilli",
        "table_pager_last": "Xōcoyōc zāzanilli",
        "table_pager_limit_submit": "Yāuh",
        "table_pager_empty": "Ahtlein",
-       "autosumm-blank": "Tlaiztāctilīlli zāzanilli",
+       "autosumm-blank": "Tlaiztaliztli in tlahcuilolamatl",
        "autoredircomment": "Mocuepahua īhuīc [[$1]]",
-       "autosumm-new": "Tlachīhualli zāzanilli īca: \"$1\"",
+       "autosumm-new": "Tlachihuhtli tlahcuilolamatl ica: \"$1\"",
        "size-bytes": "$1 B",
        "size-kilobytes": "$1 KB",
        "size-megabytes": "$1 MB",
        "specialpages-group-changes": "Yancuīc tlapatlaliztli īhuān tlahcuilōlloh",
        "specialpages-group-users": "Tlatequitiltilīlli īhuān huelītiliztli",
        "specialpages-group-highuse": "Zāzaniltin tlatequitiliztechcopa",
-       "specialpages-group-pages": "Mochīntīn zāzaniltin",
+       "specialpages-group-pages": "Mochintin tlahcuilolamameh",
        "specialpages-group-redirects": "Tlatēmoliztli īhuān  tlacuepaliztli",
        "blankpage": "Iztāc zāzanilli",
-       "htmlform-selectorother-other": "Occē",
+       "htmlform-selectorother-other": "Occe",
        "rightsnone": "ahtlein",
        "revdelete-summary": "ticpatlāz tlahcuilōltōn",
        "searchsuggest-search": "Tlatemoliztli",
        "api-error-verification-error": "Inìn èwalli welis îtlakauhtok, noso âmò kualli motzòwîtok.",
        "expand_templates_ok": "Cualli",
        "expand_templates_preview": "Xiquitta achtochīhualiztli",
-       "special-characters-group-latin": "Latintlahcuilōlli",
-       "special-characters-group-latinextended": "Mantoc latintlahcuilōlli",
+       "special-characters-group-latin": "Latintlahcuilolli",
+       "special-characters-group-latinextended": "Mantoc latintlahcuilolli",
        "special-characters-group-greek": "Greciatlahcuilōlli",
        "special-characters-group-cyrillic": "Cirilotlahcuilōlli",
        "special-characters-group-arabic": "Arabiatlahcuilōlli",
index 2b1bc3e..3d79d8c 100644 (file)
@@ -7,7 +7,8 @@
                        "아라",
                        "唐吉訶德的侍從",
                        "Luuva",
-                       "Macofe"
+                       "Macofe",
+                       "進也"
                ]
        },
        "tog-underline": "Liân-kiat oē té-sûn:",
        "category-media-header": "Tī lūi-pia̍t \"$1\" ê mûi-thé",
        "category-empty": "''Chit-má chit ê lūi-pia̍t  bô ia̍h ia̍h-sī mûi-thé.''",
        "hidden-categories": "{{PLURAL:$1|Hidden category|Chhàng khí-lâi ê lūi-pia̍t}}",
-       "hidden-category-category": "Chhàng--khí-lâi ê lūi piat",
+       "hidden-category-category": "Chhàng--khí-lâi ê lūi-piat",
        "category-subcat-count": "{{PLURAL:$2|Chit ê lūi-piat chí-ū ē-bīn ê ē-lūi-pia̍t.|Chit ê lūi-piat ū ē-bīn {{PLURAL:$1| ê ē-lūi-piat|$1 ê ē-lūi-piat}}, choân-pō͘ $2 ê.}}",
        "category-subcat-count-limited": "Chit ê lūi-piat ū ē-bīn ê {{PLURAL:$1| ē-lūi-pia̍t|$1 ē-lūi-pia̍t}}.",
        "category-article-count": "{{PLURAL:$2|Chit ê lūi-piat chí-ū ē-bīn ê ia̍h.|Ē-bīn {{PLURAL:$1|bīn ia̍h sī|$1bīn ia̍h sī}} tī chit lūi-pia̍t, choân-pō͘ $2 bīn ia̍h}}",
        "category-file-count-limited": "Chit-má chit-ê lūi-pia̍t ū {{PLURAL:$1| ê tóng-àn}}",
        "listingcontinuesabbrev": "(chiap-sòa thâu-chêng)",
        "index-category": "Ū sek-ín ê ia̍h",
-       "noindex-category": "Bī sik-ín ê ia̍h.",
-       "broken-file-category": "Sit-khì tóng-àn liân-kiat ê ia̍h.",
+       "noindex-category": "Bī sik-ín ê ia̍h",
+       "broken-file-category": "Sit-khì tóng-àn liân-kiat ê ia̍h",
        "about": "Koan-hē",
        "article": "Loē-iông ia̍h",
        "newwindow": "(ē khui sin thang-á hián-sī)",
        "expandtemplates": "Khok-chhiong pang-bô͘",
        "expand_templates_input": "Su-ji̍p bûn-jī:",
        "expand_templates_output": "Kiat-kó:",
-       "expand_templates_remove_comments": "Comments the̍h tiāu"
+       "expand_templates_remove_comments": "Comments the̍h tiāu",
+       "mw-widgets-mediasearch-input-placeholder": "搜揣媒體"
 }
index 1a46560..80765e6 100644 (file)
        "search-external": "Eksternt søk",
        "searchdisabled": "Søkefunksjonen er slått av. Du kan søke via Google i mellomtiden. Merk at Googles indeksering av {{SITENAME}} muligens er utdatert.",
        "search-error": "En feil oppsto under søk: $1",
+       "search-warning": "En advarsel oppsto under søk: $1",
        "preferences": "Innstillinger",
        "mypreferences": "Innstillinger",
        "prefs-edits": "Antall redigeringer:",
        "userrights-user-editname": "Fyll inn et brukernavn:",
        "editusergroup": "Last brukergrupper",
        "editinguser": "Endrer brukerrettighetene for {{GENDER:$1|bruker}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Viser {{GENDER:$1|brukerrettighetene}} til  <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Rediger brukergrupper",
+       "userrights-viewusergroup": "Se brukergrupper",
        "saveusergroups": "Lagre {{GENDER:$1|brukergrupper}}",
        "userrights-groupsmember": "Medlem av:",
        "userrights-groupsmember-auto": "Implisitt medlem av:",
        "action-upload_by_url": "laste opp denne filen fra en URL",
        "action-writeapi": "bruke skrive-API-en",
        "action-delete": "slette denne siden",
-       "action-deleterevision": "slette denne revisjonen",
-       "action-deletedhistory": "se denne sidens slettede historikk",
+       "action-deleterevision": "slett revisjoner",
+       "action-deletelogentry": "slette loggoppføringer",
+       "action-deletedhistory": "se en sides slettede historikk",
+       "action-deletedtext": "se slettet revisjonstekst",
        "action-browsearchive": "søke i slettede sider",
-       "action-undelete": "gjenopprette denne siden",
-       "action-suppressrevision": "se og gjenopprette denne skjulte revisjonen",
+       "action-undelete": "gjenopprette sider",
+       "action-suppressrevision": "se gjennom og gjenopprette skjulte revisjoner",
        "action-suppressionlog": "se denne private loggen",
        "action-block": "blokkere denne brukeren fra å redigere",
        "action-protect": "endre denne sidens beskyttelsesnivåer",
        "action-userrights-interwiki": "endre brukerrettigheter for brukere på andre wikier",
        "action-siteadmin": "låse eller låse opp databasen",
        "action-sendemail": "sende e-poster",
+       "action-editmyoptions": "redigere innstillingene dine",
        "action-editmywatchlist": "redigere din overvåkningsliste",
        "action-viewmywatchlist": "Vis din overvåkningsliste",
        "action-viewmyprivateinfo": "vise din private informasjon",
        "emailccsubject": "Kopi av din beskjed til $1: $2",
        "emailsent": "E-post sendt",
        "emailsenttext": "E-postbeskjeden er sendt",
-       "emailuserfooter": "Denne e-posten ble {{GENDER:$1|sendt}} av $1 til {{GENDER:$2|$2}}  via funksjonen «{{int:emailuser}}» på {{SITENAME}}.",
+       "emailuserfooter": "Denne e-posten ble {{GENDER:$1|sendt}} av $1 til {{GENDER:$2|$2}}  via funksjonen «{{int:emailuser}}» på {{SITENAME}}. {{GENDER:$2|Din}} epost vil bli sendt direkte til {{GENDER:$1|opprinnelig avsender}} og avsløre {{GENDER:$2|din}} epost-addresse til {{GENDER:$1|dem}}.",
        "usermessage-summary": "Etterlater en systembeskjed.",
        "usermessage-editor": "Systembudbringer",
        "watchlist": "Overvåkningsliste",
        "wlshowhidemine": "mine redigeringer",
        "wlshowhidecategorization": "sidekategorisering",
        "watchlist-options": "Alternativ for overvåkningslisten",
+       "watchlist-mark-all-visited": "Er du sikker på at du vil resette usette endringer på overvåkningslisten ved å merke alle sider som besøkte?",
        "watching": "Overvåker…",
        "unwatching": "Fjerner fra overvåkningsliste…",
        "watcherrortext": "Det oppsto en feil under endring av overvåkningsinnstillingene dine for «$1».",
        "mw-widgets-dateinput-no-date": "Ingen dato valgt",
        "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Søk etter media",
+       "mw-widgets-mediasearch-noresults": "Ingen resultater funnet.",
        "mw-widgets-titleinput-description-new-page": "siden eksisterer ikke ennå",
        "mw-widgets-titleinput-description-redirect": "omdiriger til $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Legg til en kategori …",
        "usercssispublic": "Merk: CSS-undersidene bør ikke inneholde konfidensielle data siden de kan ses av andre brukere.",
        "restrictionsfield-badip": "Ugyldig IP-adresse eller intervall: $1",
        "restrictionsfield-label": "Tillatte IP-intervaller:",
-       "restrictionsfield-help": "Én IP-adresse eller CIDR-intervall per linje. For å slå på alt, bruk <br /><code>0.0.0.0/0</code><br /><code>::/0</code>"
+       "restrictionsfield-help": "Én IP-adresse eller CIDR-intervall per linje. For å slå på alt, bruk <br /><code>0.0.0.0/0</code><br /><code>::/0</code>",
+       "pageid": "side-ID $1"
 }
index 5679677..528d6ee 100644 (file)
        "views": "Weergaven",
        "toolbox": "Hulpmiddelen",
        "tool-link-userrights": "{{GENDER:$1|Gebruikersgroepen}} wijzigen",
+       "tool-link-userrights-readonly": "{{GENDER:$1|Gebruikers}}groepen weergeven",
        "tool-link-emailuser": "Deze {{GENDER:$1|gebruiker}} e-mailen",
        "userpage": "Gebruikerspagina bekijken",
        "projectpage": "Projectpagina bekijken",
        "log-show-hide-patrol": "markeerlogboek $1",
        "log-show-hide-tag": "labellogboek $1",
        "confirm-markpatrolled-button": "OK",
+       "confirm-markpatrolled-top": "Wilt u bewerking $3 van $2 als gecontroleerd markeren?",
        "deletedrevision": "De oude versie $1 is verwijderd",
        "filedeleteerror-short": "Fout bij het verwijderen van bestand: $1",
        "filedeleteerror-long": "Er zijn fouten opgetreden bij het verwijderen van het bestand:\n\n$1",
        "htmlform-user-not-exists": "<strong>$1</strong> bestaat niet.",
        "htmlform-user-not-valid": "<strong>$1</strong> is geen geldige gebruikersnaam.",
        "logentry-delete-delete": "$1 {{GENDER:$2|heeft}} de pagina $3 verwijderd",
+       "logentry-delete-delete_redir": "$1 heeft de doorverwijzing $3 {{GENDER:$2|verwijderd}} door deze te overschrijven",
        "logentry-delete-restore": "$1 {{GENDER:$2|heeft}} de pagina $3 teruggeplaatst",
        "logentry-delete-event": "$1 {{GENDER:$2|heeft}} de zichtbaarheid van {{PLURAL:$5|een logboekregel|$5 logboekregels}} van $3 gewijzigd: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|heeft}} de zichtbaarheid van {{PLURAL:$5|een versie|$5 versies}} van de pagina $3 gewijzigd: $4",
index 3493eb7..eb65fbf 100644 (file)
        "views": "Afichatges",
        "toolbox": "Aisinas",
        "tool-link-userrights": "Modificar los gropes de {{GENDER:$1|l’utilizaire|l’utilizaira}}",
+       "tool-link-userrights-readonly": "Veire los {{GENDER:$1|gropes utilizaire}}",
        "tool-link-emailuser": "Mandar un corrièr electronic a {{GENDER:$1|l’utilizaire|l’utilizaira}}",
        "userpage": "Pagina d'utilizaire",
        "projectpage": "Pagina meta",
        "passwordreset-emaildisabled": "Las foncionalitats e-mail son estadas desactivadas sus aqueste wiki.",
        "passwordreset-username": "Nom d'utilizaire :",
        "passwordreset-domain": "Domeni:",
-       "passwordreset-capture": "Veire lo corrièl resultant ?",
-       "passwordreset-capture-help": "Se marcatz aquesta casa, lo corrièr electronic (amb lo senhal temporari) vos serà afichat al meteis temps que serà mandat a l'utilizaire.",
        "passwordreset-email": "Adreça de corrièr electronic :",
        "passwordreset-emailtitle": "Detailhs d'un compte per {{SITENAME}}",
        "passwordreset-emailtext-ip": "Qualqu'un (probablament vos, dempuèi l'adreça IP $1) a demandat una reïnicializacion de vòstre senhal per {{SITENAME}} ($4). {{PLURAL:$3|Lo compte d'utilizaire seguent es associat|Los comptes d'utilizaires seguents son associats}} a aquesta adreça de corrièr electronic :\n\n$2\n\n{{PLURAL:$3|Aqueste senhal temporari expirarà|Aquestes senhals temporaris expiraràn}} dins {{PLURAL:$5|un jorn|$5 jorns}}. Ara, vos cal vos connectar e causir un senhal novèl. Se aquesta demanda proven pas de vos, o que vos sètz remembrat de vòstre senhal inicial, e que volètz pas mai lo modificar, podètz ignorar aqueste messatge e contunhar d'utilizar vòstre ancian senhal.",
        "columns": "Colomnas :",
        "searchresultshead": "Recèrca",
        "stub-threshold": "Limit pel formatatge dels ligams d’esbòs ($1) :",
+       "stub-threshold-sample-link": "exemple",
        "stub-threshold-disabled": "Desactivat",
        "recentchangesdays": "Nombre de jorns d'afichar dins los darrièrs cambiaments :",
        "recentchangesdays-max": "(maximum $1 {{PLURAL:$1|jorn|jorns}})",
        "prefs-help-recentchangescount": "Aquò inclutz las modificacions recentas, las paginas d’istorics e los jornals.",
        "prefs-help-watchlist-token2": "Aquí la clau secreta del flux Web de vòstra lista de seguiment.\nTota persona que la coneis poirà legir vòstra lista de seguiment, doncas, la comuniquetz pas.\n[[Special:ResetTokens|Clicatz aicí se la vos cal reïnicializar]].",
        "savedprefs": "Las preferéncias son estadas salvadas.",
+       "savedrights": "Los dreits d'utilizaire de {{GENDER:$1|$1}} son estats enregistrats.",
        "timezonelegend": "Fus orari :",
        "localtime": "Ora locala :",
        "timezoneuseserverdefault": "Utilizar la valor del servidor ($1)",
        "badsig": "Signatura bruta incorrècta, verificatz vòstras balisas HTML.",
        "badsiglength": "Vòstra signatura es tròp longa.\nDeu aver, al maximum $1 caractèr{{PLURAL:$1||s}}.",
        "yourgender": "Cossí vos agrada mai d'èsser descrit ?",
-       "gender-unknown": "M'agrada mai sens detalh",
+       "gender-unknown": "Quand farà mencion de vos, lo logicial utilizarà de mots de genre neutre, quand serà possible",
        "gender-male": "Modifica de pagina del wiki",
        "gender-female": "Modifica de paginas del wiki",
        "prefs-help-gender": "Definir aquesta preferéncia es facultatiu.\nAqueste logicial utiliza sa valor per s’adreçar a vos e vos mencionar als autres en utilizant lo bon genre gramatical.\nAquesta informacion serà publica.",
        "prefs-help-prefershttps": "Aquesta preferéncia serà efectiva al moment de vòstra connexion que ven.",
        "prefs-tabs-navigation-hint": "Astúcia : Podètz utilizar las sagetas d'esquèrra e de dreita per navigar entre los onglets.",
        "userrights": "Gestion dels dreits d'utilizaire",
-       "userrights-lookup-user": "Gestion dels dreits d'utilizaire",
+       "userrights-lookup-user": "Seleccionar un utilizaire",
        "userrights-user-editname": "Entrar un nom d’utilizaire :",
-       "editusergroup": "Modificacion dels gropes d’{{GENDER:$1|utilizaires}}",
+       "editusergroup": "Cargar de gropes d’utilizaires",
        "editinguser": "Modificacion dels dreits de l’{{GENDER:$1|utilizaire|utilizaira}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Modificar los gropes de l’utilizaire",
        "saveusergroups": "Enregistrar los gropes de l’{{GENDER:$1|utilizaire|utilizaira}}",
        "userrights-reason": "Motiu :",
        "userrights-no-interwiki": "Sètz pas abilitat per modificar los dreits dels utilizaires sus d'autres wikis.",
        "userrights-nodatabase": "La basa de donadas « $1 » existís pas o es pas en local.",
-       "userrights-nologin": "Vos cal [[Special:UserLogin|vos connectar]] amb un compte d'administrator per balhar los dreits d'utilizaire.",
-       "userrights-notallowed": "Avètz pas la permission d'apondre o suprimir de dreits d'utilizaire.",
        "userrights-changeable-col": "Los gropes que podètz cambiar",
        "userrights-unchangeable-col": "Los gropes que podètz pas cambiar",
        "userrights-conflict": "Conflicte de modificacion de dreits d'utilizaire ! Relegissètz e confirmatz vòstras modificacions.",
-       "userrights-removed-self": "Avètz suprimit vòstres pròpris dreits. Del còp, podètz pas mai accedir a aquesta pagina.",
        "group": "Grop :",
        "group-user": "Utilizaires",
        "group-autoconfirmed": "Utilizaires enregistrats",
        "right-siteadmin": "Verrolhar e desverrolhar la basa de donadas",
        "right-override-export-depth": "Exportar las paginas en incluent las paginas ligadas fins a una prigondor de 5 nivèls",
        "right-sendemail": "Mandar un corrièl als autres utilizaires",
-       "right-passwordreset": "Veire los corrièrs electronics de reïnicializacion dels senhals",
        "right-applychangetags": "Aplicar [[Special:Tags|las balisas]] amb sas pròprias modificacions",
        "grant-generic": "ensemble de dreits « $1 »",
+       "grant-group-email": "Mandar un corrièr electronic",
        "grant-blockusers": "Blocar e desblocar d'utilizaires",
+       "grant-createaccount": "Crear de comptes",
+       "grant-createeditmovepage": "Crear, modificar e desplaçar de paginas",
        "grant-patrol": "Verificar las modificacions de paginas",
+       "grant-basic": "Dreits de basa",
        "newuserlogpage": "Istoric de las creacions de comptes",
        "newuserlogpagetext": "Jornal de las creacions de comptes d'utilizaires.",
        "rightslog": "Istoric de las modificacions d'estatut",
        "recentchanges-label-plusminus": "La talha de la pagina a cambiat d'aqueste nombre d’octets.",
        "recentchanges-legend-heading": "<strong>Legenda :</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (veire tanben la [[Special:NewPages|lista de las paginas novèlas]]).",
+       "recentchanges-submit": "Afichar",
        "rcnotefrom": "Çaijós {{PLURAL:$5|la modificacion efectuada|las modificacions efectuadas}} dempuèi lo <strong>$3, $4</strong> (afichadas fins a <strong>$1</strong>).",
        "rclistfrom": "Afichar las modificacions novèlas dempuèi lo $3 $2",
        "rcshowhideminor": "$1 los cambiaments menors",
        "rcshowhidemine": "$1 mas modificacions",
        "rcshowhidemine-show": "Afichar",
        "rcshowhidemine-hide": "Amagar",
+       "rcshowhidecategorization": "$1 la categorizacion de las paginas",
+       "rcshowhidecategorization-show": "Afichar",
+       "rcshowhidecategorization-hide": "Amagar",
        "rclinks": "Afichar los $1 darrièrs cambiaments efectuats al cors dels $2 darrièrs jorns<br />$3.",
        "diff": "dif",
        "hist": "ist",
        "recentchangeslinked-summary": "Aquesta pagina especiala fa veire los darrièrs cambiaments sus las paginas que son ligadas. Las paginas de [[Special:Watchlist|vòstra lista de seguimznt]] son '''en gras'''.",
        "recentchangeslinked-page": "Nom de la pagina :",
        "recentchangeslinked-to": "Afichar los cambiaments cap a las paginas ligadas al luòc de la pagina donada",
+       "recentchanges-page-added-to-category": "[[:$1]] apondut a la categoria",
        "upload": "Importar un fichièr",
        "uploadbtn": "Importar un fichièr",
        "reuploaddesc": "Anullar lo cargament e tornar al formulari.",
        "upload-too-many-redirects": "L'URL conten tròp de redireccions",
        "upload-http-error": "Una error HTTP es intervenguda : $1",
        "upload-copy-upload-invalid-domain": "La còpia dels telecargaments es pas disponibla dempuèi aqueste domeni.",
+       "upload-dialog-title": "Mandar un fichièr",
+       "upload-dialog-button-cancel": "Anullar",
+       "upload-dialog-button-back": "Retorn",
+       "upload-dialog-button-done": "Acabat",
+       "upload-dialog-button-save": "Enregistrar",
+       "upload-dialog-button-upload": "Mandar",
+       "upload-form-label-infoform-title": "Detalhs",
+       "upload-form-label-infoform-name": "Nom",
+       "upload-form-label-infoform-description": "Descripcion",
+       "upload-form-label-usage-title": "Utilizacion",
+       "upload-form-label-usage-filename": "Nom del fichièr",
+       "upload-form-label-own-work": "Soi l'autor d'aquesta òbra",
+       "upload-form-label-infoform-categories": "Categorias",
+       "upload-form-label-infoform-date": "Data",
        "backend-fail-stream": "Impossible de legir lo fichièr $1.",
        "backend-fail-backup": "Impossible de salvar lo fichièr $1.",
        "backend-fail-notexists": "Lo fichièr $1 existís pas.",
        "uploadstash-nofiles": "Avètz pas de fichièrs en cache d'impòrt.",
        "uploadstash-errclear": "La supression dels fichièrs a fracassat.",
        "uploadstash-refresh": "Actualizar la lista dels fichièrs",
+       "uploadstash-thumbnail": "afichar una miniatura",
        "invalid-chunk-offset": "Offset de segment invalid",
        "img-auth-accessdenied": "Accès refusat",
        "img-auth-nopathinfo": "PATH_INFO mancant. Vòstre servidor es pas parametrat per passar aquesta informacion.\nBenlèu que fonciona en CGI e supòrta pas img_atuh. Consultatz https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "mostrevisions": "Articles mai modificats",
        "prefixindex": "Totas las paginas que començan per…",
        "prefixindex-namespace": "Totas las paginas amb prefix (espaci de noms $1)",
+       "prefixindex-submit": "Afichar",
        "prefixindex-strip": "Levar lo prefix dins la lista",
        "shortpages": "Paginas brèvas",
        "longpages": "Paginas longas",
        "protectedpages-performer": "Proteccion de l’utilizaire",
        "protectedpages-params": "Paramètres de proteccion",
        "protectedpages-reason": "Motiu",
+       "protectedpages-submit": "Afichar las paginas",
        "protectedpages-unknown-timestamp": "Desconegut",
        "protectedpages-unknown-performer": "Utilizaire desconegut",
        "protectedtitles": "Títols protegits",
        "protectedtitlesempty": "Cap de títol es pas actualament protegit amb aquestes paramètres.",
+       "protectedtitles-submit": "Afichar los títols",
        "listusers": "Lista dels participants",
        "listusers-editsonly": "Far veire sonque los utilizaires qu'an al mens una contribucion",
        "listusers-creationsort": "Triar per data de creacion",
        "usereditcount": "$1 {{PLURAL:$1|cambiament|cambiaments}}",
        "usercreated": "{{GENDER:$3|Creat}} lo $1 a $2",
        "newpages": "Paginas novèlas",
+       "newpages-submit": "Afichar",
        "newpages-username": "Nom d'utilizaire :",
        "ancientpages": "Articles mai ancians",
        "move": "Renomenar",
        "apihelp-no-such-module": "Lo modul « $1 » es introbable.",
        "apisandbox": "Nauc de sabla API",
        "apisandbox-api-disabled": "API es desactivat sus aqueste site.",
+       "apisandbox-fullscreen": "Espandir lo panèl",
+       "apisandbox-unfullscreen": "Afichar la pagina",
        "apisandbox-submit": "Far la demanda",
        "apisandbox-reset": "Escafar",
+       "apisandbox-retry": "Ensajar tornarmai",
+       "apisandbox-helpurls": "Ligams d'ajuda",
        "apisandbox-examples": "Exemples",
+       "apisandbox-dynamic-parameters": "Paramètres suplementaris",
+       "apisandbox-dynamic-parameters-add-label": "Apondon del paramètre",
+       "apisandbox-dynamic-parameters-add-placeholder": "Nom del paramètre",
+       "apisandbox-deprecated-parameters": "Paramètres obsolèts",
        "apisandbox-results": "Resultats",
        "apisandbox-request-url-label": "Requèsta URL :",
        "apisandbox-request-time": "Durada de la demanda : {{PLURAL:$1|$1 ms}}",
+       "apisandbox-continue": "Contunhar",
+       "apisandbox-continue-clear": "Escafar",
        "booksources": "Obratges de referéncia",
        "booksources-search-legend": "Recercar demest d'obratges de referéncia",
        "booksources-isbn": "ISBN :",
        "specialloguserlabel": "Autor :",
        "speciallogtitlelabel": "Cibla (títol o {{ns:user}}:nom d'utilizaire) :",
        "log": "Jornals",
+       "logeventslist-submit": "Afichar",
        "all-logs-page": "Totas las operacions publicas",
        "alllogstext": "Afichatge combinat de totes los jornals de {{SITENAME}}.\nPodètz restrénher la vista en seleccionant un tipe de jornal, un nom d’utilizaire (cassa sensibla) o una pagina ciblada (idem).",
        "logempty": "I a pas res dins l’istoric per aquesta pagina.",
        "log-title-wildcard": "Recercar de títols que començan per aqueste tèxte",
        "showhideselectedlogentries": "Afichar/amagar las entradas de jornal seleccionadas",
+       "checkbox-select": "Seleccionar : $1",
+       "checkbox-all": "Tot",
+       "checkbox-none": "Pas cap",
+       "checkbox-invert": "Inversar",
        "allpages": "Totas las paginas",
        "nextpage": "Pagina seguenta ($1)",
        "prevpage": "Pagina precedenta ($1)",
        "cachedspecial-viewing-cached-ttl": "Visualizatz una version d'aquesta pagina mesa en cache, que pòt èsser datada d’al mai $1.",
        "cachedspecial-refresh-now": "Veire lo mai recent.",
        "categories": "Categorias",
+       "categories-submit": "Afichar",
        "categoriespagetext": "{{PLURAL:$1|La categoria seguenta es utilizada|Las categorias seguentas son utilizadas}} per de paginas o de fichièrs.\n[[Special:UnusedCategories|Las categorias inutilizadas]] son pas afichadas aicí.\nVejatz tanben [[Special:WantedCategories|las categorias demandadas]].",
        "categoriesfrom": "Afichar las categorias que començan a :",
        "deletedcontributions": "Contribucions suprimidas d’un utilizaire",
        "listgrouprights-namespaceprotection-header": "Restriccions d'espaci de noms",
        "listgrouprights-namespaceprotection-namespace": "Espaci de noms",
        "listgrouprights-namespaceprotection-restrictedto": "Dreit(s) que permet(on) a l'utilizaire de modificar",
+       "listgrants": "Autorizacions",
+       "listgrants-grant": "Acordar",
+       "listgrants-rights": "Dreits",
        "trackingcategories": "Categorias de seguiment",
        "trackingcategories-msg": "Categoria de seguiment",
        "trackingcategories-name": "Nom del messatge",
        "wlheader-showupdated": "Las paginas que son estadas modificadas dempuèi vòstra darrièra visita son afichadas en '''gras'''.",
        "wlnote": "Çaijós {{PLURAL:$1|figura la darrièra modificacion efectuada|figuran las <strong>$1</strong> darrièras modificacions efectuadas}} pendent {{PLURAL:$2|la darrièra ora|las <strong>$2</strong> darrièras oras}}, dempuèi $3, $4.",
        "wlshowlast": "Far veire las darrièras $1 oras, los darrièrs $2 jorns",
+       "watchlist-hide": "Amagar",
+       "watchlist-submit": "Afichar",
        "wlshowhideminor": "cambiaments menors",
+       "wlshowhidebots": "Robòts",
+       "wlshowhideliu": "utilizaires enregistrats",
+       "wlshowhideanons": "utilizaires anonims",
+       "wlshowhidepatr": "modificacions repassadas",
+       "wlshowhidemine": "mas modificacions",
        "watchlist-options": "Opcions de la lista de seguiment",
        "watching": "Seguit...",
        "unwatching": "Fin del seguit...",
        "delete-confirm": "Escafar «$1»",
        "delete-legend": "Escafar",
        "historywarning": "<strong>Atencion :</strong> la pagina que sètz a mand de suprimir a un istoric amb $1 {{PLURAL:$1|version|versions}} :",
+       "historyaction-submit": "Afichar",
        "confirmdeletetext": "Sètz a mand de suprimir una pagina o un fichièr, e mai totas sas versions anterioras istorizadas.\nConfirmatz qu'es plan çò que volètz far, que ne comprenètz las consequéncias e que fasètz aquò en acòrdi amb las [[{{MediaWiki:Policy-url}}|règlas intèrnas]].",
        "actioncomplete": "Accion efectuada",
        "actionfailed": "L’accion a fracassat",
        "changecontentmodel-title-label": "Títol de la pagina",
        "changecontentmodel-model-label": "Novèl modèl de contengut",
        "changecontentmodel-reason-label": "Motiu :",
+       "changecontentmodel-submit": "Modificar",
        "logentry-contentmodel-change-revertlink": "restablir",
        "logentry-contentmodel-change-revert": "restablir",
        "protectlogpage": "Istoric de las proteccions",
        "ipb-unblock": "Desblocar un compte d'utilizaire o una adreça IP",
        "ipb-blocklist": "Vejatz los blocatges existents",
        "ipb-blocklist-contribs": "Contribucions per {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "$1 restant",
        "unblockip": "Desblocar un utilizaire o una adreça IP",
        "unblockiptext": "Utilizatz lo formulari çaijós per restablir l'accès en escritura\na partir d'una adreça IP precedentament blocada.",
        "ipusubmit": "Suprimir aqueste blocatge",
        "pageinfo-article-id": "Numèro de la pagina",
        "pageinfo-language": "Lenga del contengut de la pagina",
        "pageinfo-content-model": "Modèl de contengut de la pagina",
+       "pageinfo-content-model-change": "modificar",
        "pageinfo-robot-policy": "Indexacion per robòts",
        "pageinfo-robot-index": "Autorizada",
        "pageinfo-robot-noindex": "Interdicha",
        "pageinfo-category-pages": "Nombre de paginas",
        "pageinfo-category-subcats": "Nombre de soscategorias",
        "pageinfo-category-files": "Nombre de fichièrs",
+       "pageinfo-user-id": "ID de l'utilizaire",
        "markaspatrolleddiff": "Marcar coma essent pas un vandalisme",
        "markaspatrolledtext": "Marcar aqueste article coma pas vandalizat",
        "markedaspatrolled": "Marcat coma pas vandalizat",
        "patrol-log-header": "Vaquí un jornal de las versions patrolhadas.",
        "log-show-hide-patrol": "$1 l'istoric de las relecturas",
        "log-show-hide-tag": "$1 lo jornal de las balisas",
+       "confirm-markpatrolled-button": "D'acòrdi",
        "deletedrevision": "La version anciana $1 es estada suprimida.",
        "filedeleteerror-short": "Error al moment de la supression del fichièr : $1",
        "filedeleteerror-long": "D'errors son estadas rencontradas al moment de la supression del fichièr :\n\n$1",
        "confirm-watch-top": "Apondre aquesta pagina a vòstra lista de seguiment ?",
        "confirm-unwatch-button": "D'acòrdi",
        "confirm-unwatch-top": "Levar aquesta pagina de vòstra lista de seguiment ?",
+       "confirm-rollback-button": "D'acòrdi",
        "colon-separator": "&nbsp;:&#32;",
        "quotation-marks": "« $1 »",
        "imgmultipageprev": "← pagina precedenta",
        "watchlisttools-edit": "Veire e modificar la lista de seguiment",
        "watchlisttools-raw": "Modificar la lista (mòde brut)",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|discussion]])",
+       "timezone-local": "Local",
        "duplicate-defaultsort": "Atencion : La clau de triada per defaut « $2 » espotís la mai recenta « $1 ».",
        "duplicate-displaytitle": "<strong>Atencion :</strong> Lo títol d'afichatge «$2» remplaça l'ancian títol d'afichatge «$1».",
        "version": "Version",
        "redirect-page": "ID de pagina",
        "redirect-revision": "Revision de la pagina",
        "redirect-file": "Nom del fichièr",
+       "redirect-logid": "ID de jornal",
        "redirect-not-exists": "Valor pas trobada",
        "fileduplicatesearch": "Recèrca dels fichièrs en doble",
        "fileduplicatesearch-summary": "Recèrca de las còpias de fichièrs identics d'aprèp lor emprenta de hachatge.",
        "tags-actions-header": "Accions",
        "tags-active-yes": "Òc",
        "tags-active-no": "Non",
-       "tags-source-extension": "Definida per una extension",
+       "tags-source-extension": "Definit pel logicial",
        "tags-source-manual": "Aplicada manualament pels utilizaires e los bòts",
        "tags-source-none": "Obsolèt",
        "tags-edit": "modificar",
        "htmlform-cloner-create": "Apondre encara",
        "htmlform-cloner-delete": "Suprimir",
        "htmlform-cloner-required": "Una valor al mens es obligatòria.",
+       "htmlform-date-placeholder": "AAAA-MM-JJ",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "AAAA-MM-JJ HH:MM:SS",
        "logentry-delete-delete": "$1 {{GENDER:$2|a suprimit}} la pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restablit}} la pagina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a modificat}} la visibilitat {{PLURAL:$5|d'un eveniment del jornal|de $5 eveniments del jornal}} sus $3 : $4",
        "mediastatistics-header-text": "Textual",
        "mediastatistics-header-executable": "Executables",
        "mediastatistics-header-archive": "Formats compressats",
+       "mediastatistics-header-total": "Totes los fichièrs",
        "json-error-state-mismatch": "JSON invalid o mal format",
        "json-error-syntax": "Error de sintaxi",
        "headline-anchor-title": "Ligam cap a aquesta seccion",
        "randomrootpage": "Pagina raiç aleatòria",
        "log-action-filter-rights": "Tipe de cambiament de dreits :",
        "log-action-filter-suppress": "Tipe de supression :",
+       "log-action-filter-all": "Tot",
+       "log-action-filter-block-block": "Blocatge",
+       "log-action-filter-block-unblock": "Desblocar",
+       "authmanager-email-label": "Corrièr electronic",
+       "authmanager-email-help": "Adreça de corrièr electronic",
+       "authmanager-realname-label": "Nom vertadièr",
+       "authmanager-realname-help": "Nom real de l'utilizaire",
+       "authprovider-resetpass-skip-label": "Sautar",
        "changecredentials": "Modificar las informacions d’identificacion"
 }
index 0156bdb..9c135cb 100644 (file)
@@ -50,7 +50,7 @@
        "tog-enotifminoredits": "ਸਫ਼ਿਆਂ ਅਤੇ ਫ਼ਾਈਲਾਂ ਦੀਆਂ ਛੋਟੀਆਂ ਤਬਦੀਲੀਆਂ ਲਈ ਵੀ ਮੈਨੂੰ ਈ-ਮੇਲ ਭੇਜੋ",
        "tog-enotifrevealaddr": "ਇਤਲਾਹ ਦੇਣ ਵਾਲੀਆਂ ਈ-ਮੇਲਾਂ ਵਿੱਚ ਮੇਰਾ ਈ-ਮੇਲ ਪਤਾ ਜ਼ਾਹਰ ਕਰੋ",
        "tog-shownumberswatching": "ਨਜ਼ਰ ਰੱਖ ਰਹੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀ ਗਿਣਤੀ ਵਖਾਓ",
-       "tog-oldsig": "ਮੌਜੂਦਾ ਦਸਤਖ਼ਤ:",
+       "tog-oldsig": "ਤà©\81ਹਾਡà©\87 à¨®à©\8cà¨\9cà©\82ਦਾ à¨¦à¨¸à¨¤à¨\96਼ਤ:",
        "tog-fancysig": "ਦਸਤਖ਼ਤ ਨੂੰ ਬਤੌਰ ਵਿਕੀਲਿਖਤ ਮੰਨੋ (ਬਿਨਾਂ ਆਟੋਮੈਟਿਕ ਲਿੰਕ)",
        "tog-uselivepreview": "ਮੌਜੂਦਾ ਝਲਕ ਵਰਤੋ",
        "tog-forceeditsummary": "ਜਦੋਂ ਮੈਂ ਖ਼ਾਲੀ ਸੋਧ ਸਾਰ ਦੇਵਾਂ ਤਾਂ ਮੈਨੂੰ ਆਗਾਹ ਕਰੋ",
        "talk": "ਚਰਚਾ",
        "views": "ਵਿਊ",
        "toolbox": "ਸੰਦ",
+       "tool-link-emailuser": "ਇਹ {{GENDER:$1|ਯੂਜ਼ਰ}} ਨੂੰ ਈਮੇਲ ਭੇਜੋ",
        "userpage": "ਵਰਤੋਂਕਾਰ ਸਫ਼ਾ ਵੇਖੋ",
        "projectpage": "ਪ੍ਰੋਜੈਕਟ ਸਫ਼ਾ ਵੇਖੋ",
        "imagepage": "ਫਾਇਲ ਸਫ਼ਾ ਵੇਖੋ",
        "createacct-yourpasswordagain-ph": "ਪਾਸਵਰਡ ਫੇਰ ਦਿਉ",
        "userlogin-remembermypassword": "ਮੈਨੂੰ ਲਾਗਇਨ ਰੱਖੋ",
        "userlogin-signwithsecure": "ਸੁਰੱਖਿਅਤ ਕੁਨੈਕਸ਼ਨ ਰੱਖੋ",
+       "cannotcreateaccount-title": "ਖਾਤੇ ਨਹੀਂ ਬਣ ਸਕੇ",
        "yourdomainname": "ਤੁਹਾਡਾ ਡੋਮੇਨ:",
        "password-change-forbidden": "ਇਸ ਵਿਕੀ ਤੇ ਤੁਸੀਂ ਪਾਸਵਰਡ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।",
        "externaldberror": "ਜਾਂ ਤਾਂ ਪ੍ਰਮਾਣਕੀ ਡਾਟਾਬੇਸ ਦੋਸ਼ ਆਇਆ ਹੈ ਜਾਂ ਤੁਹਾਨੂੰ ਆਪਣੇ ਬਾਹਰੀ ਖਾਤੇ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।",
        "passwordreset-emaildisabled": "ਇਹ ਵਿਕਿ ਉੱਤੇ ਈਮੇਲ ਫੀਚਰ ਬੰਦ ਕੀਤਾ ਹੋਇਆ ਹੈ।",
        "passwordreset-username": "ਵਰਤੋਂਕਾਰ ਨਾਂ:",
        "passwordreset-domain": "ਡੋਮੇਨ:",
-       "passwordreset-capture": "ਨਤੀਜੇ ਵਜੋਂ ਬਣਦੀ ਈਮੇਲ ਵੇਖੋ?",
-       "passwordreset-capture-help": "ਜੇਕਰ ਤੁਸੀਂ ਇਹ ਬਕਸਾ ਸਹੀ ਕਰਦੇ ਹੋ ਤਾਂ ਇਹ ਈਮੇਲ (ਅਸਥਾਈ ਪਾਸਵਰਡ ਸਮੇਤ) ਤੁਹਾਨੂੰ ਵਿਖਾਈ ਜਾਵੇਗੀ ਅਤੇ ਵਰਤੋਂਕਾਰ ਨੂੰ ਵੀ ਭੇਜੀ ਜਾਵੇਗੀ।",
        "passwordreset-email": "ਈਮੇਲ ਐਡਰੈੱਸ:",
        "passwordreset-emailtitle": "{{SITENAME}} ਤੇ ਖਾਤੇ ਦੀ ਜਾਣਕਾਰੀ",
        "passwordreset-emailtext-ip": "ਕਿਸੇ ਨੇ (ਸ਼ਾਇਦ ਤੁਸੀਂ, IP ਪਤਾ $1 ਤੋਂ) {{SITENAME}}\n($4) ਲਈ ਖਾਤਾ ਤਫ਼ਸੀਲ ਯਾਦ-ਦਹਾਨੀ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ। ਇਹ {{PLURAL:\n$3|ਖਾਤਾ ਇਸ ਈ-ਮੇਲ ਪਤੇ ਨਾਲ਼ ਜੁੜਿਆ ਹੈ|ਖਾਤੇ ਇਸ ਈ-ਮੇਲ ਪਤੇ ਨਾਲ਼ ਜੁੜੇ ਹਨ}}:\n$2\n\nਇਹ ਆਰਜ਼ੀ ਪਾਸਵਰਡ\n{{PLURAL:$5|ਇੱਕ ਦਿਨ|$5 ਦਿਨਾਂ}} ਵਿਚ ਖ਼ਤਮ ਹੋ {{PLURAL:$3|ਜਾਵੇਗਾ|ਜਾਣਗੇ}}।\nਤੁਹਾਨੂੰ ਹੁਣੇ ਲਾਗਇਨ ਕਰਕੇ ਨਵਾਂ ਪਾਸਵਰਡ ਬਣਾਉਣਾ ਚਾਹੀਦਾ ਹੈ। ਜੇ ਕਿਸੇ ਹੋਰ ਨੇ ਇਹ ਬੇਨਤੀ ਕੀਤੀ ਸੀ ਜਾਂ ਜੇ ਤੁਹਾਨੂੰ ਆਪਣਾ ਪਾਸਵਰਡ ਯਾਦ ਹੈ ਅਤੇ ਤੁਸੀਂ ਇਸਨੂੰ ਬਦਲਣਾ ਨਹੀਂ ਚਾਹੁੰਦੇ ਤਾਂ ਤੁਸੀਂ ਇਸ ਸੁਨੇਹੇ ਨੂੰ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰ ਕੇ ਆਪਣਾ ਪੁਰਾਣਾ ਪਾਸਵਰਡ ਵਰਤਣਾ ਜਾਰੀ ਰੱਖ ਸਕਦੇ ਹੋ।",
        "searchprofile-advanced-tooltip": "ਆਪਣੀਆਂ ਬਣਾਈਆਂ ਨਾਂ-ਥਾਂਵਾਂ ਵਿੱਚ ਖੋਜੋ",
        "search-result-size": "$1 ({{PLURAL:$2|1 ਸ਼ਬਦ|$2 ਸ਼ਬਦ}})",
        "search-result-category-size": "{{PLURAL:$1|1 ਮੈਂਬਰ|$1 ਮੈਂਬਰ}} ({{PLURAL:$2|1 ਉਪਸ਼੍ਰੇਣੀ|$2 ਉਪਸ਼੍ਰੇਣੀਆਂ}}, {{PLURAL:$3|1 ਫ਼ਾਈਲ|$3 ਫ਼ਾਈਲਾਂ}})",
-       "search-redirect": "($1 à¨°à©\80ਡਿਰੈਕਟ)",
+       "search-redirect": "($1 à¨¤à©\8bà¨\82 à¨°à©\80ਡਾà¨\87ਰੈਕਟ)",
        "search-section": "(ਹਿੱਸਾ $1)",
        "search-category": "(ਸ਼੍ਰੇਣੀ $1)",
        "search-file-match": "(ਫ਼ਾਈਲ ਦੀ ਸਮੱਗਰੀ ਨਾਲ਼ ਰਲ਼ਦਾ-ਮਿਲ਼ਦਾ)",
        "userrights-reason": "ਕਾਰਨ:",
        "userrights-no-interwiki": "ਤੁਹਾਨੂੰ ਦੂਜੇ ਵਿਕੀਆਂ ਤੇ ਮੈਂਬਰਾਂ ਦੇ ਹੱਕਾਂ ਵਿਚ ਤਬਦੀਲੀ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।",
        "userrights-nodatabase": "ਡੈਟਾਬੇਸ $1 ਮੌਜੂਦ ਨਹੀਂ ਜਾਂ ਮਕਾਮੀ ਨਹੀਂ ਹੈ।",
-       "userrights-notallowed": "ਤੁਹਾਡੇ ਕੋਲ਼ ਵਰਤੋਂਕਾਰ ਹੱਕ ਦੇਣ ਜਾਂ ਖੋਹਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।",
        "userrights-changeable-col": "ਉਹ ਸਮੂਹ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਬਦਲ ਸਕਦੇ ਹੋ",
        "userrights-unchangeable-col": "ਉਹ ਸਮੂਹ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਬਦਲ ਨਹੀਂ ਸਕਦੇ",
        "group": "ਟੋਲੀ:",
        "right-userrights-interwiki": "ਦੂਜੇ ਵਿਕੀਆਂ ਤੇ ਮੈਂਬਰਾਂ ਦੇ ਮੈਂਬਰ ਹੱਕਾਂ ਵਿਚ ਸੋਧ ਕਰਨਾ",
        "right-siteadmin": "ਡੈਟਾਬੇਸ ਨੂੰ ਤਾਲਾ ਲਾਉਣਾ ਤੇ ਖੋਲ੍ਹਣਾ",
        "right-sendemail": "ਦੂਜੇ ਮੈਂਬਰਾਂ ਨੂੰ ਈ-ਮੇਲ ਭੇਜਣਾ",
-       "right-passwordreset": "ਪਾਸਵਰਡ ਮੁੜ-ਸੈੱਟ ਈਮੇਲ ਵੇਖੋ",
        "grant-group-email": "ਈ-ਮੇਲ ਭੇਜੋ",
        "grant-group-customization": "ਅਨੁਕੂਲਨ ਅਤੇ ਪਸੰਦਾੰ",
        "grant-createaccount": "ਖਾਤੇ ਬਣਾਓ",
        "feedback-submit": "ਹਵਾਲੇ ਕਰੋ",
        "feedback-thanks-title": "ਧੰਨਵਾਦ!",
        "feedback-useragent": "ਉਪਭੋਗੀ ਏਜੰਟ:",
-       "searchsuggest-search": "ਖੋਜ",
+       "searchsuggest-search": "ਖੋਜ {{SITENAME}}",
        "api-error-badaccess-groups": "ਤੁਹਾਨੂੰ ਇਸ ਵਿਕੀ ਉੱਤੇ ਫ਼ਾਈਲਾਂ ਅੱਪਲੋਡ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।",
        "api-error-badtoken": "ਅੰਦਰੂਨੀ ਦੋਸ਼: ਗ਼ਲਤ ਟੋਕਨ",
        "api-error-empty-file": "ਤੁਹਾਡੇ ਵਲੋਂ ਦਿੱਤੀ ਫ਼ਾਈਲ ਖ਼ਾਲੀ ਸੀ।",
index d3f89d6..4197703 100644 (file)
        "search-external": "Wyszukiwanie zewnętrzne",
        "searchdisabled": "Wyszukiwanie w {{GRAMMAR:MS.lp|{{SITENAME}}}} zostało wyłączone.\nW międzyczasie możesz skorzystać z wyszukiwania Google.\nJednak informacje o treści {{GRAMMAR:D.lp|{{SITENAME}}}} mogą być w Google nieaktualne.",
        "search-error": "Wystąpił błąd podczas wyszukiwania: $1",
+       "search-warning": "Podczas wyszukiwania wystąpił błąd: $1",
        "preferences": "Preferencje",
        "mypreferences": "Preferencje",
        "prefs-edits": "Liczba edycji:",
        "action-userrights-interwiki": "edytowania uprawnień użytkowników na innych witrynach wiki",
        "action-siteadmin": "blokowania i odblokowywania bazy danych",
        "action-sendemail": "wysyłania e-maili",
+       "action-editmyoptions": "edycja swoich preferencji",
        "action-editmywatchlist": "edycji swojej listy obserwowanych stron",
        "action-viewmywatchlist": "zobaczenia swojej listy obserwowanych stron",
        "action-viewmyprivateinfo": "zobaczenia swoich prywatnych danych",
        "activeusers-count": "w ciągu {{PLURAL:$3|ostatniego dnia|ostatnich $3 dni}} {{GENDER:$2|wykonał|wykonała|wykonał}} $1 {{PLURAL:$1|operację|operacje|operacji}}",
        "activeusers-from": "Wyświetl użytkowników, zaczynając od:",
        "activeusers-groups": "Wyświetl użytkowników należących do grup:",
+       "activeusers-excludegroups": "Wyklucz użytkowników należących do grup:",
        "activeusers-noresult": "Nie odnaleziono żadnego użytkownika.",
        "activeusers-submit": "Wyświetl aktywnych użytkowników",
        "listgrouprights": "Uprawnienia grup użytkowników",
        "rollbacklinkcount-morethan": "cofnij więcej niż $1 {{PLURAL:$1|edycję|edycje|edycji}}",
        "rollbackfailed": "Nie udało się cofnąć zmiany",
        "rollback-missingparam": "W żądaniu nie podano wymaganych parametrów.",
+       "rollback-missingrevision": "Nie udało się załadować informacji o wersji.",
        "cantrollback": "Nie można cofnąć edycji tego autora, ponieważ jest jedynym autorem tej strony.",
        "alreadyrolled": "Nie można dla strony [[:$1|$1]] cofnąć ostatniej zmiany, którą wykonał [[User:$2|$2]] ([[User talk:$2|dyskusja]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]).\nKtoś inny zdążył już to zrobić lub wprowadził własne poprawki do treści strony.\n\nAutorem ostatniej zmiany jest teraz [[User:$3|$3]] ([[User talk:$3|dyskusja]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Edycję opisał: <em>$1</em>.",
        "changecontentmodel-emptymodels-text": "Zawartość [[:$1]] nie może być przekształcona do żadnego typu.",
        "log-name-contentmodel": "Rejestr zmian modelu zawartości",
        "log-description-contentmodel": "Wydarzenia związane z modelami zawartości stron",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|utworzył|utworzyła}} stronę $3 o niebędącym domyślnym modelu zawartości \"$5\"",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|zmienił|zmieniła|zmienił(a)}} model zawartości strony $3 z „$4” na „$5”",
        "logentry-contentmodel-change-revertlink": "Przywróć",
        "logentry-contentmodel-change-revert": "Przywróć",
        "undeletehistorynoadmin": "Ta strona została usunięta.\nPrzyczyna usunięcia podana jest w podsumowaniu poniżej, razem z danymi użytkownika, który edytował stronę przed usunięciem.\nSama treść usuniętych wersji jest dostępna jedynie dla administratorów.",
        "undelete-revision": "Usunięto wersję $1 (z $5 $4) autorstwa $3:",
        "undeleterevision-missing": "Nieprawidłowa lub brakująca wersja.\nMożesz mieć zły link lub wersja mogła zostać odtworzona lub usunięta z archiwum.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Jedna wersja|$1 wersji}} nie może zostać przywróconych, ponieważ {{PLURAL:$1|jej|ich}} <code>rev_id</code> jest już w użyciu.",
        "undelete-nodiff": "Nie znaleziono poprzednich wersji.",
        "undeletebtn": "Odtwórz",
        "undeletelink": "pokaż lub odtwórz",
        "movelogpagetext": "Lista stron, które ostatnio zostały przeniesione.",
        "movesubpage": "{{PLURAL:$1|Podstrona|Podstrony}}",
        "movesubpagetext": "Ta strona posiada $1 {{PLURAL:$1|podstronę|podstrony|podstron}}:",
+       "movesubpagetalktext": "Odpowiadająca strona dyskusji ma $1 {{PLURAL:$1|podstronę|podstron}} pokazanych poniżej.",
        "movenosubpage": "Ta strona nie posiada podstron.",
        "movereason": "Powód:",
        "revertmove": "cofnij",
        "pageinfo-watchers": "Liczba obserwujących",
        "pageinfo-visiting-watchers": "Liczba obserwujących stronę, którzy ostatnio ją odwiedzili",
        "pageinfo-few-watchers": "Mniej niż $1 {{PLURAL:$1|obserwujący|obserwujących}}",
+       "pageinfo-few-visiting-watchers": "Może być lub nie użytkownik, który odwiedza ostatnie edycje",
        "pageinfo-redirects-name": "Liczba przekierowań do tej strony",
        "pageinfo-subpages-name": "Liczba podstron tej strony",
        "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|przekierowanie|przekierowania|przekierowań}}; $3 {{PLURAL:$3|bez przekierowania|bez przekierowań|bez przekierowań}})",
        "confirmemail_body_set": "Ktoś łącząc się z komputera o adresie IP $1\nustawił w {{GRAMMAR:MS.lp|{{SITENAME}}}} dla konta „$2” adres e‐mail na ten właśnie.\n\nAby potwierdzić, że to Ty {{GENDER:|ustawiłeś|ustawiłaś}} adres otwórz w swojej\nprzeglądarce ten link:\n\n$3\n\nJeśli *nie* jest to Twoje konto, otwórz w swojej przeglądarce\nponiższy link, aby anulować potwierdzenie adresu e‐mail:\n\n$5\n\nKod zawarty w linku straci ważność $4.",
        "confirmemail_invalidated": "Potwierdzenie adresu e‐mail zostało anulowane",
        "invalidateemail": "Anulowanie potwierdzenia adresu e‐mail",
+       "notificationemail_subject_changed": "Został zmieniony zarejestrowany adres e-mail na {{SITENAME}}",
        "scarytranscludedisabled": "[Transkluzja przez interwiki jest wyłączona]",
        "scarytranscludefailed": "[Pobranie szablonu dla $1 nie powiodło się]",
        "scarytranscludefailed-httpstatus": "[Pobranie szablonu dla $1 nie powiodło się: HTTP $2]",
        "mw-widgets-dateinput-no-date": "Nie wybrano daty",
        "mw-widgets-dateinput-placeholder-day": "RRRR-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "RRRR-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Szukaj multimediów",
+       "mw-widgets-mediasearch-noresults": "Nie znaleziono wyników.",
        "mw-widgets-titleinput-description-new-page": "strona jeszcze nie istnieje",
        "mw-widgets-titleinput-description-redirect": "przekierowanie do $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Dodaj kategorię...",
index 1830765..368907c 100644 (file)
                        "Cristofer Alves",
                        "Tark",
                        "O Andarilho",
-                       "Bruno.S.Alves 270"
+                       "Bruno.S.Alves 270",
+                       "!Silent"
                ]
        },
        "tog-underline": "Sublinhar links:",
        "feedback-thanks": "Obrigado! O seu comentário foi adicionado à página \"[$2 $1]\".",
        "feedback-thanks-title": "Obrigado!",
        "feedback-useragent": "Agente de usuário:",
-       "searchsuggest-search": "Pesquisa",
+       "searchsuggest-search": "Pesquisar em {{SITENAME}}",
        "searchsuggest-containing": "páginas contendo…",
        "api-error-badaccess-groups": "Você não tem permissão para enviar arquivos para este wiki.",
        "api-error-badtoken": "Erro interno: token inválido.",
index 4a75635..72d119e 100644 (file)
@@ -72,7 +72,8 @@
                        "Luan",
                        "Gato Preto",
                        "Jdforrester",
-                       "Mansil"
+                       "Mansil",
+                       "Ngl2016"
                ]
        },
        "tog-underline": "Sublinhar ligações:",
        "search-external": "Pesquisa externa",
        "searchdisabled": "Foi impossibilitada a realização de pesquisas na wiki {{SITENAME}}.\nEntretanto, pode realizar pesquisas através do Google.\nNote, no entanto, que a indexação da wiki {{SITENAME}} neste motor de busca pode estar desatualizada.",
        "search-error": "Um erro ocorreu enquanto se efectuava a pesquisa: $1",
+       "search-warning": "Ocorreu um aviso ao pesquisar: $1",
        "preferences": "Preferências",
        "mypreferences": "Preferências",
        "prefs-edits": "Número de edições:",
        "userrights-user-editname": "Introduza um nome de utilizador(a):",
        "editusergroup": "Carregar grupos do utilizador",
        "editinguser": "A modificar os privilégios {{GENDER:$1|do utilizador|da utilizadora|do(a) utilizador(a)}}  <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Editar grupos {{GENDER:$1|do utilizador|da utilizadora|do(a) utilizador(a)}}",
+       "viewinguserrights": "A ver os privilégios {{GENDER:$1|do utilizador|da utilizadora}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Editar grupos {{GENDER:$1|do utilizador|da utilizadora}}",
+       "userrights-viewusergroup": "Ver grupos do utilizador",
        "saveusergroups": "Gravar grupos {{GENDER:$1|do utilizador|da utilizadora|do(a) utilizador(a)}}",
        "userrights-groupsmember": "Membro de:",
        "userrights-groupsmember-auto": "Membro implícito de:",
        "action-upload_by_url": "enviar este ficheiro através de um URL",
        "action-writeapi": "utilizar o modo de escrita da API",
        "action-delete": "eliminar esta página",
-       "action-deleterevision": "eliminar esta edição",
-       "action-deletedhistory": "ver o histórico de edições eliminadas desta página",
+       "action-deleterevision": "eliminar revisões",
+       "action-deletelogentry": "eliminar entradas de registo",
+       "action-deletedhistory": "ver o histórico de edições eliminadas de uma página",
+       "action-deletedtext": "ver o texto de uma revisão eliminada",
        "action-browsearchive": "pesquisar páginas eliminadas",
-       "action-undelete": "restaurar esta página",
-       "action-suppressrevision": "rever e restaurar esta edição oculta",
+       "action-undelete": "restaurar páginas",
+       "action-suppressrevision": "rever e restaurar edições ocultas",
        "action-suppressionlog": "ver este registo privado",
        "action-block": "impedir este utilizador de editar",
        "action-protect": "alterar os níveis de proteção desta página",
        "action-userrights-interwiki": "editar privilégios de utilizadores de outras wikis",
        "action-siteadmin": "bloquear ou desbloquear a base de dados",
        "action-sendemail": "enviar correio eletrónico",
+       "action-editmyoptions": "editar as suas preferências",
        "action-editmywatchlist": "editar a sua lista de páginas vigiadas",
        "action-viewmywatchlist": "ver a sua lista de páginas vigiadas",
        "action-viewmyprivateinfo": "ver a sua informação privada",
        "emailccsubject": "Cópia da sua mensagem para $1: $2",
        "emailsent": "Mensagem enviada",
        "emailsenttext": "A sua mensagem foi enviada.",
-       "emailuserfooter": "Esta mensagem foi {{GENDER:$1|enviada}} por $1 para {{GENDER:$2|$2}} através da opção \"{{int:emailuser}}\" da wiki {{SITENAME}}.",
+       "emailuserfooter": "Esta mensagem foi enviada {{GENDER:$1|pelo utilizador|pela utilizadora}} $1 para {{GENDER:$2|$2}} através da opção \"{{int:emailuser}}\" da wiki {{SITENAME}}. {{GENDER:$2|A sua}} resposta será enviada diretamente para {{GENDER:$1|o|a}} remetente original, e irá revelar-lhe {{GENDER:$2|o seu}} endereço de correio eletrónico.",
        "usermessage-summary": "Deixar mensagem de sistema.",
        "usermessage-editor": "Editor de mensagens de sistema",
        "watchlist": "Páginas vigiadas",
        "mw-widgets-dateinput-no-date": "Não foi selecionada nenhuma data",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Procurar ficheiros multimédia",
+       "mw-widgets-mediasearch-noresults": "Não foram encontrados resultados.",
        "mw-widgets-titleinput-description-new-page": "a página ainda não existe.",
        "mw-widgets-titleinput-description-redirect": "redirecionar para $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Adicionar uma categoria...",
        "usercssispublic": "Nota: As subpáginas de CSS não devem conter dados confidenciais porque podem ser vistas por outros utilizadores.",
        "restrictionsfield-badip": "Endereço IP (ou gama de endereços IP) inválido: $1",
        "restrictionsfield-label": "Gamas de endereços IP permitidas:",
-       "restrictionsfield-help": "Um endereço IP ou uma gama CIDR por linha. Para ativar todos,\nuse<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Um endereço IP ou uma gama CIDR por linha. Para ativar todos,\nuse<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "identificador de página $1"
 }
index 7327012..e4cfb10 100644 (file)
        "october-gen": "{{doc-months|10|genitive}}\n{{Identical|October}}",
        "november-gen": "{{doc-months|11|genitive}}\n{{Identical|November}}",
        "december-gen": "{{doc-months|12|genitive}}\n{{Identical|December}}",
-       "jan": "{{doc-months|1|short}}",
-       "feb": "{{doc-months|2|short}}",
-       "mar": "{{doc-months|3|short}}",
-       "apr": "{{doc-months|4|short}}",
-       "may": "{{doc-months|5|short}}",
-       "jun": "{{doc-months|6|short}}",
-       "jul": "{{doc-months|7|short}}",
-       "aug": "{{doc-months|8|short}}",
-       "sep": "{{doc-months|9|short}}",
-       "oct": "{{doc-months|10|short}}",
-       "nov": "{{doc-months|11|short}}",
-       "dec": "{{doc-months|12|short}}",
+       "jan": "{{doc-months|1|short}}\n{{Identical|January}}",
+       "feb": "{{doc-months|2|short}}\n{{Identical|February}}",
+       "mar": "{{doc-months|3|short}}\n{{Identical|March}}",
+       "apr": "{{doc-months|4|short}}\n{{Identical|April}}",
+       "may": "{{doc-months|5|short}}\n{{Identical|May}}",
+       "jun": "{{doc-months|6|short}}\n{{Identical|June}}",
+       "jul": "{{doc-months|7|short}}\n{{Identical|July}}",
+       "aug": "{{doc-months|8|short}}\n{{Identical|August}}",
+       "sep": "{{doc-months|9|short}}\n{{Identical|September}}",
+       "oct": "{{doc-months|10|short}}\n{{Identical|October}}",
+       "nov": "{{doc-months|11|short}}\n{{Identical|November}}",
+       "dec": "{{doc-months|12|short}}\n{{Identical|December}}",
        "january-date": "A date in the Gregorian month of January. $1 is the numerical date, for example \"23\".\n{{Identical|January}}",
        "february-date": "A date in the Gregorian month of February. $1 is the numerical date, for example \"23\".\n{{Identical|February}}",
        "march-date": "A date in the Gregorian month of March. $1 is the numerical date, for example \"23\".\n{{Identical|March}}",
-       "april-date": "A date in the Gregorian month of April. $1 is the numerical date, for example \"23\".",
+       "april-date": "A date in the Gregorian month of April. $1 is the numerical date, for example \"23\".\n{{Identical|April}}",
        "may-date": "A date in the Gregorian month of May. $1 is the numerical date, for example \"23\". The month name is not abbreviated.\n{{Identical|May}}",
-       "june-date": "A date in the Gregorian month of June. $1 is the numerical date, for example \"23\".",
-       "july-date": "A date in the Gregorian month of July. $1 is the numerical date, for example \"23\".",
-       "august-date": "A date in the Gregorian month of August. $1 is the numerical date, for example \"23\".",
-       "september-date": "A date in the Gregorian month of September. $1 is the numerical date, for example \"23\".",
-       "october-date": "A date in the Gregorian month of October. $1 is the numerical date, for example \"23\".",
+       "june-date": "A date in the Gregorian month of June. $1 is the numerical date, for example \"23\".\n{{Identical|June}}",
+       "july-date": "A date in the Gregorian month of July. $1 is the numerical date, for example \"23\".\n{{Identical|July}}",
+       "august-date": "A date in the Gregorian month of August. $1 is the numerical date, for example \"23\".\n{{Identical|August}}",
+       "september-date": "A date in the Gregorian month of September. $1 is the numerical date, for example \"23\".\n{{Identical|September}}",
+       "october-date": "A date in the Gregorian month of October. $1 is the numerical date, for example \"23\".\n{{Identical|October}}",
        "november-date": "A date in the Gregorian month of November. $1 is the numerical date, for example \"23\".\n{{Identical|November}}",
-       "december-date": "A date in the Gregorian month of December. $1 is the numerical date, for example \"23\".",
+       "december-date": "A date in the Gregorian month of December. $1 is the numerical date, for example \"23\".\n{{Identical|December}}",
        "period-am": "Text indicating the first period of the day when using a 12-hour calendar.",
        "period-pm": "Text indicating the second period of the day when using a 12-hour calendar.",
        "pagecategories": "Used in the categories section of pages.\n\nFollowed by a colon and a list of categories.\n\nParameters:\n* $1 - number of categories\n{{Identical|Category}}",
        "searchdisabled": "{{doc-singularthey}}\nIn this sentence, \"their indexes\" refers to \"Google's indexes\".\n\nShown on [[Special:Search]] when the internal search is disabled.",
        "googlesearch": "{{notranslate}}\nShown when [[mw:Manual:$wgDisableTextSearch|$wgDisableTextSearch]] is set to true and no [[mw:Manual:$wgSearchForwardUrl|$wgSearchForwardUrl]] is set.\n\nParameters:\n* $1 - the search term\n* $2 - \"UTF-8\" (hard-coded)\n* $3 - the message {{msg-mw|Searchbutton}}",
        "search-error": "Shown when an error has occurred when performing a search. Parameters:\n* $1 - the localized error that was returned",
+       "search-warning": "Shown when a warning has occured when performing a search. Parameters:\n* $1 - the localized warning that was returned.",
        "opensearch-desc": "{{ignored}}Link description of the [www.opensearch.org/ OpenSearch] link in the HTML head of pages.",
        "preferences": "Title of the [[Special:Preferences]] page.\n{{Identical|Preferences}}",
        "preferences-summary": "{{doc-specialpagesummary|preferences}}",
        "userrights-lookup-user": "Label text when managing user rights ([[Special:UserRights]])",
        "userrights-user-editname": "Displayed on [[Special:UserRights]].",
        "editusergroup": "Button name, in page [[Special:Userrights]], in the section named {{MediaWiki:userrights-lookup-user}}. The username or gender of the user is not known when this message is displayed.",
-       "editinguser": "Appears on [[Special:UserRights]]. Parameters:\n* $1 - a plaintext username\n* $2 - user tool links. e.g. \"(Talk | contribs | block | send email)\"",
-       "userrights-editusergroup": "Parameter:\n* $1 - (Optional) a username, can be used for GENDER",
+       "editinguser": "Appears on [[Special:UserRights]]. Parameters:\n* $1 - a plaintext username\n* $2 - user tool links. e.g. \"(Talk | contribs | block | send email)\"\n\nRelated messages:\n* {{msg-mw|viewinguserrights}}",
+       "viewinguserrights": "Appears on [[Special:UserRights]]. Parameters:\n* $1 - a plaintext username\n* $2 - user tool links. e.g. \"(Talk | contribs | block | send email)\"\n\nRelated messages:\n* {{msg-mw|editinguser}}",
+       "userrights-editusergroup": "Parameter:\n* $1 - (Optional) a username, can be used for GENDER\n\nRelated messages:\n* {{msg-mw|userrights-viewusergroup}}",
+       "userrights-viewusergroup": "Parameter:\n* $1 - (Optional) a username, can be used for GENDER\n\nRelated messages:\n* {{msg-mw|userrights-editusergroup}}",
        "saveusergroups": "Button text when editing user groups.\nParameters:\n* $1 - username, for GENDER support",
        "userrights-groupsmember": "Used when editing user groups in [[Special:Userrights]].\n\nThe message is followed by a list of group names.\n\nParameters:\n* $1 - (Optional) the number of items in the list following the message, for PLURAL\n* $2 - (Optional) the user name, for GENDER",
        "userrights-groupsmember-auto": "Used when editing user groups in [[Special:Userrights]]. The message is followed by a list of group names.\n\n\"Implicit\" is for groups that the user was automatically added to (such as \"autoconfirmed\"); cf. {{msg-mw|userrights-groupsmember}}\n\nParameters:\n* $1 - (Optional) the number of items in the list following the message, for PLURAL\n* $2 - (Optional) the user name, for GENDER",
        "action-writeapi": "{{Doc-action|writeapi}}\n\nAPI is an abbreviation for [[w:API|application programming interface]].",
        "action-delete": "{{Doc-action|delete}}",
        "action-deleterevision": "{{Doc-action|deleterevision}}",
+       "action-deletelogentry": "{{Doc-action|deletelogentry}}",
        "action-deletedhistory": "{{Doc-action|deletedhistory}}",
+       "action-deletedtext": "{{Doc-action|deletedtext}}",
        "action-browsearchive": "{{Doc-action|browsearchive}}",
        "action-undelete": "{{Doc-action|undelete}}",
        "action-suppressrevision": "{{Doc-action|suppressrevision}}",
        "action-userrights-interwiki": "{{Doc-action|userrights-interwiki}}",
        "action-siteadmin": "{{Doc-action|siteadmin}}",
        "action-sendemail": "{{doc-action|sendemail}}\n{{Identical|E-mail}}",
+       "action-editmyoptions": "{{Doc-action|editmyoptions}}",
        "action-editmywatchlist": "{{doc-action|editmywatchlist}}\n{{Identical|Edit your watchlist}}",
        "action-viewmywatchlist": "{{doc-action|viewmywatchlist}}\n{{Identical|View your watchlist}}",
        "action-viewmyprivateinfo": "{{doc-action|viewmyprivateinfo}}",
        "listgrants-summary": "Explanatory text shown at the top of the grant/rights mapping table.\n\nRefers to {{msg-mw|Listgrouprights-helppage}}.",
        "listgrants-grant": "Used as table header for the grant/rights mapping table.\n{{Identical|Grant}}",
        "listgrants-rights": "Used as table header for the grant/rights mapping table.\n{{Identical|Right}}",
+       "listgrants-grant-display": "{{optional}}\nUsed to display the code name of a grant next to the grant. Parameters:\n* $1 - the text from the \"grant-...\" messages, i.e. {{msg-mw|Grant-highvolume}}\n* $2 - the codename of this grant",
        "trackingcategories": "[[Special:TrackingCategories]] page implementing list of Tracking categories [[mw:Special:MyLanguage/Help:Tracking categories|tracking category]].\n{{Identical|Tracking category}}",
        "trackingcategories-summary": "Description for [[Special:TrackingCategories]] page [[mw:Help:Tracking categories|tracking category]]",
        "trackingcategories-msg": "Header for the message column of the table on [[Special:TrackingCategories]]. This column lists the mediawiki message that controls the tracking category in question.\n{{Identical|Tracking category}}",
        "wlshowhidemine": "Option text in [[Special:Watchlist]]. Cf. {{msg-mw|rcshowhidemine}}.",
        "wlshowhidecategorization": "Option text in [[Special:Watchlist]]. Cf. {{msg-mw|rcshowhidecategorization}}.",
        "watchlist-options": "Legend of the fieldset of [[Special:Watchlist]]\n\nSee also:\n* {{msg-mw|Watchlist-details|watchlist header}}\n* {{msg-mw|Wlheader-enotif|watchlist header}}\n* {{msg-mw|enotif reset|Submit button text}}",
+       "watchlist-mark-all-visited": "Dialog text in [[Special:Watchlist]] displayed for confirming whether the user wants to reset unseen watchlist changes by marking all pages as visited.",
        "watching": "Text displayed when clicked on the watch tab: {{msg-mw|Watch}}. It means the wiki is adding that page to your watchlist.",
        "unwatching": "Text displayed when clicked on the unwatch tab: {{msg-mw|Unwatch}}. It means the wiki is removing that page from your watchlist.",
        "watcherrortext": "When a user clicked the watch/unwatch tab and the action did not succeed, this message is displayed.\n\nThis message is used raw and should not contain wikitext.\n\nParameters:\n* $1 - ...\nSee also:\n* {{msg-mw|Addedwatchtext}}",
        "mw-widgets-dateinput-no-date": "Label of a date input field when no date has been selected.",
        "mw-widgets-dateinput-placeholder-day": "[[File:DateInputWidget active, empty.png|frame|Screenshot]]\nPlaceholder displayed in a date input field when it's empty, representing a date format with 4 digits for year, 2 digits for month, and 2 digits for day, separated with hyphens. This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
        "mw-widgets-dateinput-placeholder-month": "Placeholder displayed in a date input field when it's empty, representing a date format with 4 digits for year and 2 digits for month, separated with hyphens (without a day). This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
+       "mw-widgets-mediasearch-input-placeholder": "Place holder text for media search input",
+       "mw-widgets-mediasearch-noresults": "Label notifying the user no results were found for the media search.",
        "mw-widgets-titleinput-description-new-page": "Description label for a new page in the title input widget.",
        "mw-widgets-titleinput-description-redirect": "Description label for a redirect in the title input widget.",
        "mw-widgets-categoryselector-add-category-placeholder": "Placeholder displayed in the category selector widget after the capsules of already added categories.",
        "usercssispublic": "A reminder to users that CSS subpages are not preferences but normal pages, and thus can be viewed by other users and the general public. This message is shown to a user whenever they are editing a subpage in their own user-space that ends in .css. See also {{msg-mw|userjsispublic}}",
        "restrictionsfield-badip": "An error message shown when one entered an invalid IP address or range in a restrictions field (such as Special:BotPassword). $1 is the IP address.",
        "restrictionsfield-label": "Field label shown for restriction fields (e.g. on Special:BotPassword).",
-       "restrictionsfield-help": "Placeholder text displayed in restriction fields (e.g. on Special:BotPassword)."
+       "restrictionsfield-help": "Placeholder text displayed in restriction fields (e.g. on Special:BotPassword).",
+       "revid": "Used to format a revision ID number in text. Parameters:\n* $1 - Revision ID number.",
+       "pageid": "Used to format a page ID number in text. Parameters:\n* $1 - Page ID number."
 }
index 0d4a8b2..f6a017d 100644 (file)
        "passwordreset-emaildisabled": "Funcțiile de e-mail au fost dezactivate de pe acest wiki.",
        "passwordreset-username": "Nume de utilizator:",
        "passwordreset-domain": "Domeniu:",
-       "passwordreset-capture": "Vizualizați e-mailul rezultat?",
-       "passwordreset-capture-help": "Dacă bifați această căsuță, e-mailul (conținând parola temperară) vă va fi afișat, dar va fi trimis și utilizatorului.",
        "passwordreset-email": "Adresă de e-mail:",
        "passwordreset-emailtitle": "Detalii despre cont pe {{SITENAME}}",
        "passwordreset-emailtext-ip": "Cineva (probabil dumneavoastră, de la adresa IP $1) a solicitat resetarea parolei \npentru {{SITENAME}} ($4). {{PLURAL:$3|Următorul cont este asociat|Următoarele conturi sunt asociate}}\ncu această adresă de e-mail:\n\n$2\n\n{{PLURAL:$3|Această parolă temporară va|Aceste parole temporare vor}} expira {{PLURAL:$5|într-o zi|în $5 zile}}.\nAr trebui să vă autentificați și să schimbați parola acum. Dacă altcineva a făcut această cerere \nsau dacă v-ați reamintit parola inițială și nu mai doriți să o schimbați,\nputeți ignora acest mesaj, continuând să utilizați vechea parolă.",
        "userrights-reason": "Motiv:",
        "userrights-no-interwiki": "Nu aveți permisiunea de a modifica permisiunile utilizatorilor pe alte wiki.",
        "userrights-nodatabase": "Baza de date $1 nu există sau nu este locală.",
-       "userrights-nologin": "Trebuie să te [[Special:UserLogin|autentifici]] cu un cont de administrator pentru a atribui permisiuni utilizatorilor.",
-       "userrights-notallowed": "Nu aveți permisiunea de a acorda sau elimina drepturi utilizatorilor.",
        "userrights-changeable-col": "Grupuri pe care le puteți schimba",
        "userrights-unchangeable-col": "Grupuri pe care nu le puteți schimba",
        "userrights-conflict": "Conflict al schimbării drepturilor de utilizator! Reverificați și confirmați-vă modificările.",
-       "userrights-removed-self": "V-ați eliminat propriile drepturi. Ca urmare, nu mai puteți accesa această pagină.",
        "group": "Grup:",
        "group-user": "Utilizatori",
        "group-autoconfirmed": "Utilizatori autoconfirmați",
        "right-siteadmin": "Blochează și deblochează baza de date",
        "right-override-export-depth": "Exportă inclusiv paginile legate până la o adâncime de 5",
        "right-sendemail": "Trimite e-mail altor utilizatori",
-       "right-passwordreset": "Vizualizează e-mailurile de reinițializare a parolelor",
        "right-managechangetags": "Creează și (dez)activează [[Special:Tags|etichete]]",
        "right-applychangetags": "Aplică [[Special:Tags|etichete]] asociate modificărilor unui utilizator",
        "right-changetags": "Adaugă și înlătură [[Special:Tags|etichete]] arbitrare din versiuni și intrări de jurnal individuale",
        "grant-oversight": "Ascunde utilizatori și suprimă versiuni",
        "grant-patrol": "Patrulează schimbările paginilor",
        "grant-basic": "Drepturi de bază",
+       "grant-viewmywatchlist": "Vezi lista de pagini urmărite",
        "newuserlogpage": "Jurnal utilizatori noi",
        "newuserlogpagetext": "Acesta este jurnalul creărilor conturilor de utilizator.",
        "rightslog": "Jurnal permisiuni de utilizator",
index deeece5..026e178 100644 (file)
@@ -47,6 +47,7 @@
        "tog-watchlistreloadautomatically": "Recareche automaticamende l'eleghe de le pàggene condrollate quanne cange 'nu filtre (richieste Javascript)",
        "tog-watchlisthideanons": "Scunne le cangiaminde de l'utinde scanusciute da l'elenghe de le pàggene condrollate",
        "tog-watchlisthidepatrolled": "Scunne le cangiaminde condrollate jndr'à l'elenghe de le pàggene condrollate",
+       "tog-watchlisthidecategorization": "Scunne 'a categorizzazzione d'a vôsce",
        "tog-ccmeonemails": "Manneme 'na copie de le mail ca je manne a l'ôtre utinde",
        "tog-diffonly": "No fà vedè le pàggene cu le condenute sotte a le differenze",
        "tog-showhiddencats": "Fa vedè le categorije scunnute",
        "talk": "'Ngazzaminde",
        "views": "Visite",
        "toolbox": "Struminde",
+       "tool-link-userrights": "Cange le gruppe {{GENDER:$1|utinde}}",
+       "tool-link-userrights-readonly": "'Ndruche le gruppe {{GENDER:$1|utinde}}",
+       "tool-link-emailuser": "Manne 'na mail a stu {{GENDER:$1|utende}}",
        "userpage": "Vide a pàgene de l'utende",
        "projectpage": "Vide a pàgene de le pruggette",
        "imagepage": "Vide a pàgene de le file",
        "mypreferencesprotected": "Non ge tìne le permesse pe cangià le preferenze tune.",
        "ns-specialprotected": "Le pàgene speciale no ponne essere cangete.",
        "titleprotected": "Stu titele ha state prutette da 'a ccreazione da [[User:$1|$1]].\n'U mutive jè <em>$2</em>.",
-       "filereadonlyerror": "Non ge pozze cangià 'u file \"$1\" purcé l'archivije de le file \"$2\" ste in mode sola letture.\n\nL'amministratore ca l'ha bloccate dèje sta spiegazione: \"$3\".",
+       "filereadonlyerror": "Non ge pozze cangià 'u file \"$1\" purcé l'archivije de le file \"$2\" ste in sola letture.\n\nL'amministratore d'u sisteme ca l'ave bloccate dèje sta spiegazione: \"$3\".",
        "invalidtitle-knownnamespace": "Titole invalide cu 'u namespace \"$2\" e teste \"$3\"",
        "invalidtitle-unknownnamespace": "Titele invalide cu numere de namespace scanusciute $1 e teste \"$2\"",
        "exception-nologin": "Non ge sì collegate",
        "userlogin-remembermypassword": "Arrecuèrdeme",
        "userlogin-signwithsecure": "Ause 'na connessione secure",
        "cannotlogin-title": "Non ge puè trasé",
+       "cannotcreateaccount-title": "Non ge pué ccrejà le cunde utinde",
        "yourdomainname": "'U nome d'u dominie tue:",
        "password-change-forbidden": "Non ge puè cangià le passuord sus a sta uicchi.",
        "externaldberror": "Vide bbuene, o stè 'n'errore de autendicazione a 'u database oppure tu non ge puè aggiorna 'u cunde tue esterne.",
        "noname": "Non gìè specifichete 'nu nome utende valide.",
        "loginsuccesstitle": "Tutte a poste, è trasute!",
        "loginsuccess": "'''Mò tu si colleghete jndr'à {{SITENAME}} cumme \"$1\".'''",
-       "nosuchuser": "Non g'esiste n'utende cu 'u nome \"$1\".\nFà attenzione ca le nome de l'utinde so senzibbele a le lettere granne e piccenne.\nVide bbuene a cumme l'è scritte, o [[Special:CreateAccount|ccreje n'utende nuève]].",
+       "nosuchuser": "Non g'esiste n'utende cu 'u nome \"$1\".\nLe nome de l'utinde so senzibbele a le lettere granne e piccenne.\nVide bbuene a cumme l'è scritte, o [[Special:CreateAccount|ccreje n'utende nuève]].",
        "nosuchusershort": "Non ge ste nisciune utende cu 'u nome \"$1\".\nCondrolle accume l'è scritte.",
        "nouserspecified": "A scrivere pe forze 'u nome de l'utende.",
        "login-userblocked": "Stu utende jè bloccate. Non ge puè trasè.",
        "noemail": "Non ge stonne email reggistrete pe l'utende \"$1\".",
        "noemailcreate": "Tu ha mèttere 'n'indirizze e-mail valide",
        "passwordsent": "'Na nova passuord ha state mannete a l'indirizze e-mail reggistrete pe \"$1\".\nPe piacere, colleghete n'otra vota quanne l'è ricevute.",
-       "blocked-mailpassword": "L'indirizze IP tue jè blocchete pe le cangiaminde e accussì tu non ge puè ausà 'a funzione de recupere d'a password pe prevenìe l'abbuse.",
+       "blocked-mailpassword": "L'indirizze IP tune jè bloccate pe le cangiaminde. Tu non ge puè ausà 'a funzione de recupere d'a password pe prevenìe l'abbuse.",
        "eauthentsent": "'N'e-mail de conferme ha state mannate a l'indirizze ca tu è ditte.\nApprime ca otre e-mail avènene mannate a 'u cunde tune, tu ha seguì le 'struzione ca stonne jndr'à l'e-mail, pe confermà ca 'u cunde jè une de le tune.",
        "throttled-mailpassword": "'Nu arrecordatore de passuord ha stete già mannate jndr'à {{PLURAL:$1|l'urtema ore|l'urteme $1 ore}}.\nPe prevenì l'abbuse, sulamende 'nu arrecordatore de passuord avene mannate ogne {{PLURAL:$1|ore|$1 ore}}.",
        "mailerror": "Errore mannanne 'a mail: $1",
-       "acct_creation_throttle_hit": "Le visitature de sta Uicchi ca stonne ausene stu indirizze IP onne ccrejete {{PLURAL:$1|'nu cunde utende|$1 cunde utinde}} jndr'à l'urteme giurne, e onne raggiunde 'u numere massime ca se pò fà jndr'à stu periode.\n'U resultete jè ca le visitature ca stonne ausene stu indirizze IP non ge ponne ccrejà otre cunde utinde nuève jndr'à stu mumende.",
+       "acct_creation_throttle_hit": "Le visitature de sta Uicchi ca stonne ausene stu indirizze IP onne ccrejate {{PLURAL:$1|'nu cunde utende|$1 cunde utinde}} jndr'à l'urteme $2, e onne raggiunde 'u numere massime ca se pò fà jndr'à stu periode.\n'U resultate éte ca le visitature ca stonne ausene stu indirizze IP non ge ponne ccrejà otre cunde utinde nuève jndr'à stu mumende.",
        "emailauthenticated": "L'indirizze e-mail ca ne date ha state confermate 'u sciurne $2 a le $3.",
        "emailnotauthenticated": "L'indirizze e-mail tune non g'a state angore confermate.\nNisciuna mail t'avène mannate pe tutte le seguende dettaglie.",
        "noemailprefs": "Specifiche 'n'indirizze e-mail pe ste dettaglie ca onne essere fatiete.",
        "createacct-another-realname-tip": "'U nome vere jè facoltative.\nCe tu scacchie de metterle, quiste avène ausate pe dà 'u giuste merite a 'a fatìe de l'utende.",
        "pt-login": "Tràse",
        "pt-login-button": "Tràse",
+       "pt-login-continue-button": "Condinue a trasé",
        "pt-createaccount": "Ccreje 'nu cunde utende",
        "pt-userlogout": "Isse",
        "php-mail-error-unknown": "Errore scanusciute jndr'à funzione PHP mail()",
        "resetpass_submit": "'Mboste 'a passuord e colleghete",
        "changepassword-success": "'A password toje ha state cangiate!",
        "changepassword-throttled": "Tu è pruvate 'nu sacche de vote a trasè.\nPe piacere aspitte $1 apprime de pruvà arrete.",
+       "botpasswords": "Password d'u bot",
+       "botpasswords-label-appid": "Nome d'u bot:",
+       "botpasswords-label-create": "Ccreje",
+       "botpasswords-label-update": "Aggiorne",
+       "botpasswords-label-cancel": "Annulle",
+       "botpasswords-label-delete": "Scangìlle",
+       "botpasswords-label-resetpassword": "Azzere 'a passuord",
        "resetpass_forbidden": "Le Password non ge ponne cangià",
        "resetpass-no-info": "Tu a essere colleghete pe accedere a sta pàgene direttamende.",
        "resetpass-submit-loggedin": "Cange 'a password",
        "resetpass-submit-cancel": "Annulle",
-       "resetpass-wrong-oldpass": "'A password temboranea o quedda corrende jè invalide.\nPò essere ca tu è già cangete 'a password toje o è richieste una temboranea nove.",
+       "resetpass-wrong-oldpass": "'A password temboranée o quedda corrende non g'è valide.\nPò essere ca tu è già cangiate 'a password toje o n'è cercate una nove temboranée.",
        "resetpass-recycled": "Pe piacere azzere 'a password toje cu 'n'otra password deverse da quedde de mò.",
        "resetpass-temp-emailed": "Tu è trasute cu 'nu codece email tembaranèe.\nPe spiccià de trasè, tu ha 'mbostà 'na password nove aqquà:",
        "resetpass-temp-password": "Password temboranea:",
        "passwordreset-emaildisabled": "Le funziune de l'email onne state disabbilitate sus a sta uicchi.",
        "passwordreset-username": "Nome utende:",
        "passwordreset-domain": "Dominie:",
-       "passwordreset-capture": "Vide 'a mail resultande?",
-       "passwordreset-capture-help": "Ce tu signe sta sckatele, 'a mail (cu 'a passuord temboranèe) t'avène fatte vedè cumme adda essere mannate a l'utende.",
        "passwordreset-email": "Indirizze e-mail:",
        "passwordreset-emailtitle": "Dettaglie d'u cunde utende sus a {{SITENAME}}",
        "passwordreset-emailtext-ip": "Quacchedune (pò essere tu, da 'u 'ndirizze IP $1) ha richieste 'na mail pe arrecurdarse de le dettaglie d'u cunde sue pe {{SITENAME}} ($4). {{PLURAL:$3|'U cunde utende seguende jè|le cunde utinde seguende sonde}} associate cu st'indirizze e-mail:\n\n$2\n\n{{PLURAL:$3|Sta passuord temboranèe scade|Ste passuord temboranèe scadene}} 'mbrà {{PLURAL:$5|'nu sciurne|$5 sciurne}}.\nTu avissa trasè e scacchià 'na passuord nova. Ce quacchedun'otre ha fatte sta richieste, o ce tu t'è arrecurdate 'a passuord origgenale toje, e non g'a vuè ccu cange cchiù, tu puè ignorà stu messagge e condinuà ausanne 'a passuord vecchie.",
        "passwordreset-emailtext-user": "L'utende $1 sus a {{SITENAME}} ave richieste 'na mail pe arrecurdarse le dettaglie d'u cunde sue pe {{SITENAME}}\n($4). {{PLURAL:$3|'U cunde utende seguende jè|le cunde utinde seguende sonde}} associate cu st'indirizze e-mail:\n\n$2\n\n{{PLURAL:$3|Sta passuord temboranèe scade|Ste passuord temboranèe scadene}}  'mbrà {{PLURAL:$5|'nu sciurne|$5 sciurne}}.\nTu avissa trasè e scacchià 'na passuord nova. Ce quacchedun'otre ha fatte sta richieste, o ce tu t'è arrecurdate 'a passuord origgenale toje, e non g'a vuè ccu cange cchiù, tu puè ignorà stu messagge e condinuà ausanne 'a passuord vecchie.",
        "passwordreset-emailelement": "Nome utende: \n$1\n\nPassuord temboranèe: \n$2",
-       "passwordreset-emailsentemail": "Ce quiste jè 'n'e-mail pu cunde tune, allore 'na password azzerate ha state mannate addà.",
+       "passwordreset-emailsentemail": "Ce queste éte 'n'e-mail pu cunde tune, allore 'na password azzerate ha state mannate addà.",
        "changeemail": "Cange o live 'u 'ndirizze e-mail",
        "changeemail-header": "Comblete stu module pe cangià 'u 'ndirizze email. Ce tu vuè ccu live l'associazione cu ogne indirizze email da 'u cunde tune, lasse 'u 'ndirizze email vacande quanne conferme 'u module.",
        "changeemail-no-info": "Tu a essere collegate pe accedere a sta pàgene direttamende.",
        "accmailtext": "'A passuord ccrejate a uecchije pe [[User talk:$1|$1]] ha state mannate sus a $2.\n\n'A passuord pe stu cunde utende pò essere cangiate sus a pàgene ''[[Special:ChangePassword|cange passuord]]'' 'na vote ca è trasute.",
        "newarticle": "(Nuève)",
        "newarticletext": "Tu ste segue 'nu collegamende a pàgene ca angore non g'esiste.\nPe ccrejà 'a pàgene, accuminze a scrivere jndr'à 'u scatole de sotte (vide 'a [$1 pàggene d'ajute] pe avè cchiù 'mbormaziune).\nCe tu te iacche aqquà e manghe tu 'u se purcè, allore cazze 'u buttone '''back''' d'u brauser.",
-       "anontalkpagetext": "----''Queste jè 'na pàgene de 'ngazzaminde pe 'n'utende anonime, ca non ge vò ccu ccreje angore 'nu cunde utende, o de ce non g'u use.\nNuje auseme 'n'indirizze IP (ca jè numereche) pe identificarle.\nE' normale ca essende 'n'indirizze IP pò essere ausete pure da otre utinde ca 'u pigghiene.\nCe tu non ge si 'n'utende anonime e pinze ca le commende ca so revolte a te sonde studecarije, pe piacere [[Special:CreateAccount|ccreje 'nu cunde utende]] o [[Special:UserLogin|tràse]] pe no fà confusione jndr'à 'u future cu otre utinde anoneme.''",
+       "anontalkpagetext": "----\n<em>Queste jè 'na pàgene de 'ngazzaminde pe 'n'utende anonime, ca non ge vò ccu ccreje angore 'nu cunde utende, o de ce non g'u use.</em>\nNuje ausame 'n'indirizze IP (ca éte numereche) pe identificarle.\nE' normale ca pu fatte ca 'n'indirizze IP pò essere ausate pure da otre utinde ca 'u pigghiane.\nCe tu non ge si 'n'utende anonime e pinze ca le commende ca te arrivane sonde studecarije, pe piacere [[Special:CreateAccount|ccreje 'nu cunde utende]] o [[Special:UserLogin|tràse]] pe no fà confusione jndr'à 'u future cu otre utinde anoneme.",
        "noarticletext": "Non ge stè scritte ninde jndr'à sta pàgene.\nTu puè [[Special:Search/{{PAGENAME}}|cercà pe quiste titole]] jndr'à otre pàggene, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cirche l'archivije sue] o [{{fullurl:{{FULLPAGENAME}}|action=edit}} ccreje sta pàgene]</span>.",
        "noarticletext-nopermission": "Pe mò non ge stè teste jndr'à sta pàgene.\nTu puè [[Special:Search/{{PAGENAME}}|cercà pe stu titole]] jndr'à otre pàggene,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cirche jndr'à l'archivije cullegate]</span>, ma non ge tìne le permesse pe ccrejà sta pàgene.",
        "missing-revision": "'A revisione #$1 d'a pàgene chiamate \"{{FULLPAGENAME}}\" non g'esiste.\n\nQuiste succede normalmende purcé 'u cunde jè collegate a 'na pàgene ca ha state scangellate.\nLe dettaglie le puè acchià jndr'à l'[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} archivije de le scangellaziune].",
        "userpage-userdoesnotexist": "'U cunde utende \"<nowiki>$1</nowiki>\" non g'è reggistrete.\nPe piacere, condrolle ce tu vuè cu ccreje/cange sta pàgene.",
        "userpage-userdoesnotexist-view": "'U cunde utende \"$1\" non g'è reggistrate.",
        "blocked-notice-logextract": "Stu utende jè correndemende bloccate.<br />\nL'urteme archivije de le bloccaminde se iacche aqquà sotte pe referimende:",
-       "clearyourcache": "'''Vide Bbuene''' - Apprisse 'a reggistrazione, tu puè zumbà 'a cache d'u browser tune pe vedè le cangiaminde.\n*'''Firefox / Safari:''' cazze 'u ''Shift'' e condemboraneamende cazze 'u buttone ''Aggiorna'', o cazze 'nzieme ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' sus a 'nu Mac);\n*'''Google Chrome:''' cazze ''Ctrl-Shift-R'' (''⌘-Shift-R'' sus a 'nu Mac)\n*'''Internet Explorer:''' cazze ''Ctrl'' e condemboraneamende cazze ''Aggiorna,'' o cazze ''Ctrl-F5''.\n*'''Opera:''' pulizze 'a cache da ''Tools → Preferences'' (in inglese) (Struminde - Preferenze in tarandine);",
+       "clearyourcache": "<strong>Vide Bbuene</strong> - Apprisse 'a reggistrazione, tu puè zumbà 'a cache d'u browser tune pe vedè le cangiaminde.\n*<strong>Firefox / Safari:</strong> cazze 'u <em>Shift</em> e condemboraneamende cazze 'u buttone <em>Aggiorne</em>, o cazze 'nzieme <em>Ctrl-F5</em> o <em>Ctrl-R</em> (<em>⌘-R</em> sus a 'nu Mac);\n*<strong>Google Chrome:</strong> cazze <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> sus a 'nu Mac)\n*<strong>Internet Explorer:</strong> cazze ''Ctrl'' e condemboraneamende cazze <em>Aggiorne</em> o cazze <em>Ctrl-F5</em>.\n*<strong>Opera:</strong> pulizze 'a cache da <em>Tools → Preferences</em> (in inglese) (Struminde - Preferenze in tarandine);",
        "usercssyoucanpreview": "'''Conziglie:''' Ause 'u buttone \"{{int:showpreview}}\" pe condrollà 'u CSS nuève apprime de reggistrà.",
        "userjsyoucanpreview": "'''Conziglie:''' Ause 'u buttone \"{{int:showpreview}}\" pe condrollà 'u JavaScript nuève apprime de reggistrà.",
        "usercsspreview": "'''Arrecuerdete ca tu ste vide sulamende in andeprime 'u CSS tue.'''\n'''Non g'à state angore reggistrete ninde!'''",
        "editusergroup": "Cange le gruppe utinde",
        "editinguser": "Stè cange le deritte de {{GENDER:$1|l'utende}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Cange le gruppe d'utinde",
+       "userrights-viewusergroup": "'Ndruche le gruppe d'utinde",
        "saveusergroups": "Reggistre le gruppe d'utinde",
        "userrights-groupsmember": "Membre de:",
        "userrights-groupsmember-auto": "Membre imblicite de:",
        "userrights-reason": "Mutive:",
        "userrights-no-interwiki": "Tu non ge tìne le permesse pe cangià le deritte utende sus a l'otre uicchi.",
        "userrights-nodatabase": "'U Database $1 non g'esiste o non g'è lochele.",
-       "userrights-nologin": "Tu à essere [[Special:UserLogin|colleghete]] cu 'nu cunde utende d'amministratore pe assignà le deritte utende.",
-       "userrights-notallowed": "Non ge tìne le permesse pe aggiungere o luà le deritte a le utinde.",
        "userrights-changeable-col": "Gruppe ca tu puè cangià",
        "userrights-unchangeable-col": "Gruppe ca tu non ge puè cangià",
        "userrights-irreversible-marker": "$1*",
        "userrights-conflict": "Conflitte sus a le cangiaminde de le deritte utende! Pe piacere revide e conferme le cangiaminde tune.",
-       "userrights-removed-self": "T'è luate le deritte tune. Mò non ge puè cchiù trasè jndr'à sta pàgene.",
        "group": "Gruppe:",
        "group-user": "Utinde",
        "group-autoconfirmed": "Utinde auto confermatarije",
        "right-siteadmin": "Blocche e sblocche 'u database",
        "right-override-export-depth": "L'esportazione de pàggene inglude pàggene collegate 'mbonde a 'na profonnetà de 5",
        "right-sendemail": "Manne 'a mail a otre utinde",
-       "right-passwordreset": "Vide l'e-mail de azzeramende d'a passuord",
        "right-managechangetags": "CCreje e scangìlle [[Special:Tags|tag]] da 'u database",
        "right-applychangetags": "Appleche [[Special:Tags|tag]] sus a 'u de le cangiaminde tune",
        "right-changetags": "Aggiunge e live arbitrariamende [[Special:Tags|tag]] sus a le revisiune individuale e vôsce de l'archivije",
        "apisandbox-examples": "Esembie",
        "apisandbox-results": "Resultate",
        "apisandbox-request-url-label": "URL richieste:",
-       "apisandbox-request-time": "Tiembe cercate: $1",
+       "apisandbox-request-time": "Tiembe cercate: {{PLURAL:$1|$1 ms}}",
        "booksources": "Sorgende de le libbre",
        "booksources-search-legend": "Cirche pe le fonde de le libbre",
        "booksources-isbn": "ISBN:",
        "tags-actions-header": "Aziune",
        "tags-active-yes": "Sìne",
        "tags-active-no": "None",
-       "tags-source-extension": "Definite da 'n'estenzione",
+       "tags-source-extension": "Definite da 'u softuer",
        "tags-source-manual": "Applicate a màne da l'utinde e da le bot",
        "tags-source-none": "No cchiù ausate",
        "tags-edit": "cange",
        "tags-delete-not-allowed": "Tag definite da 'n'estenzione non ge ponne essere scangellate senze ca l'estenzione specifiche 'u permette.",
        "tags-delete-not-found": "'U tag \"$1\" non g'esiste.",
        "tags-delete-too-many-uses": "'U tag \"$1\" jè applecate a cchiù de $2 {{PLURAL:$2|revisione|revisiune}}, ca signifeche ca non ge pò essere scangellate.",
-       "tags-delete-warnings-after-delete": "'U tag \"$1\" ha state scangellate, ma  {{PLURAL:$2|ha assute 'u seguende avvise|onne assute le seguende avvise}}:",
+       "tags-delete-warnings-after-delete": "'U tag \"$1\" ha state scangellate, ma {{PLURAL:$2|ha assute 'u seguende avvise|onne assute le seguende avvise}}:",
        "tags-activate-title": "Attive 'u tag",
        "tags-activate-question": "Tu ste attive 'u tag \"$1\".",
        "tags-activate-reason": "Mutive:",
        "tags-edit-revision-legend": "Aggiunge o live le tag da {{PLURAL:$1|sta revisione|tutte le $1 revisiune}}",
        "tags-edit-logentry-legend": "Aggiunge o live le tag da {{PLURAL:$1|sta vôsce de l'archivije|tutte le $1 vôsce de l'archivije}}",
        "tags-edit-existing-tags": "Tag esistende:",
-       "tags-edit-existing-tags-none": "\"Nisciune\"",
+       "tags-edit-existing-tags-none": "<em>isciune</em>",
        "tags-edit-new-tags": "Tag nuève:",
        "tags-edit-add": "Agigunge ste tag:",
        "tags-edit-remove": "Live ste tag:",
index e567a2f..296c3cc 100644 (file)
        "tog-hidepatrolled": "Скрывать патрулированные правки в списке свежих правок",
        "tog-newpageshidepatrolled": "Скрывать отпатрулированные страницы в списке новых страниц",
        "tog-hidecategorization": "Скрывать категоризацию страниц",
-       "tog-extendwatchlist": "Расширенный список наблюдения, включающий все изменения, а не только последние",
+       "tog-extendwatchlist": "Расширенный список наблюдения, включающий все изменения, а не только последние <small>(они могут быть сгруппированы настройкой на вкладке «[[Служебная:Настройки#mw-prefsection-rc|Свежие правки]]»)</small>",
        "tog-usenewrc": "Группировать изменения в свежих правках и списке наблюдения",
        "tog-numberheadings": "Автоматически нумеровать заголовки",
        "tog-showtoolbar": "Показывать панель инструментов при редактировании",
        "search-external": "Внешний поиск",
        "searchdisabled": "Извините, но встроенный полнотекстовый поиск выключен. Вы можете воспользоваться поиском по сайту через поисковые системы общего назначения, однако имейте в виду, что копия сайта в их кэше может быть несколько устаревшей.",
        "search-error": "Произошла ошибка при поиске: $1",
+       "search-warning": "Во время поиска выдано предупреждение: $1",
        "preferences": "Настройки",
        "mypreferences": "Настройки",
        "prefs-edits": "Количество правок:",
        "userrights-user-editname": "Введите имя учётной записи:",
        "editusergroup": "Загрузка групп участников",
        "editinguser": "Изменение прав {{GENDER:$1|участника|участницы}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Просмотр прав {{GENDER:$1|участника|участницы}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Изменение членства в группах",
+       "userrights-viewusergroup": "Просмотр групп участника",
        "saveusergroups": "Сохранить группы {{GENDER:$1|участника|участницы}}",
        "userrights-groupsmember": "Состоит в группах:",
        "userrights-groupsmember-auto": "Неявно состоит в группах:",
        "action-upload_by_url": "загрузку этого файла с адреса URL",
        "action-writeapi": "использование API для правок",
        "action-delete": "удаление этой страницы",
-       "action-deleterevision": "удаление этой версии страницы",
-       "action-deletedhistory": "просмотр удалённой истории этой страницы",
+       "action-deleterevision": "удаление версий страниц",
+       "action-deletelogentry": "удаление записей журнала",
+       "action-deletedhistory": "просмотр удалённой истории страницы",
+       "action-deletedtext": "просмотр текста удалённой версии",
        "action-browsearchive": "поиск удалённых страниц",
-       "action-undelete": "воÑ\81Ñ\81Ñ\82ановление Ñ\8dÑ\82ой Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b",
-       "action-suppressrevision": "пÑ\80оÑ\81моÑ\82Ñ\80 Ð¸ Ð²Ð¾Ñ\81Ñ\81Ñ\82ановление Ñ\8dÑ\82ой Ñ\81кÑ\80Ñ\8bÑ\82ой Ð²ÐµÑ\80Ñ\81ии Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b",
+       "action-undelete": "воÑ\81Ñ\81Ñ\82ановление Ñ\81Ñ\82Ñ\80аниÑ\86",
+       "action-suppressrevision": "пÑ\80оÑ\81моÑ\82Ñ\80 Ð¸ Ð²Ð¾Ñ\81Ñ\81Ñ\82ановление Ñ\81кÑ\80Ñ\8bÑ\82Ñ\8bÑ\85 Ð²ÐµÑ\80Ñ\81ий Ñ\81Ñ\82Ñ\80аниÑ\86",
        "action-suppressionlog": "просмотр этого частного журнала",
        "action-block": "ограничивать возможность редактирования для этого участника",
        "action-protect": "изменение уровня защиты этой страницы",
        "action-userrights-interwiki": "изменение прав участников в других вики",
        "action-siteadmin": "блокировка и разблокировка базы данных",
        "action-sendemail": "отправка электронных писем",
+       "action-editmyoptions": "редактирование своих настроек",
        "action-editmywatchlist": "редактирование вашего списка наблюдения",
        "action-viewmywatchlist": "просмотр вашего списка наблюдения",
        "action-viewmyprivateinfo": "просмотр вашей частной информации",
        "emailccsubject": "Копия вашего сообщения для $1: $2",
        "emailsent": "Письмо отправлено",
        "emailsenttext": "Ваше электронное сообщение отправлено.",
-       "emailuserfooter": "Это письмо было отправлено {{GENDER:$2|участнику|участнице}} $2 от {{GENDER:$1|участника|участницы}} $1 с помощью функции «{{int:emailuser}}» проекта {{SITENAME}}.",
+       "emailuserfooter": "Это письмо было отправлено {{GENDER:$2|участнику|участнице}} $2 от {{GENDER:$1|участника|участницы}} $1 с помощью функции «{{int:emailuser}}» проекта {{SITENAME}}.\n{{GENDER:$2|Письмо}} будет отослано напрямую {{GENDER:$1|отправителю}}, так что {{GENDER:$2|ваш}} адрес электронной почты станет известен {{GENDER:$1|ему|ей}}.",
        "usermessage-summary": "Оставить системное сообщение.",
        "usermessage-editor": "Системная доставка",
        "watchlist": "Список наблюдения",
        "wlshowhidemine": "мои правки",
        "wlshowhidecategorization": "категоризацию страниц",
        "watchlist-options": "Настройки списка наблюдения",
+       "watchlist-mark-all-visited": "Вы уверены, что хотите сбросить непросмотренные изменения списка наблюдения, отметив все страницы как посещённые?",
        "watching": "Добавление в список наблюдения…",
        "unwatching": "Удаление из списка наблюдения…",
        "watcherrortext": "Произошла ошибка при изменении настройки наблюдения для «$1».",
        "mw-widgets-dateinput-no-date": "Дата не выбрана",
        "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
+       "mw-widgets-mediasearch-input-placeholder": "Поиск мультимедиа",
+       "mw-widgets-mediasearch-noresults": "Ничего не найдено.",
        "mw-widgets-titleinput-description-new-page": "страница ещё не существует",
        "mw-widgets-titleinput-description-redirect": "перенаправление на $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Добавить категорию…",
        "usercssispublic": "Обратите внимание: подстраницы CSS не должны содержать конфиденциальные сведения, поскольку они доступны для просмотра другим участникам.",
        "restrictionsfield-badip": "Недопустимый IP-адрес или диапазон адресов: $1",
        "restrictionsfield-label": "Разрешённые диапазоны IP-адресов:",
-       "restrictionsfield-help": "По одному IP-адресу или CIDR-диапазону в строке. Чтобы разрешить всё, используйте <br /><code>0.0.0.0/0</code><br /><code>::/0</code>"
+       "restrictionsfield-help": "По одному IP-адресу или CIDR-диапазону в строке. Чтобы разрешить всё, используйте <br /><code>0.0.0.0/0</code><br /><code>::/0</code>",
+       "pageid": "ID страницы $1"
 }
index ea05650..b5706c5 100644 (file)
        "search-external": "Тастан көрдөөһүн",
        "searchdisabled": "{{SITENAME}} көрдүүр тэрилэ араарыллыбыт. Атын көрдүүр системаларынан наадыйар сирэйдэргитин көрдөтүөххүтүн сөп. Ол гынан баран поисковик кээһигэр баар торум эргэрбит буолуон сөп.",
        "search-error": "Көрдүүр кэмҥэ алҕас таҕыста: $1",
+       "search-warning": "Көрдүүр кэмҥэ сэрэтии таҕыста: $1",
        "preferences": "Уларытыылар",
        "mypreferences": "Туруоруулар",
        "prefs-edits": "Көннөрүү ахсаана:",
        "userrights-user-editname": "Кыттааччы аата:",
        "editusergroup": "Кыттааччы бөлөхтөрүн көрдөрүү",
        "editinguser": "<strong>[[User:$1|$1]]</strong> кыттааччы $2 быраабын уларытыы",
+       "viewinguserrights": "{{GENDER:$1|Кыттааччы}} быраабын көрүү <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Кыттааччы бөлөхтөрүн уларытарга",
+       "userrights-viewusergroup": "Кыттааччы бөлөхтөрүн көрүү",
        "saveusergroups": "{{GENDER:$1|Кыттааччы}} бөлөхтөрүн бигэргэт",
        "userrights-groupsmember": "Бу бөлөхтөргө киирэр:",
        "userrights-groupsmember-auto": "Көстүбэт чилиэн:",
        "action-upload_by_url": "бу билэни URL-аадырыстан киллэрии",
        "action-writeapi": "көннөрөргө API-ны туһаныы",
        "action-delete": "бу сирэйи сотуу",
-       "action-deleterevision": "бу торуму сотуу",
-       "action-deletedhistory": "бу сирэй сотуллубут историятын көрүү",
+       "action-deleterevision": "торумнары сотуу",
+       "action-deletelogentry": "сурунаал суруктарын сотуу",
+       "action-deletedhistory": "сирэй сотуллубут устуоруйатын көрүү",
+       "action-deletedtext": "сотуллубут торум тиэкиһин көрүү",
        "action-browsearchive": "сотуллубут сирэйдэри көрдөөһүн",
-       "action-undelete": "бу сирэйи төннөрүү",
-       "action-suppressrevision": "Ñ\81иÑ\80Ñ\8dй Ð±Ñ\83 ÐºÐ¸Ñ\81Ñ\82Ñ\8dммиÑ\82 Ñ\82оÑ\80Ñ\83мÑ\83н ÐºÓ©Ñ\80Ò¯Ò¯ Ñ\83онна Ñ\82өннөÑ\80Ò¯Ò¯",
+       "action-undelete": "сирэйи төннөрүү",
+       "action-suppressrevision": "сирэй кистэммит торумун көрүү уонна төннөрүү",
        "action-suppressionlog": "бу тус сурунаалы көрүү",
        "action-block": "кыттааччы уларытыыны оҥорорун бобуу",
        "action-protect": "бу сирэй харысхалын таһымын уларытыы",
        "action-userrights-interwiki": "атын биикигэ кыттыы бырааптарын уларытыы",
        "action-siteadmin": "билэ олоҕун хааччахтааһын уонна хааччахтааһынын устуу",
        "action-sendemail": "сурук ыытыы",
+       "action-editmyoptions": "бэйэ туруорууларын көрүү",
        "action-editmywatchlist": "кэтиир тиһиккин уларыт",
        "action-viewmywatchlist": "кэтиир тиһиккин көрүү",
        "action-viewmyprivateinfo": "бэйэҥ тускунан көрүү",
        "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 көр.",
        "filerevert-submit": "Төннөрүү",
        "filerevert-success": "'''[[Media:$1|$1]]''' бу торумҥа төннөрүлүннэ [$4 торум Filetype-missingот $3, $2].",
        "filerevert-badversion": "Бу билэ этиллибит күҥҥэ-ыйга/кэмҥэ оҥоһуллубут урукку торума суох.",
+       "filerevert-identical": "Талбыт торумуҥ билиҥҥи торуму кытта үүт үкчүлэр.",
        "filedelete": "Сот: $1",
        "filedelete-legend": "Билэни сот",
        "filedelete-intro": "Бу билэни '''[[Media:$1|$1]]''' туох баар суруллубут историятын кытта сотон эрэҕин.",
        "apisandbox": "API песочница",
        "apisandbox-jsonly": "API-песочницаны туһанарга JavaScript ирдэнэр.",
        "apisandbox-api-disabled": "Бу сайтка API араарыллыбыт.",
+       "apisandbox-intro": "Бу сирэйи <strong>MediaWiki API</strong> тургутан көрөргө туһан.\nAPI-ни туттар туһунан сиһилии манна ааҕыахха сөп [[mw:API:Main page|API туһунан]]. Холобура, [https://www.mediawiki.org/wiki/API#A_simple_example Сүрүн сирэй иһинээҕитин хайдах ылар туһунан]. Атын холобурдары көрөргө сигэни баттаа.\nБолҕой: бу тургутар сирэй эрээри, манна суруйбутуҥ биикигэ уларытыыны оҥоруон сөп.",
        "apisandbox-fullscreen": "Паныалы арыйыы.",
        "apisandbox-fullscreen-tooltip": "Браузеры толорорго песочница паныалын арыйыы.",
        "apisandbox-unfullscreen": "Сирэйи көрдөрүү",
        "apisandbox-submit": "Ыйытык оҥоруу",
        "apisandbox-reset": "Сот",
        "apisandbox-retry": "Хатылаа",
+       "apisandbox-loading": "«$1» API-модульга аналлаах хачайдана турар…",
+       "apisandbox-load-error": "«$1» API-модулга аналлаах хачайданарыгар алҕас таҕыста: $2",
+       "apisandbox-no-parameters": "Бу API-модулга туруоруута (параметра) суох.",
        "apisandbox-helpurls": "Көмө сигэлэр",
        "apisandbox-examples": "Холобурдар",
        "apisandbox-dynamic-parameters": "Дьайыы кээмэйдэрэ.",
        "apisandbox-results": "Түмүк",
        "apisandbox-sending-request": "API-көрдөбүлү ыытыы…",
        "apisandbox-loading-results": "API-түмүгүн ылыы…",
+       "apisandbox-results-error": "Көрдөбүлгэ API-хоруйу киллэрии  кэмигэр алҕас таҕыста: $1.",
+       "apisandbox-request-url-label": "Көрдөбүл URL-аадырыһа:",
+       "apisandbox-request-time": "Көрдөбүл болдьоҕо: {{PLURAL:$1|$1 мс}}",
        "apisandbox-results-fixtoken": "Токены көннөрөн баран саҥаттан ыыт.",
+       "apisandbox-results-fixtoken-fail": "«$1» токены ыҥырар табыллыбата.",
+       "apisandbox-alert-page": "Бу сирэй хонуулара алҕастаах.",
+       "apisandbox-alert-field": "Хонуу суолтата алҕастаах.",
+       "apisandbox-continue": "Салгыы",
+       "apisandbox-continue-clear": "Сот",
+       "apisandbox-continue-help": "{{int:apisandbox-continue}} бүтэһик көрдөбүлү [https://www.mediawiki.org/wiki/API:Query#Continuing_queries салгыаҕа]; {{int:apisandbox-continue-clear}} салҕааһыны кытта ситимнээх туруоруулары ырастыа.",
+       "apisandbox-param-limit": "Муҥутуур болдьох <kbd>муҥутуурдук</kbd> туттулларын туоруор.",
+       "apisandbox-multivalue-all-namespaces": "$1 (Аат даллара барыта)",
+       "apisandbox-multivalue-all-values": "$1 (Бары суолталара)",
        "booksources": "Кинигэлэр источниктара",
        "booksources-search-legend": "Кинигэ туһунан көрдөө",
        "booksources-search": "Бул",
        "booksources-text": "Манна кинигэ туһунан атын саайтарга ыйынньыктар хомулуннулар, онно баҕар эбии информация көстүөҕэ.",
        "booksources-invalid-isbn": "ISBN, арааһа, сыыһалаах. Нүөмэр көһөрөргө алҕас тахсыбатаҕын хат көр эрэ.",
+       "magiclink-tracking-rfc": "RFC сигэлээх сирэйдэр",
+       "magiclink-tracking-rfc-desc": "Бу сирэй RFC сигэлээх. Сиһилии [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org].",
+       "magiclink-tracking-pmid": "PMID сигэлээх сирэйдэр",
+       "magiclink-tracking-pmid-desc": "Бу сирэйгэ PMID сигэ туттуллар. Сиһилии [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org].",
+       "magiclink-tracking-isbn": "ISBN сигэлээх сирэйдэр",
+       "magiclink-tracking-isbn-desc": "Бу сирэйгэ ISBN сигэ туттуллар. Сиһилии [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org].",
        "specialloguserlabel": "Толорооччу:",
        "speciallogtitlelabel": "Сыал (тиэкис эбэтэр {{ns:user}}:кыттааччы аата):",
        "log": "Сурунааллар",
        "activeusers-intro": "Бу кэлиҥҥи $1 {{PLURAL:$1|күҥҥэ|күннэргэ}} тугу эмэ гыммыт кыттааччылар тиһиктэрэ.",
        "activeusers-count": "Кэнники $3 күҥҥэ саҥа $1 көннөрүү киирбит",
        "activeusers-from": "Мантан саҕалаан кыттааччылары көрүү:",
+       "activeusers-groups": "Маннык бөлөххө киирэр кыттааччылары көрдөр:",
+       "activeusers-excludegroups": "Маннык бөлөххө киирэр кыттааччылары көрдөрүмэ:",
        "activeusers-noresult": "Кыттааччылар көстүбэтилэр.",
        "activeusers-submit": "Көхтөөх кыттааччылары көрдөр",
        "listgrouprights": "Кыттааччылар бөлөхтөрүн бырааптара",
        "listgrouprights-namespaceprotection-namespace": "Аат дала",
        "listgrouprights-namespaceprotection-restrictedto": "Кыттааччы көннөрөр бырааба",
        "listgrants": "Көҥүл",
+       "listgrants-summary": "Манна кыахтар тиһиликтэрэ, туох быраабы биэрэллэрэ көстөр. Кыттааччы ханнык эмит бырагыраамаҕа бэйэтин аатыттан тугу эрэ гынары көҥүллүөн сөп, ол эрээри онто быраабын таһымынан хааччахтанар. Бырагыраама кыттааччы аатыттан тугу эрэ гынар түгэнигэр ол аакка иҥэриллибит бырааптан уратыны оҥорор кыаҕа суох. \nБыраап туһунан [[{{MediaWiki:Listgrouprights-helppage}}|сиһилии манна ааҕыахха сөп]].",
        "listgrants-grant": "Көҥүл",
        "listgrants-rights": "Быраап",
        "trackingcategories": "Кэтиир категориялар",
        "trackingcategories-msg": "Кэтиир категория",
        "trackingcategories-name": "Этии аата",
        "trackingcategories-desc": "Категорияҕа киирии киритиэрийэ",
+       "restricted-displaytitle-ignored": "Болҕомтоҕо ылыллыбат ааттаах сирэйдэр",
+       "restricted-displaytitle-ignored-desc": "Сирэй болҕомтоҕо ылыллыбат, дьиҥнээх аатын кытта сөп түбэспэт <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> диэн ааттаах эбит.",
        "noindex-category-desc": "Бу сирэйи көрдүүр роботтар болҕомотоҕо ылбаттар, тоҕо диэтэххэ <code><nowiki>__NOINDEX__</nowiki></code> диэн «аптаах тыл» туттуллубут. Сирэй инньэ гынарга көҥүллэммит аат далыгар баар эбит.",
        "index-category-desc": "Сирэйгэ <nowiki>__INDEX__</nowiki> диэн «аптаах тыл» баар эбит (сирэй ону көҥүллүүр аат далыгар баар эбит), онон көрдүүр роботтар кинини болҕомтоҕо ылыа да суох түгэннэргэ көрөллөр эбит.",
        "post-expand-template-inclusion-category-desc": "Халыыптары барытын көрдөрдөххө сирэй ыйааһына маны <code>$wgMaxArticleSize</code> куоһарыан сөп, онон сорҕото эрэ көрдөрүлүннэ.",
        "emailccsubject": "Эн суругуҥ куоппуйата $1: $2",
        "emailsent": "Сурук барда",
        "emailsenttext": "Эн суругуҥ ыытылынна.",
-       "emailuserfooter": "Бу сурук $2 кыттааччыга $1 кыттааччыттан «Сурукта ыыт» диэн тэрил көмөтүнэн {{SITENAME}} ситим-сиртэн ыытыллыбыт.",
+       "emailuserfooter": "Бу сурук {{GENDER:$2|$2}} кыттааччыга {{GENDER:$1|$1}} кыттааччыттан «Сурукта ыыт» (\"{{int:emailuser}}\") диэн тэрил көмөтүнэн {{SITENAME}} ситим-сиртэн ыытыллыбыт. {{GENDER:$2|Эн}} электрон аадырыһыҥ ыыппыт {{GENDER:$1|киһигэр}} көстүөҕэ.",
        "usermessage-summary": "Тиһилик биллэриитин хааллар.",
        "usermessage-editor": "Тиһилик биллэрээччитэ",
        "watchlist": "Кэтэбилим тиһигэ",
        "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": "Бу сирэйи кэтээ",
        "delete-toobig": "Бу сирэй уларытыыларын историята уһун, хас да ($1) {{PLURAL:$1|хат көрүүлээх|хат көрүүлэрдээх}}. Маннык сирэйдэри сотор хааччахтанар, тоҕо диэххэ алҕас {{SITENAME}} алдьаныан сөп.",
        "delete-warning-toobig": "Бу сирэй уларыылара уһун историялаах, хас да ($1) {{PLURAL:$1|хат көрүүлээх|хат көрүүлэрдээх}}. Маны соттоххуна, {{SITENAME}} билэтин тиһигин алдьатыан сөп; салгыыр буоллаххына сэрэнэн үлэлээ.",
        "deleteprotected": "Бу сирэйи, көмүскэллээх буолан, сотор кыаҕыҥ суох эбит.",
-       "deleting-backlinks-warning": "'''Сэрэтии.''' Сотоору гынар сирэйгэр [[Special:WhatLinksHere/{{FULLPAGENAME}}|атын сирэйдэр]] сигэнэллэр эбит.",
+       "deleting-backlinks-warning": "<strong>Сэрэтии.</strong>\nСотоору гынар сирэйгэр [[Special:WhatLinksHere/{{FULLPAGENAME}}|атын сирэйдэр]] сигэнэллэр эбит.",
        "rollback": "Уруккутугар төннөр",
        "rollbacklink": "төннөр",
        "rollbacklinkcount": "$1 көннөрүүнү суох гын",
        "rollbacklinkcount-morethan": "$1 элбэх көннөрүүнү суох гын",
        "rollbackfailed": "Төннөрөргө моһуок үөскээтэ",
+       "rollback-missingparam": "Көрдөбүлгэ баар буолуохтаах булгуччулаах туруоруулар суохтар эбит.",
+       "rollback-missingrevision": "Торум туһунан хачайдаан ылар сатаммата.",
        "cantrollback": "Төннөрөр кыах суох; бүтэһик көннөрүүнү оҥорбут киһи бу сирэй соҕотох ааптара буолар.",
        "alreadyrolled": "Бүтэһик [[User:$2|$2]] ([[User talk:$2|Ырытыы]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) оҥорбут [[:$1]] уларытыыларын төннөрөр кыах суох;\nким эрэ атын номнуо бу сирэйи уларыппыт эбэтэр уруккутун төннөрбүт.\n\nКэнники уларытыыны [[User:$3|$3]] ([[User talk:$3|Ырытыы]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) оҥорбут.",
        "editcomment": "Уларытыыны маннык быһаарбыттар: <em>$1</em>.",
        "revertpage": "([[User talk:$2|Ырытыы]]) көннөрүүлэрэ: [[Special:Contributions/$2|$2]] бу торумҥа: [[User:$1|$1]] төннөрүлүннүлэр",
        "revertpage-nouser": "Аата кистэммит киһи уларытыылара суох оҥоһуллан, ыстатыйа бу киһи барылыгар төннөрүлүннэ: {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "$1 көннөрүүлэр бу торумҥа төннөрүлүннүлэр: $2.",
+       "rollback-success-notify": "$1 уларытыылара сотулуннулар; \n$2 тиһэх торумугар төннөрүлүннэ. [$3 Уларытыыны көрдөр]",
        "sessionfailure-title": "Сиэссийэ алҕаһа",
        "sessionfailure": "Арааһа туох эрэ сатаммата, дьайыыҥ оҥоһуллубата. Браузергар \"Төнүн\" тимэҕи баттаа уонна бу иннинээҕи сирэйгин иккистээн киллэрэн көр.",
        "changecontentmodel": "Сирэй ис тутулун киэбин уларытыы",
        "changecontentmodel-title-label": "Сирэй баһа",
        "changecontentmodel-model-label": "Иһинээҕитин саҥа киэбэ",
        "changecontentmodel-reason-label": "Төрүөтэ:",
+       "changecontentmodel-submit": "Уларыйыы",
        "changecontentmodel-success-title": "Иһинээҕитин киэбэ уларыйда",
        "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 $3 диэн сирэйи маны туһанан «$5» {{GENDER:$2|айбыт}}",
        "logentry-contentmodel-change": "$1 кыттааччы $3 сирэй иһинээҕитин мадьыалын мантан «$4» манна «$5» {{GENDER:$2|уларыппыт}}",
        "logentry-contentmodel-change-revertlink": "төннөрүү",
        "logentry-contentmodel-change-revert": "төннөрүү",
        "modifiedarticleprotection": "\"[[$1]]\" сирэй уларытыытын таһыма уларыйда",
        "unprotectedarticle": "[[$1]]\" көмүскэлэ суох оҥоһулунна",
        "movedarticleprotection": "көмүскэл туруоруутун мантан \"[[$2]]\" манна \"[[$1]]\" көһөрдө",
+       "protectedarticle-comment": "Маны «[[$1]]» {{GENDER:$2|көмүскээбит}}",
+       "modifiedarticleprotection-comment": "«[[$1]]» көмүскэлин таһымын {{GENDER:$2|уларыппыт}}",
+       "unprotectedarticle-comment": "«[[$1]]» көмүскэлин {{GENDER:$2|соппут}}",
        "protect-title": "\"$1\": Уларытыы таһымын туруоруу",
        "protect-title-notallowed": "Харысхал \"$1\" таһымын көрүү",
        "prot_1movedto2": "[[$1]] аата манныкка уларытылынна: [[$2]]",
        "undeletehistorynoadmin": "Ыстатыйа сотуллубут.\nСотуу төрүөтэ уонна ыстатыйаны суруйбут кыттааччылар испииһэктэрэ манна көрдөрүлүннэ.\nСотуллубут ыстатыйа ис хоһоонун дьаһабыллар эрэ көрүөхтэрин сөп.",
        "undelete-revision": "$3 суруйбут $1 ыстатыйа сотуллубут торума (суруйуу кэмэ: $4, $5):",
        "undeleterevision-missing": "Сыыһа эбэтэр суох торум.\nАрааһа ыйынньыгыҥ сыыһа суруллубут, эбэтэр бу торум архыыптан сотуллан хаалбыт.",
+       "undeleterevision-duplicate-revid": "$1 {{PLURAL:$1|торум}} сөргүтүллэр кыаҕа суох, тоҕо диэтэххэ {{PLURAL:$1|кини|кинилэр}} <code>rev_id</code>{{PLURAL:$1|-та|-лара}} хайыы-үйэ туһаныллар эбит.",
        "undelete-nodiff": "Ханнык да иннинээҕи торум көстүбэтэ.",
        "undeletebtn": "Төннөр",
        "undeletelink": "көрүү/төннөрүү",
        "undeletedrevisions": "{{PLURAL:$1|1 уларытыы|$1 уларытыы}} төннөрүлүннэ",
        "undeletedrevisions-files": "{{PLURAL:$1|1 уларытыы|$1 уларытыы}}  уонна {{PLURAL:$2|1 билэ|$2 билэ}} төннөрүлүннэ",
        "undeletedfiles": "{{PLURAL:$1|1 билэ|$1 билэ}} төннөрүлүннэ",
-       "cannotundelete": "Сөргүтүү алҕаһа:\n$1",
+       "cannotundelete": "Сөргүтүү сороҕо табыллыбата:\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": "кыттааччы сотуллубут көннөрүүлэрэ",
-       "sp-contributions-deleted": "кыттааччы сотуллубут көннөрүүлэрэ",
+       "sp-contributions-suppresslog": "{{GENDER:$1|кыттааччы}} сотуллубут көннөрүүлэрэ",
+       "sp-contributions-deleted": "{{GENDER:$1|кыттааччы}} сотуллубут көннөрүүлэрэ",
        "sp-contributions-uploads": "киллэриилэр",
        "sp-contributions-logs": "сурунааллар",
        "sp-contributions-talk": "ырытыы",
        "sp-contributions-username": "IP аадырыһа эбэтэр аата:",
        "sp-contributions-toponly": "Кэнники барыллары эрэ көрдөр",
        "sp-contributions-newonly": "Саҥаттан оҥоһуллубут сирэйдэри эрэ көрдөр",
+       "sp-contributions-hideminor": "Суолтата суох уларытыылары көрдөрүмэ",
        "sp-contributions-submit": "Көрдөө",
        "whatlinkshere": "Манна сигэнэллэр",
        "whatlinkshere-title": "Сирэй манна сигэнэр \"$1\"",
        "unblock": "Кытааччы хааччаҕын устуу",
        "blockip": "{{GENDER:$1|Кыттааччыны}} хааччахтаа",
        "blockip-legend": "Кыттааччыны хааччахтааһын",
-       "blockiptext": "Ханнык эмит IP-ттан суруйары манна баар форманы туһанан боп.\nВандализмы утаран уонна [[{{MediaWiki:Policy-url}}]]\nбыраабылалрын тутуһан эрэ бобуохтааххын.\nХайаан да бобуу төрүөтүн кэпсээ (холобур, вандализм баар сирэйдэриттэн\nбыһа тардан манна көрдөр).",
+       "blockiptext": "Ханнык эмит IP-ттан биитэр бэлиэ-ааттан суруйары манна баар форманы туһанан боп.\nБандааллааһыны утаран уонна [[{{MediaWiki:Policy-url}}|быраабылалары]]\nтутуһан эрэ бобуохтааххын.\nХайаан да бобуу төрүөтүн кэпсээ (холобур, бандааллааһын баар сирэйдэриттэн\nбыһа тардан көрдөр).\nIP-аадырыс диапазонун [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] туһанан хааччахтыаххын сөп.\nХааччах муҥутуур диапазона IPv4 боротокуолга - /$1, IPv6 боротокуолга - /$2.",
        "ipaddressorusername": "IP аадырыһа эбэтэр кыттааччы аата:",
        "ipbexpiry": "Түмүктэниэ:",
        "ipbreason": "Төрүөтэ:",
        "ipb-unblock": "Кыттаачыны эбэтэр IP-ны бобуллубуттар испииһэктэриттэн таһаар",
        "ipb-blocklist": "Бобуулары көрдөр",
        "ipb-blocklist-contribs": "{{GENDER:$1|$1}} суруйуута",
+       "ipb-blocklist-duration-left": "$1 хаалла",
        "unblockip": "Кыттааччыны көҥүллээ",
        "unblockiptext": "IP эбэтэр кыттааччы көннөрөр/уларытар быраабын манна баар форманы туһанан төннөр.",
        "ipusubmit": "Хааччаҕын уһул",
        "lockdbsuccesstext": "Билии олоҕун (database) уларытар бобулунна.\n<br />Үлэлээн бүттэххинэ [[Special:UnlockDB|уларытары көҥүллүүргүн]] умнума.",
        "unlockdbsuccesstext": "Билии олоҕун (database) уларытар көҥүллэннэ.",
        "lockfilenotwritable": "Бобуу билэтигэр суруйар кыаҕыҥ суох. БД уларытары көҥүллүүргэ эбэтэр боборго веб-сервер бу билэни уларытар бырааптаах буолуохтаах.",
+       "databaselocked": "Билии олоҕо хайыы-үйэ хааччахтаммыт.",
        "databasenotlocked": "БД уларытааһын бобуллубата.",
        "lockedbyandtime": "($1 $2 $3)",
        "move-page": "$1 - аатын уларытыы",
        "movelogpagetext": "Манна ааттара уларытыллыбыт сирэйдэр испииһэктэрэ көстөр.",
        "movesubpage": "{{PLURAL:$1|Алын сирэй|Алын сирэйдэр}}",
        "movesubpagetext": "Бу сирэй $1 {{PLURAL:$1|алын сирэйдээх|алын сирэйдэрдээх}}.",
+       "movesubpagetalktext": "Ырытыытын сирэйэ манна көстөр $1 {{PLURAL:$1|алын сирэйдээх|алын сирэйдэрдээх}}.",
        "movenosubpage": "Бу сирэй алын сирэйэ суох.",
        "movereason": "Төрүөтэ:",
        "revertmove": "төннөрүү",
        "export-download": "Билэ быһыытынан хаалларыахха диэ",
        "export-templates": "Халыыптары киллэрии",
        "export-pagelinks": "Бачча дириҥҥэ дылы ситимнээх сирэйдэри киллэр:",
+       "export-manual": "Сирэйдэри бэйэҥ киллэр:",
        "allmessages": "Систиэмэ (тиһик) биллэриилэрэ",
        "allmessagesname": "Биллэрии",
        "allmessagesdefault": "Туспа этиллибэтэҕинэ суруллар тиэкис",
        "import-nonewrevisions": "Биир да уларытыы импортаммата (бу иннинэ таҥастаммыттар, биитэр алҕастаах буолан көтүтүллүбүттэр).",
        "xml-error-string": "$1 - $2 строка, $3 колонка ($4 байт): $5",
        "import-upload": "XML-дааннайдары киллэр",
-       "import-token-mismatch": "Арахсан хаалбыт. Өссө киирэн көр.",
+       "import-token-mismatch": "Арахсан хаалбыт. \n\nБаҕар тахсан хаалбытыҥ буолуо. <strong>Бэлиэтэммит ааккынан киирэн олороргун тургутан баран хатылаан көр.</strong>.\nӨскөтө син биир көмөлөспөтөҕүнэ [[Special:UserLogout|тахсан баран]] төттөрү киирэн көр уонна браузерыҥ куукалары ыларын тургут.",
        "import-invalid-interwiki": "Бу биикиттэн импорт оҥорор сатаммат(а).",
        "import-error-edit": "«$1» сирэй көһөрүллүбэтэ, тоҕо диэтэххэ кинини уларытарыҥ көҥүллэммэт эбит.",
        "import-error-create": "«$1» сирэй киллэриллибэтэ, тоҕо диэтэххэ кинини айарыҥ сатаммат эбит.",
        "tooltip-feed-rss": "RSS бу сирэйгэ",
        "tooltip-feed-atom": "Atom бу сирэйгэ",
        "tooltip-t-contributions": "{{GENDER:$1|Бу кыттааччы}} уларыппыт сирэйдэрин тиһилигэ",
-       "tooltip-t-emailuser": "Бу киһиэхэ сурук ыытарга",
+       "tooltip-t-emailuser": "{{GENDER:$1|Бу киһиэхэ}} сурук ыытарга",
        "tooltip-t-info": "Бу сирэй туһунан сиһилии",
        "tooltip-t-upload": "Билэлэри суруттарыы",
        "tooltip-t-specialpages": "Анал сирэйдэр испииһэктэрэ",
        "tooltip-ca-nstab-category": "Категория туһунан",
        "tooltip-minoredit": "Уларытыыны суолтата кыра курдук бэлиэтээ",
        "tooltip-save": "Уларытыыны бигэргэтии",
+       "tooltip-publish": "Уларытыыларгын бэчээттээһин",
        "tooltip-preview": "Уларытыах иннинэ көрүү; бука диэн маны туһан!",
        "tooltip-diff": "Уларытыах иннинэ баар тиэкиһи кытта тэҥнээһин.",
        "tooltip-compareselectedversions": "Икки талыллыбыт торумнар ыккардыларынааҕы уратыны көрдөр.",
        "pageinfo-article-id": "Сирэй нүөмэрэ",
        "pageinfo-language": "Сирэй омугун тыла",
        "pageinfo-content-model": "Сирэй иһинээҕитин модела",
+       "pageinfo-content-model-change": "уларыт",
        "pageinfo-robot-policy": "Роботтар көрдөөһүннэрин туруга",
        "pageinfo-robot-index": "Көҥүллэммит",
        "pageinfo-robot-noindex": "Араарыллыбыт",
        "pageinfo-category-pages": "Сирэй ахсаана",
        "pageinfo-category-subcats": "Субкатегория ахсаана",
        "pageinfo-category-files": "Билэ ахсаана",
+       "pageinfo-user-id": "Кыттааччы нүөмэрэ",
        "markaspatrolleddiff": "Бэрэбиэркэлэммит курдук бэлиэтээ",
        "markaspatrolledtext": "Бу ыстатыйаны бэрэбиэркэлэммит курдук бэлиэтээ",
+       "markaspatrolledtext-file": "Билэ бу торумун ботуруулламмыт курдук бэлиэтээ",
        "markedaspatrolled": "Бэрэбиэркэлэммит курдук бэлиэтэннэ",
        "markedaspatrolledtext": "[[:$1]] ыстатыйа барыла ботуруулламмыт курдук бэлиэтэннэ.",
        "rcpatroldisabled": "Кэлиҥҥи уларытыылары бэрэбиэркэлиир бобулунна",
        "patrol-log-header": "Ботуруулламмыт торумнар сурунааллара.",
        "log-show-hide-patrol": "$1 ботурууллааһын сурунаала",
        "log-show-hide-tag": "$1 тиэк сурунаала",
+       "confirm-markpatrolled-button": "Сөп",
+       "confirm-markpatrolled-top": "$2 сирэй $3 торумун ботуруулламмыт курдук бэлиэтиигин дуо?",
        "deletedrevision": "$1 урукку торума сотулунна",
        "filedeleteerror-short": "Билэни сотор сатаммата: $1",
        "filedeleteerror-long": "Билэни сотуу кэмигэр кэккэ моһоллор таҕыстылар:\n\n$1",
        "newimages-showbots": "Руобаттар хачайдааһыннарын көрдөр",
        "newimages-hidepatrolled": "Кэтэммит хачайданыылары сабыы.",
        "noimages": "Ойуу суох.",
+       "gallery-slideshow-toggle": "Ойуучааннары уларыт",
        "ilsubmit": "Көрдөт",
        "bydate": "айыллыбыт кэминэн",
        "sp-newimages-showfrom": "Баччаттан киирбит саҥа ойуулары көрдөр: $2, $1",
        "confirmemail_body_set": "Ким эрэ (баҕар эн буолуо) маннык IP-ттан: $1\nбу аадырыһы «$2» диэн {{SITENAME}} кыттааччыта бэйэтин аадырыһын курдук эттэ.\n\nМаны бигэргэтэр буоллаххына,\nуонна {{SITENAME}} ситим-сириттэн эйиэхэ сурук кэлэрин сөбүлэһэр буоллаххына, аллара баар сигэни баттаа:\n\n$3\n\nӨскө бу аат эйиэхэ сыһыана *суох* буоллаҕына, бу сигэнэн бараҥҥын,\nаадырыс бигэргэтиитин уурат:\n\n$5\n\nБигэргэтии куода баччаҕа дылы болдьохтоох: $4.",
        "confirmemail_invalidated": "Электроннай почта аадырыһын бигэргэтиини суох оҥоһулунна",
        "invalidateemail": "Эл. почта бигэргэтээһинин араарга",
+       "notificationemail_subject_changed": "{{SITENAME}} эл. почтатын аадырыһа уларыйда",
+       "notificationemail_subject_removed": "{{SITENAME}} эл. почтатын аадырыһа сотулунна",
+       "notificationemail_body_changed": "Ким эрэ, $1 IP-аадрырыстан,\n{{SITENAME}} \"$2\" бэлиэ-аатын эл. почтатын аадырыһын манныкка уларытта \"$3\".\n\nӨскөтө бэйэҥ уларыппатах буоллаххына, суһаллык ситим-сир дьаһабылыгар тахса сырыт.",
+       "notificationemail_body_removed": "Ким эрэ, $1 IP-аадрырыстан,\n{{SITENAME}} \"$2\" бэлиэ-аатын эл. почтатын сотон кэбистэ.\n\nӨскөтө бэйэҥ соппотох буоллаххына, суһаллык ситим-сир дьаһабылыгар тахса сырыт.",
        "scarytranscludedisabled": "[Interwiki transcluding араҕыста]",
        "scarytranscludefailed": "[$1 халыыбы туһанар табыллыбата]",
        "scarytranscludefailed-httpstatus": "[Манна $1 анаммыт халыыбы холбуур сатаммата: HTTP $2]",
        "scarytranscludetoolong": "[URL наһаа уһун]",
        "deletedwhileediting": "'''Болҕой''': Сирэйи көннөрө олордоххуна ким эрэ сотон кэбистэ!",
-       "confirmrecreate": "[[User:$1|$1]] ([[User talk:$1|ырытыыта]]) бу сирэйи эн уларыта олордоххуна сотон кэбистэ, төрүөтэ:\n: ''$2''\nБука диэн сирэйи төннөрөргүн бигэргэт.",
+       "confirmrecreate": "[[User:$1|$1]] ([[User talk:$1|ырыт.]]) бу сирэйи эн уларыта олордоххуна сотон кэбиспит, төрүөтэ:\n: <em>$2</em>\nБука диэн, сирэйи хат оҥорор буоллаххына бигэргэт.",
        "confirmrecreate-noreason": "[[User:$1|$1]] ([[User talk:$1|ырытыыта]]) бу сирэйи эн уларыта олордоххуна сотон кэбиспит. Бука диэн сирэйи кырдьык төннөрүөххүн баҕараргын бигэргэт.",
        "recreate": "Саҥаттан оҥоруу",
        "confirm_purge_button": "Сөп",
        "confirm-watch-top": "Бу сирэйи кэтээһин тиһигэр киллэрэҕин дуо?",
        "confirm-unwatch-button": "Сөп",
        "confirm-unwatch-top": "Бу сирэйи кэтээһин тиһигиттэн сотоҕун дуо?",
+       "confirm-rollback-button": "Сөп",
+       "confirm-rollback-top": "Бу сирэй уларытыыларын соттороҕун дуо?",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← инники сирэй",
        "imgmultipagenext": "аныгыскы сирэй →",
        "watchlistedit-raw-done": "Саҥа испииһэк бигэргэтилиннэ.",
        "watchlistedit-raw-added": "Тиһиккэ {{PLURAL:$1|1 пуун эбилиннэ|$1 пуун эбии киирдэ}}:",
        "watchlistedit-raw-removed": "Испииһэктэн {{PLURAL:$1|1 пуун|$1 пуун}} көҕүрээтэ:",
-       "watchlistedit-clear-title": "Ð\9aÑ\8dÑ\82Ñ\8dбил Ñ\82иһигÑ\8d Ñ\8bÑ\80ааÑ\81Ñ\82анна",
+       "watchlistedit-clear-title": "Ð\9aÑ\8dÑ\82Ñ\8dбил Ñ\82иһилигин Ñ\8bÑ\80ааÑ\81Ñ\82аа",
        "watchlistedit-clear-legend": "Кэтэбил тиһигин сот",
        "watchlistedit-clear-explain": "Кэтэбилиҥ тиһигиттэн бары суруктар сотуллуохтара",
        "watchlistedit-clear-titles": "Баһа:",
index be9c104..4683271 100644 (file)
        "tog-numberheadings": "سُرخين کي خودڪاراً نمبر ڏيو",
        "tog-showtoolbar": "سنوار اوزار ڏيکاريو",
        "tog-editondblclick": "ٻٽي ڪلڪ تي صفحا سنواريو",
-       "tog-watchcreations": "منهنجا سرجيل صفحا ۽ منهنجا چاڙهيل فائيل منهنجي زيرِ نظر فهرست تي رکو",
-       "tog-watchdefault": "منهنجا ترميميل صفحا ۽ فائيل  منهنجي نظرھيٺ فھرست ۾ رکو",
-       "tog-watchmoves": "جيڪي صفحا ۽ فائيل آءُٗ چوريان، سي منهنجي نظرھيٺ فھرست ۾ شامل ڪريو.",
-       "tog-watchdeletion": "آءُٗ جيڪي صفحا ۽ فائيل  ڊاهيان، سي منهنجي نظرھيٺ فھرست تي رکو",
-       "tog-watchuploads": "منهنجا نوان چاڙهيل فائيلس ٽيٽ فهرست ۾ شامل ڪريو",
-       "tog-watchrollback": "انهن صفحن کي منهنجي نظرھيٺ فھرست تي رکو، جن ۾ تبديلين کي مون واپس ورايو آهي.",
+       "tog-watchcreations": "منھنجا سرجيل صفحا ۽ منھنجا چاڙھيل فائيل منھنجي نظر ۾ فھرست تي رکو",
+       "tog-watchdefault": "منھنجا ترميميل صفحا ۽ فائيل  منھنجي نظر ۾ فھرست ۾ رکو",
+       "tog-watchmoves": "جيڪي صفحا ۽ فائيل آءُٗ چوريان، سي منهنجي نظر ۾ فھرست ۾ شامل ڪريو",
+       "tog-watchdeletion": "آءُٗ جيڪي صفحا ۽ فائيل  ڊاهيان، سي منهنجي نظر ۾ فھرست تي رکو",
+       "tog-watchuploads": "منھنجا نوان چاڙهيل فائيلس نظر ۾ فھرست ۾ شامل ڪريو",
+       "tog-watchrollback": "انهن صفحن کي منهنجي نظر ۾ فھرست تي رکو، جن ۾ تبديلين کي مون واپس ورايو آهي",
        "tog-minordefault": "سمورين تبديلين کي بنان چئي معمولي ترميم تصور ڪريو",
        "tog-previewontop": "ترميمي دٻيءَ مٿان پيش نگاهہ ڏيکاريو",
        "tog-previewonfirst": "پهرين ترميم تي پيش نگاهہ ڏيکاريو",
-       "tog-enotifwatchlistpages": "Ù\85Ù\86Ù\87Ù\86جÙ\8a Ù½Ù\8aÙ½ Ù\81ھرست Ø§Ù\86در Ø´Ø§Ù\85Ù\84 ÚªÙ\86Ù\87Ù\86 ØµÙ\81Ø­Ù\8a Ù\8aا Ù\81ائÙ\8aÙ\84 Û¾ ØªØ¨Ø¯Ù\8aÙ\84 Ù¾Ù\8aØ´ Ø§Ú\86Ù\8a Ù\85Ù\88Ù\86 Ú©Ù\8a Ø¨Ø±Ù\82 ٽپال اماڻيو",
-       "tog-enotifusertalkpages": "منهنجي مباحثي صفحي ۾ تبديليءَ جي صورت ۾ مون کي برق ٽپال اماڻيو",
-       "tog-enotifminoredits": "صفحن ۾ معمولي ترميمن جي صورت ۾ بہ مون کي برق ٽپال ڪريو",
-       "tog-enotifrevealaddr": "پڌراين ۾ منهنجو برق ٽپال پتو ظاهر ڪريو.",
+       "tog-enotifwatchlistpages": "Ù\85Ù\86Ù\87Ù\86جÙ\8a Ù\86ظر Û¾ Ù\81ھرست Ø§Ù\86در Ø´Ø§Ù\85Ù\84 ÚªÙ\86Ù\87Ù\86 ØµÙ\81Ø­Ù\8a Ù\8aا Ù\81ائÙ\8aÙ\84 Û¾ ØªØ¨Ø¯Ù\8aÙ\84 Ù¾Ù\8aØ´ Ø§Ú\86Ù\8a Ù\85Ù\88Ù\86 Ú©Ù\8a Ø¨Ø±Ù\82ٽپال اماڻيو",
+       "tog-enotifusertalkpages": "منهنجي مباحثي صفحي ۾ تبديليءَ جي صورت ۾ مون کي برقٽپال اماڻيو",
+       "tog-enotifminoredits": "صفحن ۾ معمولي ترميمن جي صورت ۾ بہ مون کي برقٽپال ڪريو",
+       "tog-enotifrevealaddr": "پڌراين ۾ منهنجو برقٽپال پتو ظاهر ڪريو",
        "tog-shownumberswatching": "ڏسندڙ يوزرس جو انگ ڏيکاريو",
        "tog-oldsig": "توھان جو موجوده دستخط:",
        "tog-fancysig": "صحيح کي وڪيٽيڪسٽ سمجھو (ڪنھن خوڪار ڳنڍڻي کانسواءِ)",
        "tog-uselivepreview": "سڌي سنئين پيش نگاھہ استعمال ڪريو",
-       "tog-watchlisthideown": "زير نظر فهرست مان منهنجون ڪيل ترميمون لڪايو",
-       "tog-watchlisthidebots": "Ù½Ù\8aÙ½ Ù\81Ù\87رست تان بوٽ جون ترميمون لڪايو",
-       "tog-watchlisthideminor": "Ù½Ù\8aÙ½ Ù\81Ù\87رست تان معمولي ترميمون لڪايو",
-       "tog-watchlisthideliu": "لاگ اِن ٿيل يوزرس جون ڪيل ترميمون زيرنظر فهرست ۾ نہ ڏيکاريو",
-       "tog-watchlisthideanons": "Ù½Ù\8aÙ½ Ù\81Ù\87رست تان اڻڄاتل يوزر جون ترميمون لڪايو",
-       "tog-watchlisthidepatrolled": "Ù½Ù\8aÙ½ فھرست مان گشت ڪيل ترميمون لڪايو",
+       "tog-watchlisthideown": "نظر ۾ فھرست مان منهنجون ڪيل ترميمون لڪايو",
+       "tog-watchlisthidebots": "Ù\86ظر Û¾ Ù\81Ú¾رست تان بوٽ جون ترميمون لڪايو",
+       "tog-watchlisthideminor": "Ù\86ظر Û¾ Ù\81Ú¾رست تان معمولي ترميمون لڪايو",
+       "tog-watchlisthideliu": "داخل ٿيل يوزرس جون ڪيل ترميمون نظر ۾ فھرست ۾ نہ ڏيکاريو",
+       "tog-watchlisthideanons": "Ù\86ظر Û¾ Ù\81Ú¾رست تان اڻڄاتل يوزر جون ترميمون لڪايو",
+       "tog-watchlisthidepatrolled": "Ù\86ظر Û¾ فھرست مان گشت ڪيل ترميمون لڪايو",
        "tog-watchlisthidecategorization": "صفحن جا زمرا لڪايو",
-       "tog-ccmeonemails": "ٻين يوزرس ڏانهن منهنجي موڪليل برق ٽپال جو پرت مون کي اماڻيو",
+       "tog-ccmeonemails": "ٻين يوزرس ڏانھن منهنجي موڪليل برقٽپال جو پرت مون کي اماڻيو",
        "tog-diffonly": "تفاوت هيٺان صفحي جو مواد نہ ڏيکاريو",
        "tog-showhiddencats": "لڪل زمرا ڏيکاريو",
        "tog-norollbackdiff": "واپس ورائڻ کان پوءِ تفاوت نہ ڏيکاريو",
@@ -74,7 +74,7 @@
        "february": "فيبروري",
        "march": "مارچ",
        "april": "اپريل",
-       "may_long": "مَي",
+       "may_long": "مَئي",
        "june": "جُونِ",
        "july": "جُولاءِ",
        "august": "آگسٽ",
@@ -98,7 +98,7 @@
        "feb": "فيبروري",
        "mar": "مارچ",
        "apr": "اپريل",
-       "may": "مَي",
+       "may": "مَئي",
        "jun": "جُونِ",
        "jul": "جُولاءِ",
        "aug": "آگسٽ",
        "category-empty": "''في‌الوقت هن زمري ۾ ڪي بہ صفحا يا ذريعات شامل ناهن.''",
        "hidden-categories": "{{PLURAL:$1|لڪيل زمرو|لڪيل زمرا}}",
        "hidden-category-category": "لڪل زمرا",
-       "category-subcat-count": "{{PLURAL:$2|هن ذمري ۾ رڳو هيٺيون ذيلي ذمرو آهي.|هن ذمري ۾ ڪل $2 مان هيٺيان {{PLURAL:$1|subcategory|$1 ذيلي ذمرا}} آهن.}}",
+       "category-subcat-count": "{{PLURAL:$2|ھن زمري ۾ رڳو ھيٺيون ذيلي زمرو آهي.|هن زمري ۾ ڪل $2 مان ھيٺيان {{PLURAL:$1|subcategory|$1 ذيلي زمرا}} آھن.}}",
        "category-subcat-count-limited": "هن زمري ۾ هيٺيان {{PLURAL:$1|ننڍا زمرا آهن|$1 subcategories}}.",
-       "category-article-count": "{{PLURAL:$2|هن زمري ۾ صرف هيٺيون صفحو آهي.|هيٺيان {{PLURAL:$1|صفحو آهي|$1 صفحا آهن}} هن زمري ۾, سمورن $2 مان.}}",
+       "category-article-count": "{{PLURAL:$2|هن زمري ۾ صرف هيٺيون صفحو آهي.|هيٺيان {{PLURAL:$1|صفحو آهي|$1 صفحا آهن}} هن زمري ۾، سمورن $2 مان.}}",
        "category-article-count-limited": "هيٺِون {{PLURAL:$1|صفحو آهي|$1 صفحا آهن}} تازي زمري ۾.",
        "category-file-count": "{{PLURAL:$2|هن زمري ۾ صرف هيٺيون فائيل آهي.|هيٺيون يا هيٺيان {{PLURAL:$1|فائيل آهي|$1 فائيل آهن}} هن زمري ۾، سمورن $2 مان.}}",
        "category-file-count-limited": "هيٺيون يا هيٺيان {{PLURAL:$1|فائيل آهي|$1 فائيل آهن}} هن تازي زمري ۾.",
        "mypage": "منهنجو صفحو",
        "mytalk": "بحث",
        "anontalk": "بحث",
-       "navigation": "رهنمائي",
+       "navigation": "رھنمائي",
        "and": "&#32؛۽",
        "qbfind": "ڳوليو",
        "qbbrowse": "جھانگيو",
        "actions": "ڪارگذاريون",
        "namespaces": "نانءُپولارَ",
        "variants": "بَدَلَ",
-       "navigation-heading": "رهنما مينيو",
+       "navigation-heading": "رھنما مينيو",
        "errorpagetitle": "چُڪَ",
-       "returnto": "$1 ڏانهن وَرو.",
+       "returnto": "$1 ڏانھن وَرو.",
        "tagline": "{{SITENAME}} طرفان",
        "help": "مدد",
        "search": "ڳولا",
        "views": "ڏيٺون",
        "toolbox": "اوزارَ",
        "tool-link-userrights": "{{GENDER:$1|يوزر}} گروھ تبديل ڪريو",
+       "tool-link-userrights-readonly": "{{GENDER:$1|يوزر}} گروھ ڏسو",
        "tool-link-emailuser": "ھن {{GENDER:$1|يوزر}} ڏانھن برقٽپال موڪليو",
        "userpage": "يوزر صفحو ڏسو",
        "projectpage": "رٿائي صفحو ڏسو",
        "redirectedfrom": "($1 کان چوريل)",
        "redirectpagesub": "چوريل صفحو",
        "redirectto": "ڏانھن چوريو:",
-       "lastmodifiedat": "هيءُ صفحو آخري دفعو $2، $1ع تي سنواريو ويو هو.",
+       "lastmodifiedat": "ھيءُ صفحو آخري دفعو $2، $1ع تي سنواريو ويو ھو",
        "viewcount": "هيءُ صفحو {{PLURAL:$1|دفعو|$1 دفعا}} ڏسجي چڪو آهي.",
        "protectedpage": "تحفظيل صفحو",
        "jumpto": "ڏانھن ٽپ ڏيو:",
-       "jumptonavigation": "رهنمائي",
+       "jumptonavigation": "رھنمائي",
        "jumptosearch": "ڳولا",
        "view-pool-error": "معذرت سان سرور هاڻي تمام گھڻو سُڪ آهي.\nتمام گھڻا يوزر ھن صفحي کي ڏسڻ جي ڪوشش ڪري رھيا آھن.\nمهرباني ڪري ٿورو ترسو انکان اڳ جو توھان ھن صفحي تائين رسڻ لاءِ ٻيھر ڪوشش ڪريو.\n\n$1",
        "generic-pool-error": "معذرت سان سرور هاڻي تمام گھڻو سُڪ آهي.\nتمام گھڻا يوزر هتي موجود آهن.\nمهرباني ڪري ٿورو ترسي پوءِ ڪوشش ڪريو.",
-       "pool-errorunknown": "اڻ ڄاتل چُڪَ",
+       "pool-errorunknown": "اڻڄاتل چُڪَ",
        "poolcounter-usage-error": "استعمال جي خرابي: $1",
        "aboutsite": "{{SITENAME}} بابت",
        "aboutpage": "Project:بابت",
        "sort-descending": "لهندڙ ترتيب ڏيو",
        "sort-ascending": "چڙهندڙ ترتيب ڏيو",
        "nstab-main": "صفحو",
-       "nstab-user": "تعارفي صفحو",
+       "nstab-user": "يُوزر صفحو",
        "nstab-media": "ذريعاتي صفحو",
        "nstab-special": "خاص صفحو",
        "nstab-project": "رٿائي صفحو",
        "mycustomcssprotected": "توهان کي هيءُ CSS صفحو سنوارڻ جي اجازت نہ آهي.",
        "mycustomjsprotected": "توهان کي هيءُ جاوا اسڪرپٽ صفحو سنوارڻ جي اجازت حاصل ڪانهي.",
        "myprivateinfoprotected": "توهان کي پنهنجي ذاتي معلومات سنوارڻ جي اجازت حاصل نہ آهي.",
-       "mypreferencesprotected": "توهان جي پنهنجون ترجيحات سنوارڻ جي اجات حاصل ڪانهي.",
+       "mypreferencesprotected": "توھان کي پنھنجون ترجيحون سنوارڻ جي اجات حاصل ڪانھي.",
        "ns-specialprotected": "خاص صفحا سنواري نٿا سگھجن.",
        "titleprotected": "[[User:$1|$1]] اهڙي عنوان سان صفحو سرجڻ تي روڪ لڳائي ڇڏي آهي. سبب <em>$2</em> ڄاڻايو ويو آهي.",
        "exception-nologin": "داخل ٿيل نہ آهيو",
-       "virus-unknownscanner": "اڻ ڄاتل نِس وائرس:",
-       "cannotlogoutnow-title": "ھاڻي ٻاھر نٿو نڪري سگھجي",
-       "cannotlogoutnow-text": "$1 استعمال ڪرڻ دوران ٻاھر نڪرڻ ممڪن نہ آھي.",
+       "virus-unknownscanner": "اڻڄاتل نِس وائرس:",
+       "cannotlogoutnow-title": "ھاڻي خارج نٿو ٿي سگھجي",
+       "cannotlogoutnow-text": "$1 استعمال ڪرڻ دوران خارج ٿيڻ ممڪن نہ آھي.",
        "welcomeuser": "ڀلي ڪري آيا، $1!",
        "yourname": "يُوزرنانءُ:",
        "userlogin-yourname": "يوزرنانءُ",
-       "userlogin-yourname-ph": "پنهنجو يوزرنانءُ ڄاڻايو",
+       "userlogin-yourname-ph": "پنھنجو يوزرنانءُ ڄاڻايو",
        "createacct-another-username-ph": "يُوزرنانءُ ڄاڻايو",
        "yourpassword": "ڳجھولفظ:",
        "userlogin-yourpassword": "ڳجھولفظ",
        "userlogin-yourpassword-ph": "پنهنجو ڳجھولفظ ڄاڻايو",
-       "createacct-yourpassword-ph": "ڳجھولفظ ڄاڻايو",
+       "createacct-yourpassword-ph": "ÚªÙ\88 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ú\84اڻاÙ\8aÙ\88",
        "yourpasswordagain": "يُوزرنان ٻيهر ٽائيپ ڪريو:",
-       "createacct-yourpasswordagain": "ڳجھي لفظ جي خاطري ڪريو",
+       "createacct-yourpasswordagain": "ڳجھي لفظ جي پڪ ڪريو",
        "createacct-yourpasswordagain-ph": "ٻيھر ڳجھولفظ داخل ڪريو",
        "userlogin-remembermypassword": "مون کي داخل ٿيل رکو",
        "userlogin-signwithsecure": "محفوظ ڳانڍاپو استعمال ڪريو",
        "nav-login-createaccount": "داخل ٿيو / کاتو کوليو",
        "userlogin": "داخل ٿيو / کاتو کوليو",
        "userloginnocreate": "داخل ٿيو",
-       "logout": "ٻاھر نڪرو",
-       "userlogout": "ٻاھر نڪرو",
+       "logout": "خارج ٿيو",
+       "userlogout": "خارج ٿيو",
        "notloggedin": "داخل ٿيل نہ آهيو",
        "userlogin-noaccount": "کاتو نہ ٿا رکو؟",
        "userlogin-joinproject": "{{SITENAME}} ۾ شامل ٿيو",
        "userlogin-reauth": "اھو پڪ ڪرڻ لاءِ ته توھان {{GENDER:$1|$1}} آھيو توھان کي ٻيھر داخل ٿيڻو پوندو.",
        "userlogin-createanother": "ٻيو کاتو کوليو",
        "createacct-emailrequired": "برق ٽپال پتو",
-       "createacct-emailoptional": "برق ٽپال پتو (مرضيءَ موجب)",
-       "createacct-email-ph": "پنهنجو برق ٽپال پتو ڄاڻايو",
+       "createacct-emailoptional": "برقٽپال پتو (مرضيءَ موجب)",
+       "createacct-email-ph": "پنھنجو برقٽپال پتو ڄاڻايو",
        "createacct-another-email-ph": "برق ٽپال پتو ڄاڻايو",
-       "createaccountmail": "ڪو بہ عارضي ڳجھو لفظ استعمال ڪريو ۽ ڄاڻايل برق ٽپال پتي تي اماڻيو",
+       "createaccountmail": "ڪو بہ عارضي ڳجھولفظ استعمال ڪريو ۽ ڄاڻايل برقٽپال پتي تي اماڻيو",
        "createacct-realname": "اصل نالو (مرضيءَ موجب)",
        "createaccountreason": "سبب:",
        "createacct-reason": "سبب",
        "createacct-reason-ph": "توهان ٻيو کاتو ڇو کولي رهيا آهيو",
-       "createacct-submit": "پنهنجو کاتو کوليو",
+       "createacct-submit": "پنھنجو کاتو کوليو",
        "createacct-another-submit": "کاتو کوليو",
        "createacct-continue-submit": "کاتو کولڻ جاري رکو",
        "createacct-another-continue-submit": "کاتو کولڻ جاري رکو",
-       "createacct-benefit-heading": "{{SITENAME}} توهان جهڙن سڄڻن ٺاهيو آهي.",
+       "createacct-benefit-heading": "{{SITENAME}} توھان جھڙن سڄڻن ٺاھيو آھي.",
        "createacct-benefit-body1": "{{PLURAL:$1|ترميم|ترميمون}}",
        "createacct-benefit-body2": "{{PLURAL:$1|صفحو|صفحا}}",
        "createacct-benefit-body3": "ھاڻوڪا {{PLURAL:$1|ڀاڱيدار}}",
        "usernameinprogress": "ان يُوزرنانءُ لاءِ کاتو اڳ ۾ ئي تياريءَ هيٺ آهي. مهرباني ڪري انتظار فرمايو.",
        "userexists": "ڄاڻايل يوزرنانءُ اڳ ۾ ئي استعمال هيٺ آهي. مهرباني ڪري ڪو ٻيو يُوزرنانءُ چونڊيو.",
        "loginerror": "داخل ٿيڻ ۾ چُڪَ",
-       "createacct-error": "کاٿو کولڻ ۾ چُڪَ",
+       "createacct-error": "کاتو کولڻ ۾ چُڪَ",
        "createaccounterror": "کاتو کُلي نہ سگھيو: $1",
        "nocookiesnew": "يُوزر کاتو کلي چڪو، پر توهان داخل نہ ٿيا آهيو. يُوزرس کي داخل ڪرڻ لاءِ {{SITENAME}} ڪوڪيز استعمال ڪندي آهي. توهان ڪوڪيز کي ناڪاره بڻائي رکيو آهي. داخل ٿيڻ لاءِ ڪوڪيز کي ڪارائتو بڻايو.",
-       "nocookieslogin": "يُوزرس کي لاگ اِن ڪرڻ لاءِ {{SITENAME}} ڪوڪيز استعمال ڪندي آهي. توهان ڪوڪيز کي ناڪاره بڻائي رکيو آهي. لاگ اِن ٿيڻ لاءِ ڪوڪيز کي ڪارائتو بڻايو.",
+       "nocookieslogin": "يُوزرس کي داخل ڪرڻ لاءِ {{SITENAME}} ڪوڪيز استعمال ڪندي آهي.\nتوھان ڪوڪيز کي ناڪاره بڻائي رکيو آھي.\nداخل ٿيڻ لاءِ ڪوڪيز کي ڪارائتو بڻايو.",
        "noname": "توهان جو ڄاڻايل يُوزرنانءُ ناقابل ڪار آهي.",
        "loginsuccesstitle": "داخل ٿيل",
        "loginsuccess": "'''هاڻي توهان {{SITENAME}} تي بطور \"$1\" داخل ٿيل آهيو.'''",
        "password-name-match": "توهان جو ڳجھولفظ توهان جي يوزرنانءُ کان مختلف هجڻ گھرجي.",
        "mailmypassword": "ڳجھولفظ ٻيھر مقرر ڪريو",
        "passwordremindertitle": "{{SITENAME}} لاءِ نئون عارضي ڳجھولفظ",
-       "passwordremindertext": "ڪنهن (شايد توهان آءِ پي پتي $1 تان) اسان کي {{SITENAME}} ($4) لاءِ نئون ڳجھو لفظ اماڻڻ جي گھُرَ ڪئي.\"$2\" يوزر لاءِ هڪ ڳجھُ لفظ تخليق ڪيو ويو آهي \"$3\" تي ترتيب ڏنو ويو هو. جيڪڏهن اهو توهان جي ارادو هيو، ته هاڻي توهان کي هينئر ئي لاگ اِن ٿي پنهنجو ڳجھو لفظ تبديل ڪرڻ گھرجي.\nتوهان جو عارضي ڳجھو لفظ {{PLURAL:$5|هڪ ڏينهُن|$5 ڏينهَن}} ۾ ختم ٿيندو.\n\nجيڪڏهن اها گھُرَ اوهان نه ڪئي هئي، يا هاڻي اوهان کي پنهنجو ڳجھو لفظ ياد اچي ويو آهي ۽ توهان ان کي تبديل ڪرڻ نه ٿا چاهيو، ته توهان هن نياپي کي نظر انداز ڪندي پنهنجو پراڻو ڳجھو لفظ ئي استعمال ڪري سگھو ٿا.",
-       "noemail": "يُوزر \"$1\" جي ڪو بہ برق ٽپال پتو درج ٿيل ناهي.",
-       "noemailcreate": "توهان کي قابل ڪار برق ٽپال پتو مهيا ڪرڻو پوندو.",
-       "passwordsent": "يوزر \"$1\" لاءِ هڪ نئون ڳجھو لفظ برق ٽپال ذريعي اماڻيو ويو آهي.  مهرباني ڪري اهو حاصل ڪرڻ بعد لاگ اِن ٿيندا.",
+       "passwordremindertext": "ڪنھن (شايد توھان آءِپي پتي $1 تان) اسان کي {{SITENAME}} ($4) لاءِ نئون ڳجھولفظ اماڻڻ جي گُھرَ ڪئي.\"$2\" يوزر لاءِ ھڪ ڳجھولفظ تخليق ڪيو ويو آهي \"$3\" تي ترتيب ڏنو ويو ھو. جيڪڏھن اھو توھان جو ارادو ھيو، تہ ھاڻي توھان کي ھينئر ئي داخل ٿي پنھنجو ڳجھولفظ تبديل ڪرڻ گھرجي.\nتوھان جو عارضي ڳجھولفظ {{PLURAL:$5|هڪ ڏينھُن|$5 ڏينھَن}} ۾ ختم ٿيندو.\n\nجيڪڏھن اھا گُھرَ اوھان نہ ڪئي ھئي، يا ھاڻي اوھان کي پنھنجو ڳجھولفظ ياد اچي ويو آھي ۽ توھان ان کي تبديل ڪرڻ نٿا چاھيو، تہ توھان ھن نياپي کي نظر انداز ڪندي پنھنجو پراڻو ڳجھولفظ ئي استعمال ڪري سگھو ٿا.",
+       "noemail": "يُوزر \"$1\" جي ڪو بہ برقٽپال پتو درج ٿيل ناهي.",
+       "noemailcreate": "توھان کي قابلڪار برقٽپال پتو مھيا ڪرڻو پوندو.",
+       "passwordsent": "يوزر \"$1\" لاءِ ھڪ نئون ڳجھولفظ برقٽپال ذريعي اماڻيو ويو آهي.  مھرباني ڪري اھو حاصل ڪرڻ بعد داخل ٿيندا.",
        "mailerror": "ٽپال اماڻڻ ۾ چُڪَ: $1",
        "acct_creation_throttle_hit": "توهان جي آءِپي پتي تان هن وڪيءَ تي پوئين $2، کان {{PLURAL:$1|1 کاتو|$1 کاتا}} کلي چڪا آهن، جيڪو وڌ ۾ وڌ اجازت ڏنل وقت آهي. \nنتيجتاً ساڳي آءِپي پتي تان في‌الوقت وڌيڪ کاتا کولي نٿا سگھجن.",
-       "emailauthenticated": "توهان جي برق ٽپال پتي جي تصديق $2 تي $3 بجي ڪئي وئي.",
-       "emailnotauthenticated": "توهان جو برق ٽپال پتي جي تصديق اڃا ٿي نہ سگھي آهي.",
-       "noemailprefs": "انهن فيچرس کي فعال بڻائڻ لاءِ پنهنجي ترجيحات ۾ برق ٽپال پتو ڄاڻايو.",
-       "emailconfirmlink": "پنهنجي برق ٽپال پتي جي پَڪَ ڪندا",
-       "invalidemailaddress": "Ù\87Ù\8a Ø§Ù\8aÙ\85Ù\8aÙ\84 Ù¾ØªÙ\88 Ù\82بÙ\88Ù\84 Ù\86Ù¿Ù\88 ÚªÚªØ±Ù\8a Ø³Ú¯Ú¾Ø¬Ù\8a Ø§Ù\8aئÙ\86 Ù¿Ù\88 Ù\84Ú³Ù\8a ØªÙ\87 Ø§Ù\86جÙ\88 Ù\81ارÙ\85Ù\8aÙ½ Ù\82ابÙ\84 Ù\82بÙ\88Ù\84 Ù\86Ù\87 Ø¢Ù\87Ù\8a.\nبراءÙ\90 Ù\85Ù\87رباني هڪ قابل قبول فارميٽ وارو پتو موڪليو يا ان جڳھ کي کالي ڇڏيو.",
-       "cannotchangeemail": "هن وڪيءَ تي کاتيدار جو برق ٽپال پتو بدلائي نہ ٿو سگھجي.",
-       "emaildisabled": "هيءَ سرزمين برق ٽپال اماڻي نہ ٿي سگھي.",
+       "emailauthenticated": "توھان جي برقٽپال پتي جي تصديق $2 تي $3 بجي ڪئي وئي.",
+       "emailnotauthenticated": "توھان جو برقٽپال پتي جي تصديق اڃا ٿي نہ سگھي آھي.",
+       "noemailprefs": "انھن فيچرس کي فعال بڻائڻ لاءِ پنهنجي ترجيحن ۾ برقٽپال پتو ڄاڻايو.",
+       "emailconfirmlink": "پنھنجي برقٽپال پتي جي پَڪَ ڪندا",
+       "invalidemailaddress": "Ù\87Ù\8a Ø¨Ø±Ù\82ٽپاÙ\84 Ù¾ØªÙ\88 Ù\82بÙ\88Ù\84 Ù\86Ù¿Ù\88 ÚªØ±Ù\8a Ø³Ú¯Ú¾Ø¬Ù\8a Ø§Ù\8aئÙ\86 Ù¿Ù\88 Ù\84Ú³Ù\8a ØªÛ\81 Ø§Ù\86جÙ\88 Ù\81ارÙ\85Ù\8aÙ½ Ù\82ابÙ\84 Ù\82بÙ\88Ù\84 Ù\86Û\81 Ø¢Ù\87Ù\8a.\nبراءÙ\90 Ù\85Ú¾رباني هڪ قابل قبول فارميٽ وارو پتو موڪليو يا ان جڳھ کي کالي ڇڏيو.",
+       "cannotchangeemail": "هن وڪيءَ تي کاتيدار جو برقٽپال پتو بدلائي نہ ٿو سگھجي.",
+       "emaildisabled": "هيءَ سرزمين برقٽپال اماڻي نہ ٿي سگھي.",
        "accountcreated": "کاتو کلي چڪو",
        "accountcreatedtext": "يوزر کاتو [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) جي لاءِ تخليق ٿي چڪو آهي.",
        "createaccount-title": "{{SITENAME}} تي کاتو کولڻ",
        "pt-login-button": "داخل ٿيو",
        "pt-login-continue-button": "داخل ٿيڻ جاري رکو",
        "pt-createaccount": "کاتو کوليو",
-       "pt-userlogout": "ٻاھر نڪرو",
+       "pt-userlogout": "خارج ٿيو",
        "php-mail-error-unknown": "پي ايڇ پي جي  ڪاڄ اندر اڻڄاتل چُڪَ.",
-       "user-mail-no-addy": "برق ٽپال پتو ڄاڻائڻ کان سواءِ برق ٽپال اماڻڻ جي ڪوشش ڪئي وئي.",
+       "user-mail-no-addy": "برقٽپال پتو ڄاڻائڻ کان سواءِ برق ٽپال اماڻڻ جي ڪوشش ڪئي وئي.",
        "changepassword": "ڳجھولفظ تبديل ڪريو",
        "resetpass_announce": "داخل ٿيڻ جو عمل پورو ڪرڻ لاءِ، توهان کي نئون ڳجھولفظ اختيار مقرر ڪرڻو پوندو.",
        "resetpass_header": "کاتي جو ڳجھولفظ بدلايو",
        "resetpass_submit": "ڳجھولفظ طَي ڪريو ۽ داخل ٿيو",
        "changepassword-success": "توهان جو ڳجھولفظ بدلايو ويو آھي!",
        "changepassword-throttled": "توهان تازو ئي داخل ٿيڻ جون هيڪانديون گھڻيون ڪوششون ڪيون آهن. مهرباني ڪري $1 لاءِ ترسي پوءِ وري ڪوشش ڪريو.",
+       "botpasswords": "بوٽ جو ڳجھولفظ",
+       "botpasswords-disabled": "بوٽ ڳجھالفظ ناقابلِڪار ڪيل آھن.",
        "botpasswords-label-create": "سرجيو",
        "botpasswords-label-update": "تجديد",
        "botpasswords-label-cancel": "رد",
        "resetpass-submit-cancel": "رد",
        "resetpass-wrong-oldpass": "ناقابل ڪار هاڻوڪو يا عارضي ڳجھولفظ. \nتوهان پنهنجو ڳجھو لفظ اڳ ۾ ئي بدلائي چڪا آهيو يا نئين ڳجھي لفظ لاءِ درخواست ڏئي چڪا آهيو.",
        "resetpass-recycled": "مهرباني ڪري پنهنجي هاڻوڪي ڳجھي لفظ کان ڪو مختلف ڳجھو لفظ چونڊيو.",
-       "resetpass-temp-emailed": "توهان برق ٽپال ذريعي اماڻيل عارضي ڳجھي لفظ سان لاگ اِن ٿيا آهيو. لاگ اِن کي مڪمل ڪرڻ لاءِ توهان کي هتي نئون ڳجھو لفظ طَي ڪرڻو ئي پوندو:",
+       "resetpass-temp-emailed": "توهان برقٽپال ذريعي اماڻيل عارضي ڳجھي لفظ سان داخل ٿيا آهيو. داخل ٿيڻ کي مڪمل ڪرڻ لاءِ توهان کي هتي نئون ڳجھولفظ طَي ڪرڻو ئي پوندو:",
        "resetpass-temp-password": "عارضي ڳجھولفظ:",
        "resetpass-expired": "توهان جو ڳجھولفظ مدي خارج ٿي چڪو آهي. نئون ڳجھولفظ مقرر ڪريو ۽ داخل ٿيو.",
        "resetpass-expired-soft": "توهان جو ڳجھو لفظ مدي خارج ٿي چڪو آهي. مهرباني ڪري نئون ڳجھو لفظ چونڊيو، يا ساڳيو ڪم ڪنهن ٻي وقت ڪرڻ لاءِ \"{{int:authprovider-resetpass-skip-label}}\" تي ڪلڪ ڪريو.",
        "resetpass-validity-soft": "توهان جو ڳجھولفظ ناقابل ڪار آهي: $1\nمهرباني ڪري نئون ڳجھولفظ چونڊيو، يا ساڳيو ڪم ڪنهن ٻي وقت ڪرڻ لاءِ \"{{int:authprovider-resetpass-skip-label}}\" تي ڪلڪ ڪريو.",
        "passwordreset": "ڳجھولفظ مَٽايو",
-       "passwordreset-text-one": "برق ٽپال ذريعي عارضي ڳجھولفظ حاصل ڪرڻ لاءِ هيءُ فارم پُر ڪريو.",
+       "passwordreset-text-one": "برقٽپال ذريعي عارضي ڳجھولفظ حاصل ڪرڻ لاءِ هيءُ فارم پُر ڪريو.",
        "passwordreset-disabled": "هن وڪيءَ تي ڳجھولفظ ٻيھر مقرر ڪرڻ وارو چارو غير فعال بڻايو ويو آهي.",
        "passwordreset-emaildisabled": "هن وڪيءَ تي برق‌ٽپال واريون خصوصيتون غير فعال بڻايون ويون آهن.",
        "passwordreset-username": "يُوزرنانءُ:",
        "passwordreset-email": "برق ٽپال پتو:",
        "passwordreset-emailtitle": "{{SITENAME}} واري کاتي جا تفصيل",
        "passwordreset-emailelement": "يُوزر نانءُ: \n$1\n\nعارضي ڳجھو لفظ:\n$2",
-       "passwordreset-invalidemail": "ناقابل ڪار برق ٽپال پتو",
-       "changeemail": "برق ٽپال پتو مِٽايو يا بدلايو",
-       "changeemail-oldemail": "هاڻوڪو برق ٽپال پتو:",
-       "changeemail-newemail": "نئون برق ٽپال پتو:",
+       "passwordreset-invalidemail": "ناقابلڪار برقٽپال پتو",
+       "changeemail": "برقٽپال پتو مِٽايو يا بدلايو",
+       "changeemail-oldemail": "هاڻوڪو برقٽپال پتو:",
+       "changeemail-newemail": "نئون برقٽپال پتو:",
        "changeemail-none": "(ڪو بہ نہ)",
        "changeemail-password": "توهان جو {{SITENAME}} ڳجھو لفظ:",
-       "changeemail-submit": "برق ٽپال پتو بدلايو",
-       "changeemail-throttled": "توهان تازو ئي لاگ اِن ٿيڻ جون هيڪانديون گھڻيون ڪوششون ڪيون آهن. مهرباني ڪري $1 لاءِ ترسي پوءِ وري ڪوشش ڪريو.",
-       "changeemail-nochange": "مهرباني ڪري مختلف نئون برق ٽپال پتو ڄاڻايو.",
-       "resettokens": "ٻيهر ترتيب ڪرڻ جا ٽوڪن",
-       "resettokens-no-tokens": "ٻيهر ترتيب ڪرڻ لاءِ ڪي بہ ٽوڪن نہ آهن.",
+       "changeemail-submit": "برقٽپال پتو بدلايو",
+       "changeemail-throttled": "توھان تازو ئي داخل ٿيڻ جون هيڪانديون گھڻيون ڪوششون ڪيون آهن. مھرباني ڪري $1 لاءِ ترسي پوءِ وري ڪوشش ڪريو.",
+       "changeemail-nochange": "مھرباني ڪري مختلف نئون برقٽپال پتو ڄاڻايو.",
+       "resettokens": "ٻيھر ترتيب ڪرڻ جا ٽوڪن",
+       "resettokens-no-tokens": "ٻيھر ترتيب ڪرڻ لاءِ ڪي بہ ٽوڪن نہ آھن.",
        "resettokens-tokens": "ٽوڪنس:",
        "resettokens-token-label": "$1 (حاليہ قدر: $2)",
-       "resettokens-resetbutton": "چونڊيل ٽوڪن ٻيهر ترتيب ڪريو",
+       "resettokens-resetbutton": "چونڊيل ٽوڪن ٻيھر ترتيب ڪريو",
        "bold_sample": "گھري لکت",
        "bold_tip": "گھري لکت",
        "italic_sample": "ترڇي لکت",
        "extlink_tip": "خارجي ڳنڍڻو (اڳياڙي http://  نہ وساريندا)",
        "headline_sample": "سرخي جي لکت",
        "headline_tip": "سطح 2 جي سرخي",
+       "nowiki_sample": "غير-فارميٽڊ لکت شامل ڪريو",
        "nowiki_tip": "وڪي فارميٽڱ کي نظرانداز ڪريو",
        "image_tip": "جَڙيل فائيل",
        "media_tip": "فائيل جو ڳنڍڻو",
-       "sig_tip": "توهان جي صحيح بمع اوقاتي مهر",
-       "hr_tip": "افقي لڪير (غيرضروري استعمال کان پاسو ڪندا)",
+       "sig_tip": "توھان جي صحيح بمع اوقاتي مھر",
+       "hr_tip": "افقي لڪير (ڪفايت سان استعمال ڪريو)",
        "summary": "تَتُ:",
        "subject": "موضوع:",
-       "minoredit": "هيءَ هڪ معمولي ترميم آهي",
-       "watchthis": "هيءُ صفحو سانڍيو",
+       "minoredit": "ھيءَ ھڪ معمولي ترميم آھي",
+       "watchthis": "هيءُ صفحو نظر ۾ رکو",
        "savearticle": "صفحو سانڍيو",
        "savechanges": "تبديليون سانڍيو",
        "publishpage": "صفحو ڇاپيو",
        "preview": "پيش نگاھ",
        "showpreview": "پيش نگاھ",
        "showdiff": "تبديليون ڏيکاريو",
-       "anoneditwarning": "<strong>خبردار:</strong> توهان داخل ٿيل نہ آهيو. جيڪڏهن توهان ڪي ترميمون ڪيون تہ هن صفحي جي سوانح ۾ توهان جو آءِ پي پتو درج ڪيو ويندو. جي توهان <strong>[$1 داخل]</strong> ٿيو ٿا < يا strong>[$2 کاتو کوليو] </strong> ٿا، تہ توهان جو ترميمون توهان جي يوزرنانءُ سان منسوب ڪيون وينديون، جنهن جا ٻيا بہ فائدا ٿي سگھن ٿا.",
+       "anoneditwarning": "<strong>چتاءُ:</strong> توھان داخل ٿيل نہ آھيو. توھان جو آءِپي پتو عوامي طور ظاھر ٿيندو جي توھان ڪي ترميمون ڪريو ٿا. جيڪڏھن توھان <strong>[$1 داخل ٿيو]</strong> ٿا يا <strong>[$2 کاتو کوليو]</strong> ٿا، تہ ٻين فائدن سان گڏ توھان جون ترميمون توھان جي يوزرنانءَ سان منسوب ڪيون وينديون.",
        "anonpreviewwarning": "توهان داخل ٿيل نہ آهيو. جيڪڏهن توهان صفحي ۾ تبديليون سانڍيون تہ اهڙين تبديلين ساڻ توهان جو آءِپي پتو درج ڪيو ويندو.",
-       "missingcommenttext": "براءِ مهرباني هيٺ پنهنجا تاثرات درج ڪندا.",
+       "missingcommenttext": "براءِ مھرباني هيٺ پنهنجو تاثر درج ڪندا.",
        "summary-preview": "تت تي پيش نگاھ:",
        "subject-preview": "موضوع پيش نگاھ:",
        "blockedtitle": "يُوزر بندشيل آهي.",
-       "blockedtext": "'''توهان جي يوزرنانءُ يا آءِ پي کي بندشيو ويو آهي.'''\n\nبندش $1 هنئي. جڏهن تہ ڄاڻايل سبب ''$2'' آهي.\n\n\n* بندش جو آغاز: $8\n* بندش جو انجام: $6\n* بندش جو هدف: $7\n\nاهڙي روڪ تي بحث ڪرڻ لاءِ توهان $1 يا ڪنهن ٻي [[{{MediaWiki:Grouppage-sysop}}|منتظم]] سان رابطو ڪري سگھو ٿا. جيڪڏهن توهان جو درست [[Special:ترجيحات|کاتو ترجيحات]] ۾ درست برق ٽپال پتو درج ٿيل نہ آهي تہ توهان 'هن يوزر کي برق ٽپال ڪريو' وارو فيچر نہ ٿا \nYou cannot use the 'e-mail this user' feature unless a valid e-mail address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nاستعمال ڪري سگھو. توهان جو هاڻوڪو آءِ پي پتو $3 آهي، ۽ بندش سڃاڻپ $5 آهي. مهرباني ڪري ڪنهن بہ پڇا ڳاڇا يا لهوچڙ لاءِ انهن مان ڪنهن هڪ يا ٻنهي جو حوالو ڏيندا.",
+       "blockedtext": "'''توھان جي يوزرنانءُ يا آءِ پي کي بندشيو ويو آھي.'''\n\nبندش $1 ھني. جڏھن تہ ڄاڻايل سبب ''$2'' آهي.\n\n\n* بندش جو آغاز: $8\n* بندش جو انجام: $6\n* بندش جو هدف: $7\n\nاھڙي روڪ تي بحث ڪرڻ لاءِ توھان $1 يا ڪنھن ٻي [[{{MediaWiki:Grouppage-sysop}}|منتظم]] سان رابطو ڪري سگھو ٿا. جيڪڏهن توھان جو درست [[Special:ترجيحون|کاتو ترجيحون]] ۾ درست برقٽپال پتو درج ٿيل نہ آهي تہ توهان 'هن يوزر کي برقٽپال ڪريو' وارو فيچر نہ ٿا \nYou cannot use the 'e-mail this user' feature unless a valid e-mail address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nاستعمال ڪري سگھو. توھان جو ھاڻوڪو آءِپي پتو $3 آھي، ۽ بندش سڃاڻپ $5 آهي. مھرباني ڪري ڪنھن بہ پڇا ڳاڇا يا لھوچڙ لاءِ انھن مان ڪنھن ھڪ يا ٻنھي جو حوالو ڏيندا.",
        "blockednoreason": "سبب اڻڄاڻايل",
-       "whitelistedittext": "صفحا سنوارڻ لاءِ مهرباني ڪري $1.",
-       "confirmedittext": "صفحا سنوارڻ کان اڳ توهان کي پنهنجي ايميل پتي جي تصديق ڪرڻي پوندي. مهرباني ڪري [[Special:Preferences|use preferences]] ذريعي پنهنجو ايميل پتو ڄاڻايو ۽ تصديقيو.",
-       "nosuchsectiontitle": "سيڪشن نٿو لهي سگهي",
+       "whitelistedittext": "صفحا سنوارڻ لاءِ مھرباني ڪري $1.",
+       "confirmedittext": "صفحا سنوارڻ کان اڳ توھان کي پنھنجي برقٽپال پتي جي تصديق ڪرڻي پوندي. مھرباني ڪري [[Special:Preferences|يوزر ترجيحن]] ذريعي پنھنجو برقٽپال پتو ڄاڻايو ۽ تصديقيو.",
+       "nosuchsectiontitle": "سيڪشن نٿو لھي سگھي",
        "loginreqtitle": "داخل ٿيڻ گھربل آهي",
        "loginreqlink": "داخل ٿيو",
        "loginreqpagetext": "ٻيا صفحا ڏسڻ لاءِ مهرباني ڪري $1",
        "accmailtitle": "ڳجھولفظ اماڻجي چڪو",
        "newarticle": "(نئون)",
-       "newarticletext": "توهان اهڙي صفحي جو ڳنڍڻو وٺي هتي پهتا آهيو، جيڪو اڃا وجود نٿو رکي.\nاهڙو صفحو جوڙڻ لاءِ، هيٺين باڪس ۾ ٽائيپ ڪرڻ شروع ڪريو (وڌيڪ ڄاڻڻ لاءِ [$1 امدادي صفحو] ڏسندا).\nجي توهان هتي غلطيءَ ۾ اچي ويا آهيو، تہ رڳو پنهنجي جهانگُوءَ جو <strong>back</strong> تي ٽڙڪ ڪريو.",
-       "noarticletext": "في‌الوقت هن صفحي اندر ڪو بہ ٽيڪسٽ نہ آهي.\nتوهان ٻين صفحن ۾ [[Special:Search/{{PAGENAME}}|search ساڳي عنوان جي ڳولا]] ڪري سگھو ٿا،  \n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} لاڳاپيل لاگس ۾ ڳوليو]،\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} هيءُ صفحو ترميميو]</span>.",
+       "newarticletext": "توھان اھڙي صفحي جو ڳنڍڻو وٺي ھتي پھتا آھيو، جيڪو اڃا وجود نٿو رکي.\nاھڙو صفحو جوڙڻ لاءِ، ھيٺين دٻي ۾ لکڻ شروع ڪريو (وڌيڪ ڄاڻڻ لاءِ [$1 امدادي صفحو] ڏسندا).\nجي توھان ھتي غلطيءَ ۾ اچي ويا آهيو، تہ رڳو پنھنجي جھانگُوءَ جي <strong>back</strong> بٽڻ تي ٽڙڪ ڪريو.",
+       "noarticletext": "في‌الوقت هن صفحي اندر ڪو بہ ٽيڪسٽ نہ آهي.\nتوهان ٻين صفحن ۾ [[Special:Search/{{PAGENAME}}|search ساڳي عنوان جي ڳولا]] ڪري سگھو ٿا،  \n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} لاڳاپيل لاگس ۾ ڳوليو]،\nيا [{{fullurl:{{FULLPAGENAME}}|action=edit}} هيءُ صفحو ترميميو]</span>.",
+       "noarticletext-nopermission": "ھن وقت ھن صفحي ۾  ڪا بہ لکت نہ آھي.\nتوھان ٻين صفحن ۾ [[Special:Search/{{PAGENAME}}|ھن صفحي جي عنوان سان ڳولا ڪري سگھو ٿا]]، يا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} لاڳاپيل لاگس ڳوليو]</span>، پر توھان کي ان جي تخليق ڪرڻ جي اجازت نہ آھي.",
        "userpage-userdoesnotexist-view": "يُوزر کاتو $1 درج ٿيل نہ آهي.",
        "blocked-notice-logextract": "هيءَ يُوزر في‌الحال بندشيل آهي. تازو بندش لاگ حوالي طور پيش ڪجي ٿو:",
        "updated": "(تجديديل)",
        "note": "<strong>نوٽ:</strong>",
        "previewnote": "<strong>هيءَ فقط پيش نگاھ آهي.</strong>\nتوھان جون ترميمون اڃان نہ سانڍيون ويون آھن!",
-       "continue-editing": "ترميم گاھ ڏانهن وڃو",
+       "continue-editing": "ترميم گاھ ڏانھن وڃو",
        "editing": "$1 سنواريندي",
        "creating": "$1 سرجيندي",
        "editingsection": "زير ترميم $1 (سيڪشن)",
        "template-protected": "(تحفظيل)",
        "template-semiprotected": "(نيم-تحفظيل)",
        "hiddencategories": "هيءُ صفحو  {{PLURAL:$1|1 لڪل زمري|$1 لڪل زمرن}}: جو رڪن آهي:",
-       "nocreatetext": "{{SITENAME}} نوان صفحا سرجڻ جي روڪَ ڪئي آهي.\nتوهان اڳي ئي موجود صفحن کي سنواري سگھو ٿا، يا [[Special:UserLogin|لاگ اِن ٿي يا نئون کاتو کولي سگھو ٿا]].",
+       "nocreatetext": "{{SITENAME}} نوان صفحا سرجڻ جي روڪَ ڪئي آھي.\nتوھان اڳ ئي موجود صفحن کي سنواري سگھو ٿا، يا [[Special:UserLogin|داخل ٿي يا نئون کاتو کولي سگھو ٿا]].",
        "nocreate-loggedin": "توهان کي نوان صفحا سرجڻ جي اجازت حاصل ڪانهي.",
        "sectioneditnotsupported-title": "سيڪشن جي سنوار ممڪن نہ آهي",
        "sectioneditnotsupported-text": "هن صفحي تي سيڪشن کي سنوارڻ ممڪن نہ آهي.",
        "permissionserrors": "اجازتنامي جي چُڪَ",
        "permissionserrorstext": "هيٺين {{PLURAL:$1|سبب|سببن}} ڪري، توهان کي اهو ڪرڻ جي اجازت حاصل ڪانهي.",
-       "permissionserrorstext-withaction": "هيٺين {{PLURAL:$1|سبب|سببن}} ڪري، توهان کي $2 جي اجازت ڪانهي.",
-       "recreate-moveddeleted-warn": "'''خبردار: توهان اهڙو صفحو نئين سِر سرجي رهيا آهيو جيڪو اڳي ڊاٺو ويو آهي.'''\n\nبهتر ٿيندو تہ توهان سوچي وٺو تہ ڇا ان صفحي کي سنوارڻ چڱو ٿيندو.\nتوهآن جي سهوليت خاطر هتي ان صفحي جو ڊاٺ لاگ ميسر ڪجي ٿو:",
-       "moveddeleted-notice": "هيءُ صفحو ڊهي چڪو آهي. \nحوالي طور ڊاٺ ۽ چور لاگ هيٺ ڏجي ٿو.",
+       "permissionserrorstext-withaction": "ھيٺين {{PLURAL:$1|سبب|سببن}} ڪري، توھان کي $2 جي اجازت ڪانھي.",
+       "recreate-moveddeleted-warn": "'''خبردار: توھان اھڙو صفحو نئين سِر سرجي رھيا آھيو جيڪو اڳ ڊاٺو ويو آھي.'''\n\nبھتر ٿيندو تہ توھان سوچي وٺو تہ ڇا ان صفحي کي سنوارڻ چڱو ٿيندو.\nتوهان جي سھولت خاطر ھتي ان صفحي جو ڊاٺ لاگ ميسر ڪجي ٿو:",
+       "moveddeleted-notice": "ھيءُ صفحو ڊھي چڪو آهي. \nحوالي طور ڊاٺ ۽ چور لاگ ھيٺ ڏجن ٿا.",
        "moveddeleted-notice-recent": "معاف ڪندا، هيءُ صفحو تازو ئي ڊاٺو ويو آهي (پوين 24 ڪلاڪن اندر). حوالي طور ڊاٺ ۽ چور لاگ هيٺ پيش ڪجي ٿو:",
        "log-fulllog": "پُورو لاگ ڏسو",
        "edit-conflict": "سنوار تڪرار",
        "lineno": "سِٽَ $1:",
        "compareselectedversions": "چونڊيل پرت ڀيٽيو",
        "editundo": "اڻڪريو",
-       "diff-empty": "(ڪو بہ تفاوت ڪونهي)",
+       "diff-empty": "(ڪو بہ تفاوت ڪونھي)",
        "searchresults": "ڳولا نتيجا",
        "searchresults-title": "”$1“ لاءِ ڳولا نتيجا",
-       "titlematches": "صفحي جو عنوان مشابهت رکي ٿو",
-       "textmatches": "صفحي جو متن مشابهت رکي ٿو",
+       "titlematches": "صفحي جو عنوان مشابھت رکي ٿو",
+       "textmatches": "صفحي جو متن مشابھت رکي ٿو",
        "notextmatches": "ڪنهن به صفحي جو متن مشابهت نٿو رکي",
        "prevn": "پويان {{PLURAL:$1|$1}}",
        "nextn": "اڳيان {{PLURAL:$1|$1}}",
        "nextn-title": "{{PLURAL:$1|ٻيو|ٻيا}} $1 {{PLURAL:$1|نتيجو|نتيجا}}",
        "shown-title": "$1 {{PLURAL:$1|نتيجو|نتيجا}} في صفحو ڏيکاريو",
        "viewprevnext": "ڏسو ($1 {{int:pipe-separator}} $2) ($3)",
+       "searchmenu-new": "<strong>ھن وڪيءَ تي صفحو تخليق ڪريو \"[[:$1]]\"!</strong> {{PLURAL:$2|0=|توھان جي ڳولا سان لڌل صفحو ڏسو.|لڌل ڳولا نتيجا پڻ ڏسو.}}",
        "searchprofile-articles": "موادي صفحا",
        "searchprofile-images": "گھڻذريعات",
-       "searchprofile-everything": "هر شَي",
+       "searchprofile-everything": "ھر شَيءِ",
        "searchprofile-advanced": "متقدم",
        "searchprofile-articles-tooltip": "$1 ۾ ڳوليو",
        "searchprofile-images-tooltip": "فائيلن جي ڳولا",
-       "searchprofile-everything-tooltip": "سموري مواد ۾ ڳوليو",
+       "searchprofile-everything-tooltip": "سموري مواد ۾ ڳوليو (بحث صفحن سميت)",
        "searchprofile-advanced-tooltip": "مرضيءَ جي نانءُپولارن ۾ ڳوليو",
        "search-result-size": "$1 ({{PLURAL:$2|لفظُ|$2 لفظَ}})",
        "search-redirect": "($1 کان چوريو)",
        "searchrelated": "لاڳاپيل",
        "searchall": "سڀ",
        "search-showingresults": "{{PLURAL:$4|نتيجو <strong>$1</strong> of <strong>$3</strong>|نتيجا <strong>$1 - $2</strong> of <strong>$3</strong>}}",
-       "search-nonefound": "توهان جي ڳولا جي نتيجي ۾ ڪجھہ بہ ڪو نہ لڌو.",
+       "search-nonefound": "توھان جي ڳولا جي نتيجي ۾ ڪجھہ بہ نہ لڌو.",
        "powersearch-legend": "اعليٰ ڳولا",
        "powersearch-togglelabel": "چڪاسيو:",
        "powersearch-toggleall": "سڀ",
        "powersearch-togglenone": "ڪو بہ نہ",
        "search-external": "خارجي ڳولا",
        "search-error": "$1 ۾ ڳولا ڪندي چُڪَ ٿي.",
-       "preferences": "ترجيحات",
-       "mypreferences": "ترجيحات",
+       "preferences": "ترجيحون",
+       "mypreferences": "ترجيحون",
        "prefs-edits": "ترميمن جو تعداد:",
-       "prefsnologintext2": "پنهنجون ترجيحات بدلائڻ لاءِ داخل ٿيو.",
+       "prefsnologintext2": "پنھنجون ترجيحون بدلائڻ لاءِ داخل ٿيو.",
        "prefs-skin": "چَمَ",
        "skin-preview": "پيش نگاهہ",
-       "datedefault": "بلا ترجيحا",
+       "datedefault": "بلا ترجيح",
        "prefs-user-pages": "يُوزر صفحو",
        "prefs-personal": "يُوزر جو خدوخال",
        "prefs-rc": "تازيون تبديليون",
-       "prefs-watchlist": "نظرھيٺ فھرست",
-       "prefs-editwatchlist": "نظرھيٺ فھرست کي سنواريو",
-       "prefs-editwatchlist-label": "پنهنجي نظرھيٺ فھرست ۾ درج ٿيل شين کي سنواريو:",
-       "prefs-editwatchlist-edit": "پنھنجي نظرھيٺ فھرست ۾ موجود عنوان ڏسو ۽ مٽايو",
-       "prefs-editwatchlist-raw": "ڪچي نظرھيٺ فھرست سنواريو",
-       "prefs-editwatchlist-clear": "پنهنجي نظرھيٺ فھرست ڊاهيو",
-       "prefs-watchlist-days": "نظرھيٺ فھرست ۾ ڏيکارڻ لاءِ ڏينهن:",
-       "prefs-watchlist-days-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينهن}}",
+       "prefs-watchlist": "نظر ۾ فھرست",
+       "prefs-editwatchlist": "نظر ۾ فھرست کي سنواريو",
+       "prefs-editwatchlist-label": "پنهنجي نظر ۾ فھرست ۾ درج ٿيل شين کي سنواريو:",
+       "prefs-editwatchlist-edit": "پنھنجي نظر ۾ فھرست ۾ موجود عنوان ڏسو ۽ مٽايو",
+       "prefs-editwatchlist-raw": "ڪچي نظر ۾ فھرست سنواريو",
+       "prefs-editwatchlist-clear": "پنهنجي نظر ۾ فھرست ڊاهيو",
+       "prefs-watchlist-days": "نظر ۾ فھرست ۾ ڏيکارڻ لاءِ ڏينهن:",
+       "prefs-watchlist-days-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينھن}}",
        "prefs-watchlist-edits-max": "وڌ ۾ وڌ تعداد: 1000",
-       "prefs-watchlist-token": "نظرھيٺ فھرست جو ٽوڪن:",
+       "prefs-watchlist-token": "نظر ۾ فھرست جو ٽوڪن:",
        "prefs-misc": "متفرق",
        "prefs-resetpass": "ڳجھولفظ بدلايو",
-       "prefs-changeemail": "برق ٽپال پتو مِٽايو يا بدلايو",
-       "prefs-setemail": "ڪو برق ٽپال پتو ڄاڻايو",
-       "prefs-email": "برق ٽپال چارا",
+       "prefs-changeemail": "برقٽپال پتو مِٽايو يا بدلايو",
+       "prefs-setemail": "ڪو برقٽپال پتو ڄاڻايو",
+       "prefs-email": "برقٽپال چارا",
        "prefs-rendering": "حليو",
        "saveprefs": "سانڍيو",
        "prefs-editing": "سنوارڻ",
        "recentchangesdays-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينهن}}",
        "recentchangescount": "عدم پيروي جي صورت ۾ ڏيکارڻ جي لاءِ ترميمون:",
        "prefs-help-recentchangescount": "ان ۾ تازيون تبديليون، صفحن جي سوانح، ۽ لاگ شامل آهن.",
-       "savedprefs": "توهان جون ترجيحات سانڍجي چڪيون آهن.",
+       "savedprefs": "توھان جون ترجيحون سانڍجي چڪيون آھن.",
        "savedrights": "{{GENDER:$1|$1}} جا يوزر گروھ سانڍجي چڪا آھن.",
        "timezonelegend": "اوقاتي زون:",
        "localtime": "مقامي وقت:",
        "timezoneregion-europe": "يُورپ",
        "timezoneregion-indian": "سنڌي ساگر",
        "timezoneregion-pacific": "ماٺو ساگر",
-       "allowemail": "ٻين يُوزرس کان ايندڙ برق ٽپال بحال ڪريو",
+       "allowemail": "ٻين يُوزرس کان ايندڙ ٽپال بحال ڪريو",
        "prefs-searchoptions": "ڳولا",
        "prefs-namespaces": "نانءُپولار",
        "default": "ڏنل",
        "prefs-files": "فائيلس",
-       "prefs-emailconfirm-label": "برق ٽپال جي خاطري:",
-       "youremail": "برق ٽپال:",
+       "prefs-emailconfirm-label": "برقٽپال خاطري:",
+       "youremail": "برقٽپال:",
        "username": "{{GENDER:$1|يُوزرنانءُ}}",
        "prefs-registration": "رجسٽريشن جو وقت:",
        "yourrealname": "اصل نالو:",
        "gender-unknown": "توهان جو ذڪر ڪندي، جيترو ٿي سگھيو، منطقگري بي جنس لفظن جو استعمال ڪندي.",
        "gender-male": "هيءُ وڪي صفحا سنواريندو آهي",
        "gender-female": "هيءَ وڪي صفحا سنواريندي آهي",
-       "email": "برق ٽپال",
+       "email": "برقٽپال",
        "prefs-help-realname": "اصل نالو اختياري آهي.\nجيڪڏهن توهان اصل نالو ڄاڻائڻ جو فيصلو ٿا ڪريو، تہ اهو توهان کي توهان جي ڪم جي مڃتا ڏيڻ لاءِ ڪم آندو ويندو.",
-       "prefs-help-email": "برق ٽپال ڄاڻائڻ اختياري آهي، پر جڏهن توهان ڳجھو لفظ وسري ويندا آهيو، تڏهن ان جو استعمال توهان کي نئون ڳجھو لفظ ڏيڻ لاءِ استعمال ڪيو ويندو آهي.",
-       "prefs-help-email-required": "برق ٽپال پتو گھربل آهي.",
+       "prefs-help-email": "برقٽپال ڄاڻائڻ اختياري آهي، پر جڏهن توهان ڳجھولفظ وسري ويندا آهيو، تڏهن ان جو استعمال توهان کي نئون ڳجھولفظ ڏيڻ لاءِ استعمال ڪيو ويندو آهي.",
+       "prefs-help-email-required": "برقٽپال پتو گھربل آهي.",
        "prefs-info": "بنيادي ڄاڻ",
        "prefs-i18n": "بين‌الاقوامڪاري",
        "prefs-signature": "صحيح",
        "prefs-diffs": "تفاوت",
        "prefs-help-prefershttps": "هيءَ ترجيح توهان جي ايند داخل ٿيڻ تي عمل ۾ ايندي.",
        "userrights": "يُوزر حقن جو بندوبست",
-       "userrights-lookup-user": "يوزر گروپَ سنڀاليو",
+       "userrights-lookup-user": "ڪو يوزر چونڊيو",
        "userrights-user-editname": "يُوزرنانءُ ڄاڻايو:",
-       "editusergroup": "{{GENDER:$1|يوزر}} گروھ ترميميو",
+       "editusergroup": "يوزر گروھ اتاريو",
        "userrights-editusergroup": "يوزر گروپَ سنواريو",
        "saveusergroups": "{{GENDER:$1|يوزر}} گروھ سانڍيو",
        "userrights-groupsmember": "برڪن:",
        "right-undelete": "ڪو صفحو اڻڊاهيو",
        "right-unblockself": "ڪنهن تان بندش ختم ڪريو",
        "right-editinterface": "يُوزر باهمرُو کي سنواريو",
-       "right-viewmywatchlist": "پنهنجي نظرھيٺ فھرست ڏسو",
-       "right-editmyoptions": "پنهنجون ترجيحات سنواريو",
+       "right-viewmywatchlist": "پنهنجي نظر ۾ فھرست ڏسو",
+       "right-editmyoptions": "پنهنجون ترجيحون سنواريو",
        "right-import": "ٻين وڪيز کان صفحا درآمديو",
        "right-importupload": "ڪو فائيل چاڙهي صفحا درآمديو",
        "right-mergehistory": "صفحن جي سوانح سنواريو",
        "right-override-export-depth": "5ئين اونهائيءَ تائين ڳنڍيل صفحن سميت صفحا برآمديو",
        "right-sendemail": "ٻين يوزرس ڏانهن ايميل موڪليو",
        "right-managechangetags": "[[Special:Tags|ٽيگس]] سرجيو ۽ ڊاهيو.",
-       "grant-group-email": "برق ٽپال اماڻيو",
+       "grant-group-email": "برقٽپال اماڻيو",
        "grant-blockusers": "يُوزرس کي بندشيو ۽ اڻبندشيو",
        "grant-createaccount": "نئون کاتو کوليو",
-       "grant-editmywatchlist": "پنھنجي نظرھيٺ فھرست سنواريو",
+       "grant-editmywatchlist": "پنھنجي نظر ۾ فھرست سنواريو",
        "grant-editprotected": "تحفظيل صفحا سنواريو",
        "grant-rollback": "صفحن ۾ ڪيل تبديليون واپس ورايو",
-       "grant-sendemail": "Ù»Ù\8aÙ\86 Ù\8aÙ\88زرس Ú\8fاÙ\86Ù\87Ù\86 Ø§Ù\8aÙ\85Ù\8aل موڪليو",
+       "grant-sendemail": "Ù»Ù\8aÙ\86 Ù\8aÙ\88زرس Ú\8fاÙ\86Ù\87Ù\86 Ø¨Ø±Ù\82ٽپال موڪليو",
        "grant-uploadeditmovefile": "فائيل چاڙهيو، مَٽايو، ۽ ڊاهيو",
        "grant-uploadfile": "نئون فائيل چاڙهيو",
        "grant-basic": "بنيادي حقَ",
        "grant-viewdeleted": "ڊَٺَلَ فائيلَ ۽ صفحا ڏسو",
        "grant-viewmywatchlist": "پنهنجي نظرھيٺ فھرست ڏسو",
-       "newuserlogpage": "يوزر کاتن جو لاگ",
+       "newuserlogpage": "يوزر تخليق لاگ",
        "rightslog": "يُوزر حق لاگ",
        "action-read": "هي صفحو پڙهو",
        "action-edit": "هن صفحي کي سسنواريو",
        "action-movefile": "هيءُ فائيل چوريو",
        "action-upload": "هيءُ فائيل چاڙهيو",
        "action-delete": "هيءُ صفحو ڊاهيو",
-       "action-deleterevision": "هيءُ ڀيرو ڊاهيو",
-       "action-deletedhistory": "هن صفحي جي ڊاٺ سوانح ڏسو",
+       "action-deleterevision": "ڀيرا ڊاھيو",
+       "action-deletedhistory": "ڪنھن صفحي جي ڊاھ سوانح ڏسو",
        "action-browsearchive": "ڊاٺل صفحن ۾ ڳوليو",
-       "action-undelete": "هيءُ صفحو اڻڊاهيو",
-       "action-suppressrevision": "Ù\87Ù\86 Ù\84ÚªÙ\8aÙ\84 Ú\80Ù\8aرÙ\8a تي نظرثاني ڪريو ۽ بحاليو",
+       "action-undelete": "صفحا اڻڊاھيو",
+       "action-suppressrevision": "Ù\84ÚªÙ\8aÙ\84 Ú\80Ù\8aرÙ\86 تي نظرثاني ڪريو ۽ بحاليو",
        "action-suppressionlog": "هيءُ ذاتي لاگ ڏسو",
        "action-block": "هن يُوزر کي سنوارڻ کان روڪيو",
        "action-protect": "هن صفحي جي تحفظاتي سطح بدلايو",
        "action-userrights": "سڀ يوزر حق ترميم ڪريو",
        "action-userrights-interwiki": "ٻين وڪيز جي يوزرس جا حق ترميم ڪريو",
        "action-siteadmin": "اعدادخاني کي بند ڪريو يا کوليو",
-       "action-sendemail": "برق ٽپال اماڻيو",
-       "action-editmywatchlist": "پنھنجي نظرھيٺ فھرست سنواريو",
-       "action-viewmywatchlist": "پنهنجي نظرھيٺ فھرست ڏسو",
+       "action-sendemail": "برقٽپال اماڻيو",
+       "action-editmywatchlist": "پنھنجي نظر ۾ فھرست سنواريو",
+       "action-viewmywatchlist": "پنهنجي نظر ۾ فھرست ڏسو",
        "action-viewmyprivateinfo": "پنهنجي ذاتي معلومات ڏسو",
        "action-editmyprivateinfo": "پنهنجي ذاتي معلومات سنواريو",
        "nchanges": "$1 {{PLURAL:$1|تبديلي|تبديليون}}",
        "enhancedrc-history": "سوانح",
        "recentchanges": "تازيون تبديليون",
        "recentchanges-legend": "تازين تبديلين جا چارا",
-       "recentchanges-summary": "هن صفحي تي وڪيءَ ۾ ڪيل تازيون ترين ترميمون ڏيکاريو.",
+       "recentchanges-summary": "ھن صفحي تي وڪيءَ ۾ ڪيل تازيون ترين ترميمون ڏيکاريو.",
        "recentchanges-feed-description": "ۡهن روان رسد ۾ آيل تازيون تبديليون لهو",
        "recentchanges-label-newpage": "هن ترميم سان نئون صفحو جڙيو",
-       "recentchanges-label-minor": "هيء هڪ معمولي ترميم آهي",
-       "recentchanges-label-bot": "هيءُ ترميم بوٽ عمل ۾ آندي.",
+       "recentchanges-label-minor": "ھيءَ ھڪ معمولي ترميم آھي",
+       "recentchanges-label-bot": "ھيءَ ترميم بوٽ عمل ۾ آندي",
        "recentchanges-label-unpatrolled": "ھن ترميم جو اڃان گشت نہ ڪيو ويو آھي",
-       "recentchanges-label-plusminus": "هن صفحي جي ماپ ۾ هيترين بائيٽس جو ڦير آيو آهي",
+       "recentchanges-label-plusminus": "ھن صفحي جي ماپ ۾ ھيترين بائيٽس جو ڦير آيو آھي",
        "recentchanges-legend-heading": "<strong>ڪنجي:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (پڻ ڏسو [[Special:NewPages|نون صفحن جي فهرست]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (پڻ ڏسو [[Special:NewPages|نون صفحن جي فھرست]])",
        "recentchanges-submit": "ڏيکاريو",
        "rcnotefrom": "هيٺ {{PLURAL:$5|تبديلي آهي|تبديليون آهن}} کان <strong>$3, $4</strong> (تائين <strong>$1</strong> ) ڏيکاريل آهن.",
        "rclistfrom": "$2، $3 کان شروع ٿيندڙ نيون تبديليون ڏيکاريو",
        "rcshowhidecategorization": "$1 صفحاتي زمراڪاري",
        "rcshowhidecategorization-show": "ڏيکاريو",
        "rcshowhidecategorization-hide": "لڪايو",
-       "rclinks": "پوين $2 ڏينهن ۾ آيل پويون $1 تبديليون ڏيکاريو <br />$3",
+       "rclinks": "پوين $2 ڏينھن ۾ آيل پويون $1 تبديليون ڏيکاريو <br />$3",
        "diff": "تفاوت",
        "hist": "سوانح",
        "hide": "لڪايو",
        "boteditletter": "گ",
        "number_of_watching_users_pageview": "[$1، {{PLURAL:$1|يُوزر|يُوزرس}} کي ٽيٽيندي]",
        "rc_categories_any": "چونڊيلن مان ڪو بہ",
-       "rc-change-size-new": "$1 {{PLURAL:$1|بائيٽ|بائيٽس}} تبديليءَ کان پوءِ",
+       "rc-change-size-new": "$1 {{PLURAL:$1|بائيٽ|بائيٽس}} تبديليءَ کانپوءِ",
        "newsectionsummary": "/* $1 */ نئون سيڪشن",
        "rc-enhanced-expand": "تفصيل ڏيکاريو",
        "rc-enhanced-hide": "تفصيل لڪايو",
        "recentchangeslinked-title": "\"$1\" سان لاڳاپيل تبديليون",
        "recentchangeslinked-page": "صفحي جو نالو:",
        "recentchangeslinked-to": "رڳو ڄاڻايل صفحي سان ڳانڍيل صفحن ۾ ٿيل تبديليون نمايو",
-       "upload": "فائيل چاڙهيو",
+       "upload": "فائيل چاڙھيو",
        "uploadbtn": "فائيل چاڙهيو",
        "uploadnologin": "داخل ٿيل ناھيو",
        "uploadnologintext": "فائيل چاڙهڻ لاءِ $1.",
        "uploaderror": "چاڙھ چُڪَ",
        "uploadlogpage": "چاڙھ لاگ",
        "filename": "فائيل نانءُ",
-       "filedesc": "Ø®Ù\84اصÙ\88",
+       "filedesc": "تÙ\8eتÙ\8f",
        "fileuploadsummary": "خلاصو:",
        "filereuploadsummary": "فائيل تبديليون:",
        "filesource": "ذريعو:",
-       "ignorewarnings": "وارنڱس کي نظرانداز ڪريو",
+       "ignorewarnings": "چتائن کي نظرانداز ڪريو",
        "badfilename": "فائيل‌نانءُ بدلائي \"$1\" رکيو ويو آهي.",
        "empty-file": "توهان جو جمع ڪرايل فائيل خالي آهي.",
        "filename-tooshort": "فائيل نانءَُ هيڪاندو ننڍو آهي.",
        "filehist-revert": "واپس ورايو",
        "filehist-current": "حاليہ",
        "filehist-datetime": "تاريخ/وقت",
-       "filehist-thumb": "آڱوٺي ننهن",
+       "filehist-thumb": "آڱوٺي ننھن",
        "filehist-thumbtext": "$1 جي نظرثاني لاءِ تصويري نشان",
        "filehist-nothumb": "ٽِڪِلِي اڻموجود",
        "filehist-user": "يُوزر",
        "filehist-dimensions": "ماپَ",
        "filehist-filesize": "فائيل سائيز",
-       "filehist-comment": "تاثرات",
+       "filehist-comment": "تاثر",
        "imagelinks": "فائيل جو استعمال",
-       "linkstoimage": "هن فائيل سان {{PLURAL:$1|هيٺيون صفحو ڳنڍيل آهي |$1 هيٺيان صفحا ڳنڍيل آهن}}:",
+       "linkstoimage": "ھن فائيل سان {{PLURAL:$1|ھيٺيون صفحو ڳنڍيل آھي |$1 ھيٺيان صفحا ڳنڍيل آھن}}:",
        "nolinkstoimage": "هن فائيل سان ڪي بہ صفحا ڳنڍيل ناهن.",
        "sharedupload": "هيءَ فائيل $1 کان آهي ۽ ان کي ٻيون رٿائون به استعمال ڪري سگھن ٿيون.",
-       "sharedupload-desc-here": "هي فائيل $1 مان آهي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگهي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] هيٺان ڏنل آهي.",
+       "sharedupload-desc-here": "ھي فائيل $1 مان آھي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگھي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] ھيٺان ڏنل آھي.",
        "uploadnewversion-linktext": "هن فائيل جو نئون پرت چاڙهيو",
        "shared-repo-from": "$1 کان",
-       "upload-disallowed-here": "توهان هن فائيل مٿان لکي نہ ٿا سگھو.",
+       "upload-disallowed-here": "توھان ھن فائيل مٿان لکي نہ ٿا سگھو.",
        "filerevert-comment": "سبب:",
        "filerevert-submit": "واپس ورايو",
        "filedelete": "$1 کي ڊاهيو",
        "listgrouprights": "يوزر گروپ جا حق",
        "listgrouprights-group": "گروهہ:",
        "listgrouprights-rights": "حق",
-       "listgrouprights-members": "(رڪÙ\86Ù\86 Ø¬Ù\8a Ù\84سٽ)",
+       "listgrouprights-members": "(رڪÙ\86Ù\86 Ø¬Ù\8a Ù\81ھرست)",
        "listgrouprights-addgroup-all": "سڀ گروپَ شامل ڪريو",
        "listgrouprights-removegroup-all": "سڀ گروپ هٽايو",
        "listgrouprights-namespaceprotection-namespace": "نانءُ پولار:",
        "emailsubject": "موضوع:",
        "emailmessage": "نياپو:",
        "emailsend": "اماڻيو",
-       "emailccme": "نياپي جو پرت مون کي برق ٽپال ڪريو.",
-       "emailsent": "برق ٽپال اماڻجي چڪي",
-       "emailsenttext": "توهان جو برق ٽپال نياپو اماڻجي چڪو آهي.",
-       "watchlist": "نظرھيٺ فھرست",
-       "mywatchlist": "نظرھيٺ فھرست",
-       "addwatch": "نظرھيٺ فھرست ۾ شامل ڪريو",
-       "addedwatchtext": "صفحو\"[[:$1]]\" ان جي بحث وارو صفحو اوهان جي [[Special:Watchlist|نظرھيٺ فھرست]] ۾ شامل ڪيو ويو آهي.",
-       "removewatch": "نظرھيٺ فھرست مان ھٽايو",
-       "removedwatchtext": "صفحو بعنوان \"[[:$1]]\" توهان جي [[Special:Watchlist|نظرھيٺ فھرست]] مان هٽي چڪو آهي.",
-       "removedwatchtext-short": "\"صفحو بعنوان \"$1\" توهان جي نظرھيٺ فھرست مان هٽي چڪو آهي.\"",
-       "watch": "نظرھيٺ رکو",
-       "watchthispage": "هيءُ صفحو نظرھيٺ رکو",
-       "unwatch": "نظرھيٺ نہ رکو",
-       "unwatchthispage": "نظرھيٺ رکڻ ڇڏيو",
+       "emailccme": "نياپي جو پرت مون کي برقٽپال ڪريو.",
+       "emailsent": "برقٽپال اماڻجي چڪي",
+       "emailsenttext": "توهان جو برقٽپال نياپو اماڻجي چڪو آهي.",
+       "watchlist": "نظر ۾ فھرست",
+       "mywatchlist": "نظر ۾ فھرست",
+       "addwatch": "نظر ۾ فھرست ۾ شامل ڪريو",
+       "addedwatchtext": "صفحو\"[[:$1]]\" ان جي بحث وارو صفحو اوهان جي [[Special:Watchlist|نظر ۾ فھرست]] ۾ شامل ڪيو ويو آهي.",
+       "removewatch": "نظر ۾ فھرست مان ھٽايو",
+       "removedwatchtext": "صفحو بعنوان \"[[:$1]]\" توهان جي [[Special:Watchlist|نظر ۾ فھرست]] مان هٽي چڪو آهي.",
+       "removedwatchtext-short": "\"صفحو بعنوان \"$1\" توهان جي نظر ۾ فھرست مان هٽي چڪو آهي.\"",
+       "watch": "نظر ۾ رکو",
+       "watchthispage": "هيءُ صفحو نظر ۾ رکو",
+       "unwatch": "نظر ۾ نہ رکو",
+       "unwatchthispage": "نظر ۾ رکڻ ڇڏيو",
        "notanarticle": "غير موادي صفحو",
        "watchlist-details": "{{PLURAL:$1|$1 صفحو|$1 صفحا}} توهان جي ٽيٽ فهرست، ڳالھ ٻولھ جا صفحا الڳ شمار نٿا ٿين.",
        "wlshowlast": "گذريل $1 ڪلاڪ $2 ڏينهن ڏيکاريو",
        "wlshowhideliu": "کاتيدار يُوزرس",
        "wlshowhideanons": "گمنام يُوزرس",
        "wlshowhidemine": "منهنجون ترميمون",
-       "watchlist-options": "نظرھيٺ فھرست جا چارا",
-       "watching": "نظرھيٺ رکندي...",
-       "unwatching": "نظرھيٺان ڪڍندي...",
+       "watchlist-options": "نظر ۾ فھرست جا چارا",
+       "watching": "نظر ۾ رکندي...",
+       "unwatching": "نظر مان ڪڍندي...",
        "enotif_reset": "سڀ گھميل صفحن تي نشان لڳايو",
        "enotif_impersonal_salutation": "{{SITENAME}} يُوزر",
        "enotif_lastdiff": "هي تبديلي ڏسڻ لاءِ $1 ڏسو",
        "delete-edit-reasonlist": "ڊاٺ جا سبب سنواريو",
        "rollback": "ترميمن کي واپس ورايو",
        "rollbacklink": "واپس ورايو",
-       "rollbacklinkcount": "اڻ ڪريو $1 {{PLURAL:$1|ترميم|ترميمون}}",
+       "rollbacklinkcount": "اڻڪريو $1 {{PLURAL:$1|ترميم|ترميمون}}",
        "changecontentmodel-title-label": "صفحي جو عنوان",
        "changecontentmodel-reason-label": "سبب:",
        "logentry-contentmodel-change-revertlink": "واپس ورايو",
        "undelete-search-submit": "ڳوليو",
        "undelete-error-short": "هيءُ فائيل اڻڊاهيندي چُڪَ ٿي آهي: $1",
        "undelete-show-file-submit": "ها",
-       "namespace": "نانءُ پولار:",
+       "namespace": "نانءُپولار:",
        "invert": "چونڊ ابتيو",
-       "tooltip-invert": "هن دٻي تي نشان لڳايو صحفن ۾ تبديليون لڪائڻ لاءِ چونڊيل نيم اسپيس مان (۽ لاڳاپيل نيم اسپيس جيڪڏهن نشان لڳل)",
+       "tooltip-invert": "هن دٻي تي نشان لڳايو صحفن ۾ تبديليون لڪائڻ لاءِ چونڊيل نانءَپولار مان (۽ لاڳاپيل نانءُپولار جيڪڏهن نشان لڳل)",
        "namespace_association": "منسلڪ نانءُپولار",
        "blanknamespace": "(مُک)",
        "contributions": "{{GENDER:$1|يوزر}} جون ڀاڱيداريون",
        "contribsub2": "{{GENDER:$3|$1}} ($2) لاءِ",
        "contributions-userdoesnotexist": "يُوزر کاتو \"$1\" درج ٿيل نہ آهي.",
        "uctop": "(هاڻوڪو)",
-       "month": "مهيني کان (۽ اڳوڻيون):",
+       "month": "مھيني کان (۽ اڳوڻيون):",
        "year": "سال کان (۽ اڳوڻيون):",
        "sp-contributions-newbies": "صرف نون کاتن جون ڀاڱيداريون ڏيکاريو",
        "sp-contributions-newbies-sub": "نون کاتن لاءِ",
        "sp-contributions-username": "آءِپي پتو يا يوزرنانءُ:",
        "sp-contributions-submit": "ڳوليو",
        "whatlinkshere": "هتان ڇا ڳنڍيل آهي",
-       "whatlinkshere-title": "$1 سان ڳنڍيل صفحا",
+       "whatlinkshere-title": "\"$1\" سان ڳنڍيندڙ صفحا",
        "whatlinkshere-page": "صفحو:",
        "linkshere": "هيٺيان صفحا <strong>[[:$1]]</strong> سان ڳنڍيل آهن:",
        "nolinkshere": "'''[[:$1]]''' سان ڪو بہ صفحو ڳنڍيل ناهي.",
        "blocklink": "بندشيو",
        "unblocklink": "اڻبندشيو",
        "contribslink": "ڀاڱيداريون",
-       "emaillink": "برق ٽپال اماڻيو",
+       "emaillink": "برقٽپال اماڻيو",
        "blocklogpage": "بندش لاگ",
        "blocklogentry": "\"[[$1]]\" کي بندشيو ويو $2 $3 جي عرصي لاء",
        "unblocklogentry": "$1 تان بندش هٽائي وئي",
        "block-log-flags-anononly": "فقط نامعلوم يوزرس",
-       "block-log-flags-noemail": "برق ٽپال غير فعال",
+       "block-log-flags-noemail": "برقٽپال غير فعال",
        "block-log-flags-hiddenname": "لڪل يُوزرنانءُ",
        "ipb_already_blocked": "\"$1\" اڳ ۾ ئي بندشيل آهي.",
        "ipbnounblockself": "توهان پنهنجو پاڻ تان بندش هٽائي نہ ٿا سگھو.",
        "importlogpage": "درآمد لاگ",
        "tooltip-pt-userpage": "{{GENDER:|توھانجو يوزر}} صفحو",
        "tooltip-pt-mytalk": "{{GENDER:|توھانجو}} يوزر صفحو",
-       "tooltip-pt-preferences": "{{GENDER:|توھانجون}} ترجيحات",
-       "tooltip-pt-watchlist": "تÙ\88ھاÙ\86 Ø¬Ù\8a ØªØ¨Ø¯Ù\8aÙ\84Ù\8aÙ\86 Ø¬Ù\8a Ù\86ظرھÙ\8aÙº ØµÙ\81Ø­Ù\86 Ø¬Ù\8a Ù\81ھرست",
+       "tooltip-pt-preferences": "{{GENDER:|توھانجون}} ترجيحون",
+       "tooltip-pt-watchlist": "صÙ\81Ø­Ù\86 Ø¬Ù\8a Ù\81ھرست Ø¬Ù\8aÚªÙ\8a ØªÙ\88ھاÙ\86 ØªØ¨Ø¯Ù\8aÙ\84Ù\8aÙ\86 Ù\84اءÙ\90 Ù\86ظر Û¾ Ø±Ú©Ù\8aا Ø¢Ú¾Ù\86",
        "tooltip-pt-mycontris": "{{GENDER:|توھانجي}} ڀاڱيدارين جي فھرست",
        "tooltip-pt-login": "توھان کي ھمٿائجي ٿو تہ توهان داخل ٿيو؛ بھرحال، اھو لازمي ناھي",
-       "tooltip-pt-logout": "ٻاھر نڪرو",
+       "tooltip-pt-logout": "خارج ٿيو",
        "tooltip-pt-createaccount": "کاتو کولڻ ۽ داخل ٿيڻ تي توھان کي ھمٿايو وڃي ٿو؛  جيتوڻيڪ، اهو ضروري ناھي",
        "tooltip-ca-talk": "موادي صفحي تي بحث",
        "tooltip-ca-edit": "هيءُ صفحو سنواريو",
        "tooltip-ca-addsection": "نئون سيڪشن شروع ڪريو",
-       "tooltip-ca-viewsource": "هيءُ صفحو تحفظيل آهي.\nتوهان ان جو ذريعو ڏسي سگھو ٿا",
-       "tooltip-ca-history": "هن صفحي جا اڳوڻا ڀيرا",
+       "tooltip-ca-viewsource": "ھيءُ صفحو تحفظيل آھي.\nتوھان ان جو ذريعو ڏسي سگھو ٿا",
+       "tooltip-ca-history": "ھن صفحي جا اڳوڻا ڀيرا",
        "tooltip-ca-protect": "هيءُ صفحو تحفظيو",
        "tooltip-ca-delete": "هيءُ صفحو ڊاهيو",
-       "tooltip-ca-move": "هيءُ صفحو چوريو",
-       "tooltip-ca-watch": "هيءُ صفحو پنهنجي نظرھيٺ فھرست ۾ شامل ڪريو",
-       "tooltip-ca-unwatch": "هيءُ صفحو پنهنجي نظرھيٺ فھرست تان هٽايو",
+       "tooltip-ca-move": "ھيءُ صفحو چوريو",
+       "tooltip-ca-watch": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪريو",
+       "tooltip-ca-unwatch": "هيءُ صفحو پنهنجي نظر ۾ فھرست تان هٽايو",
        "tooltip-search": "{{SITENAME}} ۾ ڳوليو",
-       "tooltip-search-go": "تز ان ئي نالي سان ڪو صفحو موجود آهي تہ کوليو",
-       "tooltip-search-fulltext": "هن متن لاءِ صفحا ڳوليو",
+       "tooltip-search-go": "تز ان ئي نالي سان ڪو صفحو موجود آھي تہ کوليو",
+       "tooltip-search-fulltext": "ھن متن لاءِ صفحا ڳوليو",
        "tooltip-p-logo": "مک صفحو گھمو",
        "tooltip-n-mainpage": "مک صفحو گھمو",
        "tooltip-n-mainpage-description": "مک صفحو گھمو",
        "tooltip-t-recentchangeslinked": "ويجھڙائيءَ ۾ صفحن ۾ ٿيل تبديليون هن صفحي سان ڳنڍيل آهن",
        "tooltip-feed-atom": "هن صفحي لاءِ ايٽم فيڊ",
        "tooltip-t-contributions": "{{GENDER:$1|ھن يوزر}} جي ڀاڱيدارين جي فھرست",
-       "tooltip-t-emailuser": "{{GENDER:$1|Ú¾Ù\86 Ù\8aÙ\88زر}} Ú\8fاÙ\86Ú¾Ù\86 Ø§Ù\8aÙ\85Ù\8aل موڪليو",
+       "tooltip-t-emailuser": "{{GENDER:$1|Ú¾Ù\86 Ù\8aÙ\88زر}} Ú\8fاÙ\86Ú¾Ù\86 Ø¨Ø±Ù\82ٽپال موڪليو",
        "tooltip-t-upload": "فائيل چاڙهيو",
-       "tooltip-t-specialpages": "سڀني خاص صفحن جي فهرست",
+       "tooltip-t-specialpages": "سڀني خاص صفحن جي فھرست",
        "tooltip-t-print": "هن صفحي جو ڇاپائتو پرت",
-       "tooltip-t-permalink": "صفحي جي ان نظرثاليءَ ڏانهن مستقل ڳنڍڻو",
+       "tooltip-t-permalink": "صفحي جي ان نظرثاليءَ ڏانھن مستقل ڳنڍڻو",
        "tooltip-ca-nstab-main": "مواد جي صفحي کي ڏسو",
-       "tooltip-ca-nstab-user": "هن جو يُوزر صفحو ڏسو",
-       "tooltip-ca-nstab-special": "هيءُ خاص صفحو آهي، ان ۾ ترميم ڪري نٿي سگھجي",
+       "tooltip-ca-nstab-user": "ھن جو يُوزر صفحو ڏسو",
+       "tooltip-ca-nstab-special": "هيءُ خاص صفحو آهي، ان ۾ ترميم نٿي ڪري سگھجي",
        "tooltip-ca-nstab-project": "رٿائي صفحو ڏسو",
-       "tooltip-ca-nstab-image": "هن فائيل جو صفحو ڏسو",
+       "tooltip-ca-nstab-image": "ھن فائيل جو صفحو ڏسو",
        "tooltip-ca-nstab-template": "سانچو ڏسو",
        "tooltip-ca-nstab-help": "امدادي صفحو ڏسو",
        "tooltip-ca-nstab-category": "هن زمري جو صفحو ڏسو",
        "tooltip-minoredit": "ان کي هڪ معمولي ترميم ڄاڻايو",
-       "tooltip-save": "پنهنجون تبديليون سانڍيو",
+       "tooltip-save": "پنھنجون تبديليون سانڍيو",
        "tooltip-preview": "پنھنجي تبديلين تي نگاھ وجھو. براءِ مھرباني اھو سانڍڻ کان اڳ ڪندا.",
-       "tooltip-diff": "لکت ۾ ڪيل پنهنجون تبديليون ڏسو",
+       "tooltip-diff": "لکت ۾ ڪيل پنھنجون تبديليون ڏسو",
        "tooltip-compareselectedversions": "هن صفحي جن ٻن چونڊيل پرتن درميان تفاوت ڏسو.",
-       "tooltip-watch": "هيءُ صفحو پنهنجي نظرھيٺ فھرست ۾ شامل ڪريو",
-       "tooltip-rollback": "\"Ù\88اپس Ù¾Ø±Ø§Ù\8aÙ\88\" Ù\87Ù\86 ØµÙ\81Ø­Ù\8a Û¾ Ù¾Ù\88ئÙ\8aÙ\86 Ú\80اڱÙ\8aدار Ø¬Ù\8a ÚªÙ\8aÙ\84 ØªØ±Ù\85Ù\8aÙ\85 (ترÙ\85Ù\8aÙ\85Ù\86) Ú©Ù\8a Ù\87Úª ÚªÙ\84Úª Ø³Ø§Ù\86 Ø§Ú»ÚªØ±Ù\8a Ù¿Ù\88.",
+       "tooltip-watch": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪريو",
+       "tooltip-rollback": "\"Ù\88اپس Ù\88راÙ\8aÙ\88\" Ú¾Ù\86 ØµÙ\81Ø­Ù\8a Û¾ Ù¾Ù\88ئÙ\8aÙ\86 Ú\80اڱÙ\8aدار Ø¬Ù\8a ÚªÙ\8aÙ\84 ØªØ±Ù\85Ù\8aÙ\85\86) Ú©Ù\8a Ú¾Úª Ù½Ú\99Úª Ø³Ø§Ù\86 Ø§Ú»ÚªØ±Ù\8a Ù¿Ù\88",
        "tooltip-summary": "ننڍو خلاصو ڏيو",
        "anonymous": "گمنام {{PLURAL:$1|يوزر|يوزرس}} جو {{SITENAME}}",
+       "simpleantispam-label": "اينٽي-اسپام روڪ.\nھن کي <strong>نہ</strong> ڀريو!",
        "pageinfo-toolboxlink": "صفحي جي معلومات",
        "pageinfo-contentpage-yes": "ها",
        "pageinfo-protect-cascading-yes": "ها",
        "pageinfo-category-subcats": "ذيلي زمرن جو تعداد",
        "pageinfo-category-files": "صفحن جو تعداد",
        "previousdiff": "← اڳوڻي ترميم",
-       "nextdiff": "نئين ترميم-->",
+       "nextdiff": "نئين تر ترميم →",
        "file-info-size": "$1 × $2 عڪسلون، فائيل سائيز: $3، MIME ٽائيپ: $4",
-       "file-nohires": "اڃا سنهو تحلل ميسر ناهي.",
-       "svg-long-desc": "ايس وي جي فائيل، اٽڪل $1 × $2 عڪسلون، فائيل سائيز: $3",
+       "file-nohires": "اڃان سنھو تحلل ميسر ناھي.",
+       "svg-long-desc": "ايسويجي فائيل، اٽڪل $1 × $2 عڪسلون، فائيل سائيز: $3",
        "show-big-image": "اصلوڪو فائيل",
        "show-big-image-preview": "هن پيش نگاھ جي ماپ: $1",
        "show-big-image-other": "ٻيا {{PLURAL:$2|تحلل}}:$1",
        "metadata": "اعدادِ اعداد",
        "metadata-expand": "توسيعي تفصيل ڏيکاريو",
        "metadata-collapse": "توسيعي تفصيل لڪايو",
-       "metadata-fields": "اÙ\85Ù\8aج Ø¬Ù\8a Ù\85Ù\8aٽا Ú\8aÙ\8aٽا Ù\81Ù\8aÙ\84Ú\8a Ù\84سٽ ÚªÙ\8aÙ\84 Ø¢Ù\87Ù\8a Ù\87Ù\86 Ù¾Ù\8aغاÙ\85 Û¾ Ø¬Ù\8aÚªÙ\88 Ø´Ø§Ù\85Ù\84 ÚªÙ\8aÙ\88 Ù\88Ù\8aÙ\86دÙ\88 Ø§Ù\85Ù\8aج جي صفحي جي ڊسپلي تي، جڏهن ميٽا ڊيٽا جي ٽيبل ختم ٿيندي، ٻيا طئي ٿيل طريقي سان لڪل هوندا. \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": "عڪس Ø¬Ù\8a Ù\85Ù\8aٽا Ú\8aÙ\8aٽا Ù\81Ù\8aÙ\84Ú\8a Ù\84سٽ ÚªÙ\8aÙ\84 Ø¢Ù\87Ù\8a Ù\87Ù\86 Ù¾Ù\8aغاÙ\85 Û¾ Ø¬Ù\8aÚªÙ\88 Ø´Ø§Ù\85Ù\84 ÚªÙ\8aÙ\88 Ù\88Ù\8aÙ\86دÙ\88 Ø¹ÚªØ³ جي صفحي جي ڊسپلي تي، جڏهن ميٽا ڊيٽا جي ٽيبل ختم ٿيندي، ٻيا طئي ٿيل طريقي سان لڪل هوندا. \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": "ٻٽڻيون في جُز",
        "exif-disclaimer": "غيرجوابدارينامو",
        "exif-copyrighted-true": "حق ۽ واسطا محفوظيل",
        "exif-unknowndate": "نامعلوم تاريخ",
-       "exif-orientation-1": "نارمل",
+       "exif-orientation-1": "رواجي",
        "exif-orientation-3": "180° موڙيل",
        "exif-componentsconfiguration-0": "وجود نہ ٿو رکي",
        "exif-exposureprogram-1": "دستينامو",
        "exif-saturation-0": "نارمل",
        "exif-saturation-1": "ننڍ رچاءُ",
        "exif-saturation-2": "وَڏ رچاءُ",
-       "exif-sharpness-0": "نارمل",
+       "exif-sharpness-0": "رواجي",
        "exif-subjectdistancerange-0": "نامعلوم",
        "exif-subjectdistancerange-3": "ڏورانهين نگاهہ",
        "exif-gpslatitude-s": "ڏاکڻي ويڪرائي ڦاڪَ",
        "exif-iimcategory-wea": "موسم",
        "namespacesall": "سڀ",
        "monthsall": "سڀ",
-       "confirmemail": "برق ٽپال پتي جي پَڪَ ڪندا",
+       "confirmemail": "برقٽپال پتي جي پَڪَ ڪندا",
        "confirmemail_send": "خاطري ڪوڊ اماڻيو",
-       "confirmemail_sent": "خاطري برق ٽپال اماڻي وئي.",
-       "confirmemail_success": "توهان جي برق ٽپال پتي جي تصديق ڪئي وئي آهي.\nهاڻ توهان [[Special:UserLogin|لاگ اِن]] ٿي ۽ وڪي جو مزو وٺي سگھو ٿا.",
-       "confirmemail_loggedin": "توهان جي برق ٽپال پتي جي تصديق هاڻي ٿي چڪي آهي.",
-       "confirmemail_subject": "{{SITENAME}} برق ٽپال پتي جي تصديق",
+       "confirmemail_sent": "خاطري برقٽپال اماڻي وئي.",
+       "confirmemail_success": "توهان جي برقٽپال پتي جي تصديق ڪئي وئي آهي.\nهاڻ توهان [[Special:UserLogin|داخل ٿي]] ۽ وڪي جو مزو وٺي سگھو ٿا.",
+       "confirmemail_loggedin": "توهان جي برقٽپال پتي جي تصديق هاڻي ٿي چڪي آهي.",
+       "confirmemail_subject": "{{SITENAME}} برقٽپال پتي جي تصديق",
        "recreate": "ورسرجيو",
        "confirm-watch-button": "ٺيڪ",
-       "confirm-watch-top": "هيءُ صفحو پنهنجي نظرھيٺ فھرست ۾ شامل ڪندا؟",
+       "confirm-watch-top": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪندا؟",
        "confirm-unwatch-button": "ٺيڪ",
-       "confirm-unwatch-top": "Ù\87Ù\8aØ¡Ù\8f ØµÙ\81Ø­Ù\88 Ù¾Ù\86Ù\87Ù\86جÙ\8a Ù½Ù\8aÙ½ فهرست مان هٽائيندا؟",
+       "confirm-unwatch-top": "Ù\87Ù\8aØ¡Ù\8f ØµÙ\81Ø­Ù\88 Ù¾Ù\86Ù\87Ù\86جÙ\8a Ù\86ظر Û¾ فهرست مان هٽائيندا؟",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← اڳوڻو صفحو",
        "imgmultipagenext": "ايندڙ صفحو →",
        "table_pager_limit_label": "وَٿُون في صفحو:",
        "table_pager_limit_submit": "ھلو",
        "table_pager_empty": "ڪو بہ نتيجو نہ مليو",
-       "watchlistedit-normal-title": "نظرھيٺ فھرست کي سنواريو",
+       "watchlistedit-normal-title": "نظر ۾ فھرست کي سنواريو",
        "watchlistedit-raw-titles": "عنوانَ:",
        "watchlistedit-clear-titles": "عنوانَ:",
        "watchlisttools-view": "لاڳاپيل تبديليون ڏسو",
-       "watchlisttools-edit": "Ù½Ù\8aÙ½ فهرست ڏسو ۽ سنواريو",
-       "watchlisttools-raw": "ÚªÚ\86Ù\8a Ù½Ù\8aÙ½ Ù\81Ù\87رست سنواريو",
+       "watchlisttools-edit": "Ù\86ظر Û¾ فهرست ڏسو ۽ سنواريو",
+       "watchlisttools-raw": "ÚªÚ\86Ù\8a Ù\86ظر Û¾ Ù\81Ú¾رست سنواريو",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|ڳالھ]])",
        "version": "ڀيرو",
        "version-extensions": "تنصيب شده توسيعات",
        "fileduplicatesearch-submit": "ڳوليو",
        "specialpages": "خاص صفحا",
        "specialpages-note-top": "ڪُنجي",
-       "specialpages-group-login": "لاگ اِن ٿيو / کاتو کوليو",
+       "specialpages-group-login": "داخل ٿيو / کاتو کوليو",
        "specialpages-group-users": "يوزرس ۽ حق",
        "blankpage": "خالي صفحو",
        "intentionallyblankpage": "هيءُ صفحو ڄاڻي خالي ڇڏيو ويو آهي.",
        "htmlform-cloner-create": "ٻيا بہ شامل ڪريو",
        "htmlform-cloner-delete": "هٽايو",
        "htmlform-title-not-exists": "$1 وجود نٿو رکي.",
-       "logentry-delete-delete": "$1 {{GENDER:$2|Ú\8aاٺو}} صفحو $3",
+       "logentry-delete-delete": "$1 {{GENDER:$2|Ú\8aاٿو}} صفحو $3",
        "revdelete-uname-hid": "يُوزرنانءُ لڪل",
        "logentry-move-move": "$1 {{GENDER:$2|چوريو}} صفحو $3 ڏانهن $4",
        "logentry-newusers-create": "يوزر کاتو $1 {{GENDER:$2|سرجيو ويو}}",
index 8b905f2..0da808b 100644 (file)
        "yourpasswordagain": "Pakartuoket slaptažuodė:",
        "createacct-yourpasswordagain": "Čīstā tuokis slaptažuodis?",
        "createacct-yourpasswordagain-ph": "Apent ožrašīkat slaptažuodi",
-       "userlogin-remembermypassword": "Ka liktō prisėjongis",
+       "userlogin-remembermypassword": "Ka liktō prisijongė̄s",
        "userlogin-signwithsecure": "Apsauguots rīšīs",
        "cannotlogin-title": "Nēn prisijongtė",
        "cannotlogin-text": "Nie galam prisijongtė.",
index 8e8c7fb..c8103b6 100644 (file)
        "recentchangeslinked-page": "Názov stránky:",
        "recentchangeslinked-to": "Zobraziť zmeny na stránkach, ''ktoré odkazujú na'' zadanú stránku",
        "recentchanges-page-added-to-category": "[[:$1]] zaradená do kategórie",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] zaradená do kategórie. [[Special:WhatLinksHere/$1|Táto stránka je vložená do iných stránok.]",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] zaradená do kategórie, [[Special:WhatLinksHere/$1|táto stránka je vložená do iných stránok]]",
        "recentchanges-page-removed-from-category": "[[:$1]] vyradená z kategórie",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] odstránená z kategórie. [[Special:WhatLinksHere/$1|Táto stránka je vložená do iných stránok.]",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] odstránená z kategórie, [[Special:WhatLinksHere/$1|táto stránka je vložená do iných stránok]]",
        "autochange-username": "Automatická úprava MediaWiki",
        "upload": "Nahrať súbor",
        "uploadbtn": "Nahrať súbor",
index 12d7974..01e785c 100644 (file)
@@ -12,7 +12,8 @@
                        "MaGa",
                        "Skalcaa",
                        "Janezdrilc",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "NegativeTwelveDollars"
                ]
        },
        "tog-underline": "Podčrtavanje povezav:",
        "search-external": "Zunanji iskalnik",
        "searchdisabled": "Iskanje po {{GRAMMAR:dajalnik|{{SITENAME}}}} je onemogoočeno.\nMedtem lahko iščete preko Googla.\nUpoštevajte, da so njihovi podatki vsebine {{GRAMMAR:rodilnik|{{SITENAME}}}} morda zastareli.",
        "search-error": "Med iskanjem je prišlo do napake: $1",
+       "search-warning": "Med iskanjem je prišlo do napake: $1",
        "preferences": "Nastavitve",
        "mypreferences": "Nastavitve",
        "prefs-edits": "Število urejanj:",
        "userrights-user-editname": "Vpišite uporabniško ime:",
        "editusergroup": "Naloži uporabniške skupine",
        "editinguser": "Urejanje pravic {{GENDER:$1|uporabnika|uporabnice}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Ogledovanje uporabniških pravic {{GENDER:$1|uporabnika|uporabnice}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Urejanje uporabniških skupin",
+       "userrights-viewusergroup": "Ogled skupin uporabnikov",
        "saveusergroups": "Shrani {{GENDER:$1|uporabnikove|uporabničine}} skupine",
        "userrights-groupsmember": "Član skupine:",
        "userrights-groupsmember-auto": "Posreden član:",
        "action-upload_by_url": "nalaganje te datoteke iz URL-naslova",
        "action-writeapi": "uporabo API-ja za pisanje",
        "action-delete": "brisanje te strani",
-       "action-deleterevision": "brisanje te redakcije",
-       "action-deletedhistory": "pregled zgodovine izbrisanih redakcij te strani",
+       "action-deleterevision": "brisanje redakcij",
+       "action-deletelogentry": "brisanje dnevniških vnosov",
+       "action-deletedhistory": "ogled zgodovine izbrisanih redakcij strani",
+       "action-deletedtext": "ogled besedila izbrisanih redakcij",
        "action-browsearchive": "iskanje izbrisanih strani",
-       "action-undelete": "obnavljanje te strani",
-       "action-suppressrevision": "vpogled in obnavljanje te skrite redakcije",
+       "action-undelete": "Obnovi strani",
+       "action-suppressrevision": "vpogled in obnavljanje skritih redakcij",
        "action-suppressionlog": "vpogled tega zasebnega dnevnika",
        "action-block": "blokiranje urejanja s tega uporabniškega računa",
        "action-protect": "spremembo stopnje zaščite te strani",
        "action-userrights-interwiki": "upravljanje uporabniških pravic za uporabnike drugih wikijev",
        "action-siteadmin": "zaklenitev ali odklepanje podatkovne baze",
        "action-sendemail": "pošiljanje e-sporočil",
+       "action-editmyoptions": "urejanje svojih nastavitev",
        "action-editmywatchlist": "urejanje svojega spiska nadzorov",
        "action-viewmywatchlist": "ogleda svojega spiska nadzorov",
        "action-viewmyprivateinfo": "ogled svojih zasebnih informacij",
        "emailccsubject": "Kopija tvojega sporočila iz $1: $2",
        "emailsent": "E-pismo je poslano!",
        "emailsenttext": "E-pismo je poslano.",
-       "emailuserfooter": "To e-poštno sporočilo je {{GENDER:$1|poslal|poslala|poslal(-a)}} $1 uporabniku {{GENDER:$2|$2}} s funkcijo »{{int:emailuser}}« na {{GRAMMAR:dative|{{SITENAME}}}}.",
+       "emailuserfooter": "To e-poštno sporočilo je {{GENDER:$1|poslal|poslala|poslal(-a)}} $1 uporabniku {{GENDER:$2|$2}} s funkcijo »{{int:emailuser}}« na {{GRAMMAR:dative|{{SITENAME}}}}. {{GENDER:$2|Vaše}} e-poštno sporočilo bo poslano neposredno {{GENDER:$1|izvornemu pošiljatelju|izvorni pošiljateljici}}, kar {{GENDER:$1|mu|ji}} bo razkrilo {{GENDER:$2|vaš}} e-poštni naslov.",
        "usermessage-summary": "Pusti sistemsko sporočilo.",
        "usermessage-editor": "Sistemski sporočevalec",
        "watchlist": "Spisek nadzorov",
        "wlshowhidemine": "moja urejanja",
        "wlshowhidecategorization": "kategorizacija strani",
        "watchlist-options": "Možnosti spiska nadzorov",
+       "watchlist-mark-all-visited": "Ste prepričani, da želite ponastaviti neogledane spremembe na spisku nadzorov tako, da vse strani označite kot ogledane?",
        "watching": "Nadziranje ...",
        "unwatching": "Nenadziranje ...",
        "watcherrortext": "Med spreminjanjem vaših nastavitev spiska nadzora za »$1« je prišlo do napake.",
        "tooltip-pt-mytalk": "{{GENDER:|Tvoja}} pogovorna stran",
        "tooltip-pt-anontalk": "Pogovor o urejanjih s tega IP-naslova",
        "tooltip-pt-preferences": "{{GENDER:|Tvoje}} nastavitve",
-       "tooltip-pt-watchlist": "Seznam strani, katerih spremembe spremljate",
+       "tooltip-pt-watchlist": "Seznam strani, katerih spremembe spremljaš",
        "tooltip-pt-mycontris": "Seznam {{GENDER:|tvojih}} prispevkov",
        "tooltip-pt-anoncontribs": "Seznam urejanj s tega IP-naslova",
        "tooltip-pt-login": "Prijava ni obvezna, vendar je zaželena",
        "mw-widgets-dateinput-no-date": "Datum ni izbran",
        "mw-widgets-dateinput-placeholder-day": "LLLL-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "LLLL-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Iskanje predstavnosti",
+       "mw-widgets-mediasearch-noresults": "Ni zadetkov.",
        "mw-widgets-titleinput-description-new-page": "stran še ne obstaja",
        "mw-widgets-titleinput-description-redirect": "preusmeritev na $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Dodaj kategorijo ...",
        "usercssispublic": "Pomnite: Podstrani CSS naj ne vsebujejo zaupnih podatkov, saj so vidne tudi drugim uporabnikom.",
        "restrictionsfield-badip": "Neveljaven IP-naslov ali obseg: $1",
        "restrictionsfield-label": "Dovoljeni IP-obsegi:",
-       "restrictionsfield-help": "En IP-naslov ali CIDR-območje na vrstico. Da omogočite vse, uporabite<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "En IP-naslov ali CIDR-območje na vrstico. Da omogočite vse, uporabite<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "ID strani $1"
 }
index f3f52f1..3f3c720 100644 (file)
        "passwordreset-emaildisabled": "Karakteristikat e  Email janë të paaftë në këtë wiki.",
        "passwordreset-username": "Nofka:",
        "passwordreset-domain": "Domain:",
-       "passwordreset-capture": "Dëshiron të shikosh e-mail-in që rezulton?",
-       "passwordreset-capture-help": "Nëse shënoni këtë kuti, e-mail-i (dhe fjalekalimi i përkohshëm) që do t'i dërgohen përdoruesit, do të të tregohen edhe ty.",
        "passwordreset-email": "Posta elektronike",
        "passwordreset-emailtitle": "Detajet e llogarisë në {{SITENAME}}",
        "passwordreset-emailtext-ip": "Dikush (ndoshta ju, nga IP adresa $1) kërkoi një kujtesë për \ndetajet e llogarisë suaj {{SITENAME}} ($4).Përdoruesi në vijim {{PLURAL:$3|llogari është|llogaritë janë}} të lidhur me këtë postë elektronike:\n\n$2\n\n{{PLURAL:$3|Ky fjalëkalim i përkohshëm|Këto fjalëkalime të përkohshme}} do të përfundojë për {{PLURAL:$5|një ditë|$5 ditë}}.\n\nJu duhet të kyçeni dhe të zgjidhni një fjalëkalim të ri tani. Nëse dikush tjetër e ka bërë këtë kërkesës, ose në qoftë se ju mbani mend fjalëkalimin tuaj origjinal, dhe ju nuk dëshirojni të ndryshoni atë, ju mund të injoroni këtë mesazh dhe do të vazhdoni përdorimin e fjalëkalimit tuaj të vjetër.",
        "blockedtitle": "Përdoruesi është bllokuar",
        "blockedtext": "'''Llogaria juaj ose adresa e IP është bllokuar'''\n\nBllokimi u bë nga $1 dhe arsyeja e dhënë ishte '''$2'''.\n\n*Fillimi i bllokimit: $8\n*Skadimi i bllokimit: $6\n*I bllokuari i shënjestruar: $7\n\nMund të kontaktoni $1 ose një nga [[{{MediaWiki:Grouppage-sysop}}|administruesit]] e tjerë për të diskutuar bllokimin.\n\nVini re se nuk mund t'i dërgoni email përdoruesit nëse nuk keni një adresë të saktë të dhënë tek [[Special:Preferences|parapëlqimet e përdoruesit]] ose nëse kjo është një nga mundësitë që ju është bllokuar.\n\nAdresa e IP-së që keni është $3 dhe numri i identifikimit të bllokimit është #$5. Përfshini këto dy të dhëna në çdo ankesë.",
        "autoblockedtext": "IP adresa juaj është bllokuar automatikisht sepse ishte përdorur nga një përdorues tjetër i cili ishte bllokuar nga $1.\nArsyeja e dhënë për këtë është:\n\n:''$2''\n\n* Fillimi i bllokimit: $8\n* Kalimi i kohës së bllokimit: $6\n* Zgjatja e bllokimit: $7\n\nJu mund të kontaktoni $1 ose një tjetër [[{{MediaWiki:Grouppage-sysop}}|administrues]] për ta diskutuar bllokimin.\n\nVini re : që nuk mund ta përdorni mundësinë \"dërgo porosi elektronike\" përveç nëse keni një postë elektronike të vlefshme të regjistruar në [[Special:Preferences|preferencat tuaja]] dhe nuk jeni bllokuar nga përdorimi i saj.\n\nIP adresa juaj e tanishme është $3 dhe ID e bllokimit është #$5.\nJu lutemi përfshini këto detaje në të gjitha kërkesat që i bëni.",
-       "blockednoreason": "nuk është dhënë ësnje arsye",
+       "blockednoreason": "nuk është dhënë asnjë arsye",
        "whitelistedittext": "Ju duhet të $1 për të redaktuar faqet.",
        "confirmedittext": "Ju duhet së pari ta vërtetoni e-mail adresen para se të redaktoni. Ju lutem plotësoni dhe vërtetoni e-mailin tuaj  te [[Special:Preferences|parapëlqimet]] e juaja.",
        "nosuchsectiontitle": "Paragrafi nuk mund të gjendet",
        "userrights-reason": "Arsyeja:",
        "userrights-no-interwiki": "Nuk keni leje për të ndryshuar privilegjet e përdoruesve në wiki të tjera.",
        "userrights-nodatabase": "Regjistri $1 nuk ekziston ose nuk është vendor.",
-       "userrights-nologin": "Duhet të [[Special:UserLogin|hyni brenda]] me një llogari administrative për të ndryshuar privilegjet e përdoruesve.",
-       "userrights-notallowed": "Ju nuk keni leje për të shtuar ose hequr privilegjet e përdoruesve.",
        "userrights-changeable-col": "Grupe që mund të ndryshoni",
        "userrights-unchangeable-col": "Grupe që s'mund të ndryshoni",
        "userrights-conflict": "Konflikt në ndryshimin e të drejtave të përdoruesit! Të lutem të rishiko dhe konfirmo ndryshimet e tua.",
        "right-siteadmin": "Mbyll ose hap bazën e të dhënave",
        "right-override-export-depth": "Eksoprto faqet duke përfshirë e lidhura deri në një thellësi prej 5",
        "right-sendemail": "Dërgo e-mail tek përdoruesit e tjerë",
-       "right-passwordreset": "Shiko e-mail-et e rivendosjes së fjalëkalimit",
        "right-managechangetags": "Krijoni dhe fshini [[Special:Tags|tags]] nga baza e të dhënave",
        "right-applychangetags": "Aplikoni [[Special:Tags|tags]] së bashku me ndryshimet",
        "right-changetags": "Shtoni dhe të largoni në mënyrë arbitrare [[Special:Tags|tags]] në rishikimet individuale dhe regjistrimet e historikut",
index 4446e01..729b09d 100644 (file)
        "views": "Прегледи",
        "toolbox": "Алатке",
        "tool-link-userrights": "Уреди {{GENDER:$1|корисничке}} групе",
+       "tool-link-userrights-readonly": "{{GENDER:$1|Корисничке}} групе",
        "tool-link-emailuser": "Пошаљи {{GENDER:$1|имејл}}",
        "userpage": "Погледај корисничку страницу",
        "projectpage": "Погледај страницу пројекта",
        "password-change-forbidden": "Не можете да промените лозинку на овом викију.",
        "externaldberror": "Дошло је до грешке при препознавању базе података или немате овлашћења да ажурирате свој спољни налог.",
        "login": "Пријави ме",
+       "login-security": "Ваерификација вашег индентитета",
        "nav-login-createaccount": "Пријава/регистрација",
        "userlogin": "Пријава/регистрација",
        "userloginnocreate": "Пријава",
        "userlogin-resetpassword-link": "Заборавили сте лозинку?",
        "userlogin-helplink2": "Помоћ при пријављивању",
        "userlogin-loggedin": "Већ сте пријављени као {{GENDER:$1|$1}}.\nКористите доњи образац да бисте се пријавили као други корисник.",
+       "userlogin-reauth": "Морате се поново пријавити да би верификовали да сте {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Отвори још један налог",
        "createacct-emailrequired": "Имејл адреса",
        "createacct-emailoptional": "Имејл адреса (опционо)",
        "createacct-another-realname-tip": "Право име није обавезно.\nАко изаберете да га унесете, оно ће бити коришћено за приписивање вашег рада.",
        "pt-login": "Пријави ме",
        "pt-login-button": "Пријави ме",
+       "pt-login-continue-button": "Настави пријављивање",
        "pt-createaccount": "Отвори налог",
        "pt-userlogout": "Одјави ме",
        "php-mail-error-unknown": "Непозната грешка у функцији PHP mail().",
        "userrights-user-editname": "Корисничко име:",
        "editusergroup": "Уреди {{GENDER:$1|корисничке}} групе",
        "editinguser": "Мењате корисничка права {{GENDER:$1|корисника|кориснице}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Корисничка права {{GENDER:$1|корисника|кориснице}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Промена корисничких група",
+       "userrights-viewusergroup": "Преглед корисничких група",
        "saveusergroups": "Сачувај {{GENDER:$1|корисничке}} групе",
        "userrights-groupsmember": "Члан:",
        "userrights-groupsmember-auto": "Подразумевано члан и:",
        "right-managechangetags": "прављење и (де)активирање [[Special:Tags|ознака]]",
        "right-applychangetags": "примењивање [[Special:Tags|ознака]] на нечије измене",
        "right-changetags": "додавање и уклањање разних [[Special:Tags|ознака]] на појединачним изменама и уносима у дневницима",
+       "right-deletechangetags": "брисање [[Special:Tags|ознака]] из базе података",
        "grant-group-page-interaction": "Уређивање страница",
        "grant-group-file-interaction": "Уређивање датотека",
        "grant-group-watchlist-interaction": "Уређивање вашег списка надгледања",
        "grant-editmywatchlist": "Уређивање вашег списка надгледања",
        "grant-editpage": "Уређивање постојећих страница",
        "grant-editprotected": "Уређивање заштићених страница",
+       "grant-highvolume": "Масовно уређивање",
        "grant-patrol": "Патролирање измена",
        "grant-protect": "Закључавање и откључавање страница",
        "grant-rollback": "Враћање измена",
        "reuploaddesc": "Назад на образац за отпремање",
        "upload-tryagain": "Пошаљи измењени опис датотеке",
        "uploadnologin": "Нисте пријављени",
-       "uploadnologintext": "$1 да бисте отпремали датотеке.",
+       "uploadnologintext": "Морате бити $1 да бисте отпремали датотеке.",
        "upload_directory_missing": "Фасцикла за слање ($1) недостаје и сервер је не може направити.",
        "upload_directory_read_only": "Сервер не може да пише по фасцикли за слање ($1).",
        "uploaderror": "Грешка при отпремању",
        "confirmemail_invalidated": "Потврда имејл адресе је отказана",
        "invalidateemail": "Отказивање потврде имејла",
        "notificationemail_body_changed": "Неко, вероватно Ви је променио имејл адресу налога из $2“ у „$3“ са IP адресе $1 на сајту {{SITENAME}}.\n\nАко ово нисте били Ви, одмах обавестите администраторе сајта.",
+       "notificationemail_body_removed": "Неко, вероватно Ви са ИП адресе $1 је уклонио имејл адресу за налог „$2“ на {{SITENAME}}.\n\n\nАко ово нисте били Ви, одмах обавестите администраторе сајта.",
        "scarytranscludedisabled": "[Међувики укључивање шаблона је онемогућено]",
        "scarytranscludefailed": "[Добављање шаблона за $1 није успело]",
        "scarytranscludefailed-httpstatus": "[Не могу да преузмем шаблон $1: HTTP $2]",
        "confirm-watch-top": "Додати ову страницу у списак надгледања?",
        "confirm-unwatch-button": "У реду",
        "confirm-unwatch-top": "Уклонити ову страницу са списка надгледања?",
+       "confirm-rollback-button": "У реду",
        "semicolon-separator": ";&#32;",
        "comma-separator": ",&#32;",
        "colon-separator": ":&#32;",
        "htmlform-user-not-exists": "<strong>$1</strong> не постоји.",
        "htmlform-user-not-valid": "<strong>$1</strong> није исправно корисничко име.",
        "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} страницу $3",
+       "logentry-delete-delete_redir": "$1 је {{GENDER:$2|обрисао|обрисала}} преусмерење $3 преснимавањем",
        "logentry-delete-restore": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3",
        "logentry-delete-event": "$1 је {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|1=догађаја|$5 догађаја}} у дневнику $3: $4",
        "logentry-delete-revision": "$1 је {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|1=једне измене|$5 измене|$5 измена}} на страници $3: $4",
        "mw-widgets-dateinput-no-date": "Датум није изабран",
        "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
+       "mw-widgets-mediasearch-noresults": "Нема резултата.",
        "mw-widgets-titleinput-description-new-page": "страница још увек не постоји",
        "mw-widgets-titleinput-description-redirect": "преусмерава на $1",
        "randomrootpage": "Случајна коренска страница",
        "log-action-filter-block-reblock": "измена блокирања",
        "log-action-filter-block-unblock": "деблокирање",
        "log-action-filter-delete-delete": "брисање странице",
+       "log-action-filter-delete-delete_redir": "преснимавање преусмерења",
        "log-action-filter-delete-restore": "враћање странице",
        "log-action-filter-delete-event": "брисање уноса у дневницима",
        "log-action-filter-delete-revision": "брисање измене",
        "log-action-filter-upload-overwrite": "промена постојећег",
        "authmanager-email-label": "Имејл",
        "authmanager-email-help": "Имејл адреса",
-       "changecredentials": "Промјена акредитива"
+       "authprovider-resetpass-skip-label": "Прескочи",
+       "changecredentials": "Промјена акредитива",
+       "changecredentials-submit": "Промени",
+       "credentialsform-provider": "Врста акредитива:",
+       "credentialsform-account": "Назив налога:"
 }
index 3fad564..735e419 100644 (file)
        "reuploaddesc": "Nazad na obrazac za otpremanje",
        "upload-tryagain": "Pošalji izmenjeni opis datoteke",
        "uploadnologin": "Niste prijavljeni",
-       "uploadnologintext": "$1 da biste otpremali datoteke.",
+       "uploadnologintext": "Morate biti $1 da biste otpremali datoteke.",
        "upload_directory_missing": "Fascikla za slanje ($1) nedostaje i server je ne može napraviti.",
        "upload_directory_read_only": "Server ne može da piše po fascikli za slanje ($1).",
        "uploaderror": "Greška pri otpremanju",
index c24dcb6..0c9783c 100644 (file)
        "views": "Visningar",
        "toolbox": "Verktyg",
        "tool-link-userrights": "Ändra {{GENDER:$1|användargrupper}}",
+       "tool-link-userrights-readonly": "Visa {{GENDER:$1|användargrupper}}",
        "tool-link-emailuser": "Skicka e-post till denna {{GENDER:$1|användare}}",
        "userpage": "Visa användarsida",
        "projectpage": "Visa projektsida",
        "search-external": "Extern sökning",
        "searchdisabled": "Sökfunktionen på {{SITENAME}} är avstängd.\nDu kan istället göra sökningar med hjälp av Google.\nNotera dock att deras indexering av {{SITENAME}} kan vara något föråldrad.",
        "search-error": "Ett fel uppstod under sökningen: $1",
+       "search-warning": "En varning uppstod under sökning: $1",
        "preferences": "Inställningar",
        "mypreferences": "Inställningar",
        "prefs-edits": "Antal redigeringar:",
        "prefswarning-warning": "Du har gjort ändringar i dina inställningarna som inte har sparats ännu.\nOm du lämnar denna sida utan att klicka på \"$1\" kommer dina inställningar inte att uppdateras.",
        "prefs-tabs-navigation-hint": "Tips: Du kan använda vänster och höger piltangenterna för att navigera mellan flikarna i listan flikar.",
        "userrights": "Hantering av användarrättigheter",
-       "userrights-lookup-user": "Hantera användargrupper",
+       "userrights-lookup-user": "Välj en användare",
        "userrights-user-editname": "Skriv in ett användarnamn:",
-       "editusergroup": "Ändra {{GENDER:$1|användargrupper}}",
+       "editusergroup": "Läs in användargrupper",
        "editinguser": "Ändrar rättigheter för {{GENDER:$1|användaren}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Visar rättigheter för {{GENDER:$1|användaren}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Ändra användargrupper",
+       "userrights-viewusergroup": "Visa användargrupper",
        "saveusergroups": "Spara {{GENDER:$1|användargrupper}}",
        "userrights-groupsmember": "Medlem i:",
        "userrights-groupsmember-auto": "Implicit medlem av:",
        "action-upload_by_url": "ladda upp denna fil från en URL-adress",
        "action-writeapi": "använda skriv-API:t",
        "action-delete": "radera denna sida",
-       "action-deleterevision": "radera denna version",
-       "action-deletedhistory": "se denna sidas raderade historik",
+       "action-deleterevision": "radera sidversioner",
+       "action-deletelogentry": "radera loggposter",
+       "action-deletedhistory": "se en sidas raderade historik",
+       "action-deletedtext": "visa raderad sidversionstext",
        "action-browsearchive": "söka raderade sidor",
-       "action-undelete": "avradera denna sida",
-       "action-suppressrevision": "granska och återställa denna dolda version",
+       "action-undelete": "återställ sidor",
+       "action-suppressrevision": "granska och återställ dolda sidversioner",
        "action-suppressionlog": "se denna privata logg",
        "action-block": "blockera denna användare från redigering",
        "action-protect": "ändra skyddsnivå för denna sida",
        "action-userrights-interwiki": "ändra rättigheter för användare på andra wikier",
        "action-siteadmin": "låsa eller låsa upp databasen",
        "action-sendemail": "skicka e-post",
+       "action-editmyoptions": "redigera dina inställningar",
        "action-editmywatchlist": "redigera din bevakningslista",
        "action-viewmywatchlist": "visa din bevakningslista",
        "action-viewmyprivateinfo": "visa din privata information",
        "emailccsubject": "Kopia av ditt meddelande till $1: $2",
        "emailsent": "E-post har nu skickats",
        "emailsenttext": "Ditt e-postmeddelande har skickats",
-       "emailuserfooter": "Detta e-postmeddelande {{GENDER:$1|skickades}} av $1 till {{GENDER:$2|$2}} med funktionen \"{{int:emailuser}}\" på {{SITENAME}}.",
+       "emailuserfooter": "Detta e-postmeddelande {{GENDER:$1|skickades}} av $1 till {{GENDER:$2|$2}} med funktionen \"{{int:emailuser}}\" på {{SITENAME}}. {{GENDER:$2|Ditt}} e-postmeddelande kommer att skickas direkt till {{GENDER:$1|den ursprungliga avsändaren}}, vilket kommer avslöja {{GENDER:$2|din}} e-postadress för {{GENDER:$1|dem}}.",
        "usermessage-summary": "Lämnar systemmeddelande.",
        "usermessage-editor": "Systemmeddelare",
        "watchlist": "Bevakningslista",
        "wlshowhidemine": "mina redigeringar",
        "wlshowhidecategorization": "kategorisering av sidor",
        "watchlist-options": "Alternativ för bevakningslistan",
+       "watchlist-mark-all-visited": "Är du säker på att du vill återställa osedda förändringar i bevakningslistan genom att markera alla sidor som besökta?",
        "watching": "Bevakar...",
        "unwatching": "Avbevakar...",
        "watcherrortext": "Ett fel inträffade när du ändrade dina bevakningsinställningarna för \"$1\".",
        "mw-widgets-dateinput-no-date": "Inget valt datum",
        "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM",
+       "mw-widgets-mediasearch-input-placeholder": "Sök efter media",
+       "mw-widgets-mediasearch-noresults": "Inga resultat hittades.",
        "mw-widgets-titleinput-description-new-page": "sidan existerar inte ännu",
        "mw-widgets-titleinput-description-redirect": "omdirigerar till $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Lägg till en kategori...",
        "usercssispublic": "Observera: CSS-undersidor bör inte innehålla konfidentiella uppgifter eftersom de kan ses av andra användare.",
        "restrictionsfield-badip": "Ogiltig IP-adress eller intervall: $1",
        "restrictionsfield-label": "Tillåtna IP-intervall:",
-       "restrictionsfield-help": "En IP-adress eller CIDR-intervall per rad. För att aktivera allting, använd<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "En IP-adress eller CIDR-intervall per rad. För att aktivera allting, använd<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "sid-ID $1"
 }
index 3be834f..bf81066 100644 (file)
@@ -55,7 +55,7 @@
        "tog-enotifminoredits": "อีเมลหาเช่นกันสำหรับการแก้ไขหน้าและไฟล์เล็กน้อย",
        "tog-enotifrevealaddr": "เปิดเผยที่อยู่อีเมลของฉันในอีเมลแจ้งเตือน",
        "tog-shownumberswatching": "แสดงจำนวนผู้ใช้ที่เฝ้าดู",
-       "tog-oldsig": "ลายà¹\80à¸\8bà¹\87à¸\99à¸\97ีà¹\88à¹\83à¸\8aà¹\89อยู่:",
+       "tog-oldsig": "ลายà¹\80à¸\8bà¹\87à¸\99à¸\97ีà¹\88à¸\84ุà¸\93มีอยู่:",
        "tog-fancysig": "ถือลายเซ็นเป็นข้อความวิกิ (โดยไม่มีลิงก์อัตโนมัติ)",
        "tog-uselivepreview": "ใช้การแสดงตัวอย่างแบบสด",
        "tog-forceeditsummary": "เตือนเมื่อช่องคำอธิบายอย่างย่อว่าง",
@@ -72,7 +72,7 @@
        "tog-showhiddencats": "แสดงหมวดหมู่ที่ซ่อนอยู่",
        "tog-norollbackdiff": "ไม่แสดงผลต่างหลังดำเนินการย้อนกลับฉุกเฉิน",
        "tog-useeditwarning": "เตือนฉันเมื่อออกหน้าแก้ไขโดยมีการเปลี่ยนแปลงที่ยังไม่บันทึก",
-       "tog-prefershttps": "à¹\83à¸\8aà¹\89à¸\81ารà¹\80à¸\8aืà¹\88อมà¸\95à¹\88อà¸\9bลอà¸\94ภัยà¸\97ุà¸\81à¸\84รัà¹\89à¸\87à¹\80มืà¹\88อลà¹\87อà¸\81อิà¸\99",
+       "tog-prefershttps": "à¹\83à¸\8aà¹\89à¸\81ารà¹\80à¸\8aืà¹\88อมà¸\95à¹\88อà¸\9bลอà¸\94ภัยà¸\97ุà¸\81à¸\84รัà¹\89à¸\87à¹\80มืà¹\88อà¹\80à¸\82à¹\89าสูà¹\88ระà¸\9aà¸\9aà¹\81ลà¹\89ว",
        "underline-always": "ทุกครั้ง",
        "underline-never": "ไม่",
        "underline-default": "ค่าโดยปริยายของหน้าตาหรือเบราว์เซอร์",
        "october-date": "$1 ตุลาคม",
        "november-date": "$1 พฤศจิกายน",
        "december-date": "$1 ธันวาคม",
+       "period-am": "ก่อนเที่ยง",
+       "period-pm": "หลังเที่ยง",
        "pagecategories": "{{PLURAL:$1|หมวดหมู่|หมวดหมู่}}",
        "category_header": "หน้าในหมวดหมู่ \"$1\"",
        "subcategories": "หมวดหมู่ย่อย",
        "newwindow": "(เปิดหน้าต่างใหม่)",
        "cancel": "ยกเลิก",
        "moredotdotdot": "ดูเพิ่ม...",
-       "morenotlisted": "รายการนี้ไม่สมบูรณ์",
+       "morenotlisted": "รายà¸\81ารà¸\99ีà¹\89อาà¸\88à¹\84มà¹\88สมà¸\9aูรà¸\93à¹\8c",
        "mypage": "หน้า",
        "mytalk": "พูดคุย",
        "anontalk": "พูดคุย",
        "talk": "อภิปราย",
        "views": "ดู",
        "toolbox": "เครื่องมือ",
+       "tool-link-userrights": "เปลี่ยนกลุ่ม{{GENDER:$1|ผู้ใช้}}",
+       "tool-link-userrights-readonly": "ดูกลุ่ม{{GENDER:$1|ผู้ใช้}}",
+       "tool-link-emailuser": "ส่งอีเมลหา{{GENDER:$1|ผู้ใช้}}นี้",
        "userpage": "ดูหน้าผู้ใช้",
        "projectpage": "ดูหน้าโครงการ",
        "imagepage": "ดูหน้าไฟล์",
        "pool-timeout": "หมดเวลารอกำลังรอล็อก",
        "pool-queuefull": "พื้นที่รองรับคิวเต็ม",
        "pool-errorunknown": "เกิดข้อผิดพลาดไม่ทราบสาเหตุ",
+       "pool-servererror": "บริการพูลเคาน์เตอร์ไม่พร้อมใช้งาน ($1)",
+       "poolcounter-usage-error": "เกิดข้อผิดพลาดในการใช้งาน: $1",
        "aboutsite": "เกี่ยวกับ{{SITENAME}}",
        "aboutpage": "Project:เกี่ยวกับ",
        "copyright": "เนื้อหาอนุญาตให้เผยแพร่ภายใต้ $1 เว้นแต่ระบุไว้เป็นอย่างอื่น",
        "readonly_lag": "ฐานข้อมูลถูกล็อกอัตโนมัติขณะที่เซิร์ฟเวอร์ฐานข้อมูลรองกำลังปรับปรุงตามฐานข้อมูลหลัก",
        "internalerror": "ข้อผิดพลาดภายใน",
        "internalerror_info": "เกิดข้อผิดพลาดภายใน: $1",
-       "filecopyerror": "ไม่สามารถคัดลอกไฟล์ \"$1\" ไป \"$2\"",
+       "internalerror-fatal-exception": "การยกเว้นข้อผิดพลาดร้ายแรงของประเภท \"$1\"",
+       "filecopyerror": "ไม่สามารถคัดลอกไฟล์ \"$1\" ไปยัง \"$2\" ได้",
        "filerenameerror": "ไม่สามารถเปลี่ยนชื่อไฟล์ \"$1\" เป็น \"$2\"",
        "filedeleteerror": "ไม่สามารถลบไฟล์ \"$1\"",
        "directorycreateerror": "ไม่สามารถสร้างสารบบ \"$1\"",
+       "directoryreadonlyerror": "ไดเรกทอรี \"$1\" อ่านได้อย่างเดียว",
+       "directorynotreadableerror": "ไดเรกทอรี \"$1\" ไม่สามารถอ่านได้",
        "filenotfound": "ไม่พบไฟล์ \"$1\"",
        "unexpected": "ค่าไม่คาดหมาย: \"$1\"=\"$2\"",
        "formerror": "มีข้อผิดพลาด: ส่งแบบไม่ได้",
        "createacct-yourpasswordagain-ph": "กรอกรหัสผ่านอีกครั้ง",
        "userlogin-remembermypassword": "ให้ฉันอยู่ในระบบต่อ",
        "userlogin-signwithsecure": "ใช้การเชื่อมต่อที่ปลอดภัย",
+       "cannotlogin-title": "ไม่สามารถเข้าสู่ระบบได้",
+       "cannotlogin-text": "ไม่สามารถเข้าสู่ระบบได้",
        "cannotloginnow-title": "ไม่สามารถล็อกเอาต์ได้ขณะนี้",
        "cannotloginnow-text": "ไม่สามารถล็อกเอาต์ได้เมื่อกำลังใช้ $1",
+       "cannotcreateaccount-title": "ไม่สามารถสร้างบัญชีได้",
+       "cannotcreateaccount-text": "การสร้างบัญชีโดยตรงไม่ได้เปิดใช้งานบนวิกินี้",
        "yourdomainname": "โดเมนของคุณ:",
        "password-change-forbidden": "คุณไม่สามารถเปลี่ยนรหัสผ่านบนวิกินี้",
        "externaldberror": "มีข้อผิดพลาดของฐานข้อมูลการพิสูจน์ตัวจริง หรือคุณไม่ได้รับอนุญาตให้ปรับบัญชีภายนอกของคุณ",
        "eauthentsent": "อีเมลยืนยันถูกส่งไปยังที่อยู่อีเมลที่ระบุไว้แล้ว \nก่อนที่อีเมลอื่นจะถูกส่งไปยังบัญชีนั้น คุณต้องปฏิบัติตามคำสั่งในอีเมลเพื่อยืนยันว่าบัญชีนั้นเป็นของคุณจริง ๆ",
        "throttled-mailpassword": "อีเมลตั้งรหัสผ่านใหม่ถูกส่งไปแล้วใน $1 ชั่วโมงที่ผ่านมา \nอีเมลตั้งรหัสผ่านใหม่จะส่งไปหนึ่งครั้งต่อ $1 ชั่วโมงเท่านั้น เพื่อป้องกันการกระทำผิด",
        "mailerror": "ข้อผิดพลาดในการส่งเมล: $1",
-       "acct_creation_throttle_hit": "ผู้เข้าชมวิกินี้ที่ใช้เลขที่อยู่ไอพีของคุณ ได้สร้าง $1 บัญชีแล้วในวันที่ผ่านมา ซึ่งเป็นจำนวนสูงสุดที่อนุญาตในช่วงเวลาดังกล่าว\nจึงส่งผลให้ผู้เข้าชมที่ใช้เลขที่อยู่ไอพีนี้ ไม่สามารถสร้างบัญชีได้อีกในขณะนี้",
+       "acct_creation_throttle_hit": "ผู้เข้าชมวิกินี้ที่ใช้เลขที่อยู่ไอพีของคุณ ได้สร้าง {{PLURAL:$1|1 บัญชี|$1 บัญชี}}แล้วเมื่อ $2 ผ่านมา ซึ่งเป็นจำนวนสูงสุดที่อนุญาตในช่วงเวลาดังกล่าว\nจึงส่งผลให้ผู้เข้าชมที่ใช้เลขที่อยู่ไอพีนี้ ไม่สามารถสร้างบัญชีได้อีกในขณะนี้",
        "emailauthenticated": "ยืนยันที่อยู่อีเมลของคุณเมื่อวันที่ $2 เวลา $3",
        "emailnotauthenticated": "ที่อยู่อีเมลของคุณยังไม่ได้รับการยืนยัน \nจะไม่มีการส่งอีเมลสำหรับคุณลักษณะใด ๆ ต่อไปนี้",
        "noemailprefs": "ระบุที่อยู่อีเมลในการตั้งค่าของคุณเพื่อให้คุณลักษณะเหล่านี้ทำงานได้",
        "minoredit": "เป็นการแก้ไขเล็กน้อย",
        "watchthis": "เฝ้าดูหน้านี้",
        "savearticle": "บันทึกหน้า",
+       "savechanges": "บันทึกการเปลี่ยนแปลง",
        "publishpage": "เผยแพร่หน้า",
        "preview": "ตัวอย่าง",
        "showpreview": "แสดงตัวอย่าง",
        "prefs-help-recentchangescount": "นี่รวมถึงการปรับปรุงล่าสุด ประวิติหน้า และปูม",
        "prefs-help-watchlist-token2": "นี่คือแป้นลับสำหรับเข้าการป้อนเว็บรายการเฝ้าดูของคุณ\nใครก็ตามที่ทราบจะสามารถอ่านรายการเฝ้าดูของคุณได้ ฉะนั้นอย่าบอกผู้อื่น\n[[Special:ResetTokens|คลิกที่นี่หากคุณต้องการตั้งใหม่]]",
        "savedprefs": "บันทึกการตั้งค่าของคุณแล้ว",
-       "savedrights": "à¸\9aัà¸\99à¸\97ึà¸\81สิà¸\97à¸\98ิผู้ใช้ของ {{GENDER:$1|$1}} แล้ว",
+       "savedrights": "à¸\9aัà¸\99à¸\97ึà¸\81à¸\81ลุà¹\88มผู้ใช้ของ {{GENDER:$1|$1}} แล้ว",
        "timezonelegend": "เขตเวลา:",
        "localtime": "เวลาท้องถิ่น:",
        "timezoneuseserverdefault": "ใช้ค่าโดยปริยายของวิกิ ($1)",
        "thumbnail_error": "มีข้อผิดพลาดในการสร้างรูปย่อ: $1",
        "djvu_page_error": "หน้าเดจาวู (DjVu) เกินขนาด",
        "djvu_no_xml": "ไม่สามารถส่งเอกซ์เอ็มแอล (XML) สำหรับไฟล์เดจาวู (DjVu)",
+       "thumbnail-dest-create": "ไม่สามารถบันทึกรูปย่อลงในปลายทางได้",
        "thumbnail_invalid_params": "พารามิเตอร์รูปย่อไม่ถูกต้อง",
        "thumbnail_dest_directory": "ไม่สามารถสร้างสารบบปลายทาง",
        "thumbnail_image-type": "ไม่รองรับรูปแบบของไฟล์รูปภาพนี้",
        "htmlform-no": "ไม่",
        "htmlform-yes": "ใช่",
        "htmlform-chosen-placeholder": "เลือกตัวเลือก",
+       "htmlform-cloner-create": "เพิ่มอีก",
        "htmlform-cloner-required": "ต้องการอย่างน้อยหนึ่งค่า",
        "htmlform-title-badnamespace": "[[:$1]] ไม่อยู่ในเนมสเปซ \"{{ns:$2}}\"",
        "htmlform-title-not-creatable": "\"$1\" มิใช่ชื่อเรื่องหน้าที่สร้างได้",
        "api-error-filename-tooshort": "ชื่อไฟล์สั้นเกินไป",
        "api-error-filetype-banned": "ไฟล์ประเภทนี้ถูกห้าม",
        "api-error-mustbeloggedin": "กรุณาลงชื่อเข้าใช้เพื่ออัปโหลดไฟล์",
+       "api-error-ratelimited": "คุณกำลังพยายามอัปโหลดไฟล์ภายในระยะห่างเวลาที่สั้นเกินกว่าที่วิกินี้อนุญาต\nโปรดรอสักครู่แล้วลองใหม่อีกครั้ง",
        "api-error-uploaddisabled": "การอัปโหลดถูกปิดใช้งานบนวิกินี้",
        "duration-seconds": "$1 วินาที",
        "duration-minutes": "$1 นาที",
index cbf3596..8b36833 100644 (file)
@@ -88,7 +88,9 @@
                        "Kumkumuk",
                        "Basak",
                        "Ece Alpdeniz",
-                       "Superyetkin"
+                       "Superyetkin",
+                       "Alikaan",
+                       "By erdo can"
                ]
        },
        "tog-underline": "Bağlantıların altını çiz:",
        "passwordreset-emaildisabled": "Bu wiki'deki e-posta özellikleri devre dışı bırakıldı.",
        "passwordreset-username": "Kullanıcı adı:",
        "passwordreset-domain": "Domain:",
-       "passwordreset-capture": "Sonuç e-postasını görüntüle?",
-       "passwordreset-capture-help": "Bu kutuyu işaretlerseniz, e-posta (geçici şifre ile) size ve yanı sıra kullanıcıya gönderiliyor.",
        "passwordreset-email": "E-posta adresi:",
        "passwordreset-emailtitle": "{{SITENAME}} hesap detayları",
        "passwordreset-emailtext-ip": "Birisi, (muhtemelen siz, $1 IP adresinden) {{SITENAME}} ($4) için hesap bilgilerinizin \nhatırlatılmasını istedi. Aşağıdaki kullanıcı {{PLURAL:$3|hesabı|hesapları}} bu e-posta adresiyle ilişkili:\n\n$2\n\n{{PLURAL:$3|Bu geçici şifre|Bu geçici şifreler}} {{PLURAL:$5|bir gün|$5  gün}} geçerlidir.\nBu geçici parola ile giriş yapın ve yeni bir şifre seçin. Şifre değişimini siz istemediyseniz veya şifrenizi hatırladıysanız ve artık şifrenizi değiştirmek istemiyorsanız; bu iletiyi önemsemeyerek eski şifrenizi kullanmaya devam edebilirsiniz.",
        "search-external": "Dış arama",
        "searchdisabled": "{{SITENAME}} sitesinde arama yapma geçici olarak durdurulmuştur. Bu arada Google kullanarak {{SITENAME}} içinde arama yapabilirsiniz. Arama sitelerinde dizinlerin biraz eski kalmış olabileceğini göz önünde bulundurunuz.",
        "search-error": "Arama yapılırken bir hata oluştu: $1",
+       "search-warning": "Arama yapılırken bir hata oluştu: $1",
        "preferences": "Tercihler",
        "mypreferences": "Tercihler",
        "prefs-edits": "Değişiklik sayısı:",
        "editusergroup": "Kullanıcı grupları düzenle",
        "editinguser": "<strong>'''[[User:$1|$1]]'''</strong> $2 kullanıcısının yetkileri değiştiriliyor",
        "userrights-editusergroup": "Kullanıcı grupları düzenle",
+       "userrights-viewusergroup": "Kullanıcının gruplarını gör",
        "saveusergroups": "Kullanıcı grupları kaydet",
        "userrights-groupsmember": "İçinde olduğu gruplar:",
        "userrights-groupsmember-auto": "Saklı olarak içinde olduğu gruplar:",
        "userrights-reason": "Neden:",
        "userrights-no-interwiki": "Diğer vikilerdeki kullanıcıların izinlerini değiştirmeye yetkiniz yok.",
        "userrights-nodatabase": "$1 veritabanı mevcut veya bölgesel değil",
-       "userrights-nologin": "Kullanıcı haklarını atamak için hizmetli hesabı ile [[Special:UserLogin|giriş yapmanız gerekir]].",
-       "userrights-notallowed": "Kullanıcı hakları eklemek veya kaldırmak için izniniz yok.",
        "userrights-changeable-col": "Değiştirebildiğiniz gruplar",
        "userrights-unchangeable-col": "Değiştirebilmediğiniz gruplar",
        "userrights-conflict": "Kullanıcı hakları değişikliklerinde çakışma! Lütfen değişikliklerinizi gözden geçirin ve onaylayın.",
-       "userrights-removed-self": "Kendi haklarınız başarıyla kaldırıldı. Bu nedenle, artık bu sayfaya erişemeyeceksiniz.",
        "group": "Grup:",
        "group-user": "Kullanıcılar",
        "group-autoconfirmed": "Otomatik onaylanmış kullanıcılar",
        "right-siteadmin": "Veritabanını kilitle ve kilidi aç",
        "right-override-export-depth": "Sayfaları, derinlik 5'e kadar bağlantılı sayfalarla beraber, dışa aktar",
        "right-sendemail": "Diğer kullanıcılara e-posta gönder",
-       "right-passwordreset": "Parola sıfırlama e-postalarını görür",
        "right-managechangetags": "Veritabanında [[Special:Tags|etiket]] oluşturma veya silme",
        "right-applychangetags": "Değişiklikleriyle beraber [[Special:Tags|etiketleri]] uygula",
        "right-changetags": "Tekil sürümler ve günlük kayıtlarına rastgele [[Special:Tags|etiket]] ekleme veya çıkarma",
        "booksources-search": "Ara",
        "booksources-text": "Aşağıdaki, yeni ve kullanılmış kitap satan diğer sitelere bağlantıların listesidir, ve aradığınız kitaplar hakkında daha fazla bilgiye sahip olabilirler:",
        "booksources-invalid-isbn": "Verilen ISBN geçersiz gibi görünüyor; orijinal kaynaktan kopyalama hataları için kontrol edin.",
+       "magiclink-tracking-rfc": "RFC sihirli bağlantısını kullanan sayfalar",
+       "magiclink-tracking-pmid": "PMID sihirli bağlantısını kullanan sayfalar",
+       "magiclink-tracking-isbn": "ISBN sihirli bağlantısını kullanan sayfalar",
        "specialloguserlabel": "Kullanıcı:",
        "speciallogtitlelabel": "Hedef (başlık ya da kullanıcı):",
        "log": "Kayıtlar",
        "special-characters-title-emdash": "uzun çizgi",
        "special-characters-title-minus": "Eksi işareti",
        "mw-widgets-dateinput-no-date": "Hiçbir tarih seçilmedi",
+       "mw-widgets-mediasearch-input-placeholder": "Medya ara",
+       "mw-widgets-mediasearch-noresults": "Sonuç bulunamadı.",
        "mw-widgets-titleinput-description-new-page": "sayfa henüz mevcut değil",
        "mw-widgets-titleinput-description-redirect": "$1'e yönlendirildi",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "çerez tabanlı oturumlar",
index 5ea66b8..d2355c7 100644 (file)
        "eauthentsent": "Күрсәтелгән электрон почта адресына үзгәртүләрне раслау өчен хат җибәрелде. Киләчәктәдә хатлар кабул итү өчен, раслауны үтегез.",
        "throttled-mailpassword": "Серсүзне электрон почтага җибәрү гамәлен сез {{PLURAL:$1|1=соңгы $1 сәгать}} эчендә кулландыгыз инде. Бу гамәлне явызларча куллануны кисәтү максатыннан аны $1 {{PLURAL:$1|сәгать}} аралыгында бер генә тапкыр башкарып була.",
        "mailerror": "Хат җибәрү хатасы: $1",
-       "acct_creation_throttle_hit": "Сезнең IP адресыннан бу тәүлек эчендә {{PLURAL:$1|$1 хисап язмасы}} төзелде инде. Шунлыктан бу IP-адрес буенча сезнең өчен әлеге гамәл вакытлыча ябык.",
+       "acct_creation_throttle_hit": "Сезнең IP адресыннан  $2 эчендә {{PLURAL:$1|$1 хисап язмасы}} төзелде инде. Шунлыктан бу IP-адрес буенча сезнең өчен әлеге гамәл вакытлыча ябык.",
        "emailauthenticated": "Сезнең электрон почта адресыгыз $2 $3 расланды.",
        "emailnotauthenticated": "Электрон почта адресыгыз әле дәлилләнмәгән.\nХатлар әлеге мөмкинлекләргә җибәрелмәячәк.",
        "noemailprefs": "Электрон почта адресыгыз күрсәтелмәгән, шуңа викиның электрон почта белән эшләү гамәлләре сүндерелгән.",
        "passwordreset-disabled": "Бу викида серсүз бетереп булмый",
        "passwordreset-username": "Кулланучы исеме:",
        "passwordreset-domain": "Домен:",
-       "passwordreset-capture": "Килеп чыккан хатны күрсәтелсенме?",
-       "passwordreset-capture-help": "Әгәр сез бу тамганы куйсагыз, сезгә кулланучыга вакытлы серсүз белән җибәреләчәк хат күрсәтеләчәк.",
        "passwordreset-email": "E-mail адресы:",
        "passwordreset-emailtitle": "{{SITENAME}} хисап язмасы турында мәгълүматлар",
        "passwordreset-emailtext-ip": "Кемдер (бәлки, сездер, $1 IP-адресыннан) {{SITENAME}} ($4) проектында сезнең серсүзне искә төшерүне сорады.\n{{PLURAL:$3|1=Түбәндәге хисап язмасы|Түбәндәге хисап язмалары}} бу электрон әрҗә адресы белән бәйле:\n\n$2\n\n{{PLURAL:$3|1=Бу вакытлы серсүз|Бу вакытлы серсүзләр}} {{PLURAL:$5|$5 көн}} дәвамында эшлиячәкләр.\nСез системага керергә һәм яңа серсүз сайларга тиешсез.\nӘгәр сез серсүз сорамаган булсагыз яки элеккеге серсүзегезне искә төшерсәгез \nһәм аны үзгәртергә теләмәсәгез, бу хатка җавап бирмәгез\nһәм элеккеге серсүзегезне кулланыгыз.",
        "preview": "Алдан карау",
        "showpreview": "Алдан карау",
        "showdiff": "Кертелгән үзгәртүләр",
-       "anoneditwarning": "<strong>Игътибар!</strong> Сез сайтта теркәлмәдегез. Әгәрдә сез нинди дә булсә төзәтмәләр  яисә үзгәртүләр кертсәгез, сезне IP-адрес башкаларга да курсәтеләчәк. Сайтка <strong>[$1 керсәгез]</strong> яки <strong>[$2 кулланучы язмасын төзесәгез]</strong>, сез керткән үзгәртүләр яезнен кулланучы язмагызга бәйләнгән була, шулай ук башка мөмкинлекләр дә туачак.",
+       "anoneditwarning": "<strong>Игътибар!</strong> Сез сайтта теркәлмәдегез. Әгәрдә сез нинди дә булсә төзәтмәләр яисә үзгәртүләр кертсәгез, сезнең IP-адрес башка кулланучыларга да билгеле булачак. Сайтка <strong>[$1 керсәгез]</strong> яисә <strong>[$2 кулланучы язмасын төзесәгез]</strong>, сез керткән үзгәртүләр сезнең кулланучы язмагызга бәйләнгән булачак, шулай ук башка мөмкинлекләр дә туачак.",
        "anonpreviewwarning": "''Сез системада теркәлмәдегез.Сезнең тарафтан эшләнгән барлык үзгәртүләр дә сезнең IP-юлламагызны саклауга китерә.''",
        "missingsummary": "'''Искәртү.''' Сез үзгәртүгә кыскача тасвирлау язмадыгыз. Сез «Битне саклау» төймәсенә тагын бер тапкыр бассагыз, үзгәртүләр тасвирламасыз сакланачак.",
        "missingcommenttext": "Аска тасвирлама язуыгыз сорала.",
        "missingcommentheader": "<strong>Искәртү:</strong> Сез шәрехнең темасын күрсәтмәгәнсез.\n«{{int:savearticle}}» төймәсенә кабат бассагыз, үзгәртүләр темасыз язылачак.",
        "summary-preview": "Тасвирламаны алдан карау:",
-       "subject-preview": "Башисемне болай булачак:",
+       "subject-preview": "Башисем шушындый булачак:",
        "blockedtitle": "Кулланучы тыелды",
        "blockedtext": "'''Сезнең хисап язмагыз яки IP адресыгыз тыелган.'''\n\nТыючы идарәче: $1.\nКүрсәтелгән сәбәп: ''$2''.\n\n* Тыю башланган вакыт: $8\n* Тыю ахыры: $6\n* Тыелулар саны: $7\n\nСез $1 яки башка [[{{MediaWiki:Grouppage-sysop}}|идарәчегә]] тыю буенча сорауларыгызны җибәрә аласыз.\nИсегездә тотыгыз: әгәр сез теркәлмәгән һәм электрон почта адресыгызны дәлилләмәгән булсагыз ([[Special:Preferences|дәлилләү өчен шәхси көйләүләр монда]]), идарәчегә хат җибәрә алмыйсыз. Шулай ук тыю вакытында сезнең хат җибәрү мөмкинлегегезне чикләгән булырга да мөмкиннәр.\nСезнең IP адресы — $3, тыю идентификаторы — #$5.\nХатларда бу мәгълүматны күрсәтергә онытмагыз.",
        "autoblockedtext": "Сезнең IP адресыгыз, аның тыелган кулланучы тарафыннан кулланылуы сәбәпле, автомат рәвештә тыелды.\nУл кулланучыны тыючы идарәче: $1. Күрсәтелгән сәбәп:\n\n:''$2''\n\n* Тыю башланган вакыт: $8\n* Тыю ахыры: $6\n* Тыелулар саны: $7\n\nСез $1 яки башка [[{{MediaWiki:Grouppage-sysop}}|идарәчегә]] тыю буенча сорауларыгызны җибәрә аласыз.\nИсегездә тотыгыз: әгәр сез теркәлмәгән һәм электрон почта адресыгызны дәлилләмәгән булсагыз ([[Special:Preferences|дәлилләү өчен шәхси көйләүләр монда]]), идарәчегә хат җибәрә алмыйсыз. Шулай ук тыю вакытында сезнең хат җибәрү мөмкинлегегезне чикләгән булырга да мөмкиннәр.\nСезнең IP адресы — $3, тыю идентификаторы — #$5.\nХатларда бу мәгълүматны күрсәтергә онытмагыз.",
        "mergehistory-from": "Чыганак:",
        "mergehistory-into": "Төп бит:",
        "mergehistory-submit": "Төзәтмәләрне берләштерү",
+       "mergehistory-comment": "[[:$1]] [[:$2]] битенә күчерелде: $3",
        "mergehistory-reason": "Сәбәп:",
        "mergelog": "Берләштерүләр көндәлеге",
        "revertmerge": "Бүлү",
        "shown-title": "Сәхифәдә $1 {{PLURAL:$1|язма}} күрсәтелсен",
        "viewprevnext": "Күрсәтелүе: ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "<strong>Бу вики-проектта «[[:$1]]» исемле бит бар инде</strong>{{PLURAL:$2|0=|Башка эзләү нәтиҗәләрен дә карап ал.}}",
-       "searchmenu-new": "<strong>«Әлеге [[:$1]]» вики-проектта бит ясарга!</strong>\n{{PLURAL:$2|0=|Шулай ук, эзләү ярдәмендә табылган битне карагыз.|Шулай ук, эзләү ярдәмендә табылган битләрне карагыз.}}",
+       "searchmenu-new": "<strong>Әлеге вики-проектта «[[:$1]]» исемле бит ясарга!</strong>\n{{PLURAL:$2|0=|Шулай ук, эзләү ярдәмендә табылган битне карагыз.|Шулай ук, эзләү ярдәмендә табылган битләрне карагыз.}}",
        "searchprofile-articles": "Төп битләр",
        "searchprofile-images": "Мультимедиа",
        "searchprofile-everything": "Һәркайда",
        "prefs-editwatchlist-label": "Күзәтү исемлеге язмаларын үзгәртү:",
        "prefs-editwatchlist-edit": "Күзәтү исемлегеннән исемнәрне карау һәм сөртү",
        "prefs-editwatchlist-raw": "Күзәтү исемлеген текстсыман үзгәртү",
-       "prefs-editwatchlist-clear": "Ð\9aүзÓ\99Ñ\82Ò¯ Ð¸Ñ\81емлеген Ñ\82азарту",
+       "prefs-editwatchlist-clear": "Ð\9aүзÓ\99Ñ\82Ò¯ Ð¸Ñ\81емлеген Ñ\87иÑ\81Ñ\82арту",
        "prefs-watchlist-days": "Күзәтү исемлегендә көннәр санын күрсәтергә:",
        "prefs-watchlist-days-max": "Иң күбе $1 {{PLURAL:$1|1=көн|көн}}",
        "prefs-watchlist-edits": "Киңәйтелгән күзәтү исемлегендә күрсәтелүче төзәтмәләрнең максималь саны:",
        "prefs-tokenwatchlist": "Токен",
        "prefs-diffs": "Юрамалар аермасы",
        "userrights": "Кулланучы хокуклары белән идарә итү",
-       "userrights-lookup-user": "Кулланучы төркемнәре белән идарә итү",
+       "userrights-lookup-user": "Кулланучыны сайлау",
        "userrights-user-editname": "Кулланучының исемен кертегез:",
-       "editusergroup": "{{GENDER:$1|Кулланучының}} төркемнәрен алмаштыру",
+       "editusergroup": "Кулланучының төркемнәрен кую",
        "editinguser": "{{GENDER:$1|Кулланучы}} <strong>[[User:$1|$1]]</strong> $2 хокукларын үзгәртү",
        "userrights-editusergroup": "Кулланучының төркемнәрен алмаштыру",
        "saveusergroups": "{{GENDER:$1|Кулланучы}} төркемнәрен саклау",
        "userrights-reason": "Сәбәп:",
        "userrights-no-interwiki": "Сезнең башка викиларда кулланучыларның хокукларын үзгәртергә хокукларыгыз юк.",
        "userrights-nodatabase": "Бирелгән $1 базасы юк яисә  локаль булып тормый.",
-       "userrights-nologin": "Кулланучыларга вәкаләтләр билгеләү өчен идарәче хисап язмасы белән [[Special:UserLogin|керергә кирәк]]",
-       "userrights-notallowed": "Сезнең кулланучыларга вәкаләтләр тапшырырга яки кире алырга хокукы юк.",
        "userrights-changeable-col": "Сезнең тарафтан үзгәртелми торган төркемнәр",
        "userrights-unchangeable-col": "Сезнең тарафтан үзгәртелми торган төркемнәр",
        "userrights-irreversible-marker": "$1*",
        "userrights-conflict": "Кулланучы хокукларын үзгәртү конфликты! Төзәтмәләрегезне тикшерегез, аннары кабатлагыз.",
-       "userrights-removed-self": "Сез үзегезне хокукларыгыздан мәхрүм иттегез. Шул сәбәпле сез бу сәхифәгә башка керә алмыйсыз.",
        "group": "Төркем:",
        "group-user": "Кулланучылар",
        "group-autoconfirmed": "Авторасланган кулланучы",
        "autoredircomment": "[[$1]] битенә юнәлтү",
        "autosumm-new": "Яңа бит: «$1»",
        "watchlistedit-raw-titles": "Язмалар:",
+       "watchlisttools-clear": "Күзәтү исемлеген чистарту",
        "watchlisttools-view": "Соңгы үзгәртүләрне күрсәтү",
        "watchlisttools-edit": "Күзәтү исемлегене карау һәм үзгәртү",
        "watchlisttools-raw": "Текст сыман үзгәртү",
index 2269267..6316d17 100644 (file)
@@ -11,7 +11,8 @@
                        "Irus",
                        "Shklyaev",
                        "Wadorgurt",
-                       "Zpizza"
+                       "Zpizza",
+                       "Mouse21"
                ]
        },
        "tog-underline": "Чӧлсконъёсыз ултӥз гожен сызоно",
        "unprotectthispage": "Та бамлэсь утемзэ воштыны",
        "newpage": "Выль бам",
        "talkpage": "Та бам сярысь вераськыны",
-       "talkpagelinktext": "Ð\92ераськон",
+       "talkpagelinktext": "вераськон",
        "specialpage": "Ваньмыз панель",
        "personaltools": "Нимаз тӥрлыке",
        "articlepage": "Статьяез учкыны",
        "databaseerror-query": "Курон: $1",
        "databaseerror-function": "Функция: $1",
        "databaseerror-error": "Янгыш: $1",
+       "badtitle": "Умойтэм ним",
        "badtitletext": "Курем бам ним луэ мыдлань, буш либо кылъёс куспын яке викиос куспын нимыз умойтэм герӟамын.\nНимын, вылды, ярантэм символъёс вань.",
        "viewsource": "Кодзэ учкыны",
        "viewsource-title": "Кодзэ учкыны бам $1",
        "yourname": "Пырон ним:",
        "userlogin-yourname": "Пырон ним:",
        "userlogin-yourname-ph": "Гожтэ учётной записьтылэсь нимзэ",
-       "createacct-another-username-ph": "УÑ\87Ñ\91Ñ\82ной ÐºÐ½Ð¸Ð³Ð° нимъёс пыртэмын",
+       "createacct-another-username-ph": "Ð\92ики-авÑ\82оÑ\80лÑ\8dн нимъёс пыртэмын",
        "yourpassword": "Лушкемкыл:",
        "userlogin-yourpassword": "Лушкемкыл",
+       "userlogin-yourpassword-ph": "Гожтэ асьтэлэсь парольдэс",
+       "createacct-yourpassword-ph": "Гожтэ паролез",
        "createacct-yourpasswordagain": "Пароль юнматэ",
+       "createacct-yourpasswordagain-ph": "Гожтэ паролез эшшо одӥг пол",
        "userlogin-remembermypassword": "Кылем сӧзнэтэз",
        "cannotcreateaccount-title": "Уг быгатиськы гожъян кылдӥз учётной",
        "yourdomainname": "Тӥ доменэн:",
        "logout": "Кошкыны",
        "userlogout": "Потыны",
        "notloggedin": "Тон эн тусбуяськыны сӧзнэтэз",
+       "userlogin-noaccount": "Ас учётной записьты ӧвӧл?",
+       "userlogin-joinproject": "Проектэ пыриськоно",
        "nologin": "Учётной книга ӧвӧл-а? $1.",
        "nologinlink": "Выль вики-авторлэн регистрациез",
-       "createaccount": "выль вики-авторлэн регистрациез",
+       "createaccount": "Ð\92ыль вики-авторлэн регистрациез",
        "gotaccountlink": "Пырыны",
        "userlogin-resetpassword-link": "Тӥлесьтыд парольдэс куштыны?",
        "userlogin-helplink2": "Пыронъя юрттэт",
        "createacct-emailrequired": "Электронной почталэн адресэз",
        "createacct-emailoptional": "Электронной почтаезлэн адресэз (необязательное)",
+       "createacct-email-ph": "Гожтэ асьтэлэн электрон почтадылэсь адрессэ",
        "createaccountmail": "Адрес электронной почта огдырлы кутӥ вылын возьматэм образъёсыныз но соослэн случайной сгенерировать пароль ыстыны",
        "createacct-submit": "Выль вики-авторлэн регистрациез",
        "createacct-another-submit": "Выль вики-авторлэн регистрациез",
+       "createacct-benefit-heading": "{{SITENAME}} — тӥ выллем адямиослэн валче ужамзы.",
+       "createacct-benefit-body1": "{{PLURAL:$1|тупатон}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|бам}}",
+       "createacct-benefit-body3": "{{PLURAL:$1|викиавтор}} берло дыре",
        "loginerror": "Янгышъёс пырон",
        "createacct-error": "Янгышъёс бордын учётной книга кылдытыны",
        "createaccounterror": "Уг быгатиськы гожъян учётной кылдоз: $1",
        "pt-userlogout": "Потыны",
        "oldpassword": "Вуж лушкемкыл:",
        "newpassword": "Выль лушкемкыл:",
+       "passwordreset": "Пароль куштыны",
        "passwordreset-username": "Пырон ним:",
+       "bold_sample": "Зӧк шрифт",
+       "bold_tip": "Зӧк шрифт",
        "italic_sample": "Бекырес текст",
        "italic_tip": "Бекырес текст",
        "link_sample": "Чӧлсконлэн йыръянэз",
        "showdiff": "Пыртэм воштонъёс",
        "anoneditwarning": "<strong>Сак луэ!</strong> Тӥ сайтэ ӧд пырелэ. Тӥ котькыӵе тупатонъёсыз лэсьтоды бере, тӥляд IP-адресты ваньмызлы адӟытӥськоз. Тӥ <strong>[$1 пыроды]</strong> яке <strong>[$2 учётной записез кылдытоды]</strong> бере, тӥляд тупатонъёсты герӟаськозы нимдылы, мукет пайдаосын ӵош.",
        "blockedtitle": "Заблокировать пыриськисьёс",
-       "blockedtext": "<strong>Ð\9aнигае Ñ\8fке Ñ\83Ñ\87Ñ\91Ñ\82ной IP-адÑ\80еÑ\81 Ð·Ð°Ð±Ð»Ð¾ÐºÐ¸Ñ\80ован.</strong>\n\nÐ\91локиÑ\80овка Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñ\81Ñ\82Ñ\80аÑ\82оÑ\80 Ð¿Ð¾Ñ\82ӥз $1.\nÐ\92озÑ\8cмалÑ\8d Ð²Ñ\83оно Ð¼Ñ\83гез: \"\"$2\"\".\n\n* Ð\9aÑ\83Ñ\82Ñ\81кон Ð±Ð»Ð¾ÐºÐ¸Ñ\80овка: $8\n* Ð\91локиÑ\80овка Ð¾Ñ\80Ñ\82Ñ\87из: $6\n* Ð\91локиÑ\80овка Ð¼ÐµÑ\80еÑ\82Ñ\8aÑ\91Ñ\81Ñ\8bз: $7\n\nÐ\91Ñ\8bгаÑ\82Ó¥Ñ\81Ñ\8cкод-а Ñ\82он Ð³ÐµÑ\80Ó\9fаÑ\81Ñ\8cкемÑ\8bн $1 Ñ\8fке Ð¼Ñ\83кеÑ\82 ÐºÐ¾Ñ\82Ñ\8cкÑ\83дӥнÑ\8bз [[{{MediaWiki:Grouppage-sysop}}|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80Ñ\8aÑ\91Ñ\81]], Ð±Ð»Ð¾ÐºÐ¸Ñ\80овка Ð¼ÐµÐ´ Ñ\8dÑ\81кеÑ\80озÑ\8b.\nУÑ\87ком, Ð¼Ð°Ñ\80 ÐºÑ\83Ñ\82Ñ\8bнÑ\8b Ñ\83г Ð±Ñ\8bгаÑ\82о Ñ\84Ñ\83нкÑ\86изÑ\8dÑ\81 \"гожÑ\82Ñ\8dÑ\82\", Ð°Ñ\81 ÐºÐµ [[Special:Preferences|наÑ\81Ñ\82Ñ\80ойка Ð¿ÐµÑ\80Ñ\81оналÑ\8cной]] Ð·Ñ\83Ñ\80каÑ\82Ó¥Ñ\81Ñ\8c Ñ\8fке Ñ\8dлекÑ\82Ñ\80онной Ð¿Ð¾Ñ\87Ñ\82аезлÑ\8dн Ð°Ð´Ñ\80еÑ\81Ñ\8dз Ñ\8dн Ñ\87Ñ\83Ñ\80Ñ\82на Ñ\83г ÐºÐ¾Ñ\80Ñ\80екÑ\82нÑ\8bй, Ñ\8fке Ð³Ð¾Ð¶Ñ\82Ó¥Ñ\81Ñ\8cкод ÐºÐµ, Ð³Ð¾Ð¶Ñ\82Ñ\8dÑ\82 Ñ\8bÑ\81Ñ\82он Ñ\83кÑ\88аÑ\81Ñ\8c Ð±Ð»Ð¾ÐºÐ¸Ñ\80овка Ð°Ð»Ð¾Ð½.\nТон IP-адÑ\80еÑ\81 â\80\94 $3, Ð±Ð»Ð¾ÐºÐ¸Ñ\80овка Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\82оÑ\80лÑ\8dн â\80\94 $5.\nÐ\9fожалÑ\83йÑ\81Ñ\82а, Ð°Ñ\81лÑ\8dÑ\81Ñ\8cÑ\82Ñ\8bм Ñ\82одон-Ñ\82а Ð²Ð°Ð·Ð¸Ñ\81Ñ\8cконÑ\8dз ÐºÐ¾Ñ\82Ñ\8cкÑ\83 Ð²Ð¾Ð·Ñ\8cмано.",
-       "autoblockedtext": "Тон IP-адрес, герӟет автоматически заблокирован а со, мар солэн кутыны луоно азьвыл кин ке но пырисьёс пӧлысь, заблокирован {{GENDER:$4|участник|куакеч}} $1. \nБлокировка возьмано луоз вуоно мугез:\n\n: \"$2\" - лы.\n\n* Кутскон блокировка: $8\n* Блокировка ортчиз: $6\n* Блокировка меретъёсыз: $7\n\nБыгатӥськод-а тон герӟаськемын $1 яке мукет котькудӥныз [[{{MediaWiki:Grouppage-sysop}}|администраторъёс]], блокировка мед эскерозы.\n\nУчком, мар кутыны уг быгато функцизэс \"гожтэт\", ас ке [[Special:Preferences|настройка персональной]] зуркатӥсь яке электронной почтаезлэн адресэз эн чуртна уг корректный, яке гожтӥськод ке, гожтэт ыстон укшась блокировка алон.\n\nТон IP-адрес — $3, блокировка идентификаторлэн — #$5.\nПожалуйста, аслэсьтым тодон-та вазиськонэз котьку возьмано.",
+       "blockedtext": "<strong>ТӥлÑ\8fд Ñ\83Ñ\87Ñ\91Ñ\82ной Ð·Ð°Ð¿Ð¸Ñ\81Ñ\8cÑ\82Ñ\8b Ñ\8fке IP-адÑ\80еÑ\81Ñ\82Ñ\8b Ð·Ð°Ð±Ð»Ð¾ÐºÐ¸Ñ\80оваÑ\82Ñ\8c ÐºÐ°Ñ\80емÑ\8bн Ð²Ð°Ð».</strong>\n\nÐ\91локиÑ\80овкаез Ð»Ñ\8dÑ\81Ñ\8cÑ\82ӥз $1.\nÐ\9fÑ\83Ñ\81Ñ\8aем Ð¼Ñ\83гез: <em>$2</em>.\n\n* Ð\91локиÑ\80овка ÐºÑ\83Ñ\82Ñ\81киз: $8\n* Ð\91локиÑ\80овка Ð¹Ñ\8bлпÑ\83мÑ\8aÑ\8fÑ\81Ñ\8cкоз: $6\n* Ð\91локиÑ\80овкалÑ\8dн Ñ\83жпÑ\83мез: $7\n\nТӥ Ð±Ð»Ð¾ÐºÐ¸Ñ\80овка Ñ\81Ñ\8fÑ\80Ñ\8bÑ\81Ñ\8c Ð²ÐµÑ\80аÑ\81Ñ\8cкÑ\8bнÑ\8b Ð±Ñ\8bгаÑ\82Ó¥Ñ\81Ñ\8cкодÑ\8b $1 Ñ\8fке ÐºÐ¾Ñ\82Ñ\8cкин Ð¼Ñ\83кеÑ\82 [[{{MediaWiki:Grouppage-sysop}}|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80]] Ð´Ð¾Ñ\80Ñ\8b Ð³ÐµÑ\80Ó\9fаÑ\81Ñ\8cкÑ\8bÑ\81а.\n[[Special:Preferences|Ð\9dаÑ\81Ñ\82Ñ\80ойкаоÑ\81адÑ\8b]] Ñ\8dлекÑ\82Ñ\80он Ð¿Ð¾Ñ\87Ñ\82алÑ\8dн Ñ\83жаÑ\81Ñ\8c Ð°Ð´Ñ\80еÑ\81Ñ\8dз Ó§Ð²Ó§Ð» Ð´Ñ\8bÑ\80Ñ\8aÑ\8f, Ñ\82Ó¥ Â«Ð\92икиавÑ\82оÑ\80лÑ\8b Ð³Ð¾Ð¶Ñ\82Ñ\8dÑ\82» Ñ\84Ñ\83нкÑ\86иез Ñ\83же ÐºÑ\83Ñ\82Ñ\8bнÑ\8b Ñ\83д Ð±Ñ\8bгаÑ\82Ó¥Ñ\81Ñ\8cке, Ñ\81ое Ñ\82ӥленÑ\8bдÑ\8b Ñ\83же ÐºÑ\83Ñ\82он Ð´Ñ\83гдÑ\8bÑ\82Ñ\8dмÑ\8bн Ó§Ð²Ó§Ð» ÐºÐµ.\nТӥлÑ\8fд Ð°Ð»Ð¸ IP-адÑ\80еÑ\81Ñ\82Ñ\8b Ð»Ñ\83Ñ\8d $3, Ð½Ð¾ Ð±Ð»Ð¾ÐºÐ¸Ñ\80овка Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\82оÑ\80 â\80\94 #$5.\nТаÑ\83на Ð¿Ñ\8bÑ\80Ñ\82Ñ\8d Ð²Ð°Ð½Ñ\8c Ñ\82а Ð¿Ñ\8bÑ\80-поÑ\87 Ñ\81ведениоÑ\81Ñ\8bз Ð°Ñ\81Ñ\8cÑ\82Ñ\8dлÑ\8dн ÐºÑ\83Ñ\80онÑ\8aÑ\91Ñ\81адÑ\8b.",
+       "autoblockedtext": "Тӥляд IP-адресты автоматически заблокировать каремын вал, малы ке шуоно со кутӥськиз вал мукет викиавторен, кинзэ $1 заблокировать кариз.\nБлокировкалэн пусъем мугез:\n\n:<em>$2</em>\n\n* Блокировка кутскиз: $8\n* Блокировка йылпумъяськоз: $6\n* Блокировкалэн ужпумез: $7\n\nТӥ блокировка сярысь вераськыны быгатӥськоды $1 яке мукет [[{{MediaWiki:Grouppage-sysop}}|администраторъёс]] пӧлысь огез доры герӟаськыса.\n\nСак луэ, тӥ «Викиавторлы гожтэт» функциез уже кутыны уд быгатӥське [[Special:Preferences|асьтэлэн настройкаосады]] электрон почталэсь шонер адрессэ гожтытозь яке юнматытозь, либо блокировкады сыӵе амалэн гожтэтъёсыз ыстыны уг лэзьы ке.\n\nТӥляд али IP-адресты луэ $3, но блокировка идентификатор — #$5.\nТауна пыртэ вань та пыр-поч сведениосыз асьтэлэн куронъёсады.",
        "blockednoreason": "мугезлы эн возьмалэ",
        "whitelistedittext": "Тон кулэ $1 бам воштон понна.",
        "loginreqtitle": "Авторизация кулэ",
        "blocked-notice-logextract": "Пользователь заблокирован сётӥз та учырлы.\nСправка понна радъяськылӥсь журнал блокировка лапег берпуметӥ гожтэт:",
        "continue-editing": "Тупатъянэз азьланьтоно",
        "editing": "Тупатон: $1",
+       "creating": "«$1» бамез кылдытон",
        "editingsection": "Тупатон: $1 (люкет)",
+       "templatesused": "Та бам пушкы пыртэм {{PLURAL:$1|шаблон|шаблонъёс}}:",
        "template-protected": "(утемын)",
        "template-semiprotected": "(полуутемын)",
+       "hiddencategories": "Та бам пыре {{PLURAL:$1|$1 ватэм категорие}}:",
        "nocreatetext": "Та сайтлэн бамаз выль сюбегатэм луонлыкъёсын кылдытон.\nТон улыса, берлань вуэ быгатэ бам отредактировать, [[Special:UserLogin|тусбуяськыны книгае яке выль система кылдыто учётной]].",
        "nocreate-loggedin": "Тон доразы юаськыны кылдӥз выль бам ӧвӧл.",
        "permissionserrors": "Янгышъёс юаське",
        "permissionserrorstext": "Тон дорын разрешенизы ӧвӧлэн, тазэ лэсьтом шуыса, со понна вуоно {{PLURAL:$1|мугез}}:",
        "permissionserrorstext-withaction": "Правоез ӧвӧл, тон дорын $2 тӥ {{PLURAL:$1/1=мугез вуоно|мугез вуоно}}:",
+       "moveddeleted-notice": "Та бам быдтэмын вал.\nБыдтонъёсын но ним воштонъёсын журналъёсысь ярано записьёс чӧлсконлы улӥ возьматэмын.",
        "content-model-wikitext": "викитекст",
        "undo-summary": "Шонертон вошъян $1, лэсьтӥзы {{GENDER:$2|участник|куакеч}} [[Special:Contributions/$2|$2]] ([[User talk:$2|обс.]])",
        "cantcreateaccount-text": "Та книгаез кылдытонлы учётной IP-адрес (<strong>$1</strong>) заблокировать луизы [[User:$3|$3]].\n\nМугез, вайиз $3 возьматэ <em>$2</em>",
        "cantcreateaccount-range-text": "Учётной кылдытон - гожъян IP-адрес диапазонын <strong>$1</strong>, Тон пыриське со IP-адрес (<strong>$4</strong>), заблокировать луизы [[User:$3|$3]].\n\nМугез, вайиз $3 возьматэ <em>$2</em>",
        "viewpagelogs": "Та бамлы журналъёсыз возьматыны",
+       "currentrev-asof": "Алиез версия $1",
        "revisionasof": "Версия $1",
+       "revision-info": "Версия $1; {{GENDER:$6|$2}}$7",
        "previousrevision": "← Вужгем",
+       "nextrevision": "Выльгем →",
+       "currentrevisionlink": "Алиез версия",
        "cur": "али",
        "last": "азьв.",
+       "history-fieldset-title": "Историез учкыны",
        "history-show-deleted": "Ӵушылэмъёссэ гинэ",
        "rev-delundel": "возьматыны/ватыны",
        "rev-showdeleted": "возьматоно",
        "revdelete-radio-unset": "Адӟымон",
        "revdelete-reason-dropdown": "*Вӧлскем палэнскон мугъёсты\n** Авторской правоосты тӥян\n** Яке кулэтэм информациез личной комментарий\n** Логин несоответствовать\n** Курла информациез Потенциально",
        "history-title": "$1 — воштонъёслэн историзы",
+       "difference-title": "$1 — версиосыз куспын пӧртэмлык",
        "lineno": "$1-тӥ чур:",
        "compareselectedversions": "Быръем версиосыз ӵошатыны",
        "showhideselectedversions": "Возьматыны/ватыны быръем версиосыз",
        "editundo": "берытсконо",
+       "diff-multi-sameuser": "(таизлэн ик викиавторлэн {{PLURAL:$1|вискын шедьтэм $1 версиез}} возьматэмын ӧвӧл)",
        "searchresults": "Шедьтэмын",
        "searchresults-title": "утчан \"$1\"",
        "prevn": "{{PLURAL:$1|$1-лы}} берлань",
        "nextn": "{{PLURAL:$1|$1-лы}} азьлань",
+       "nextn-title": "$1 {{PLURAL:$1|шедьтэмлы}} азьлань",
        "shown-title": "Адӟытылоно $1 {{PLURAL:$1|шедьтэмез}} бамлы быдэ",
        "viewprevnext": "Учкыны ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-new": "<strong>Кылдытыны «[[:$1]]» бамез та вики-проектын!</strong> {{PLURAL:$2|0=|Со сяна утчанэныды шедьтэм бамез учке.|Со сяна шедьтэм бамъёсты учке.}}",
        "search-result-size": "$1 ({{PLURAL:$2|$2 кыл}})",
        "search-redirect": "($1 бамысь ыстон)",
        "search-section": "(«$1» люкет)",
+       "search-suggest": "Тӥ, вылды, утчаллямды «$1».",
        "search-interwiki-more": "(эшшо)",
        "searchall": "Ваньзэ",
        "search-showingresults": "{{PLURAL:$4|<strong>$3</strong> пӧлысь <strong>$1-тӥ</strong> шедьтэм|<strong>$3</strong> пӧлысь <strong>$1—$2</strong> шедьтэмъёс}}",
        "prefs-editing": "Тупатон",
        "yourlanguage": "Интерфейслэн кылыз:",
        "prefs-preview": "Бамез эскерон",
-       "editusergroup": "Группае {{GENDER:$1|пырись}} мукет",
+       "userrights": "Пыриськисьлэн правоосыныз кивалтон",
+       "editusergroup": "Викиавторлэсь группаоссэ возьматыны",
        "group-autoconfirmed": "Автоподтвержденный пыриськисьёс",
        "group-bot": "Боты",
        "group-sysop": "Администраторъёс",
        "group-all": "(ваньзэ)",
+       "grouppage-sysop": "{{ns:project}}:Администраторъёс",
        "right-read": "лыдӟыны бам",
        "right-edit": "правка бам",
        "right-createpage": "бам кылдытон-а, уг-возьматэмзэ эскерон",
        "recentchanges-legend": "Выль тупатонъёслы настройкаос",
        "recentchanges-summary": "Та бамын викилэн дыръя радъям выль воштонъёсыз возьматэмын.",
        "recentchanges-label-newpage": "Та тупатонэн выль бам кылдӥз",
-       "recentchanges-label-minor": "Ð\9fичи воштон",
-       "recentchanges-label-bot": "Та Ñ\82Ñ\83паÑ\82онÑ\8dз ÐºÐ°Ñ\80из Ð±Ð¾Ñ\82",
+       "recentchanges-label-minor": "Та Ñ\82Ñ\83паÑ\82он Ð»Ñ\83Ñ\8d Ð¿ичи воштон",
+       "recentchanges-label-bot": "Та Ñ\82Ñ\83паÑ\82онÑ\8dз Ð±Ð¾Ñ\82 ÐºÐ°Ñ\80из",
        "recentchanges-label-unpatrolled": "Та тупатонэз нокин но ӧз эскеры на",
        "recentchanges-label-plusminus": "Бамлэн быдӟалаез сомында байтъёслы воштӥськиз",
        "recentchanges-legend-heading": "<strong>Легенда:</strong>",
        "rcshowhideminor-hide": "Ватыны",
        "rcshowhidebots": "$1 ботъёсыз",
        "rcshowhidebots-show": "Возьматыны",
+       "rcshowhidebots-hide": "Ватыны",
        "rcshowhideliu": "$1 пырем викиавторъёсыз",
        "rcshowhideliu-show": "Возьматыны",
        "rcshowhideliu-hide": "Ватыны",
        "upload-dialog-button-cancel": "Берытсконо",
        "license-header": "Лицензия",
        "nolicense": "Ӧвӧл",
+       "imgfile": "файл",
        "file-anchor-link": "Файл",
        "filehist": "Файллэн историез",
        "filehist-help": "Зӥбе дата/дыр шоры, кызьы файл со дырын адӟиськемез учкыны вылысь.",
        "filehist-current": "алиез",
        "filehist-datetime": "Дата/дыр",
        "filehist-thumb": "Миниатюра",
+       "filehist-thumbtext": "$1 лэсьтэм версилэн миниатюраез",
        "filehist-user": "Викиавтор",
        "filehist-dimensions": "Быдӟала",
        "filehist-comment": "Валэктон",
        "randompage": "Олокыӵе статья",
        "withoutinterwiki-submit": "Возьматыны",
        "nbytes": "{{PLURAL:$1|$1 байт}}",
+       "nmembers": "$1 {{PLURAL:$1|объект}}",
        "prefixindex-submit": "Возьматыны",
        "newpages": "Выль бамъёс",
        "newpages-submit": "Возьматыны",
        "move": "Нимзэ воштыны",
+       "pager-older-n": "{{PLURAL:$1|вужгес $1}}",
        "booksources": "Книгаосын источникъёс",
+       "booksources-search-legend": "Книга сярысь информациез утчан",
+       "booksources-search": "Утчаны",
        "log": "Журналъёс",
        "logeventslist-submit": "Возьматыны",
        "showhideselectedlogentries": "Возьматыны/ватыны быръем журналъёсысь гожъямъёсыз",
        "checkbox-all": "Ваньзэ",
        "checkbox-none": "Номыре",
        "checkbox-invert": "Воштыны интыен",
+       "allarticles": "Ваньмыз бамъёс",
        "allpagessubmit": "Быдэстоно",
+       "categories": "Категориос",
        "categories-submit": "Возьматыны",
        "sp-deletedcontributions-contribs": "тупатонъёсыз",
        "listusers-submit": "Возьматыны",
        "watchlist-options": "Чаклан списокез тупатыны",
        "enotif_reset": "Вань бамъёсыз лыдӟем пусйыны",
        "historyaction-submit": "Возьматыны",
+       "dellogpage": "Быдтонъёсын журнал",
        "deletionlog": "палэнэ журнал",
        "rollbacklink": "ӝог берыктыны",
+       "rollbacklinkcount": "$1 {{PLURAL:$1|тупатонэз}} ӝог берыктыны",
        "revertpage": "Откат шонертон [[Special:Contributions/$2|$2]] ([[User talk:$2|обсуждение]]) доры версия [[User:$1|$1]]",
        "revertpage-nouser": "Откат шонертон (пыриськисьёс ватэм нимъёссы) доры версия {{GENDER:$1|[[User:$1|$1]]}}",
+       "protectlogpage": "Утёнъёсын журнал",
        "restriction-edit": "Тупатон",
        "undeletehistory": "Выльысь ке тон бамъёстэ, выльысь историяз луэм воштӥськонъёс вань.\nБӧрысь кылдӥзы выль бамъёс палэнэ кошконо луэ ке, сыӵе ик нимыз, историяз вошъяськонъёс предшествующий выльысь кылдозы.",
        "undeletehistorynoadmin": "Статьяос палэнтэмын вал. Мугез но палэнэ список пыриды, со статьяе редактировать-озь палэнэгес, зӧк возьматэ. Текст статьяез удаленный администраторъёс гинэ учкыны быгатод.",
        "contributions-title": "$1 викиавтор гожтэмъёсы",
        "mycontris": "Гожтэмъёс",
        "anoncontribs": "Гожтэмъёс",
+       "contribsub2": "{{GENDER:$3|$1}} гожтэмъёсы ($2)",
        "nocontribs": "Критерии нокыӵе воштӥськонъёс та соответствующий шедьтыны уг луы.",
+       "month": "Толэзьысен (вазен но):",
+       "year": "Арысен (вазен но):",
        "sp-contributions-blocklog": "блокировка",
        "sp-contributions-deleted": "шонертон палэнтыны {{GENDER:$1|участник|куакеч}}",
-       "sp-contributions-blocked-notice": "Пользователь заблокирован сётӥз та учырлы. Справка понна радъяськылӥсь журнал блокировка лапег берпуметӥ гожтэт:",
+       "sp-contributions-userrights": "пыриськисьлэн правоосыныз кивалтон",
+       "sp-contributions-blocked-notice": "Пользователь заблокировать сётӥз та учырлы. Справка понна радъяськылӥсь журнал блокировка лапег берпуметӥ гожтэт:",
        "sp-contributions-blocked-notice-anon": "Со ip-адрес вие заблокировать сётӥзы. Блокировка журналъёсты вайытэк улӥзы берпуметӥ книгаысь:",
+       "sp-contributions-submit": "Шедьтыны",
        "whatlinkshere": "Татчы чӧлсконъёс",
        "whatlinkshere-title": "«$1» вылэ чӧлскись бамъёс",
        "whatlinkshere-page": "Бам:",
        "linkshere": "Та бамъёс <strong>[[:$1]]</strong> вылэ чӧлско:",
        "isredirect": "ыстӥсь бам",
        "istemplate": "пыртон",
+       "isimage": "файл линк",
        "whatlinkshere-prev": "{{PLURAL:$1|берлань|$1-лы берлань}}",
        "whatlinkshere-next": "{{PLURAL:$1|азьлань|$1-лы азьлань}}",
        "whatlinkshere-links": "← чӧлсконъёс",
+       "whatlinkshere-hideredirs": "$1 ыстӥсь бамъёсты",
        "whatlinkshere-hidetrans": "$1 пыртонъёсты",
        "whatlinkshere-hidelinks": "$1 чӧлсконъёсты",
        "whatlinkshere-filters": "Фильтръёс",
        "block": "Блокировка пыриськисьёс",
        "blockip": "Заблокировать {{GENDER:$1|пыриськисьёс}}",
+       "blockip-legend": "Блокировка пыриськисьёс",
+       "blockiptext": "Формазэ уже кутыса, кулэ луэмезъя мед заблокировать гожъян IP-адрес яке пыриськисьёслэн нимъёссы.\nТа понна гинэ но соя гинэ лэсьтэмын луыны быгатоз вандализм предотвращение с [[{{MediaWiki:Policy-url}}|правилоосты]].\nМугез возьматись улӥзы членъёсын (кылсярысь, куд-ог тодметъёссэ вандализм цитировать кароно бам).\nТӥ быгатӥськоды диапазонэз заблокировать IP-адрес, уже кутыны [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR]-синтаксис. Максимально диапазонэз допустимый — /$1 протокол понна IPv4 но /$2 протокол понна IPv6.",
        "ipbreason-dropdown": "* Блокировка мугез кабес\n** Полы информациез оскизы\n** Вордскем палэнэ бам\n** Спам-сайтъя педпал чӧлскон\n** Текстлэсь визьем ватсан/жуг-жаг\n** Кышкытлыклэсь, пыриськыны уйиськон\n** Злоупотребление кӧня ке книга учётной\n** Пыриськисьёслэн нимъёссы пыриськисьёс",
-       "ipboptions": "2 час:2 hours,1 нуналлы:1 day,3 нуналлы:3 days,1 арняезлы:1 week,2 арняяз:2 weeks,1 толэзь:1 month,3 толэзь:3 months,6 толэзь:6 months,1 арлэн:1 year,бессрочно:infinite",
+       "ipbsubmit": "Адресъёсын та заблокировать/пыриськисьёс",
+       "ipboptions": "2 час:2 hours,1 нуналлы:1 day,3 нуналлы:3 days,1 арняезлы:1 week,2 арняяз:2 weeks,1 толэзь:1 month,3 толэзь:3 months,6 толэзь:6 months,1 арлэн:1 year,ноку:infinite",
+       "ipb-blocklist-contribs": "{{GENDER:$1|$1}} гожтэмъёсы",
        "unblocked": "[[User:$1|$1]] разблокировать",
        "unblocked-id": "Блокировка $1 басьтоно луиз",
        "blocklist-target": "Ужпумъёс",
        "blocklist-reason": "Мугез",
-       "infiniteblock": "беÑ\81Ñ\81Ñ\80оÑ\87но",
+       "infiniteblock": "нокÑ\83",
        "expiringblock": "йылпумъяськиз $1-ысь $2",
        "anononlyblock": "аноним гинэ",
        "noautoblockblock": "disconnect автоблокировка",
        "unblocklink": "разблокировать",
        "change-blocklink": "блокировка воштыны",
        "contribslink": "тупатонъёсыз",
-       "autoblocker": "Автоблокировка-со понна, мае тӥ IP-адрес кутыны али \"[[User:$1|$1]]\". \nБлокировка мугез $1: \"$2\"",
+       "autoblocker": "Автоблокировка, малы ке шуоно «[[User:$1|$1]]» тӥлесьтыд IP-адрестэс алигес уже кутӥз.\n$1 блокировкалэн мугез: «$2»",
+       "blocklogpage": "Блокировкаосын журнал",
        "blocklogentry": "заблокировать [[$1]] дыр $2 $3",
        "reblock-logentry": "блокировка воштӥз [[$1]] дыр $2 $3",
        "blocklogtext": "Блокировка но та журналлэн ужезлы разблокирование пользователь.\nЗаблокировать Автоматически IP-адрес уг возьма.\nПроизведениосыз печатласько эстониын [[Special:BlockList|сьӧд списокын]], бан список блокъёс лэсьтыны.",
        "block-log-flags-nousertalk": "тупатъяны ачиз уггес быгаты бамлэн обсуждениосаз",
        "range_block_disabled": "Администратор диапазонэз блокировать али.",
        "move-watch": "Чаклан списоке пыртоно инъет но валтӥсь бамъёсыз",
+       "movelogpage": "Нимъёсты воштонъёсын журнал",
        "export": "Бамъёсты поттон",
        "allmessagesname": "Ивортон",
        "allmessages-filter-all": "Ваньзэ",
        "tooltip-ca-addsection": "Выль люкет кылдытоно",
        "tooltip-ca-viewsource": "Та бам воштонъёслэсь утемын.\nТӥ быгатӥськоды инъет текстсэ учкыны но кӧчырыны",
        "tooltip-ca-history": "Бамлэн воштонъёсыныз журнал",
+       "tooltip-ca-move": "Та бамлэсь нимзэ воштыны",
        "tooltip-ca-watch": "Та бамез чаклан списокады пыртоно",
        "tooltip-search": "Утчано {{SITENAME}}",
        "tooltip-search-go": "Выжоно сыӵе ик нимын баме",
        "tooltip-t-whatlinkshere": "Ваньмыз бамъёс, кудъёсаз та бамлы линксы вань",
        "tooltip-t-recentchangeslinked": "Выль тупатонъёс бамъёсын, кудъёссэ та бам чӧлске",
        "tooltip-feed-atom": "Та бамлэн Atom-е трансляциез",
+       "tooltip-t-contributions": "{{GENDER:$1|Та викиавторен}} тупатэм бамъёслы список",
        "tooltip-t-upload": "Файл поныны",
        "tooltip-t-specialpages": "Специальной бамъёслэн списоксы",
        "tooltip-t-print": "Та бамысь печатламон версия",
        "tooltip-ca-nstab-main": "Валтӥсь бамез учконо",
        "tooltip-ca-nstab-user": "Викиавторлэн бамез",
        "tooltip-ca-nstab-special": "Та бам нимысьтыз, сое тупатон луонтэм",
+       "tooltip-ca-nstab-project": "Проектлэн бамез",
        "tooltip-ca-nstab-image": "Файллэн бамез",
        "tooltip-ca-nstab-template": "Шаблонлэн бамез",
        "tooltip-ca-nstab-category": "Категорилэн бамез",
        "simpleantispam-label": "Анти-спам эскерон.\n<strong>Эн</strong> гожтэ татчы!",
        "pageinfo-header-edits": "Воштонъёслэн историзы",
        "pageinfo-toolboxlink": "Бам сярысь тодэтъёс",
+       "previousdiff": "← Вужгес тупатон",
+       "nextdiff": "Выльгес тупатон →",
        "file-info-size": "$1 × $2 пиксель, файллэн быдӟалаез: $3, MIME-тип: $4",
        "file-nohires": "Бадӟымгес быдӟалаен суред ӧвӧл.",
        "svg-long-desc": "SVG файл, номинально $1 × $2 пиксель, файллэн быдӟалаез: $3",
        "metadata": "Метаданнойёс",
        "metadata-help": "Файл пушкын информация вань на, кудзэ лыдпусо камераос яке сканеръёс файлэз кылдытыку огшоры ватсалляло.\nКылдытон бере файл воштӥськиз ке, куд-огез параметръёс воштэм суредлы ярантэм луыны быгато.",
        "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-orientation": "Ориентация",
        "exif-xresolution": "Горизонтальной разрешение",
        "exif-yresolution": "Вертикальной разрешение",
        "exif-datetime": "Файлэз воштонлэн датаез но дырыз",
+       "exif-make": "Камера лэсьтӥсь",
        "exif-model": "Камералэн моделез",
        "exif-software": "Компьютер программаос",
        "exif-exifversion": "Exif версия",
        "exif-colorspace": "Буёлъёслэн пространствозы",
+       "exif-datetimeoriginal": "Нырысь дата но дыр",
+       "exif-datetimedigitized": "Цифраослы пӧрмытонлэн датаез но дырыз",
        "exif-disclaimer": "Кыл кутыны пумит луон",
+       "exif-orientation-1": "Огшоры",
        "namespacesall": "ваньзэ",
        "monthsall": "ваньзэ",
        "confirmrecreate-noreason": "{{GENDER:$1|Участник|Куакеч|}}&nbsp;[[User:$1|$1]] ([[User talk:$1|обс]]) {{GENDER:$1|палэнтыны|палэнтыны}} таиз бере бам, кызьы тон сое редактировать карыны кутскиз. Пожалуйста, подтвердите, мар тон малпаськод та бамез зэм но выльысь кылдозы.",
        "tags-title": "Меткаос",
        "logentry-delete-delete": "$1 {{GENDER:$2|палэнтыны|палэнтыны}} бам $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|выльысь}} бам $3",
+       "logentry-block-block": "$1 {{GENDER:$2|заблокировать}} {{GENDER:$4|$3}} ын дыраз $5 $6",
+       "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-move-move": "$1 $3 бамлы $4 выль ним {{GENDER:$2|сётӥз}}",
        "logentry-newusers-create": "$1 нимо учётной запись {{GENDER:$2|кылдытэмын}} вал",
+       "logentry-upload-upload": "$1 {{GENDER:$2|понӥз}} $3",
        "searchsuggest-search": "Утчано {{SITENAME}}",
        "searchsuggest-containing": "кудъёсаз вань...",
        "api-error-autoblocked": "Тон IP-адрес заблокировать эрказ луи, малы ке шуоно со заблокировать пользователь кутыны луоз.",
index 360adef..966f0c5 100644 (file)
        "toolbox": "Інструменти",
        "tool-link-userrights": "Змінити групи {{GENDER:$1|користувачів}}",
        "tool-link-userrights-readonly": "Перегляд груп {{GENDER:$1|користувача|користувачки}}",
-       "tool-link-emailuser": "Ð\9dадÑ\96Ñ\81лаÑ\82и ÐµÐ»ÐµÐºÑ\82Ñ\80онного Ð»Ð¸Ñ\81Ñ\82а {{GENDER:$1|Ñ\86Ñ\8cомÑ\83 ÐºÐ¾Ñ\80иÑ\81Ñ\82Ñ\83ваÑ\87евÑ\96\86Ñ\96й користувачці}}",
+       "tool-link-emailuser": "Ð\9bиÑ\81Ñ\82 {{GENDER:$1|коÑ\80иÑ\81Ñ\82Ñ\83ваÑ\87евÑ\96|користувачці}}",
        "userpage": "Переглянути сторінку користувача",
        "projectpage": "Переглянути сторінку проекту",
        "imagepage": "Переглянути сторінку файлу",
        "search-external": "Зовнішній пошук",
        "searchdisabled": "<p>Вибачте, повнотекстовий пошук тимчасово недоступний через перевантаження сервера; передбачається, що ця функція буде знову включена після установки нового обладнання. Поки що ми пропонуємо вам скористатися Google чи Yahoo!:</p>",
        "search-error": "Сталася помилка під час пошуку:$1",
+       "search-warning": "Попередження виникло під час пошуку: $1",
        "preferences": "Налаштування",
        "mypreferences": "Налаштування",
        "prefs-edits": "Кількість редагувань:",
        "userrights-user-editname": "Введіть ім'я користувача:",
        "editusergroup": "Завантажити групи користувачів",
        "editinguser": "Зміна прав {{GENDER:$1|користувача}} <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Змінити групи користувачів",
+       "viewinguserrights": "Перегляд прав {{GENDER:$1|користувача|користувачки}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Змінити групи {{GENDER:$1|користувача|користувачки}}",
+       "userrights-viewusergroup": "Перегляд груп користувача",
        "saveusergroups": "Зберегти групи {{GENDER:$1|користувачів}}",
        "userrights-groupsmember": "Член груп:",
        "userrights-groupsmember-auto": "Неявний член:",
        "action-upload_by_url": "завантаження цього файлу з адреси URL",
        "action-writeapi": "використання API для редагувань",
        "action-delete": "вилучення цієї сторінки",
-       "action-deleterevision": "вилучення цієї версії сторінки",
-       "action-deletedhistory": "перегляд вилученої історії редагувань цієї сторінки",
+       "action-deleterevision": "вилучення версій",
+       "action-deletelogentry": "вилучення записів журналу",
+       "action-deletedhistory": "перегляд вилученої історії редагувань сторінки",
+       "action-deletedtext": "перегляд тексту вилученої версії",
        "action-browsearchive": "пошук вилучених сторінок",
-       "action-undelete": "вÑ\96дновленнÑ\8f Ñ\86Ñ\96Ñ\94Ñ\97 Ñ\81Ñ\82оÑ\80Ñ\96нки",
-       "action-suppressrevision": "перегляд і відновлення цієї прихованої версії",
+       "action-undelete": "вÑ\96дновленнÑ\8f Ñ\81Ñ\82оÑ\80Ñ\96нок",
+       "action-suppressrevision": "перегляд і відновлення прихованих версій",
        "action-suppressionlog": "перегляд цього приватного журналу",
        "action-block": "блокування цього дописувача",
        "action-protect": "зміну рівня захисту цієї сторінки",
        "action-userrights-interwiki": "зміну прав користувачів у інших вікі",
        "action-siteadmin": "блокування і розблоковування баз даних",
        "action-sendemail": "відправка електронної пошти",
+       "action-editmyoptions": "редагування власних налаштувань",
        "action-editmywatchlist": "редагування Вашого списку спостереження",
        "action-viewmywatchlist": "перегляд власного списку спостереження",
        "action-viewmyprivateinfo": "перегляд своєї приватної інформації",
        "emailccsubject": "Копія вашого повідомлення до $1: $2",
        "emailsent": "Електронне повідомлення надіслано",
        "emailsenttext": "Ваше електронне повідомлення надіслано.",
-       "emailuserfooter": "Цей лист був надісланий {{GENDER:$2|користувачеві|користувачці}} $2 від {{GENDER:$1|користувача|користувачки}} $1 за допомогою функції «{{int:emailuser}}» проекту {{SITENAME}}.",
+       "emailuserfooter": "Цей лист був надісланий {{GENDER:$2|користувачеві|користувачці}} $2 від {{GENDER:$1|користувача|користувачки}} $1 за допомогою функції «{{int:emailuser}}» проекту {{SITENAME}}. {{GENDER:$2|Ваш}} електронний лист потрапить безпосередньо до {{GENDER:$1|початковго відправника|початкової відправниці}}, відкривши {{GENDER:$1|йому|їй}} {{GENDER:$2|Вашу}} електронну адресу.",
        "usermessage-summary": "Залишити системне повідомлення.",
        "usermessage-editor": "Системний вісник",
        "usermessage-template": "MediaWiki:UserMessage",
        "mw-widgets-dateinput-no-date": "Дати не вибрано",
        "mw-widgets-dateinput-placeholder-day": "РРРР-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "РРРР-ММ",
+       "mw-widgets-mediasearch-input-placeholder": "Пошук мультимедіа",
+       "mw-widgets-mediasearch-noresults": "Нічого не знайдено.",
        "mw-widgets-titleinput-description-new-page": "сторінка ще не існує",
        "mw-widgets-titleinput-description-redirect": "перенаправлення на $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Додати категорію...",
        "usercssispublic": "Будь ласка, зверніть увагу: підсторінки CSS не повинні містити конфіденційних даних, бо їх можуть бачити інші користувачі.",
        "restrictionsfield-badip": "Недійсна IP-адреса або діапазон: $1",
        "restrictionsfield-label": "Дозволені діапазони IP-адрес:",
-       "restrictionsfield-help": "Одна IP-адреса або CIDR-діапазон на рядок. Щоб увімкнути все, використайте<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "Одна IP-адреса або CIDR-діапазон на рядок. Щоб увімкнути все, використайте<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "ID сторінки $1"
 }
index e613b7f..d4ff264 100644 (file)
        "search-external": "Tìm kiếm từ bên ngoài",
        "searchdisabled": "Chức năng tìm kiếm tại {{SITENAME}} đã bị tắt. Bạn có tìm kiếm bằng Google trong thời gian này. Chú ý rằng các chỉ mục từ {{SITENAME}} của chúng có thể đã lỗi thời.",
        "search-error": "Đã xuất hiện lỗi khi tìm kiếm: $1",
+       "search-warning": "Đã xuất hiện lỗi khi tìm kiếm: $1",
        "preferences": "Tùy chọn",
        "mypreferences": "Tùy chọn",
        "prefs-edits": "Số lần sửa đổi:",
        "mw-widgets-dateinput-no-date": "Không chọn ngày tháng",
        "mw-widgets-dateinput-placeholder-day": "YYYY-MM-DD (năm-tháng-ngày)",
        "mw-widgets-dateinput-placeholder-month": "YYYY-MM (năm-tháng)",
+       "mw-widgets-mediasearch-input-placeholder": "Tìm phương tiện",
+       "mw-widgets-mediasearch-noresults": "Không tìm thấy kết quả.",
        "mw-widgets-titleinput-description-new-page": "trang này chưa tồn tại",
        "mw-widgets-titleinput-description-redirect": "đổi hướng đến $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Thêm thể loại…",
index 5197c00..5f342a8 100644 (file)
@@ -50,7 +50,7 @@
        "tog-enotifminoredits": "頁搭文件細編也用電子信通知我",
        "tog-enotifrevealaddr": "電子信通知單裏顯示我個電子信地址",
        "tog-shownumberswatching": "顯示關注人數",
-       "tog-oldsig": "原生个签名:",
+       "tog-oldsig": "原生个签名:",
        "tog-fancysig": "拿签名当成维基文本(弗自动链接)",
        "tog-uselivepreview": "使用实时预览",
        "tog-forceeditsummary": "朆写编辑摘要个辰光提醒我",
        "newwindow": "(用新窗口开)",
        "cancel": "取消",
        "moredotdotdot": "還多...",
-       "morenotlisted": "ç®\87張表é\82\84æ\9c\86å®\8cæ\88\90。",
+       "morenotlisted": "ç®\87å¼ æ¸\85å\8d\95ä½\9cå\85´å¼\97å®\8cæ\95´。",
        "mypage": "页面",
        "mytalk": "讲张",
        "anontalk": "讲张",
        "botpasswords-label-create": "建立",
        "botpasswords-label-update": "更新",
        "resetpass_forbidden": "密码弗好更改",
+       "resetpass_forbidden-reason": "密码弗好更改:$1",
        "resetpass-no-info": "侬必须登录著才好直接进入箇只页面。",
        "resetpass-submit-loggedin": "更改密码",
        "resetpass-submit-cancel": "取消",
        "resetpass-expired": "侬个密码到期哉。请设置新个登录密码。",
        "passwordreset": "重置密码",
        "passwordreset-username": "用戶名",
-       "passwordreset-capture": "啊要看生成个电子邮件?",
        "passwordreset-email": "电子邮件地址:",
        "passwordreset-emailtitle": "{{SITENAME}}上个账号详细信息",
        "passwordreset-emailelement": "用户名:\n$1\n\n临时密码:\n$2",
        "minoredit": "箇是小变化",
        "watchthis": "关注箇页",
        "savearticle": "保存页面",
+       "savechanges": "保存改动",
+       "publishpage": "发布页面",
+       "publishchanges": "发布改动",
        "preview": "望望相",
        "showpreview": "显示预览",
        "showdiff": "显示变化",
        "searchprofile-advanced-tooltip": "垃拉自定义名字空间里向搜索",
        "search-result-size": "$1($2个字)",
        "search-result-category-size": "$1个成员($2个儿分类,$3个文件)",
-       "search-redirect": "(重定向 $1)",
+       "search-redirect": "(从$1重定向过来)",
        "search-section": "(段落 $1)",
        "search-category": "(分类$1)",
        "search-file-match": "(匹配文件内容)",
        "recentchangescount": "默认显示个编辑数:",
        "prefs-help-recentchangescount": "迭个包括近段辰光个改动、页面历史搭著日志。",
        "savedprefs": "倷个偏好已经保存哉。",
-       "savedrights": "{{GENDER:$1|$1}}个用户权限已经畀保存。",
+       "savedrights": "{{GENDER:$1|$1}}个用户组已经保存哉。",
        "timezonelegend": "时区:",
        "localtime": "当地辰光:",
        "timezoneuseserverdefault": "使用wiki默认值($1)",
        "prefs-advancedwatchlist": "高级选项",
        "prefs-tabs-navigation-hint": "提示:侬可以用左、右箭头键来选项卡之间切换。",
        "userrights-user-editname": "输入用户名:",
-       "editusergroup": "编辑{{GENDER:$1|用户}}组",
+       "editusergroup": "加载用户组",
        "editinguser": "改动{{GENDER:$1|用户}}<strong>[[User:$1|$1]]</strong>个用户权限$2",
+       "userrights-editusergroup": "编辑用户组",
+       "userrights-viewusergroup": "望用户组",
+       "saveusergroups": "保存{{GENDER:$1|用户}}组",
+       "userrights-reason": "理由:",
        "group-bot": "机器人",
        "group-sysop": "管理员",
        "group-bureaucrat": "行政员",
        "action-reupload": "箇文件以舊換新",
        "action-upload_by_url": "從URL傳文件",
        "action-delete": "刪箇頁",
-       "action-deleterevision": "å\88ªç®\87ç\89\88æ\9c¬",
-       "action-deletedhistory": "望箇页删脱个历史",
+       "action-deleterevision": "å\88 è\84±ä¿®è®¢",
+       "action-deletedhistory": "望页面删脱个历史",
        "action-browsearchive": "尋已刪頁",
-       "action-undelete": "弗刪箇頁",
+       "action-undelete": "还原页面",
        "action-patrol": "拿别人家个编辑标记成已巡查",
        "action-userrights": "編全部用戶權",
        "action-userrights-interwiki": "編用戶徠各許維基個權",
        "brokenredirects-delete": "删除",
        "withoutinterwiki": "嘸語言鏈接個頁面",
        "withoutinterwiki-summary": "下向許頁面朆鏈接到別樣語言版本。",
+       "fewestrevisions": "版本顶少个页面",
        "nbytes": "$1字节",
        "nmembers": "$1只成员",
        "unusedimages": "朆用着个文件",
        "prefixindex": "全部带前缀个页面",
        "shortpages": "短页面",
        "longpages": "长页面",
-       "protectedpages": "受保护页面",
-       "protectedtitles": "保护个标题",
+       "protectedpages": "畀保护个页面",
+       "protectedtitles": "保护个标题",
        "listusers": "用户列表",
        "listusers-creationsort": "照建個日子排",
        "newpages": "新页",
        "ipboptions": "2个钟头:2 hours,1天:1 day,3天:3 days,1个礼拜:1 week,2个礼拜:2 weeks,1个号头:1 month,3个号头:3 months,6个号头:6 months,1年:1 year,老世:infinite",
        "badipaddress": "无效 IP 地址",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]]已经畀查封。<br />\n参看[[Special:BlockList|查封列表]]来复审查封。",
+       "blocklist": "封脱个用户",
        "ipblocklist": "封脱个用户",
        "infiniteblock": "永远",
        "blocklist-nousertalk": "弗准编辑自家个讨论页",
        "interlanguage-link-title": "̩$1 - $2",
        "anonymous": "{{SITENAME}}上个匿名{{PLURAL:$1|用户}}",
        "simpleantispam-label": "反垃圾检查。<strong>弗要</strong>填伊个!",
+       "pageinfo-title": "“$1”个信息",
+       "pageinfo-display-title": "显示题目头",
+       "pageinfo-language": "页面内容闲话",
+       "pageinfo-redirects-name": "指向箇页个重定向数目",
+       "pageinfo-firstuser": "页面建立者",
+       "pageinfo-firsttime": "页面建立日脚",
+       "pageinfo-lastuser": "阿末位编辑者",
+       "pageinfo-lasttime": "阿末趟编辑日脚",
+       "pageinfo-recent-authors": "最近作者数",
        "pageinfo-toolboxlink": "页面信息",
+       "pageinfo-contentpage": "算成内容页面",
        "deletedrevision": "拨删脱个旧修订 $1",
        "previousdiff": "←老版",
        "nextdiff": "新版→",
        "rightsnone": "(呒)",
        "revdelete-summary": "编辑摘要",
        "feedback-thanks-title": "谢谢侬!",
-       "searchsuggest-search": "搜寻",
+       "searchsuggest-search": "搜寻{{SITENAME}}",
        "pagelang-language": "闲话"
 }
index 8a9f6f1..3784cc1 100644 (file)
@@ -46,7 +46,7 @@
        "tog-enotifminoredits": "שיקט מיר ע-פאסט אויך פֿאַר מינערדיקע רעדאַקטירונגען פֿון בלעטער",
        "tog-enotifrevealaddr": "דעק אויף מיין בליצפאסט אדרעס אין פאסט מודעות",
        "tog-shownumberswatching": "ווייזן דעם נומער פון בלאט אויפֿפאסערס",
-       "tog-oldsig": "איצטיגער אונטערשריפֿט:",
+       "tog-oldsig": "×\90×\99×\99ער ×\90×\99צ×\98×\99×\92ער ×\90×\95× ×\98ערשר×\99פֿ×\98:",
        "tog-fancysig": "באַהאַנדלן  אונטערשריפט אַלס וויקיטעקסט (אָן אויטאמאטישן לינק)",
        "tog-uselivepreview": "באניצן זיך מיט גיכער פאראויסדיגער ווייזונג",
        "tog-forceeditsummary": "ווארן מיך ווען איך לייג א ליידיג קורץ ווארט ענדערונג",
@@ -62,7 +62,7 @@
        "tog-showhiddencats": "ווײַזן באהאלטענע קאטעגאריעס",
        "tog-norollbackdiff": "נישט ווײַזן אונטערשייד נאכן אויספֿירן א צוריקדריי",
        "tog-useeditwarning": "שטעלן א ווארענונג ווען איך לאז איבער א רעדאקטירונג בלאט מיט נישט אויפגעהיטענע ענדערונגען",
-       "tog-prefershttps": "ניצט שטענדיק א זיכערע פארבינדונג ווען ארײַנגלאגירט",
+       "tog-prefershttps": "ניצט שטענדיק א זיכערע פארבינדונג ווען ארײנגלאגירט",
        "underline-always": "אייביג",
        "underline-never": "קיינמאל",
        "underline-default": "בלעטערער גרונטשטעלונג",
        "newwindow": "(עפֿנט זיך אין א נײַעם פענסטער)",
        "cancel": "אַנולירן",
        "moredotdotdot": "נאך…",
-       "morenotlisted": "די ליסטע איז נישט פֿולשטענדיק.",
+       "morenotlisted": "×\93×\99 ×\9c×\99ס×\98×¢ ×\90×\99×\96 ×\90פשר × ×\99ש×\98 ×¤Ö¿×\95×\9cש×\98×¢× ×\93×\99ק.",
        "mypage": "מיין בלאַט",
        "mytalk": "שמועס",
        "anontalk": "שמועס",
        "createacct-yourpasswordagain-ph": "ארײַנגעבן פאסווארט נאכאמאל",
        "userlogin-remembermypassword": "לאז מיך בלײַבן ארײַנלאגירט",
        "userlogin-signwithsecure": "ניצן זיכערן סארווער",
+       "cannotlogin-title": "קען נישט אריינלאגירן",
        "cannotloginnow-title": "קען נישט אריינלאגירן אצינד",
        "cannotloginnow-text": "אריינלאגירן נישט מעגלעך ווען מען ניצט $1.",
        "yourdomainname": "אײַער געביט:",
        "eauthentsent": "א באשטעטיגונג ע-בריוו איז געשיקט געווארן צו דעם באשטימטן ע-פאסט אדרעס. איידער סיי וועלכע אנדערע ע-פאסט וועט ווערן געשיקט צו דער קאנטע, וועט איר דארפן פאלגן די אנווייזונגען אין דער מעלדונג כדי צו זיין זיכער אז די קאנטע איז טאקע אייערס.",
        "throttled-mailpassword": "מ'האט שוין געשיקט א בליצבריוו צוריקצושטעלן דאס פאסווארט, אין {{PLURAL:$1|דער לעצטער שעה|די לעצטע $1 שעה'ן}}. כדי צו פארמײַדן שלעכט באניצן, נאר איין פאסווארט צוריקשטעלן בליצבריוו וועט געשיקט ווערן אין {{PLURAL:$1|א שעה |$1 שעה'ן}}.",
        "mailerror": "פעלער ביים שיקן פּאָסט: $1",
-       "acct_creation_throttle_hit": "באַזוכער צו דער וויקי וואס באַניצן אייער IP אַדרעס האָבן שױן באַשאַפֿן {{PLURAL:$1|1 קאנטע|$1 קאנטעס}} במשך דעם לעצטן טאָג, דעם מאַקסימום וואָס מען ערלויבט אין דעם פעריאד.\n\nדערפֿאַר קענען באַזוכער וואס באַניצן דעם  IP אַדרעס נישט מער שאַפֿן נײַע קאָנטעס דערווײַל.",
+       "acct_creation_throttle_hit": "באַזוכער צו דער וויקי וואס באַניצן אייער IP אַדרעס האָבן שױן באַשאַפֿן {{PLURAL:$1|1 קאנטע|$1 קאנטעס}} במשך דעם לעצטן $2, דעם מאַקסימום וואָס מען ערלויבט אין דעם צייט פעריאד.\n\nדערפֿאַר קענען באַזוכער וואס באַניצן דעם  IP אַדרעס נישט מער שאַפֿן נײַע קאָנטעס דערווײַל.",
        "emailauthenticated": "אייער ע-פאסט אדרעס איז באשטעטיגט געווארן אום $2, $3.",
        "emailnotauthenticated": "אײַער ע-פאסט אדרעס איז נאכנישט באשטעטיגט. \nקיין ע-פאסט וועט נישט ווערן געשיקט פון קיין איינע פון די פאלגנדע אייגנקייטן.",
        "noemailprefs": "ספעציפֿירט אן ע־פאסט אַדרעס אין אײַער פרעפֿערענצן כדי די פֿעאיקייטן זאלן אַרבעטן.",
        "passwordreset-emaildisabled": "ע-פאסט דינסטן זענען געווארן אומאקטיווירט אויף דער דאזיקער וויקי.",
        "passwordreset-username": "באַניצער נאָמען:",
        "passwordreset-domain": "דאמען:",
-       "passwordreset-capture": "זען  דעם געשיקטן ע־בריוו?",
-       "passwordreset-capture-help": "אַז איר צייכנט דאס קעסטל, וועט מען ווײַזן דעם ע־בריוו (מיטן פראוויזארישן פאַסווארט) צו אײַך ווי אויך ווערן געשיקט צום באַניצער.",
        "passwordreset-email": "בליצפּאָסט אַדרעס:",
        "passwordreset-emailtitle": "קאנטע פרטים אין {{SITENAME}}",
        "passwordreset-emailtext-ip": "עמעצער (מסתמא איר, פון IP אדרעס $1) האט געבעטן צוריקצושטעלן אייער פאסווארט פאר {{SITENAME}} ($4). די פאלגנדע באניצער {{PLURAL:$3|קאנטע איז|קאנטעס זענען}}\nפארבונדן מיט דעם ע־פאסט אדרעס:\n\n$2\n\n{{PLURAL:$3|דאס פראוויזארישע פאסווארט|די פראוויזארישע פאסווערטער}} וועלן אויסגיין נאך {{PLURAL:$5|איין טאג|$5 טעג}}.\nאיר זאלט אריינלאגירן און קלויבן א נייע פאסווארט אצינד. טאמער א צווייטער האט געשיקט די בקשה,\nאדער ווען איר געדענקט יא אייער פריעריקע פאסווארט, און וויל עס נישט ענדערן,\n קענט איר איגנארירן דעם אנזאג און ניצן ווייטער דאס אלטע פאסווארט.",
        "searchprofile-advanced-tooltip": "זוכן אין צוגעשטעלטע ָנאָמענטיילן",
        "search-result-size": "$1 ({{PLURAL:$2|איין ווארט|$2 ווערטער}})",
        "search-result-category-size": "{{PLURAL:$1|1 מיטגליד|$1 מיטגלידער}} ({{PLURAL:$2|1 אונטער־קאַטעגאריע|$2 אונטער־קאַטעגאריעס}}, {{PLURAL:$3|1 טעקע|$3 טעקעס}})",
-       "search-redirect": "(ווײַטערפֿירן $1)",
+       "search-redirect": "(×\95×\95ײַ×\98ערפֿ×\99ר×\95× ×\92 ×¤Ö¿×\95×\9f $1)",
        "search-section": "(אפטיילונג $1)",
        "search-category": "(קאטעגאריע $1)",
        "search-file-match": "(פאסט צו טעקע אינהאלט)",
        "prefs-help-recentchangescount": "כולל לעצטע ענדערונגען, בלאַט היסטאָריעס, און לאָגביכער.",
        "prefs-help-watchlist-token2": "דאס איז דער געהיימער שליסל צום וועבפֿיד פון אײַער אויפֿפאסונג ליסטע.\nיעדער וואס ווייסט אים וועט קענען לייענען אײַער אויפֿפאסונג ליסטע; טוט אים נישט טיילן.\n[[Special:ResetTokens|קליקט דא ווען איר דארפט אים צוריקשטעלן]].",
        "savedprefs": "אייערע פרעפערענצן איז אפגעהיטן געווארן.",
-       "savedrights": "×\93×\99 ×\91×\90× ×\99צער־רע×\9b×\98×\9f פֿון {{GENDER:$1|$1}} זענען געווארן געשפייכלערט.",
+       "savedrights": "×\93×\99 ×\91×\90× ×\99צער־×\92ר×\95פעס פֿון {{GENDER:$1|$1}} זענען געווארן געשפייכלערט.",
        "timezonelegend": "צײַט זאנע:",
        "localtime": "לאקאלע צייט:",
        "timezoneuseserverdefault": "ניצן סערווירער גרונט ($1)",
        "prefswarning-warning": "איר האט געמאכט ענדערונגען צו אײַערע פרעפערענצן וואס זענען נאך נישט אויפגעהיטן.\nאז איר פארלאזט דעם בלאט אן קליקן ״$1״ וועלן אײַערע פרעפערענצן נישט ווערן דערהײַנטיקט.",
        "prefs-tabs-navigation-hint": "טיפ: איר קענט ניצן די רעכטס און לינקס פייל־קלאווישן צו נאוויגירן צווישן די צינגלעך אין דער צינגלעך־ליסטע.",
        "userrights": "באַניצער רעכטן פֿאַרוואַלטערשאפט",
-       "userrights-lookup-user": "פֿ×\90ַר×\95×\95×\90Ö·×\9c×\98×\9f ×\91×\90× ×\99צער ×\92ר×\95פעס",
+       "userrights-lookup-user": "×\90×\95×\99ס×\95×\95×\99×\99×\9c×\9f ×\90 ×\91×\90× ×\99צער",
        "userrights-user-editname": "לייגט אריין א באַניצער-נאמען:",
-       "editusergroup": "רע×\93×\90Ö·×\92×\99ר×\9f {{GENDER:$1|×\91×\90Ö·× ×\99צער}} גרופּעס",
+       "editusergroup": "×\9c×\90×\93×\9f ×\91×\90Ö·× ×\99צער גרופּעס",
        "editinguser": "ענדערן באַניצער רעכטן פון  {{GENDER:$1|באַניצער|באַניצערין}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "רעדאַקטירן באַניצער גרופעס",
        "saveusergroups": "אויפֿהיטן {{GENDER:$1|באַניצער}} גרופעס",
        "userrights-reason": "אורזאַך:",
        "userrights-no-interwiki": "איר האט נישט קיין ערלויבניש צו רעדאַקטירן באַניצער רעכטן אויף אַנדערע וויקיס.",
        "userrights-nodatabase": "דאַטנבאַזע $1 אדער עקזיסטירט נישט אדער איז נישט ארטיק.",
-       "userrights-nologin": "איר דאַרפֿט [[Special:UserLogin| אַרײַנלאגירן]] מיט א סיסאפ קאנטע צו באַשטימען באַניצער רעכטן.",
-       "userrights-notallowed": "איר האט נישט קיין ערלויבניש צוצולייגן אדער אוועקנעמען באַניצער רעכטן.",
        "userrights-changeable-col": "גרופעס איר קענט ענדערן",
        "userrights-unchangeable-col": "גרופעס איר קענט נישט ענדערן",
        "userrights-conflict": "קאנפֿליקט פון באניצער־רעכטן ענדערונגען! זייט אזוי גוט רעצענזירן און באשטעטיקן אײַערע ענדערונגען.",
-       "userrights-removed-self": "איר האט אראפגענומען אייערע אייגענע רעכטן. אזוי קענט איר מער נישט דערגרייכן דעם בלאט.",
        "group": "גרופע:",
        "group-user": "באניצערס",
        "group-autoconfirmed": "באַשטעטיקטע באַניצער",
        "right-siteadmin": "פארשליס און שליס-אויף די דאטעבאזע",
        "right-override-export-depth": "עקספארטירן בלעטער כולל געלינקטע בלעטער ביז א טיף פון 5",
        "right-sendemail": "שיקן ע-פאסט צו אנדערע באניצער",
-       "right-passwordreset": "באַקוקן פאַסווארט צוריקשטעלן ע־בריוו",
        "right-managechangetags": "שאפן און (אומ)אקטיווירן [[Special:Tags|טאגן]]",
        "right-applychangetags": "אנווענדן [[Special:Tags|טאגן]] צוזאמען מיט ענדערונגען",
        "grant-generic": "\"$1\" רעכטן־בינטל",
        "action-upload_by_url": "ארויפֿלאָדן די טעקע פֿון א URL",
        "action-writeapi": "ניצן דעם שרײַבן API",
        "action-delete": "אויסמעקן דעם בלאַט",
-       "action-deleterevision": "אויסמעקן די רעוויזיע",
-       "action-deletedhistory": "באַקוקן דעם בלאט'ס אויסגעמעקטע היסטאריע",
+       "action-deleterevision": "אויסמעקן רעוויזיעס",
+       "action-deletedhistory": "באַקוקן א בלאט'ס אויסגעמעקטע היסטאריע",
+       "action-deletedtext": "באקוקן אויסגעמעקטן רעוויזיע טעקסט",
        "action-browsearchive": "זוכן אויסגעמעקטע בלעטער",
-       "action-undelete": "צ×\95ר×\99קש×\98×¢×\9c×\9f ×\93×¢×\9d ×\91×\9c×\90×\98",
-       "action-suppressrevision": "×\90×\99×\91ער×\92×\99×\99×\9f ×\90×\95×\9f ×¦×\95ר×\99קש×\98×¢×\9c×\9f ×\93×\99 ×¤Ö¿×\90ַר×\91×\90ר×\92×\98×¢ ×¨×¢×\95×\95×\99×\96×\99×¢",
+       "action-undelete": "צ×\95ר×\99קש×\98×¢×\9c×\9f ×\91×\9c×¢×\98ער",
+       "action-suppressrevision": "×\90×\99×\91ער×\92×\99×\99×\9f ×\90×\95×\9f ×¦×\95ר×\99קש×\98×¢×\9c×\9f ×¤Ö¿×\90ַר×\91×\90ר×\92×\98×¢ ×¨×¢×\95×\95×\99×\96×\99עס",
        "action-suppressionlog": "באקוקן דעם פריוואטן לאג",
        "action-block": "בלאקירן דעם באַניצער פֿון רעדאַקטירן",
        "action-protect": "ענדערן שיצונג ניוואען פֿאַר דעם בלאַט",
        "action-userrights-interwiki": "רעדאַקטירן רעכטן פון באַניצער אויף אַנדערע וויקיס",
        "action-siteadmin": "שליסן אדער אויפשליסן די דאטנבאזע",
        "action-sendemail": "שיקן ע־פאסט",
+       "action-editmyoptions": "רעדאקטירן אײַערע פרעפערענצן",
        "action-editmywatchlist": "רעדאקטירן אײַער אויפֿפאסונג ליסטע",
        "action-viewmywatchlist": "באקוקן אײַער אויפֿפאסונג ליסטע",
        "action-viewmyprivateinfo": "באקוקן אײַער פריוואטע אינפארמאציע",
        "upload-copy-upload-invalid-domain": "ארויפלאדן טעקעס פון דעם דאמיין נישט מעגלעך.",
        "upload-dialog-title": "אַרױפֿלאָדן טעקע",
        "upload-dialog-button-cancel": "אַנולירן",
+       "upload-dialog-button-back": "צוריק",
        "upload-dialog-button-done": "ערליידיקט",
        "upload-dialog-button-save": "אויפֿהיטן",
        "upload-dialog-button-upload": "אַרויפֿלאָדן",
        "feedback-subject": "טעמע:",
        "feedback-submit": "אײַנגעבן",
        "feedback-thanks": "ייש\"כ! אײַער פֿידבעק איז געווארן ארויפגעלעגט צום בלאט \"[$2 $1]\".",
-       "searchsuggest-search": "×\96×\95×\9a",
+       "searchsuggest-search": "×\96×\95×\9b×\9f {{SITENAME}}",
        "searchsuggest-containing": "כולל…",
        "api-error-badaccess-groups": "איר האט נישט קיין רעכטן אַרויפֿלאָדן טעקעס אויף דער וויקי.",
        "api-error-badtoken": "אינערלעכער גרײַז: סימן טויג נישט.",
index d17268d..514adbe 100644 (file)
        "nonunicodebrowser": "<strong>警告:您的浏览器不兼容Unicode编码。</strong>这里有一个工作区将使您能安全地编辑页面:非ASCII字符将以十六进制编码方式出现在编辑框中。",
        "editingold": "<strong>警告:您正在编辑的是本页面的旧版本。</strong>如果您保存该编辑,该版本后的所有更改都会丢失。",
        "yourdiff": "差异",
-       "copyrightwarning": "请注意您对{{SITENAME}}的所有贡献都被认为是在$2下发布,请查看在$1的细节。\n如果您不希望您的文字被任意修改和再散布,请不要提交。<br />\n您同时也要向我们保证您所提交的内容是您自己所作,或得自一个不受版权保护或相似自由的来源。\n'''不要在未获授权的情况下发表!'''<br />",
+       "copyrightwarning": "请注意您对{{SITENAME}}的所有贡献都被认为是在$2下发布,请查看在$1的细节。如果您不希望您的文字被任意修改和再散布,请不要提交。<br />\n您同时也要向我们保证您所提交的内容是您自己所作,或得自一个不受版权保护或相似自由的来源。<strong>不要在未获授权的情况下发表!</strong>",
        "copyrightwarning2": "请注意,您对{{SITENAME}}的所有贡献都可能被其他贡献者编辑,修改或删除。如果您不希望您的文字被任意修改和再散布,请不要提交。<br />\n您同时也要向我们保证您所提交的内容是您自己所作,或得自一个不受版权保护或相似自由的来源(参阅$1的细节)。<strong>不要在未获授权的情况下发表!</strong>",
        "editpage-cannot-use-custom-model": "此页面的内容模型不能被更改。",
        "longpageerror": "<strong>错误:您所提交的文本长度有{{PLURAL:$1|1|$1}}KB,这大于{{PLURAL:$2|1|$2}}KB的最大值。</strong>\n因此,该文本无法保存。",
        "search-external": "外部搜索",
        "searchdisabled": "{{SITENAME}}的搜索已被禁用。您可以暂时使用搜索引擎进行搜索,须注意其索引的{{SITENAME}}内容可能不是最新的。",
        "search-error": "搜索时发生错误:$1",
+       "search-warning": "搜索时发生警告:$1",
        "preferences": "设置",
        "mypreferences": "参数设置",
        "prefs-edits": "编辑数:",
        "userrights-user-editname": "输入用户名:",
        "editusergroup": "加载用户组",
        "editinguser": "更改{{GENDER:$1|用户}}<strong>[[User:$1|$1]]</strong>的用户权限$2",
+       "viewinguserrights": "查看{{GENDER:$1|用户}}<strong>[[User:$1|$1]]</strong>的用户权限$2",
        "userrights-editusergroup": "编辑用户组",
+       "userrights-viewusergroup": "查看用户组",
        "saveusergroups": "保存{{GENDER:$1|用户}}组",
        "userrights-groupsmember": "用户组:",
        "userrights-groupsmember-auto": "自动用户组:",
        "action-upload_by_url": "从URL上传本文件",
        "action-writeapi": "使用写入API",
        "action-delete": "删除本页",
-       "action-deleterevision": "删除本版本",
-       "action-deletedhistory": "查看本页面被删除的历史",
+       "action-deleterevision": "删除修订",
+       "action-deletelogentry": "删除日志记录",
+       "action-deletedhistory": "查看页面被删除的历史",
+       "action-deletedtext": "查看已删除的修订版本文字",
        "action-browsearchive": "搜索已被删除的页面",
-       "action-undelete": "还原本页",
-       "action-suppressrevision": "复核并还原该隐藏版本",
+       "action-undelete": "还原页面",
+       "action-suppressrevision": "复核并还原隐藏修订版本",
        "action-suppressionlog": "查看本非公开日志",
        "action-block": "阻止该用户编辑",
        "action-protect": "更改本页面的保护级别",
        "action-userrights-interwiki": "编辑其他wiki用户的用户权限",
        "action-siteadmin": "锁定或解锁数据库",
        "action-sendemail": "发送电子邮件",
+       "action-editmyoptions": "编辑您的参数设置",
        "action-editmywatchlist": "编辑您的监视列表",
        "action-viewmywatchlist": "查看您的监视列表",
        "action-viewmyprivateinfo": "查看您的私人信息",
        "emailccsubject": "您发送给$1的消息的副本:$2",
        "emailsent": "电子邮件已发送",
        "emailsenttext": "您的电子邮件已经发出。",
-       "emailuserfooter": "本电子邮件是通过{{SITENAME}}的“{{int:emailuser}}”功能被$1{{GENDER:$1|发送}}至{{GENDER:$2|$2}}的。",
+       "emailuserfooter": "本电子邮件是通过{{SITENAME}}的“{{int:emailuser}}”功能被$1{{GENDER:$1|发送}}至{{GENDER:$2|$2}}的。{{GENDER:$2|您}}的电子邮件将直接发送至{{GENDER:$1|原始发送者}},并向{{GENDER:$1|其}}显示{{GENDER:$2|您}}的电子邮件地址。",
        "usermessage-summary": "留下系统消息。",
        "usermessage-editor": "系统信息编辑器",
        "watchlist": "监视列表",
        "wlshowhidemine": "我的编辑",
        "wlshowhidecategorization": "页面分类",
        "watchlist-options": "监视列表选项",
+       "watchlist-mark-all-visited": "您确定要通过标记所有页面为已访问,重置未查看的监视列表更改么?",
        "watching": "正在监视...",
        "unwatching": "正在取消监视...",
        "watcherrortext": "更改“$1”的监视列表设置时出错。",
        "confirm-rollback-button": "确定",
        "confirm-rollback-top": "回退此页面的编辑么?",
        "semicolon-separator": ";",
-       "comma-separator": "",
+       "comma-separator": "",
        "colon-separator": ":",
        "pipe-separator": "&#32;|&#32;",
        "word-separator": "",
        "special-characters-title-emdash": "长划线",
        "special-characters-title-minus": "减号",
        "mw-widgets-dateinput-no-date": "没有选定日期",
+       "mw-widgets-mediasearch-input-placeholder": "搜索媒体",
+       "mw-widgets-mediasearch-noresults": "找不到结果。",
        "mw-widgets-titleinput-description-new-page": "页面不存在",
        "mw-widgets-titleinput-description-redirect": "重定向至$1",
        "mw-widgets-categoryselector-add-category-placeholder": "添加分类...",
        "usercssispublic": "请注意:CSS子页面不应包含机密数据,因为它们可以被其他用户查看。",
        "restrictionsfield-badip": "无效的IP地址或段:$1",
        "restrictionsfield-label": "允许的IP段:",
-       "restrictionsfield-help": "每行一个IP地址或CIDR段。要启用所有,可使用<br><code>0.0.0.0/0</code><br><code>::/0</code>"
+       "restrictionsfield-help": "每行一个IP地址或CIDR段。要启用所有,可使用<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "pageid": "页面ID$1"
 }
index dc85e91..3b772c6 100644 (file)
@@ -76,7 +76,8 @@
                        "Cosine02",
                        "一個正常人",
                        "Wehwei",
-                       "1233thehongkonger"
+                       "1233thehongkonger",
+                       "Maskers"
                ]
        },
        "tog-underline": "底線標示連結:",
        "views": "檢視",
        "toolbox": "工具",
        "tool-link-userrights": "更改{{GENDER:$1|使用者}}群組",
+       "tool-link-userrights-readonly": "檢視{{GENDER:$1|使用者}}群組",
        "tool-link-emailuser": "寄信給這位{{GENDER:$1|使用者}}",
        "userpage": "檢視使用者頁面",
        "projectpage": "檢視專案頁面",
        "search-external": "外部搜尋",
        "searchdisabled": "{{SITENAME}} 已停用搜尋功能。\n您可以改透過 Google 搜尋。\n請注意,在 Google 中搜尋到的 {{SITENAME}} 頁面內容可能不是最新的。",
        "search-error": "搜尋時發生錯誤:$1",
+       "search-warning": "搜尋時發生錯誤:$1",
        "preferences": "偏好設定",
        "mypreferences": "偏好設定",
        "prefs-edits": "編輯次數:",
        "prefswarning-warning": "您對您的偏好設定所做的變更尚未儲存。\n若您未點選 \"$1\" 離開此頁面,將不會更新您的偏好設定。",
        "prefs-tabs-navigation-hint": "提示:您可使用左、右方向鍵切換頁籤。",
        "userrights": "使用者權限管理",
-       "userrights-lookup-user": "管理使用者群組",
+       "userrights-lookup-user": "選擇用戶",
        "userrights-user-editname": "請輸入使用者名稱:",
-       "editusergroup": "編輯{{GENDER:$1|使用者}}群組",
+       "editusergroup": "加載用戶組",
        "editinguser": "變更{{GENDER:$1|使用者}} <strong>[[User:$1|$1]]</strong> 的使用者權限 $2",
        "userrights-editusergroup": "編輯使用者群組",
+       "userrights-viewusergroup": "檢視使用者群組",
        "saveusergroups": "儲存{{GENDER:$1|使用者}}群組",
        "userrights-groupsmember": "所屬群組:",
        "userrights-groupsmember-auto": "所屬隱含群組:",
        "action-upload_by_url": "使用 URL 上傳此檔案",
        "action-writeapi": "使用寫入 API",
        "action-delete": "刪除此頁面",
-       "action-deleterevision": "刪除修訂",
-       "action-deletedhistory": "檢視頁面的刪除歷史",
+       "action-deleterevision": "刪除修訂",
+       "action-deletedhistory": "檢視頁面的刪除歷史",
        "action-browsearchive": "搜尋已刪除頁面",
-       "action-undelete": "取消刪除頁面",
-       "action-suppressrevision": "檢閱與還原隱藏修訂",
+       "action-undelete": "取消刪除頁面",
+       "action-suppressrevision": "檢閱與還原隱藏修訂",
        "action-suppressionlog": "檢視此非公開日誌",
        "action-block": "封鎖此使用者的編輯權限",
        "action-protect": "變更此頁面的保護層級",
        "markaspatrolleddiff": "標記為已巡查",
        "markaspatrolledtext": "標記此頁面為已巡查",
        "markaspatrolledtext-file": "標記此檔案版本為己巡查",
-       "markedaspatrolled": "標記為已巡查",
+       "markedaspatrolled": "標記為已巡查",
        "markedaspatrolledtext": "已標記選擇的修訂 [[:$1]] 為已巡查。",
        "rcpatroldisabled": "最近變更巡查已停用",
        "rcpatroldisabledtext": "最新變更巡查的功能目前已停用。",
        "confirm-rollback-button": "確定",
        "confirm-rollback-top": "還原編輯到此頁面?",
        "semicolon-separator": ";",
-       "comma-separator": "",
+       "comma-separator": "",
        "colon-separator": ":",
        "word-separator": "",
        "parentheses": " ($1)",
index 6a719e5..7dde2b7 100644 (file)
@@ -172,4 +172,140 @@ $specialPageAliases = [
        'Withoutinterwiki'          => [ 'আন্তঃউইকিহীন' ],
 ];
 
+$magicWords = [
+       'redirect'                => [ 0, '#পুনর্নির্দেশ', '#পুনঃর্নির্দেশ', '#পুনঃনির্দেশ', '#পুননির্দেশ', '#REDIRECT' ],
+       'notoc'                   => [ 0, '__কোন_বিষয়বস্তুর_ছক_নয়__', '__কোনবিষয়বস্তুরছকনয়__', '__কোন_বিষয়বস্তুর_টেবিল_নয়__', '__কোনবিষয়বস্তুরটেবিলনয়__', '__NOTOC__' ],
+       'nogallery'               => [ 0, '__কোনগ্যালারিনয়__', '__কোনগ্যালারীনয়__', '__কোন_গ্যালারি_নয়__', '__কোন_গ্যালারী_নয়__', '__NOGALLERY__' ],
+       'toc'                     => [ 0, '__বিষয়বস্তুর_ছক__', '__বিষয়বস্তুরছক__', '__বিষয়বস্তুর_টেবিল__', '__বিষয়বস্তুরটেবিল__', '__TOC__' ],
+       'noeditsection'           => [ 0, '__কোনসম্পাদনাঅনুচ্ছেদনয়__', '__কোন_সম্পাদনা_অনুচ্ছেদ_নয়__', '__NOEDITSECTION__' ],
+       'currentmonth'            => [ 1, 'চলতি_মাস', 'চলতিমাস', 'বর্তমান_মাস', 'বর্তমানমাস', 'বর্তমান_মাস_২', 'বর্তমানমাস২', 'CURRENTMONTH', 'CURRENTMONTH2' ],
+       'currentmonth1'           => [ 1, 'চলতি_মাস_১', 'চলতিমাস১', 'বর্তমান_মাস_১', 'বর্তমানমাস১', 'CURRENTMONTH1' ],
+       'currentmonthname'        => [ 1, 'বর্তমান_মাসের_নাম', 'বর্তমানমাসেরনাম', 'CURRENTMONTHNAME' ],
+       'currentmonthnamegen'     => [ 1, 'বর্তমান_মাসের_নাম_উৎপন্ন', 'বর্তমানমাসেরনামউৎপন্ন', 'CURRENTMONTHNAMEGEN' ],
+       'currentmonthabbrev'      => [ 1, 'বর্তমান_মাস_সংক্ষেপ', 'বর্তমানমাসসংক্ষেপ', 'বর্তমান_মাস_সংক্ষিপ্ত', 'বর্তমানমাসসংক্ষিপ্ত', 'CURRENTMONTHABBREV' ],
+       'currentday'              => [ 1, 'বর্তমান_দিন', 'বর্তমানদিন', 'আজকের_দিন', 'আজকেরদিন', 'CURRENTDAY' ],
+       'currentday2'             => [ 1, 'বর্তমান_দিন_২', 'বর্তমানদিন২', 'আজকের_দিন_২', 'আজকেরদিন২', 'CURRENTDAY2' ],
+       'currentdayname'          => [ 1, 'বর্তমান_দিনের_নাম', 'বর্তমানদিনেরনাম', 'আজকের_দিনের_নাম', 'আজকেরদিনেরনাম', 'CURRENTDAYNAME' ],
+       'currentyear'             => [ 1, 'চলতি_বছর', 'চলতিবছর', 'বর্তমান_বছর', 'বর্তমানবছর', 'CURRENTYEAR' ],
+       'currenttime'             => [ 1, 'বর্তমান_সময়', 'বর্তমানসময়', 'এখনকার_সময়', 'এখনকারসময়', 'এখন_সময়', 'CURRENTTIME' ],
+       'currenthour'             => [ 1, 'বর্তমান_ঘণ্টা', 'বর্তমানঘণ্টা', 'বর্তমান_ঘন্টা', 'বর্তমানঘন্টা', 'এখনকার_ঘণ্টা', 'এখনকারঘণ্টা', 'CURRENTHOUR' ],
+       'localmonth'              => [ 1, 'স্থানীয়_মাস', 'স্থানীয়মাস', 'স্থানীয়_মাস_২', 'স্থানীয়মাস২', 'LOCALMONTH', 'LOCALMONTH2' ],
+       'localmonth1'             => [ 1, 'স্থানীয়_মাস_১', 'স্থানীয়মাস১', 'LOCALMONTH1' ],
+       'localmonthname'          => [ 1, 'স্থানীয়_মাসের_নাম', 'স্থানীয়মাসেরনাম', 'LOCALMONTHNAME' ],
+       'localmonthnamegen'       => [ 1, 'স্থানীয়_মাসের_নাম_উৎপন্ন', 'স্থানীয়মাসেরনামউৎপন্ন', 'LOCALMONTHNAMEGEN' ],
+       'localmonthabbrev'        => [ 1, 'স্থানীয়_মাস_সংক্ষেপ', 'স্থানীয়মাসসংক্ষেপ', 'স্থানীয়_মাস_সংক্ষিপ্ত', 'স্থানীয়মাসসংক্ষিপ্ত', 'সংক্ষেপিত_স্থানীয়_মাস', 'সংক্ষেপিতস্থানীয়মাস', 'LOCALMONTHABBREV' ],
+       'localday'                => [ 1, 'স্থানীয়_দিন', 'স্থানীয়দিন', 'স্থানীয়_বার', 'স্থানীয়বার', 'LOCALDAY' ],
+       'localday2'               => [ 1, 'স্থানীয়_দিন_২', 'স্থানীয়দিন২', 'LOCALDAY2' ],
+       'localdayname'            => [ 1, 'স্থানীয়_দিনের_নাম', 'স্থানীয়দিনেরনাম', 'LOCALDAYNAME' ],
+       'localyear'               => [ 1, 'স্থানীয়_বছর', 'স্থানীয়বছর', 'LOCALYEAR' ],
+       'localtime'               => [ 1, 'স্থানীয়_সময়', 'স্থানীয়সময়', 'LOCALTIME' ],
+       'localhour'               => [ 1, 'স্থানীয়_ঘণ্টা', 'স্থানীয়ঘণ্টা', 'স্থানীয়_ঘন্টা', 'স্থানীয়ঘন্টা', 'LOCALHOUR' ],
+       'numberofpages'           => [ 1, 'পাতার_সংখ্যা', 'পাতারসংখ্যা', 'পৃষ্ঠার_সংখ্যা', 'পৃষ্ঠারসংখ্যা', 'পৃষ্ঠা_সংখ্যা', 'পৃষ্ঠাসংখ্যা', 'NUMBEROFPAGES' ],
+       'numberofarticles'        => [ 1, 'নিবন্ধের_সংখ্যা', 'নিবন্ধেরসংখ্যা', 'নিবন্ধ_সংখ্যা', 'নিবন্ধসংখ্যা', 'NUMBEROFARTICLES' ],
+       'numberoffiles'           => [ 1, 'ফাইলের_সংখ্যা', 'ফাইলেরসংখ্যা', 'ফাইল_সংখ্যা', 'ফাইলসংখ্যা', 'NUMBEROFFILES' ],
+       'numberofusers'           => [ 1, 'ব্যবহারকারীর_সংখ্যা', 'ব্যবহারকারীরসংখ্যা', 'ব্যবহারকারী_সংখ্যা', 'ব্যবহারকারীসংখ্যা', 'NUMBEROFUSERS' ],
+       'numberofactiveusers'     => [ 1, 'সক্রিয়_ব্যবহারকারীর_সংখ্যা', 'সক্রিয়ব্যবহারকারীরসংখ্যা', 'সক্রিয়_ব্যবহারকারী_সংখ্যা', 'সক্রিয়ব্যবহারকারীসংখ্যা', 'NUMBEROFACTIVEUSERS' ],
+       'numberofedits'           => [ 1, 'সম্পাদনার_সংখ্যা', 'সম্পাদনারসংখ্যা', 'সম্পাদনা_সংখ্যা', 'সম্পাদনাসংখ্যা', 'NUMBEROFEDITS' ],
+       'pagename'                => [ 1, 'পাতার_নাম', 'পাতারনাম', 'পৃষ্ঠার_নাম', 'পৃষ্ঠারনাম', 'PAGENAME' ],
+       'pagenamee'               => [ 1, 'পাতার_নামম', 'পাতারনামম', 'পৃষ্ঠার_নামম', 'পৃষ্ঠারনামম', 'PAGENAMEE' ],
+       'namespace'               => [ 1, 'নামস্থান', 'NAMESPACE' ],
+       'namespacee'              => [ 1, 'নামস্থানন', 'NAMESPACEE' ],
+       'namespacenumber'         => [ 1, 'নামস্থানের_সংখ্যা', 'নামস্থানেরসংখ্যা', 'NAMESPACENUMBER' ],
+       'talkspace'               => [ 1, 'আলাপের_স্থান', 'আলোচনার_স্থান', 'আলাপেরস্থান', 'আলোচনারস্থান', 'আলাপের_জায়গা', 'আলাপেরজায়গা', 'TALKSPACE' ],
+       'talkspacee'              => [ 1, 'আলাপের_স্থানন', 'আলোচনার_স্থানন', 'আলাপেরস্থানন', 'আলোচনারস্থানন', 'TALKSPACEE' ],
+       'subjectspace'            => [ 1, 'বিষয়ের_স্থান', 'নিবন্ধের_স্থান', 'বিষয়েরস্থান', 'নিবন্ধেরস্থান', 'SUBJECTSPACE', 'ARTICLESPACE' ],
+       'subjectspacee'           => [ 1, 'বিষয়ের_স্থানন', 'নিবন্ধের_স্থানন', 'বিষয়েরস্থানন', 'নিবন্ধেরস্থানন', 'SUBJECTSPACEE', 'ARTICLESPACEE' ],
+       'fullpagename'            => [ 1, 'পূর্ণ_পাতার_নাম', 'সম্পূর্ণ_পাতার_নাম', 'পূর্ণপাতারনাম', 'সম্পূর্ণপাতারনাম', 'পূর্ণ_পৃষ্ঠার_নাম', 'সম্পূর্ণ_পৃষ্ঠার_নাম', 'পূর্ণপৃষ্ঠারনাম', 'সম্পূর্ণপৃষ্ঠারনাম', 'FULLPAGENAME' ],
+       'fullpagenamee'           => [ 1, 'পূর্ণ_পাতার_নামম', 'সম্পূর্ণ_পাতার_নামম', 'পূর্ণপাতারনামম', 'সম্পূর্ণপাতারনামম', 'পূর্ণ_পৃষ্ঠার_নামম', 'সম্পূর্ণ_পৃষ্ঠার_নামম', 'পূর্ণপৃষ্ঠারনামম', 'সম্পূর্ণপৃষ্ঠারনামম', 'FULLPAGENAMEE' ],
+       'subpagename'             => [ 1, 'উপপাতার_নাম', 'উপপাতারনাম', 'উপপৃষ্ঠার_নাম', 'উপপৃষ্ঠারনাম', 'SUBPAGENAME' ],
+       'subpagenamee'            => [ 1, 'উপপাতার_নামম', 'উপপাতারনামম', 'উপপৃষ্ঠার_নামম', 'উপপৃষ্ঠারনামম', 'SUBPAGENAMEE' ],
+       'rootpagename'            => [ 1, 'মূল_পাতার_নাম', 'মূলপাতারনাম', 'মূল_পৃষ্ঠার_নাম', 'মূল_পৃষ্ঠার_নাম', 'ROOTPAGENAME' ],
+       'rootpagenamee'           => [ 1, 'মূল_পাতার_নামম', 'মূলপাতারনামম', 'মূল_পৃষ্ঠার_নামম', 'মূল_পৃষ্ঠার_নামম', 'ROOTPAGENAMEE' ],
+       'basepagename'            => [ 1, 'ভিত্তি_পাতার_নাম', 'ভিত্তিপাতারনাম', 'ভিত্তি_পৃষ্ঠার_নাম', 'ভিত্তি_পৃষ্ঠার_নাম', 'BASEPAGENAME' ],
+       'basepagenamee'           => [ 1, 'ভিত্তি_পাতার_নামম', 'ভিত্তিপাতারনামম', 'ভিত্তি_পৃষ্ঠার_নামম', 'ভিত্তি_পৃষ্ঠার_নামম', 'BASEPAGENAMEE' ],
+       'talkpagename'            => [ 1, 'আলাপ_পাতার_নাম', 'আলাপপাতারনাম', 'আলাপ_পৃষ্ঠার_নাম', 'আলোচনা_পৃষ্ঠার_নাম', 'আলোচনা_পাতার_নাম', 'আলোচনাপাতারনাম', 'আলোচনা_পৃষ্ঠার_নাম', 'আলোচনা_পৃষ্ঠার_নাম', 'TALKPAGENAME' ],
+       'talkpagenamee'           => [ 1, 'আলাপ_পাতার_নামম', 'আলাপপাতারনামম', 'আলাপ_পৃষ্ঠার_নামম', 'আলোচনা_পৃষ্ঠার_নামম', 'আলোচনা_পাতার_নামম', 'আলোচনাপাতারনামম', 'আলোচনা_পৃষ্ঠার_নামম', 'আলোচনা_পৃষ্ঠার_নামম', 'TALKPAGENAMEE' ],
+       'subjectpagename'         => [ 1, 'বিষয়ের_পাতার_নাম', 'বিষয়েরপাতারনাম', 'বিষয়ের_পৃষ্ঠার_নাম', 'বিষয়েরপৃষ্ঠারনাম', 'SUBJECTPAGENAME', 'ARTICLEPAGENAME' ],
+       'subjectpagenamee'        => [ 1, 'বিষয়ের_পাতার_নামম', 'বিষয়েরপাতারনামম', 'বিষয়ের_পৃষ্ঠার_নামম', 'বিষয়েরপৃষ্ঠারনামম', 'SUBJECTPAGENAMEE', 'ARTICLEPAGENAMEE' ],
+       'img_thumbnail'           => [ 1, 'থাম্ব','থাম্বনেইল', 'thumb', 'thumbnail' ],
+       'img_manualthumb'         => [ 1, 'থাম্ব=$1','থাম্বনেইল=$1', 'thumbnail=$1', 'thumb=$1' ],
+       'img_right'               => [ 1, 'ডান', 'ডানে', 'right' ],
+       'img_left'                => [ 1, 'বাম', 'বামে', 'left' ],
+       'img_none'                => [ 1, 'কিছুই_না', 'কোনটি_না', 'কিছুইনা', 'কোনটিনা', 'none' ],
+       'img_width'               => [ 1, '$1পিক্সেল', '$1পিক্স', '$1px' ],
+       'img_center'              => [ 1, 'কেন্দ্র', 'কেন্দ্রে', 'center', 'centre' ],
+       'img_framed'              => [ 1, 'ফ্রেম', 'ফ্রেমসহ', 'frame', 'framed', 'enframed' ],
+       'img_frameless'           => [ 1, 'ফ্রেমহীন', 'ফ্রেমবিহীন', 'frameless' ],
+       'img_lang'                => [ 1, 'ভাষা=$1', 'lang=$1' ],
+       'img_page'                => [ 1, 'পাতা=$1', 'পাতা $1', 'পৃষ্ঠা=$1', 'পৃষ্ঠা $1', 'page=$1', 'page $1' ],
+       'img_upright'             => [ 1, 'ডানে_উপরে', 'ডানেউপরে=$1', 'ডানদিকে_উপরে', 'ডানদিকে_উপরে $1', 'ডানদিকে_উপরে=$1', 'ডানেউপরে $1', 'upright', 'upright=$1', 'upright $1' ],
+       'img_border'              => [ 1, 'সীমানা', 'সীমান্ত', 'border' ],
+       'img_top'                 => [ 1, 'শীর্ষ', 'উপর', 'শীর্ষে', 'উপরে', 'top' ],
+       'img_text_top'            => [ 1, 'পাঠ্য-উপরে', 'লেখা-উপরে', 'পাঠ্য-উপর', 'লেখা-উপর', 'text-top' ],
+       'img_middle'              => [ 1, 'মধ্য', 'মধ্যে', 'middle' ],
+       'img_bottom'              => [ 1, 'নিচে', 'নিচ', 'নীচ', 'নীচে', 'তলদেশ', 'নিম্নে', 'bottom' ],
+       'img_text_bottom'         => [ 1, 'পাঠ্য-নীচে', 'লেখা-নীচ', 'text-bottom' ],
+       'img_link'                => [ 1, 'সংযোগ=$1', 'লিঙ্ক=$1', 'link=$1' ],
+       'img_class'               => [ 1, 'ক্লাস=$1', 'class=$1' ],
+       'sitename'                => [ 1, 'সাইটের_নাম', 'সাইটেরনাম', 'SITENAME' ],
+       'localurl'                => [ 0, 'স্থানীয়_ইউআরএল:', 'স্থানীয়ইউআরএল:', 'LOCALURL:' ],
+       'articlepath'             => [ 0, 'নিবন্ধের_পথ', 'নিবন্ধেরপথ', 'ARTICLEPATH' ],
+       'pageid'                  => [ 0, 'পাতার_আইডি', 'পাতারআইডি', 'পৃষ্ঠার_আইডি', 'পৃষ্ঠারআইডি', 'PAGEID' ],
+       'server'                  => [ 0, 'সার্ভার', 'SERVER' ],
+       'servername'              => [ 0, 'সার্ভারের_নাম', 'সার্ভারেরনাম', 'SERVERNAME' ],
+       'scriptpath'              => [ 0, 'স্ক্রিপ্টের_পথ', 'স্ক্রিপ্টেরপথ', 'SCRIPTPATH' ],
+       'stylepath'               => [ 0, 'শৈলীর_পথ', 'শৈলীরপথ', 'STYLEPATH' ],
+       'grammar'                 => [ 0, 'ব্যাকরণ:', 'GRAMMAR:' ],
+       'gender'                  => [ 0, 'লিঙ্গ:', 'GENDER:' ],
+       'bidi'                    => [ 0, 'বিআইডিআই:', 'BIDI:' ],
+       'notitleconvert'          => [ 0, '__কোন_শিরোনাম_রূপান্তরকারী_নয়__', '__কোনশিরোনামরূপান্তরকারীনয়__', '__NOTITLECONVERT__', '__NOTC__' ],
+       'nocontentconvert'        => [ 0, '__কোন_বিষয়বস্তু_রূপান্তরকারী_নয়__', '__কোনবিষয়বস্তুরূপান্তরকারীনয়__', '__NOCONTENTCONVERT__', '__NOCC__' ],
+       'currentweek'             => [ 1, 'বর্তমান_সপ্তাহ', 'বর্তমানসপ্তাহ', 'চলতি_সপ্তাহ', 'চলতিসপ্তাহ', 'CURRENTWEEK' ],
+       'currentdow'              => [ 1, 'বর্তমান_সপ্তাহের_দিন', 'বর্তমানসপ্তাহেরদিন', 'CURRENTDOW' ],
+       'localweek'               => [ 1, 'স্থানীয়_সপ্তাহ', 'স্থানীয়সপ্তাহ', 'LOCALWEEK' ],
+       'localdow'                => [ 1, 'স্থানীয়_সপ্তাহের_দিন', 'স্থানীয়সপ্তাহেরদিন', 'LOCALDOW' ],
+       'revisionid'              => [ 1, 'সংশোধনের_আইডি', 'সংশোধনেরআইডি', 'REVISIONID' ],
+       'revisionday'             => [ 1, 'সংশোধনের_দিন', 'সংশোধনেরদিন', 'REVISIONDAY' ],
+       'revisionday2'            => [ 1, 'সংশোধনের_দিন_২', 'সংশোধনেরদিন২', 'REVISIONDAY2' ],
+       'revisionmonth'           => [ 1, 'সংশোধনের_মাস', 'সংশোধনেরমাস', 'REVISIONMONTH' ],
+       'revisionmonth1'          => [ 1, 'সংশোধনের_মাস_১', 'সংশোধনেরমাস১', 'REVISIONMONTH1' ],
+       'revisionyear'            => [ 1, 'সংশোধনের_বছর', 'সংশোধনেরবছর', 'REVISIONYEAR' ],
+       'revisiontimestamp'       => [ 1, 'সংশোধনের_সময়তারিখ', 'সংশোধনেরসময়তারিখ', 'REVISIONTIMESTAMP' ],
+       'revisionuser'            => [ 1, 'সংশোধনের_ব্যবহারকারী', 'সংশোধনেরব্যবহারকারী', 'REVISIONUSER' ],
+       'revisionsize'            => [ 1, 'সংশোধনের_আকার', 'সংশোধনেরআকার', 'REVISIONSIZE' ],
+       'plural'                  => [ 0, 'বহুবচন:', 'PLURAL:' ],
+       'raw'                     => [ 0, 'অবিন্যস্ত:', 'RAW:' ],
+       'displaytitle'            => [ 1, 'প্রদর্শনের_শিরোনাম', 'প্রদর্শনেরশিরোনাম', 'প্রদর্শিত_শিরোনাম', 'প্রদর্শিতশিরোনাম', 'DISPLAYTITLE' ],
+       'newsectionlink'          => [ 1, '__নতুন_অনুচ্ছেদের_সংযোগ__', '__নতুন_অনুচ্ছেদের_লিঙ্ক__', '__নতুনঅনুচ্ছেদেরসংযোগ__', '__নতুনঅনুচ্ছেদেরলিঙ্ক__', '__NEWSECTIONLINK__' ],
+       'nonewsectionlink'        => [ 1, '__কোন_নতুন_অনুচ্ছেদের_সংযোগ_নয়__', '__কোন_নতুন_অনুচ্ছেদের_লিঙ্ক_নয়__', '__কোননতুনঅনুচ্ছেদেরসংযোগনয়__', '__কোননতুনঅনুচ্ছেদেরলিঙ্কনয়__', '__NONEWSECTIONLINK__' ],
+       'currentversion'          => [ 1, 'বর্তমান_সংস্করণ', 'বর্তমানসংস্করণ', 'CURRENTVERSION' ],
+       'currenttimestamp'        => [ 1, 'বর্তমান_সময়তারিখ', 'বর্তমান_সময়তারিখ', 'CURRENTTIMESTAMP' ],
+       'localtimestamp'          => [ 1, 'স্থানীয়_সময়তারিখ', 'স্থানীয়সময়তারিখ', 'LOCALTIMESTAMP' ],
+       'language'                => [ 0, '#ভাষা:', '#LANGUAGE:' ],
+       'contentlanguage'         => [ 1, 'বিষয়বস্তুর_ভাষা', 'বিষয়বস্তুরভাষা', 'CONTENTLANGUAGE', 'CONTENTLANG' ],
+       'pagesinnamespace'        => [ 1, 'নামস্থানে_পাতা', 'নামস্থানে_পৃষ্ঠা', 'নামস্থানেপাতা', 'নামস্থানেপৃষ্ঠা', 'PAGESINNAMESPACE:', 'PAGESINNS:' ],
+       'numberofadmins'          => [ 1, 'প্রশাসকের_সংখ্যা', 'প্রশাসকেরসংখ্যা', 'প্রশাসক_সংখ্যা', 'প্রশাসকসংখ্যা', 'NUMBEROFADMINS' ],
+       'formatnum'               => [ 0, 'নম্বর_বিন্যাস', 'নম্বরবিন্যাস', 'FORMATNUM' ],
+       'special'                 => [ 0, 'বিশেষ', 'special' ],
+       'defaultsort'             => [ 1, 'পূর্বনির্ধারিত_বাছাই', 'পূর্বনির্ধারিতবাছাই', 'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ],
+       'filepath'                => [ 0, 'ফাইলের_পথ:', 'ফাইলেরপথ:', 'FILEPATH:' ],
+       'tag'                     => [ 0, 'ট্যাগ', 'tag' ],
+       'hiddencat'               => [ 1, '__লুকায়িতবিষয়শ্রেণী__', '__লুক্কায়িতবিষয়শ্রেণী__', '__HIDDENCAT__' ],
+       'pagesincategory'         => [ 1, 'বিষয়শ্রেণীতেপাতা', 'বিষয়শ্রেণীতেপৃষ্ঠা', 'বিষয়শ্রেণীতে_পাতা', 'বিষয়শ্রেণীতে_পৃষ্ঠা', 'PAGESINCATEGORY', 'PAGESINCAT' ],
+       'pagesize'                => [ 1, 'পাতার_আকার', 'পাতারআকার', 'পৃষ্ঠার_আকার', 'পৃষ্ঠারআকার', 'PAGESIZE' ],
+       'index'                   => [ 1, '__নির্ঘণ্ট__', '__INDEX__' ],
+       'noindex'                 => [ 1, '__কোননির্ঘণ্টনয়__', '__কোন_নির্ঘণ্ট_নয়__', '__নির্ঘণ্টনয়__', '__NOINDEX__' ],
+       'numberingroup'           => [ 1, 'দলে_সংখ্যা', 'দলেসংখ্যা', 'NUMBERINGROUP', 'NUMINGROUP' ],
+       'staticredirect'          => [ 1, '__স্থির_পুনর্নির্দেশ__', '__স্থিরপুনর্নির্দেশ__', '__STATICREDIRECT__' ],
+       'protectionlevel'         => [ 1, 'সুরক্ষার_স্তর', 'সুরক্ষারস্তর', 'সুরক্ষা_স্তর', 'সুরক্ষাস্তর', 'PROTECTIONLEVEL' ],
+       'protectionexpiry'        => [ 1, 'সুরক্ষার_মেয়াদোত্তীর্ণ', 'সুরক্ষারমেয়াদোত্তীর্ণ', 'সুরক্ষা_মেয়াদোত্তীর্ণ', 'সুরক্ষামেয়াদোত্তীর্ণ', 'PROTECTIONEXPIRY' ],
+       'formatdate'              => [ 0, 'তারিখ_বিন্যাস', 'তারিখবিন্যাস', 'formatdate', 'dateformat' ],
+       'url_path'                => [ 0, 'পথ', 'PATH' ],
+       'url_wiki'                => [ 0, 'উইকি', 'WIKI' ],
+       'pagesincategory_all'     => [ 0, 'সব', 'সকল', 'all' ],
+       'pagesincategory_pages'   => [ 0, 'পাতাসমূহ', 'পৃষ্ঠাসমূহ', 'pages' ],
+       'pagesincategory_subcats' => [ 0, 'উপবিষয়শ্রেণী', '', 'subcats' ],
+       'pagesincategory_files'   => [ 0, 'ফাইলসমূহ', 'files' ],
+];
+
 $linkTrail = '/^([\x{0980}-\x{09FF}]+)(.*)$/sDu';
index 954d911..84b107e 100644 (file)
@@ -13,7 +13,7 @@
  * @author Numulunj pilgae
  */
 
-$fallbak = 'ru';
+$fallback = 'ru';
 
 $namespaceNames = [
        NS_MEDIA            => 'Медиа',
index 838ea33..29627a6 100644 (file)
@@ -12,6 +12,8 @@
  * @author Reder
  */
 
+$fallback = 'it';
+
 $specialPageAliases = [
        'Allmessages'               => [ 'TutteLeMessagge' ],
        'Preferences'               => [ 'Preferenze' ],
index 369e0be..0c381e5 100644 (file)
@@ -58,7 +58,6 @@ $separatorTransformTable = [
        '.' => ','
 ];
 
-$fallback = 'ru';
 $fallback8bitEncoding = 'windows-1251';
 $linkPrefixExtension = true;
 
@@ -86,11 +85,23 @@ $namespaceAliases = [
        'Зображення' => NS_FILE,
        'Обговорення_зображення' => NS_FILE_TALK,
        'Обговорення_шаблона' => NS_TEMPLATE_TALK,
+       // Backwards compatibility from Russian
+       'Медиа' => NS_MEDIA,
+       'Служебная' => NS_SPECIAL,
+       'Обсуждение' => NS_TALK,
+       'Участник' => NS_USER,
+       'Обсуждение_участника' => NS_USER_TALK,
+       'Обсуждение_файла' => NS_FILE_TALK,
+       'Обсуждение_MediaWiki' => NS_MEDIAWIKI_TALK,
+       'Обсуждение_шаблона' => NS_TEMPLATE_TALK,
+       'Справка' => NS_HELP,
+       'Обсуждение_справки' => NS_HELP_TALK,
+       'Категория' => NS_CATEGORY,
+       'Обсуждение_категории' => NS_CATEGORY_TALK,
+       'Изображение' => NS_FILE,
+       'Обсуждение_изображения' => NS_FILE_TALK,
 ];
 
-// Remove Russian aliases
-$namespaceGenderAliases = [];
-
 $dateFormats = [
        'mdy time' => 'H:i',
        'mdy date' => 'xg j, Y',
@@ -113,108 +124,109 @@ $bookstoreList = [
        'Amazon.com' => 'http://www.amazon.com/exec/obidos/ISBN=$1'
 ];
 
+// Russian names are kept for backwards compatibility
 $specialPageAliases = [
-       'Activeusers'               => [ 'Активні_дописувачі' ],
-       'Allmessages'               => [ 'Системні_повідомлення' ],
-       'AllMyUploads'              => [ 'Усі_мої_файли' ],
-       'Allpages'                  => [ 'Усі_сторінки' ],
+       'Activeusers'               => [ 'Активні_дописувачі', 'Активные_участники' ],
+       'Allmessages'               => [ 'Системні_повідомлення', 'Системные_сообщения' ],
+       'AllMyUploads'              => [ 'Усі_мої_файли', 'Все_мои_файлы' ],
+       'Allpages'                  => [ 'Усі_сторінки', 'Все_страницы' ],
        'Ancientpages'              => [ 'Давні_сторінки' ],
-       'Badtitle'                  => [ 'Помилковий_заголовок' ],
-       'Blankpage'                 => [ 'Порожня_сторінка' ],
-       'Block'                     => [ 'Заблокувати' ],
-       'Booksources'               => [ 'Джерела_книг' ],
-       'BrokenRedirects'           => [ 'Розірвані_перенаправлення' ],
-       'Categories'                => [ 'Категорії' ],
-       'ChangeEmail'               => [ 'Змінити_e-mail' ],
-       'ChangePassword'            => [ 'Змінити_пароль' ],
-       'ComparePages'              => [ 'Порівняння_сторінок' ],
-       'Confirmemail'              => [ 'Підтвердити_e-mail' ],
-       'Contributions'             => [ 'Внесок' ],
-       'CreateAccount'             => [ 'Створити_обліковий_запис' ],
-       'Deadendpages'              => [ 'Сторінки_без_посилань' ],
-       'DeletedContributions'      => [ 'Вилучений_внесок' ],
-       'DoubleRedirects'           => [ 'Подвійні_перенаправлення' ],
-       'EditWatchlist'             => [ 'Редагувати_список_спостереження' ],
-       'Emailuser'                 => [ 'Лист_користувачеві' ],
-       'ExpandTemplates'           => [ 'Розгортання_шаблонів' ],
-       'Export'                    => [ 'Експорт' ],
-       'Fewestrevisions'           => [ 'Найменшредаговані' ],
-       'FileDuplicateSearch'       => [ 'Пошук_дублікатів_файлів' ],
-       'Filepath'                  => [ 'Шлях_до_файлу' ],
-       'Import'                    => [ 'Імпорт' ],
-       'Invalidateemail'           => [ 'Неперевірена_email-адреса' ],
-       'JavaScriptTest'            => [ 'JavaScript_тест' ],
-       'BlockList'                 => [ 'Список_блокувань', 'Блокування', 'Блокування_IP-адрес' ],
-       'LinkSearch'                => [ 'Пошук_посилань' ],
-       'Listadmins'                => [ 'Список_адміністраторів' ],
-       'Listbots'                  => [ 'Список_ботів' ],
-       'Listfiles'                 => [ 'Список_файлів' ],
-       'Listgrouprights'           => [ 'Список_прав_груп', 'Права_груп_користувачів' ],
-       'Listredirects'             => [ 'Список_перенаправлень' ],
-       'ListDuplicatedFiles'       => [ 'Список_дубльованих_файлів' ],
-       'Listusers'                 => [ 'Список_користувачів' ],
-       'Lockdb'                    => [ 'Заблокувати_базу_даних' ],
-       'Log'                       => [ 'Журнали' ],
-       'Lonelypages'               => [ 'Ізольовані_сторінки' ],
-       'Longpages'                 => [ 'Найдовші_сторінки' ],
-       'MergeHistory'              => [ 'Об\'єднання_історії' ],
-       'MIMEsearch'                => [ 'Пошук_за_MIME' ],
-       'Mostcategories'            => [ 'Найбільш_категоризовані' ],
-       'Mostimages'                => [ 'Найуживаніші_файли' ],
-       'Mostinterwikis'            => [ 'Найбільше_інтервікі' ],
-       'Mostlinked'                => [ 'Найуживаніші_сторінки', 'Найбільше_посилань' ],
-       'Mostlinkedcategories'      => [ 'Найуживаніші_категорії' ],
-       'Mostlinkedtemplates'       => [ 'Найуживаніші_шаблони' ],
-       'Mostrevisions'             => [ 'Найбільш_редаговані' ],
-       'Movepage'                  => [ 'Перейменувати' ],
-       'Mycontributions'           => [ 'Мій_внесок' ],
-       'MyLanguage'                => [ 'Моя_мова' ],
-       'Mypage'                    => [ 'Моя_сторінка' ],
-       'Mytalk'                    => [ 'Моє_обговорення' ],
-       'Myuploads'                 => [ 'Мої_завантаження' ],
-       'Newimages'                 => [ 'Нові_файли' ],
-       'Newpages'                  => [ 'Нові_сторінки' ],
-       'PasswordReset'             => [ 'Скинути_пароль' ],
-       'PermanentLink'             => [ 'Постійне_посилання' ],
-       'Preferences'               => [ 'Налаштування' ],
-       'Prefixindex'               => [ 'Покажчик_за_початком_назви' ],
-       'Protectedpages'            => [ 'Захищені_сторінки' ],
-       'Protectedtitles'           => [ 'Захищені_назви_сторінок' ],
-       'Randompage'                => [ 'Випадкова_сторінка' ],
-       'Randomredirect'            => [ 'Випадкове_перенаправлення' ],
-       'Recentchanges'             => [ 'Нові_редагування' ],
-       'Recentchangeslinked'       => [ 'Пов\'язані_редагування' ],
+       'Badtitle'                  => [ 'Помилковий_заголовок', 'Недопустимое_название' ],
+       'Blankpage'                 => [ 'Порожня_сторінка', 'Пустая_страница' ],
+       'Block'                     => [ 'Заблокувати', 'Заблокировать' ],
+       'Booksources'               => [ 'Джерела_книг', 'Источники_книг' ],
+       'BrokenRedirects'           => [ 'Розірвані_перенаправлення', 'Разорванные_перенаправления' ],
+       'Categories'                => [ 'Категорії', 'Категории' ],
+       'ChangeEmail'               => [ 'Змінити_e-mail', 'Сменить_e-mail', 'Сменить_почту' ],
+       'ChangePassword'            => [ 'Змінити_пароль', 'Сменить_пароль' ],
+       'ComparePages'              => [ 'Порівняння_сторінок', 'Сравнение_страниц' ],
+       'Confirmemail'              => [ 'Підтвердити_e-mail', 'Подтвердить_e-mail', 'Подтвердить_почту' ],
+       'Contributions'             => [ 'Внесок', 'Вклад' ],
+       'CreateAccount'             => [ 'Створити_обліковий_запис', 'Создать_учётную_запись', 'Создать_пользователя', 'Зарегистрироваться' ],
+       'Deadendpages'              => [ 'Сторінки_без_посилань', 'Тупиковые_страницы' ],
+       'DeletedContributions'      => [ 'Вилучений_внесок', 'Удалённый_вклад' ],
+       'DoubleRedirects'           => [ 'Подвійні_перенаправлення', 'Двойные_перенаправления' ],
+       'EditWatchlist'             => [ 'Редагувати_список_спостереження', 'Править_список_наблюдения' ],
+       'Emailuser'                 => [ 'Лист_користувачеві', 'Письмо_участнику', 'Отправить_письмо' ],
+       'ExpandTemplates'           => [ 'Розгортання_шаблонів', 'Развёртка_шаблонов' ],
+       'Export'                    => [ 'Експорт', 'Экспорт', 'Выгрузка' ],
+       'Fewestrevisions'           => [ 'Найменшредаговані', 'Редко_редактируемые' ],
+       'FileDuplicateSearch'       => [ 'Пошук_дублікатів_файлів', 'Поиск_дубликатов_файлов' ],
+       'Filepath'                  => [ 'Шлях_до_файлу', 'Путь_к_файлу' ],
+       'Import'                    => [ 'Імпорт', 'Импорт' ],
+       'Invalidateemail'           => [ 'Неперевірена_email-адреса', 'Отменить_подтверждение_адреса' ],
+       'JavaScriptTest'            => [ 'JavaScript_тест', 'Тестирование_JavaScript' ],
+       'BlockList'                 => [ 'Список_блокувань', 'Блокування', 'Блокування_IP-адрес', 'Список_блокировок', 'Блокировки' ],
+       'LinkSearch'                => [ 'Пошук_посилань', 'Поиск_ссылок' ],
+       'Listadmins'                => [ 'Список_адміністраторів', 'Список_администраторов' ],
+       'Listbots'                  => [ 'Список_ботів', 'Список_ботов' ],
+       'Listfiles'                 => [ 'Список_файлів', 'Список_файлов', 'Список_изображений' ],
+       'Listgrouprights'           => [ 'Список_прав_груп', 'Права_груп_користувачів', 'Права_групп_участников', 'Список_прав_групп' ],
+       'Listredirects'             => [ 'Список_перенаправлень', 'Список_перенаправлений' ],
+       'ListDuplicatedFiles'       => [ 'Список_дубльованих_файлів', 'Список_файлов-дубликатов' ],
+       'Listusers'                 => [ 'Список_користувачів', 'Список_участников' ],
+       'Lockdb'                    => [ 'Заблокувати_базу_даних', 'Заблокировать_БД', 'Заблокировать_базу_данных' ],
+       'Log'                       => [ 'Журнали', 'Журналы', 'Журнал' ],
+       'Lonelypages'               => [ 'Ізольовані_сторінки', 'Изолированные_страницы' ],
+       'Longpages'                 => [ 'Найдовші_сторінки', 'Длинные_страницы' ],
+       'MergeHistory'              => [ 'Об\'єднання_історії', 'Объединение_историй' ],
+       'MIMEsearch'                => [ 'Пошук_за_MIME', 'Поиск_по_MIME' ],
+       'Mostcategories'            => [ 'Найбільш_категоризовані', 'Самые_категоризованные' ],
+       'Mostimages'                => [ 'Найуживаніші_файли', 'Самые_используемые_файлы' ],
+       'Mostinterwikis'            => [ 'Найбільше_інтервікі', 'Наибольшее_количество_интервики-ссылок' ],
+       'Mostlinked'                => [ 'Найуживаніші_сторінки', 'Найбільше_посилань', 'Самые_используемые_страницы' ],
+       'Mostlinkedcategories'      => [ 'Найуживаніші_категорії', 'Самые_используемые_категории' ],
+       'Mostlinkedtemplates'       => [ 'Найуживаніші_шаблони', 'Самые_используемые_шаблоны' ],
+       'Mostrevisions'             => [ 'Найбільш_редаговані', 'Наибольшее_количество_версий' ],
+       'Movepage'                  => [ 'Перейменувати', 'Переименовать_страницу', 'Переименование', 'Переименовать' ],
+       'Mycontributions'           => [ 'Мій_внесок', 'Мой_вклад' ],
+       'MyLanguage'                => [ 'Моя_мова', 'Мой_язык' ],
+       'Mypage'                    => [ 'Моя_сторінка', 'Моя_страница' ],
+       'Mytalk'                    => [ 'Моє_обговорення', 'Моё_обсуждение' ],
+       'Myuploads'                 => [ 'Мої_завантаження', 'Мои_загрузки' ],
+       'Newimages'                 => [ 'Нові_файли', 'Новые_файлы' ],
+       'Newpages'                  => [ 'Нові_сторінки', 'Новые_страницы' ],
+       'PasswordReset'             => [ 'Скинути_пароль', 'Сброс_пароля' ],
+       'PermanentLink'             => [ 'Постійне_посилання', 'Постоянная_ссылка' ],
+       'Preferences'               => [ 'Налаштування', 'Настройки' ],
+       'Prefixindex'               => [ 'Покажчик_за_початком_назви', 'Указатель_по_началу_названия' ],
+       'Protectedpages'            => [ 'Захищені_сторінки', 'Защищённые_страницы' ],
+       'Protectedtitles'           => [ 'Захищені_назви_сторінок', 'Защищённые_названия' ],
+       'Randompage'                => [ 'Випадкова_сторінка', 'Случайная_страница', 'Случайная' ],
+       'Randomredirect'            => [ 'Випадкове_перенаправлення', 'Случайное_перенаправление' ],
+       'Recentchanges'             => [ 'Нові_редагування', 'Свежие_правки' ],
+       'Recentchangeslinked'       => [ 'Пов\'язані_редагування', 'Связанные_правки' ],
        'Redirect'                  => [ 'Перенаправлення' ],
-       'Revisiondelete'            => [ 'Вилучити_редагування' ],
-       'Search'                    => [ 'Пошук' ],
-       'Shortpages'                => [ 'Короткі_сторінки' ],
-       'Specialpages'              => [ 'Спеціальні_сторінки' ],
+       'Revisiondelete'            => [ 'Вилучити_редагування', 'Удаление_правки' ],
+       'Search'                    => [ 'Пошук', 'Поиск' ],
+       'Shortpages'                => [ 'Короткі_сторінки', 'Короткие_страницы' ],
+       'Specialpages'              => [ 'Спеціальні_сторінки', 'Спецстраницы' ],
        'Statistics'                => [ 'Статистика' ],
-       'Tags'                      => [ 'Мітки' ],
-       'Unblock'                   => [ 'Розблокувати' ],
-       'Uncategorizedcategories'   => [ 'Некатегоризовані_категорії' ],
-       'Uncategorizedimages'       => [ 'Некатегоризовані_файли' ],
-       'Uncategorizedpages'        => [ 'Некатегоризовані_сторінки' ],
-       'Uncategorizedtemplates'    => [ 'Некатегоризовані_шаблони' ],
-       'Undelete'                  => [ 'Відновити' ],
-       'Unlockdb'                  => [ 'Розблокувати_базу_даних' ],
-       'Unusedcategories'          => [ 'Порожні_категорії' ],
-       'Unusedimages'              => [ 'Невикористані_файли' ],
-       'Unusedtemplates'           => [ 'Невикористані_шаблони' ],
+       'Tags'                      => [ 'Мітки', 'Метки' ],
+       'Unblock'                   => [ 'Розблокувати', 'Разблокировка' ],
+       'Uncategorizedcategories'   => [ 'Некатегоризовані_категорії', 'Некатегоризованные_категории' ],
+       'Uncategorizedimages'       => [ 'Некатегоризовані_файли', 'Некатегоризованные_файлы' ],
+       'Uncategorizedpages'        => [ 'Некатегоризовані_сторінки', 'Некатегоризованные_страницы' ],
+       'Uncategorizedtemplates'    => [ 'Некатегоризовані_шаблони', 'Некатегоризованные_шаблоны' ],
+       'Undelete'                  => [ 'Відновити', 'Восстановить', 'Восстановление' ],
+       'Unlockdb'                  => [ 'Розблокувати_базу_даних', 'Разблокировка_БД' ],
+       'Unusedcategories'          => [ 'Порожні_категорії', 'Неиспользуемые_категории' ],
+       'Unusedimages'              => [ 'Невикористані_файли', 'Неиспользуемые_файлы' ],
+       'Unusedtemplates'           => [ 'Невикористані_шаблони', 'Неиспользуемые_шаблоны' ],
        'Unwatchedpages'            => [ 'Неспостережувані' ],
-       'Upload'                    => [ 'Завантаження' ],
-       'UploadStash'               => [ 'Приховане_завантаження' ],
-       'Userlogin'                 => [ 'Вхід' ],
-       'Userlogout'                => [ 'Вихід' ],
-       'Userrights'                => [ 'Керування_правами_користувачів' ],
-       'Version'                   => [ 'Версія' ],
-       'Wantedcategories'          => [ 'Потрібні_категорії' ],
-       'Wantedfiles'               => [ 'Потрібні_файли' ],
-       'Wantedpages'               => [ 'Потрібні_сторінки' ],
-       'Wantedtemplates'           => [ 'Потрібні_шаблони' ],
-       'Watchlist'                 => [ 'Список_спостереження' ],
-       'Whatlinkshere'             => [ 'Посилання_сюди' ],
-       'Withoutinterwiki'          => [ 'Без_інтервікі' ],
+       'Upload'                    => [ 'Завантаження', 'Загрузка' ],
+       'UploadStash'               => [ 'Приховане_завантаження', 'Скрытная_загрузка' ],
+       'Userlogin'                 => [ 'Вхід', 'Вход' ],
+       'Userlogout'                => [ 'Вихід', 'Завершение_сеанса', 'Выход' ],
+       'Userrights'                => [ 'Керування_правами_користувачів', 'Управление_правами' ],
+       'Version'                   => [ 'Версія', 'Версия' ],
+       'Wantedcategories'          => [ 'Потрібні_категорії', 'Требуемые_категории' ],
+       'Wantedfiles'               => [ 'Потрібні_файли', 'Требуемые_файлы' ],
+       'Wantedpages'               => [ 'Потрібні_сторінки', 'Требуемые_страницы' ],
+       'Wantedtemplates'           => [ 'Потрібні_шаблони', 'Требуемые_шаблоны' ],
+       'Watchlist'                 => [ 'Список_спостереження', 'Список_наблюдения' ],
+       'Whatlinkshere'             => [ 'Посилання_сюди', 'Ссылки_сюда' ],
+       'Withoutinterwiki'          => [ 'Без_інтервікі', 'Без_интервики' ],
 ];
 
 $magicWords = [
@@ -368,8 +380,8 @@ $magicWords = [
        'pagesincategory_all'       => [ '0', 'усе', 'все', 'all' ],
        'pagesincategory_pages'     => [ '0', 'сторінки', 'страницы', 'pages' ],
        'pagesincategory_subcats'   => [ '0', 'підкатегорії', 'подкатегории', 'subcats' ],
+       'pagesincategory_files'     => [ '0', 'файли', 'файлы', 'files' ],
 ];
 
 $linkTrail = '/^([a-zабвгґдеєжзиіїйклмнопрстуфхцчшщьєюяёъы“»]+)(.*)$/sDu';
 $linkPrefixCharset = '„«';
-
diff --git a/maintenance/archives/patch-externallinks-el_index_60.sql b/maintenance/archives/patch-externallinks-el_index_60.sql
new file mode 100644 (file)
index 0000000..eacb107
--- /dev/null
@@ -0,0 +1,4 @@
+-- @since 1.29
+ALTER TABLE /*$wgDBprefix*/externallinks ADD COLUMN el_index_60 varbinary(60) NOT NULL DEFAULT '';
+CREATE INDEX /*i*/el_index_60 ON /*_*/externallinks (el_index_60, el_id);
+CREATE INDEX /*i*/el_from_index_60 ON /*_*/externallinks (el_from, el_index_60, el_id);
index 38daf64..18c7f11 100644 (file)
@@ -60,7 +60,7 @@ class BackupDumper extends Maintenance {
        /**
         * The dependency-injected database to use.
         *
-        * @var DatabaseBase|null
+        * @var IDatabase|null
         *
         * @see self::setDB
         */
@@ -314,7 +314,7 @@ class BackupDumper extends Maintenance {
         * @todo Fixme: the --server parameter is currently not respected, as it
         * doesn't seem terribly easy to ask the load balancer for a particular
         * connection by name.
-        * @return DatabaseBase
+        * @return IDatabase
         */
        function backupDb() {
                if ( $this->forcedDb !== null ) {
@@ -335,7 +335,7 @@ class BackupDumper extends Maintenance {
         * Force the dump to use the provided database connection for database
         * operations, wherever possible.
         *
-        * @param DatabaseBase|null $db (Optional) the database connection to use. If null, resort to
+        * @param IDatabase|null $db (Optional) the database connection to use. If null, resort to
         *   use the globally provided ways to get database connections.
         */
        function setDB( IDatabase $db = null ) {
index 453464a..bff2c13 100644 (file)
@@ -24,7 +24,6 @@
 use \Cdb\Exception as CdbException;
 use \Cdb\Reader as CdbReader;
 
-/** */
 require_once __DIR__ . '/commandLine.inc';
 
 function cdbShowHelp( $command ) {
index 914336f..a2a06d2 100644 (file)
@@ -1036,7 +1036,6 @@ csrf
 css
 cssclass
 csslinks
-cssprefs
 cta
 ctime
 ctor
index 4996446..d98e5cd 100644 (file)
@@ -32,7 +32,6 @@
 
 $optionsWithArgs = [ 'd' ];
 
-/** */
 require_once __DIR__ . "/commandLine.inc";
 
 if ( isset( $options['d'] ) ) {
index aad85da..9fe5009 100644 (file)
@@ -27,6 +27,7 @@
                                        "mw.notification",
                                        "mw.Notification_",
                                        "mw.storage",
+                                       "mw.storage.session",
                                        "mw.user",
                                        "mw.util",
                                        "mw.plugin.*",
index 8618a93..dee2164 100644 (file)
 馬鞌 马鞍
 觔斗 斤斗
 穀阳 穀阳
+伊東豊雄   伊东丰雄
index 3062c1e..3c7ea7a 100644 (file)
 划進 划進
 划過 划過
 划龍舟      划龍舟
+划龍船      划龍船
 只影響      只影響
 么弟 么弟
 六么 六么
 苧麻 苧麻
 张柏芝      張栢芝
 杜琪峰      杜琪峯
+單向 單向
+轉向 轉向 #分詞用
index dd38a30..b4cd628 100644 (file)
 碼錶
 錶冠
 魔錶
-彆口氣
-彆強
-皺彆
-一彆頭
 并州
 幽并
 併力
 於後
 猜三划五
 划龍舟
+划龍船
 南迴線
 南迴鐵路
 北迴線
 藉此
 龍捲
 捲舌
+不捲
+漫捲
+捲地
+捲瓣
+捲葉蛾
+捲尾猴
+捲積雲
 夸父
 夸克
 夸特
 水里溪
 二里頭
 年歷史
+年歷次
 西歷史
+西歷次
+西歷代
+西歷任
 國歷史
 國歷代
 國歷任
 不斗膽
 不每只
 不采聲
-專向往
+向往常
+向往日
+向往時
+向往來
+方向
+轉向
+單向 #分詞用
 丰容
 之一只
 之二只
 網球台
 合府上
 後面店
-向往常
-向往日
-向往時
-向往來
 唯一只
 喂了一聲
-喜向往
 四出徵收
 四面包
 多半只
 扎好根
 扑撻
 打吨
-折向往
 拉面上
 拉面具
 拉面前
 敢情欲
 敢斗了膽
 敲扑
-方向往
 望了望
 桌几
 每每只
 要自制
 語有云
 跌扑
-轉向往
 酒帘
 金表態
 金表情
 幸運鬍
 刮鬍
 剃鬍
-吹鬍
 蓄鬍
-白鬍
-長鬍
 鬍髯
 髯鬍
 髭鬍
 功勳
 蝎虎
 磨蝎
-方志恒
 古蹟
 瀋撫
 賦范
 乃係
 製衣
 巨製
-不捲
-漫捲
-捲地
-捲葉蛾
-捲尾猴
-捲積雲
 窗簾
 吉徵
 凶徵
 譯製
 燉製
 煮製
+熬製
 遏制 #以下分詞用
 管制
 抑制
 繫上,
 繫上。
 繫舟
+繫膜
 亂發生
 亂發脾氣
 秀發村
 啊喂
 呵喂
 呦喂
+哈囉喂
 松口鎮
+岩松了
 沙瑯
 琺瑯
 菜餚
 仁貴 #分詞用
 金聖歎
 天台 #分詞用
+性別扭曲
index a650aa0..bb47631 100644 (file)
@@ -36,11 +36,6 @@ $mmfl = false;
  * @ingroup Maintenance
  */
 class MergeMessageFileList extends Maintenance {
-       /**
-        * @var bool
-        */
-       protected $hasError;
-
        function __construct() {
                parent::__construct();
                $this->addOption(
@@ -106,7 +101,6 @@ class MergeMessageFileList extends Maintenance {
                                }
 
                                if ( !$found ) {
-                                       $this->hasError = true;
                                        $this->error( "Extension {$extname} in {$extdir} lacks expected entry point: " .
                                                "extension.json, skin.json, or {$extname}.php." );
                                }
@@ -119,10 +113,6 @@ class MergeMessageFileList extends Maintenance {
                        $mmfl['setupFiles'] = array_merge( $mmfl['setupFiles'], $extensionPaths );
                }
 
-               if ( $this->hasError ) {
-                       $this->error( "Some files are missing (see above). Giving up.", 1 );
-               }
-
                if ( $this->hasOption( 'output' ) ) {
                        $mmfl['output'] = $this->getOption( 'output' );
                }
index 256ee36..ba1f752 100644 (file)
@@ -389,11 +389,18 @@ CREATE TABLE /*_*/externallinks (
   -- which allows for fast searching for all pages under example.com with the
   -- clause:
   --      WHERE el_index LIKE 'http://com.example.%'
-  el_index nvarchar(450) NOT NULL
+  el_index nvarchar(450) NOT NULL,
+
+  -- This is el_index truncated to 60 bytes to allow for sortable queries that
+  -- aren't supported by a partial index.
+  -- @todo Drop the default once this is deployed everywhere and code is populating it.
+  el_index_60 varbinary(60) NOT NULL default ''
 );
 
 CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from);
 CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index);
+CREATE INDEX /*i*/el_index_60 ON /*_*/externallinks (el_index_60, el_id);
+CREATE INDEX /*i*/el_from_index_60 ON /*_*/externallinks (el_from, el_index_60, el_id);
 -- el_to index intentionally not added; we cannot index nvarchar(max) columns,
 -- but we also cannot restrict el_to to a smaller column size as the external
 -- link may be larger.
diff --git a/maintenance/oracle/archives/patch-externallinks-el_index_60.sql b/maintenance/oracle/archives/patch-externallinks-el_index_60.sql
new file mode 100644 (file)
index 0000000..c4b906d
--- /dev/null
@@ -0,0 +1,5 @@
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.externallinks ADD el_index_60 VARBINARY(60) NOT NULL DEFAULT '';
+CREATE INDEX &mw_prefix.externallinks_i04 ON &mw_prefix.externallinks (el_index_60, el_id);
+CREATE INDEX &mw_prefix.externallinks_i05 ON &mw_prefix.externallinks (el_from, el_index_60, el_id);
index 616b401..edb3398 100644 (file)
@@ -219,13 +219,16 @@ CREATE TABLE &mw_prefix.externallinks (
   el_id     NUMBER  NOT NULL,
   el_from   NUMBER  NOT NULL,
   el_to     VARCHAR2(2048) NOT NULL,
-  el_index  VARCHAR2(2048) NOT NULL
+  el_index  VARCHAR2(2048) NOT NULL,
+  el_index_60  VARBINARY(60) NOT NULL DEFAULT ''
 );
 ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_pk PRIMARY KEY (el_id);
 ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_fk1 FOREIGN KEY (el_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
 CREATE INDEX &mw_prefix.externallinks_i01 ON &mw_prefix.externallinks (el_from, el_to);
 CREATE INDEX &mw_prefix.externallinks_i02 ON &mw_prefix.externallinks (el_to, el_from);
 CREATE INDEX &mw_prefix.externallinks_i03 ON &mw_prefix.externallinks (el_index);
+CREATE INDEX &mw_prefix.externallinks_i04 ON &mw_prefix.externallinks (el_index_60, el_id);
+CREATE INDEX &mw_prefix.externallinks_i05 ON &mw_prefix.externallinks (el_from, el_index_60, el_id);
 
 CREATE TABLE &mw_prefix.langlinks (
   ll_from    NUMBER  NOT NULL,
index c6bd794..b41e0e0 100644 (file)
@@ -28,6 +28,10 @@ require_once __DIR__ . '/Maintenance.php';
  *  populateContentModel.php --ns=1 --table=page
  */
 class PopulateContentModel extends Maintenance {
+       protected $wikiId;
+
+       protected $wanCache;
+
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Populate the various content_* fields' );
@@ -38,6 +42,11 @@ class PopulateContentModel extends Maintenance {
 
        public function execute() {
                $dbw = $this->getDB( DB_MASTER );
+
+               $this->wikiId = $dbw->getWikiID();
+
+               $this->wanCache = ObjectCache::getMainWANInstance();
+
                $ns = $this->getOption( 'ns' );
                if ( !ctype_digit( $ns ) && $ns !== 'all' ) {
                        $this->error( 'Invalid namespace', 1 );
@@ -57,6 +66,18 @@ class PopulateContentModel extends Maintenance {
                }
        }
 
+       protected function clearCache( $page_id, $rev_id ) {
+               $contentModelKey = $this->wanCache->makeKey( 'page', 'content-model', $rev_id );
+               $revisionKey =
+                       $this->wanCache->makeGlobalKey( 'revision', $this->wikiId, $page_id, $rev_id );
+
+               // WikiPage content model cache
+               $this->wanCache->delete( $contentModelKey );
+
+               // Revision object cache, which contains a content model
+               $this->wanCache->delete( $revisionKey );
+       }
+
        private function updatePageRows( Database $dbw, $pageIds, $model ) {
                $count = count( $pageIds );
                $this->output( "Setting $count rows to $model..." );
@@ -117,6 +138,7 @@ class PopulateContentModel extends Maintenance {
                        [ $key => $ids ],
                        __METHOD__
                );
+
                $this->output( "done.\n" );
        }
 
@@ -130,19 +152,27 @@ class PopulateContentModel extends Maintenance {
                        $fields = [ 'ar_namespace', 'ar_title' ];
                        $join_conds = [];
                        $where = $ns === 'all' ? [] : [ 'ar_namespace' => $ns ];
+                       $page_id_column = 'ar_page_id';
+                       $rev_id_column = 'ar_rev_id';
                } else { // revision
                        $selectTables = [ 'revision', 'page' ];
                        $fields = [ 'page_title', 'page_namespace' ];
                        $join_conds = [ 'page' => [ 'INNER JOIN', 'rev_page=page_id' ] ];
                        $where = $ns === 'all' ? [] : [ 'page_namespace' => $ns ];
+                       $page_id_column = 'rev_page';
+                       $rev_id_column = 'rev_id';
                }
 
                $toSave = [];
+               $idsToClear = [];
                $lastId = 0;
                do {
                        $rows = $dbw->select(
                                $selectTables,
-                               array_merge( $fields, [ $model_column, $format_column, $key ] ),
+                               array_merge(
+                                       $fields,
+                                       [ $model_column, $format_column, $key, $page_id_column, $rev_id_column ]
+                               ),
                                // @todo support populating format if model is already set
                                [
                                        $model_column => null,
@@ -174,9 +204,17 @@ class PopulateContentModel extends Maintenance {
                                if ( $dbModel === null && $dbFormat === null ) {
                                        // Set the defaults
                                        $toSave[$defaultModel][] = $row->{$key};
+                                       $idsToClear[] = [
+                                               'page_id' => $row->{$page_id_column},
+                                               'rev_id' => $row->{$rev_id_column},
+                                       ];
                                } else { // $dbModel === null, $dbFormat set.
                                        if ( $dbFormat === $defaultFormat ) {
                                                $toSave[$defaultModel][] = $row->{$key};
+                                               $idsToClear[] = [
+                                                       'page_id' => $row->{$page_id_column},
+                                                       'rev_id' => $row->{$rev_id_column},
+                                               ];
                                        } else { // non-default format, just update now
                                                $this->output( "Updating model to match format for $table $id of $title... " );
                                                $dbw->update(
@@ -186,6 +224,7 @@ class PopulateContentModel extends Maintenance {
                                                        __METHOD__
                                                );
                                                wfWaitForSlaves();
+                                               $this->clearCache( $row->{$page_id_column}, $row->{$rev_id_column} );
                                                $this->output( "done.\n" );
                                                continue;
                                        }
@@ -200,6 +239,10 @@ class PopulateContentModel extends Maintenance {
                foreach ( $toSave as $model => $ids ) {
                        $this->updateRevisionOrArchiveRows( $dbw, $ids, $model, $table );
                }
+
+               foreach ( $idsToClear as $idPair ) {
+                       $this->clearCache( $idPair['page_id'], $idPair['rev_id'] );
+               }
        }
 }
 
index 2273761..61ad075 100644 (file)
@@ -254,13 +254,16 @@ CREATE INDEX cl_sortkey     ON categorylinks (cl_to, cl_sortkey, cl_from);
 
 CREATE SEQUENCE externallinks_el_id_seq;
 CREATE TABLE externallinks (
-  el_id     INTEGER  NOT NULL  PRIMARY KEY DEFAULT nextval('externallinks_el_id_seq'),
-  el_from   INTEGER  NOT NULL  REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
-  el_to     TEXT     NOT NULL,
-  el_index  TEXT     NOT NULL
+  el_id       INTEGER     NOT NULL  PRIMARY KEY DEFAULT nextval('externallinks_el_id_seq'),
+  el_from     INTEGER     NOT NULL  REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+  el_to       TEXT        NOT NULL,
+  el_index    TEXT        NOT NULL,
+  el_index_60 BYTEA       NOT NULL  DEFAULT ''
 );
 CREATE INDEX externallinks_from_to ON externallinks (el_from,el_to);
 CREATE INDEX externallinks_index   ON externallinks (el_index);
+CREATE INDEX el_index_60           ON externallinks (el_index_60, el_id);
+CREATE INDEX el_from_index_60      ON externallinks (el_from, el_index_60, el_id);
 
 CREATE TABLE langlinks (
   ll_from    INTEGER  NOT NULL  REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
index 31b2101..f6bb253 100644 (file)
@@ -41,8 +41,8 @@ class Protect extends Maintenance {
        }
 
        public function execute() {
-               $userName = $this->getOption( 'u', false );
-               $reason = $this->getOption( 'r', '' );
+               $userName = $this->getOption( 'user', false );
+               $reason = $this->getOption( 'reason', '' );
 
                $cascade = $this->hasOption( 'cascade' );
 
index df4ce56..372c352 100644 (file)
@@ -139,45 +139,53 @@ class RefreshImageMetadata extends Maintenance {
                        }
 
                        foreach ( $res as $row ) {
-                               // LocalFile will upgrade immediately here if obsolete
-                               $file = $repo->newFileFromRow( $row );
-                               if ( $file->getUpgraded() ) {
-                                       // File was upgraded.
-                                       $upgraded++;
-                                       $newLength = strlen( $file->getMetadata() );
-                                       $oldLength = strlen( $row->img_metadata );
-                                       if ( $newLength < $oldLength - 5 ) {
-                                               // If after updating, the metadata is smaller then
-                                               // what it was before, that's probably not a good thing
-                                               // because we extract more data with time, not less.
-                                               // Thus this probably indicates an error of some sort,
-                                               // or at the very least is suspicious. Have the - 5 just
-                                               // to weed out any inconsequential changes.
-                                               $error++;
-                                               $this->output( "Warning: File:{$row->img_name} used to have " .
-                                                       "$oldLength bytes of metadata but now has $newLength bytes.\n" );
-                                       } elseif ( $verbose ) {
-                                               $this->output( "Refreshed File:{$row->img_name}.\n" );
-                                       }
-                               } else {
-                                       $leftAlone++;
-                                       if ( $force ) {
-                                               $file->upgradeRow();
+                               try {
+                                       // LocalFile will upgrade immediately here if obsolete
+                                       $file = $repo->newFileFromRow( $row );
+                                       if ( $file->getUpgraded() ) {
+                                               // File was upgraded.
+                                               $upgraded++;
                                                $newLength = strlen( $file->getMetadata() );
                                                $oldLength = strlen( $row->img_metadata );
                                                if ( $newLength < $oldLength - 5 ) {
+                                                       // If after updating, the metadata is smaller then
+                                                       // what it was before, that's probably not a good thing
+                                                       // because we extract more data with time, not less.
+                                                       // Thus this probably indicates an error of some sort,
+                                                       // or at the very least is suspicious. Have the - 5 just
+                                                       // to weed out any inconsequential changes.
                                                        $error++;
-                                                       $this->output( "Warning: File:{$row->img_name} used to have " .
-                                                               "$oldLength bytes of metadata but now has $newLength bytes. (forced)\n" );
-                                               }
-                                               if ( $verbose ) {
-                                                       $this->output( "Forcibly refreshed File:{$row->img_name}.\n" );
+                                                       $this->output(
+                                                               "Warning: File:{$row->img_name} used to have " .
+                                                               "$oldLength bytes of metadata but now has $newLength bytes.\n"
+                                                       );
+                                               } elseif ( $verbose ) {
+                                                       $this->output( "Refreshed File:{$row->img_name}.\n" );
                                                }
                                        } else {
-                                               if ( $verbose ) {
-                                                       $this->output( "Skipping File:{$row->img_name}.\n" );
+                                               $leftAlone++;
+                                               if ( $force ) {
+                                                       $file->upgradeRow();
+                                                       $newLength = strlen( $file->getMetadata() );
+                                                       $oldLength = strlen( $row->img_metadata );
+                                                       if ( $newLength < $oldLength - 5 ) {
+                                                               $error++;
+                                                               $this->output(
+                                                                       "Warning: File:{$row->img_name} used to have " .
+                                                                       "$oldLength bytes of metadata but now has $newLength bytes. (forced)\n"
+                                                               );
+                                                       }
+                                                       if ( $verbose ) {
+                                                               $this->output( "Forcibly refreshed File:{$row->img_name}.\n" );
+                                                       }
+                                               } else {
+                                                       if ( $verbose ) {
+                                                               $this->output( "Skipping File:{$row->img_name}.\n" );
+                                                       }
                                                }
                                        }
+                               } catch ( Exception $e ) {
+                                       $this->output( "{$row->img_name} failed. {$e->getMessage()}\n" );
                                }
                        }
                        $conds2 = [ 'img_name > ' . $dbw->addQuotes( $row->img_name ) ];
index 28be6a3..c17ce99 100644 (file)
@@ -51,13 +51,11 @@ require_once __DIR__ . '/../Maintenance.php';
 class CompressOld extends Maintenance {
        /**
         * Option to load each revision individually.
-        *
         */
        const LS_INDIVIDUAL = 0;
 
        /**
         * Option to load revisions in chunks.
-        *
         */
        const LS_CHUNKED = 1;
 
index cf60d89..2b6ea03 100644 (file)
@@ -676,12 +676,19 @@ CREATE TABLE /*_*/externallinks (
   -- which allows for fast searching for all pages under example.com with the
   -- clause:
   --      WHERE el_index LIKE 'http://com.example.%'
-  el_index blob NOT NULL
+  el_index blob NOT NULL,
+
+  -- This is el_index truncated to 60 bytes to allow for sortable queries that
+  -- aren't supported by a partial index.
+  -- @todo Drop the default once this is deployed everywhere and code is populating it.
+  el_index_60 varbinary(60) NOT NULL default ''
 ) /*$wgDBTableOptions*/;
 
 CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
 CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
 CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+CREATE INDEX /*i*/el_index_60 ON /*_*/externallinks (el_index_60, el_id);
+CREATE INDEX /*i*/el_from_index_60 ON /*_*/externallinks (el_from, el_index_60, el_id);
 
 --
 -- Track interlanguage links
index b9baf8c..9906990 100644 (file)
@@ -2,73 +2,22 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
-use Composer\Spdx\SpdxLicenses;
-use JsonSchema\Validator;
-
 class ValidateRegistrationFile extends Maintenance {
        public function __construct() {
                parent::__construct();
                $this->addArg( 'path', 'Path to extension.json/skin.json file.', true );
        }
        public function execute() {
-               if ( !class_exists( Validator::class ) ) {
-                       $this->error( 'The JsonSchema library cannot be found, please install it through composer.', 1 );
-               } elseif ( !class_exists( SpdxLicenses::class ) ) {
-                       $this->error(
-                               'The spdx-licenses library cannot be found, please install it through composer.', 1
-                       );
-               }
-
+               $validator = new ExtensionJsonValidator( function( $msg ) {
+                       $this->error( $msg, 1 );
+               } );
+               $validator->checkDependencies();
                $path = $this->getArg( 0 );
-               $data = json_decode( file_get_contents( $path ) );
-               if ( !is_object( $data ) ) {
-                       $this->error( "$path is not a valid JSON file.", 1 );
-               }
-               if ( !isset( $data->manifest_version ) ) {
-                       $this->output( "Warning: No manifest_version set, assuming 1.\n" );
-                       // For backwards-compatability assume 1
-                       $data->manifest_version = 1;
-               }
-               $version = $data->manifest_version;
-               if ( $version !== ExtensionRegistry::MANIFEST_VERSION ) {
-                       $schemaPath = dirname( __DIR__ ) . "/docs/extension.schema.v$version.json";
-               } else {
-                       $schemaPath = dirname( __DIR__ ) . '/docs/extension.schema.json';
-               }
-
-               if ( $version < ExtensionRegistry::OLDEST_MANIFEST_VERSION
-                       || $version > ExtensionRegistry::MANIFEST_VERSION
-               ) {
-                       $this->error( "Error: $path is using a non-supported schema version, it should use "
-                               . ExtensionRegistry::MANIFEST_VERSION, 1 );
-               } elseif ( $version < ExtensionRegistry::MANIFEST_VERSION ) {
-                       $this->output( "Warning: $path is using a deprecated schema, and should be updated to "
-                               . ExtensionRegistry::MANIFEST_VERSION . "\n" );
-               }
-
-               $licenseError = false;
-               // Check if it's a string, if not, schema validation will display an error
-               if ( isset( $data->{'license-name'} ) && is_string( $data->{'license-name'} ) ) {
-                       $licenses = new SpdxLicenses();
-                       $valid = $licenses->validate( $data->{'license-name'} );
-                       if ( !$valid ) {
-                               $licenseError = '[license-name] Invalid SPDX license identifier, '
-                                       . 'see <https://spdx.org/licenses/>';
-                       }
-               }
-
-               $validator = new Validator;
-               $validator->check( $data, (object)[ '$ref' => 'file://' . $schemaPath ] );
-               if ( $validator->isValid() && !$licenseError ) {
-                       $this->output( "$path validates against the version $version schema!\n" );
-               } else {
-                       foreach ( $validator->getErrors() as $error ) {
-                               $this->output( "[{$error['property']}] {$error['message']}\n" );
-                       }
-                       if ( $licenseError ) {
-                               $this->output( "$licenseError\n" );
-                       }
-                       $this->error( "$path does not validate.", 1 );
+               try {
+                       $validator->validate( $path );
+                       $this->output( "$path validates against the schema!\n" );
+               } catch ( ExtensionJsonValidationError $e ) {
+                       $this->error( $e->getMessage(), 1 );
                }
        }
 }
index 926ac43..be9debc 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+// @codingStandardsIgnoreFile Generic.Arrays.DisallowLongArraySyntax
 /**
  * New version of MediaWiki web-based config/installation
  *
@@ -60,7 +61,7 @@ function wfInstallerMain() {
        if ( isset( $_SESSION['installData'][$fingerprint] ) ) {
                $session = $_SESSION['installData'][$fingerprint];
        } else {
-               $session = [];
+               $session = array();
        }
 
        if ( !is_null( $wgRequest->getVal( 'uselang' ) ) ) {
index 587a84d..4932a29 100644 (file)
@@ -53,9 +53,6 @@ return [
        'user' => [ 'class' => 'ResourceLoaderUserModule' ],
        'user.styles' => [ 'class' => 'ResourceLoaderUserStylesModule' ],
 
-       // Scripts generated based on the current user's preferences
-       'user.cssprefs' => [ 'class' => 'ResourceLoaderUserCSSPrefsModule' ],
-
        // Populate mediawiki.user placeholders with information about the current user
        'user.defaults' => [ 'class' => 'ResourceLoaderUserDefaultsModule' ],
        'user.options' => [ 'class' => 'ResourceLoaderUserOptionsModule' ],
@@ -1741,6 +1738,8 @@ return [
                        'mediawiki.api.watch',
                        'mediawiki.notify',
                        'mediawiki.util',
+                       'mediawiki.Title',
+                       'mediawiki.jqueryMsg',
                        'jquery.accessKeyLabel',
                        'mediawiki.RegExp',
                ],
@@ -1752,6 +1751,10 @@ return [
                        'tooltip-ca-watch',
                        'tooltip-ca-unwatch',
                        'watcherrortext',
+                       'addedwatchtext',
+                       'addedwatchtext-talk',
+                       'removedwatchtext',
+                       'removedwatchtext-talk',
                ],
        ],
        'mediawiki.page.rollback' => [
@@ -2030,6 +2033,13 @@ return [
        ],
        'mediawiki.special.watchlist' => [
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.js',
+               'messages' => 'watchlist-mark-all-visited',
+               'dependencies' => [
+                       'mediawiki.api',
+                       'mediawiki.jqueryMsg',
+                       'oojs-ui-windows',
+                       'user.options',
+               ]
        ],
        'mediawiki.special.version' => [
                'styles' => 'resources/src/mediawiki.special/mediawiki.special.version.css',
@@ -2289,6 +2299,32 @@ return [
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
+       'mediawiki.widgets.MediaSearch' => [
+               'scripts' => [
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsProvider.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsQueue.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceProvider.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchProvider.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceQueue.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchQueue.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.js',
+               ],
+               'styles' => [
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.css',
+                       'resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.css',
+               ],
+               'dependencies' => [
+                       'oojs-ui-widgets',
+                       'mediawiki.ForeignApi',
+                       'mediawiki.Title',
+               ],
+               'messages' => [
+                       'mw-widgets-mediasearch-noresults',
+                       'mw-widgets-mediasearch-input-placeholder',
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+       ],
        'mediawiki.widgets.UserInputWidget' => [
                'scripts' => [
                        'resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js',
index 8538417..98835d5 100644 (file)
@@ -24,6 +24,7 @@
        "ooui-dialog-process-dismiss": "Απόρριψη",
        "ooui-dialog-process-retry": "Δοκιμάστε ξανά",
        "ooui-dialog-process-continue": "Συνέχεια",
+       "ooui-selectfile-button-select": "Επιλέξτε ένα αρχείο",
        "ooui-selectfile-not-supported": "Επιλογή αρχείου δεν υποστηρίζεται",
        "ooui-selectfile-placeholder": "Κανένα αρχείο δεν είναι επιλεγμένο",
        "ooui-selectfile-dragdrop-placeholder": "Σύρετε το αρχείο εδώ"
index 542447d..1acedf2 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-12-06T23:32:53Z
  */
 ( function ( OO ) {
 
index bcc3778..72df673 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-element-hidden {
   display: none !important;
@@ -832,10 +832,14 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   display: none;
 }
 .oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
+.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
   display: block;
   position: absolute;
   top: 0;
+}
+.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
   height: 100%;
   -webkit-touch-callout: none;
   -webkit-user-select: none;
@@ -843,21 +847,24 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
       -ms-user-select: none;
           user-select: none;
 }
+.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
+  left: 0;
+}
+.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
+  right: 0;
+}
 .oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-labelElement-label {
   cursor: text;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
   cursor: pointer;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled input,
-.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-  -webkit-touch-callout: none;
-  -webkit-user-select: none;
-     -moz-user-select: none;
-      -ms-user-select: none;
-          user-select: none;
-}
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea,
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
   -webkit-touch-callout: none;
   -webkit-user-select: none;
@@ -865,21 +872,6 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
       -ms-user-select: none;
           user-select: none;
 }
-.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
-  display: block;
-}
-.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
-  left: 0;
-}
-.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
-.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
-  right: 0;
-}
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-  position: absolute;
-  top: 0;
-}
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
   padding: 0.5em;
index e7c2ee0..bab34b8 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-element-hidden {
   display: none !important;
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > input.oo-ui-buttonElement-button,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:active {
   color: #000;
+  box-shadow: none;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
   color: #36c;
   color: #447ff5;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active:focus > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
   color: #2a4b8d;
   box-shadow: none;
   color: #447ff5;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:active:focus > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
   color: #2a4b8d;
   box-shadow: none;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-  color: #c33;
+  color: #d33;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
-  color: #e53939;
+  color: #ff4242;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-  color: #873636;
+  color: #b32424;
   box-shadow: none;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
   box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active:focus,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-  background-color: #d9d9d9;
+  background-color: #c8ccd1;
   color: #000;
   border-color: #72777d;
+  box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
   background-color: #2a4b8d;
   box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
-  color: #c33;
+  color: #d33;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover {
   background-color: #fff;
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-  background-color: #fbf4f4;
-  color: #873636;
-  border-color: #873636;
+  background-color: #ffffff;
+  color: #b32424;
+  border-color: #b32424;
   box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus {
-  border-color: #c33;
-  box-shadow: inset 0 0 0 1px #c33;
+  border-color: #d33;
+  box-shadow: inset 0 0 0 1px #d33;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
   color: #fff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
   color: #fff;
-  background-color: #c33;
-  border-color: #c33;
+  background-color: #d33;
+  border-color: #d33;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover {
-  background-color: #e53939;
-  border-color: #e53939;
+  background-color: #ff4242;
+  border-color: #ff4242;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
   color: #fff;
-  background-color: #873636;
-  border-color: #873636;
+  background-color: #b32424;
+  border-color: #b32424;
   box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus {
-  border-color: #c33;
-  box-shadow: inset 0 0 0 1px #c33, inset 0 0 0 2px #fff;
+  border-color: #d33;
+  box-shadow: inset 0 0 0 1px #d33, inset 0 0 0 2px #fff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
 }
 .oo-ui-fieldLayout {
   display: block;
-  margin-bottom: 1em;
+  margin-top: 1.640625em;
 }
 .oo-ui-fieldLayout:before,
 .oo-ui-fieldLayout:after {
   padding: 0.5em 0.75em;
   line-height: 1.5;
 }
-.oo-ui-fieldLayout:last-child {
-  margin-bottom: 0;
+.oo-ui-fieldLayout.oo-ui-labelElement,
+.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
+  margin-top: 1.171875em;
 }
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label,
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
-  padding-top: 0.5em;
-  margin-right: 5%;
-  width: 35%;
+.oo-ui-fieldLayout:first-child,
+.oo-ui-fieldLayout.oo-ui-labelElement:first-child,
+.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline:first-child {
+  margin-top: 0;
 }
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field,
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
-  width: 60%;
+.oo-ui-fieldLayout.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+  padding-bottom: 0.3125em;
 }
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
-  margin-bottom: 1.25em;
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+  padding: 0.3125em 0.46875em;
 }
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
-  padding: 0.25em 0.25em 0.25em 0.5em;
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label,
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+  width: 35%;
+  margin-right: 5%;
+  padding-top: 0.3125em;
 }
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
-  padding-top: 0.25em;
-  padding-bottom: 0.5em;
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field,
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+  width: 60%;
+}
+.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+  color: #72777d;
 }
 .oo-ui-fieldLayout > .oo-ui-popupButtonWidget {
   margin-right: 0;
 .oo-ui-fieldLayout > .oo-ui-popupButtonWidget:last-child {
   margin-right: 0;
 }
-.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
-  color: #72777d;
-}
 .oo-ui-fieldLayout-messages {
   list-style: none none;
   margin: 0.25em 0 0 0.25em;
 }
 .oo-ui-fieldLayout-messages .oo-ui-iconWidget {
   display: table-cell;
-  border-right: 0.5em solid transparent;
 }
 .oo-ui-fieldLayout-messages .oo-ui-labelWidget {
   display: table-cell;
-  padding: 0.1em 0;
+  padding: 0.1em 0 0.1em 0.3125em;
   line-height: 1.5;
   vertical-align: middle;
 }
@@ -552,7 +559,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   margin-top: 2em;
 }
 .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
-  margin-bottom: 0.5em;
+  margin-bottom: 0.56818em;
   font-size: 1.1em;
   font-weight: bold;
 }
@@ -687,7 +694,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   background-color: transparent;
 }
 .oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-  padding: 0.25em 0.25em 0.25em 0.5em;
+  padding: 0.25em 0.25em 0.25em 0.46875em;
 }
 .oo-ui-radioOptionWidget .oo-ui-radioInputWidget {
   margin-right: 0;
@@ -881,7 +888,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 }
 .oo-ui-checkboxInputWidget {
   position: relative;
-  line-height: 1.6em;
+  line-height: 1.5625em;
   white-space: nowrap;
 }
 .oo-ui-checkboxInputWidget * {
@@ -891,8 +898,8 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 .oo-ui-checkboxInputWidget [type='checkbox'] {
   position: relative;
   max-width: none;
-  width: 1.6em;
-  height: 1.6em;
+  width: 1.5625em;
+  height: 1.5625em;
   margin: 0;
   opacity: 0;
   z-index: 1;
@@ -908,8 +915,8 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
           box-sizing: border-box;
   position: absolute;
   left: 0;
-  width: 1.6em;
-  height: 1.6em;
+  width: 1.5625em;
+  height: 1.5625em;
   border: 1px solid #72777d;
   border-radius: 2px;
 }
@@ -1031,7 +1038,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 }
 .oo-ui-radioInputWidget {
   position: relative;
-  line-height: 1.6em;
+  line-height: 1.5625em;
   white-space: nowrap;
 }
 .oo-ui-radioInputWidget * {
@@ -1041,8 +1048,8 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 .oo-ui-radioInputWidget [type='radio'] {
   position: relative;
   max-width: none;
-  width: 1.6em;
-  height: 1.6em;
+  width: 1.5625em;
+  height: 1.5625em;
   margin: 0;
   opacity: 0;
   z-index: 1;
@@ -1054,8 +1061,8 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
           box-sizing: border-box;
-  width: 1.6em;
-  height: 1.6em;
+  width: 1.5625em;
+  height: 1.5625em;
   border: 1px solid #72777d;
   border-radius: 100%;
 }
@@ -1070,11 +1077,11 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   border-radius: 100%;
 }
 .oo-ui-radioInputWidget [type='radio']:checked + span {
-  border-width: 0.4em;
+  border-width: 0.390625em;
 }
 .oo-ui-radioInputWidget [type='radio']:checked:hover + span,
 .oo-ui-radioInputWidget [type='radio']:checked:focus:hover + span {
-  border-width: 0.4em;
+  border-width: 0.390625em;
 }
 .oo-ui-radioInputWidget [type='radio']:disabled + span {
   background-color: #c8ccd1;
@@ -1182,10 +1189,14 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   display: none;
 }
 .oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
+.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget > .oo-ui-labelElement-label {
   display: block;
   position: absolute;
   top: 0;
+}
+.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
   height: 100%;
   -webkit-touch-callout: none;
   -webkit-user-select: none;
@@ -1193,21 +1204,24 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
       -ms-user-select: none;
           user-select: none;
 }
+.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
+  left: 0;
+}
+.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
+  right: 0;
+}
 .oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator,
+.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-labelElement-label {
   cursor: text;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
   cursor: pointer;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled input,
-.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-  -webkit-touch-callout: none;
-  -webkit-user-select: none;
-     -moz-user-select: none;
-      -ms-user-select: none;
-          user-select: none;
-}
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea,
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
   -webkit-touch-callout: none;
   -webkit-user-select: none;
@@ -1215,37 +1229,22 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
       -ms-user-select: none;
           user-select: none;
 }
-.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
-  display: block;
-}
-.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label {
-  left: 0;
-}
-.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator,
-.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label {
-  right: 0;
-}
-.oo-ui-textInputWidget > .oo-ui-labelElement-label {
-  position: absolute;
-  top: 0;
-}
 .oo-ui-textInputWidget input,
 .oo-ui-textInputWidget textarea {
-  margin: 0;
   font-size: inherit;
   font-family: inherit;
   background-color: #fff;
   color: #000;
   border: 1px solid #a2a9b1;
   border-radius: 2px;
-  padding: 0.625em 0.546875em 0.546875em;
 }
 .oo-ui-textInputWidget input {
+  padding: 0.625em 0.546875em 0.546875em;
   line-height: 1.172em;
 }
 .oo-ui-textInputWidget textarea {
-  line-height: 1.275;
+  padding: 0.46875em 0.546875em 0.546875em;
+  line-height: 1.4;
 }
 .oo-ui-textInputWidget .oo-ui-pendingElement-pending {
   background-color: transparent;
@@ -1310,51 +1309,50 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   border-color: #f00;
   box-shadow: inset 0 0 0 0.1em #f00;
 }
-.oo-ui-textInputWidget.oo-ui-widget-disabled input,
-.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-  background-color: #eaecf0;
-  color: #72777d;
-  text-shadow: 0 1px 1px #fff;
-  border-color: #c8ccd1;
-}
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-  opacity: 0.51;
-}
-.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
-  color: #72777d;
-  text-shadow: 0 1px 1px #fff;
-}
 .oo-ui-textInputWidget.oo-ui-iconElement input,
 .oo-ui-textInputWidget.oo-ui-iconElement textarea {
-  padding-left: 2.875em;
+  padding-left: 2.65625em;
 }
 .oo-ui-textInputWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-  left: 0;
-  height: 100%;
-  max-height: 2.375em;
-  margin-left: 0.5em;
-  background-position: right center;
+  max-height: 2.5em;
+  left: 0.46875em;
 }
 .oo-ui-textInputWidget.oo-ui-indicatorElement input,
 .oo-ui-textInputWidget.oo-ui-indicatorElement textarea {
   padding-right: 2.4875em;
 }
 .oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
-  height: 100%;
-  max-height: 2.375em;
-  margin: 0 0.775em;
+  max-height: 2.5em;
+  right: 0.625em;
 }
 .oo-ui-textInputWidget > .oo-ui-labelElement-label {
   color: #72777d;
-  padding: 0.4em;
-  line-height: 1.5;
+  right: 0.625em;
+  border: 1px solid transparent;
+  border-width: 1px 0;
+  padding: 0.625em 0 0.546875em;
+  line-height: 1.172em;
 }
 .oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
-  margin-right: 2.0875em;
+  right: 2.1875em;
 }
 .oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label {
-  margin-left: 2.475em;
+  left: 2.65625em;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
+  background-color: #eaecf0;
+  color: #72777d;
+  text-shadow: 0 1px 1px #fff;
+  border-color: #c8ccd1;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+  opacity: 0.51;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+  color: #72777d;
+  text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-menuSelectWidget {
   position: absolute;
@@ -1469,12 +1467,17 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 }
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover {
   background-color: #fff;
+  color: #444;
   border-color: #a2a9b1;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-iconElement-icon,
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-indicatorElement-indicator {
   opacity: 0.73;
 }
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:active {
+  color: #000;
+  border-color: #72777d;
+}
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:focus {
   border-color: #36c;
   outline: 0;
@@ -1610,7 +1613,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   vertical-align: middle;
 }
 .oo-ui-checkboxMultioptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-  padding: 0.25em 0.25em 0.25em 0.5em;
+  padding: 0.25em 0.25em 0.25em 0.46875em;
 }
 .oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget {
   margin-right: 0;
index fd4e033..b92094c 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-12-06T23:32:53Z
  */
 ( function ( OO ) {
 
@@ -414,7 +414,7 @@ OO.ui.infuse = function ( idOrNode ) {
                }
                return message;
        };
-} )();
+}() );
 
 /**
  * Package a message and arguments for deferred resolution.
@@ -4308,8 +4308,7 @@ OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.ClippableElement );
 OO.ui.PopupWidget.prototype.onMouseDown = function ( e ) {
        if (
                this.isVisible() &&
-               !$.contains( this.$element[ 0 ], e.target ) &&
-               ( !this.$autoCloseIgnore || !this.$autoCloseIgnore.has( e.target ).length )
+               !OO.ui.contains( this.$element.add( this.$autoCloseIgnore ).get(), e.target, true )
        ) {
                this.toggle( false );
        }
@@ -4597,7 +4596,7 @@ OO.ui.mixin.PopupElement = function OoUiMixinPopupElement( config ) {
        this.popup = new OO.ui.PopupWidget( $.extend(
                { autoClose: true },
                config.popup,
-               { $autoCloseIgnore: this.$element }
+               { $autoCloseIgnore: this.$element.add( config.popup && config.popup.$autoCloseIgnore ) }
        ) );
 };
 
@@ -9797,7 +9796,7 @@ OO.ui.ComboBoxInputWidget.prototype.setOptions = function ( options ) {
  * @throws {Error} An error is thrown if no widget is specified
  */
 OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
-       var hasInputWidget, div;
+       var hasInputWidget, $div;
 
        // Allow passing positional parameters inside the config object
        if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
@@ -9837,14 +9836,14 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
                        icon: 'info'
                } );
 
-               div = $( '<div>' );
+               $div = $( '<div>' );
                if ( config.help instanceof OO.ui.HtmlSnippet ) {
-                       div.html( config.help.toString() );
+                       $div.html( config.help.toString() );
                } else {
-                       div.text( config.help );
+                       $div.text( config.help );
                }
                this.popupButtonWidget.getPopup().$body.append(
-                       div.addClass( 'oo-ui-fieldLayout-help-content' )
+                       $div.addClass( 'oo-ui-fieldLayout-help-content' )
                );
                this.$help = this.popupButtonWidget.$element;
        } else {
@@ -10138,8 +10137,13 @@ OO.inheritClass( OO.ui.ActionFieldLayout, OO.ui.FieldLayout );
  * @constructor
  * @param {Object} [config] Configuration options
  * @cfg {OO.ui.FieldLayout[]} [items] An array of fields to add to the fieldset. See OO.ui.FieldLayout for more information about fields.
+ * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified, a "help" icon will appear
+ *  in the upper-right corner of the rendered field; clicking it will display the text in a popup.
+ *  For important messages, you are advised to use `notices`, as they are always shown.
  */
 OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
+       var $div;
+
        // Configuration initialization
        config = config || {};
 
@@ -10148,7 +10152,7 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
 
        // Mixin constructors
        OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: $( '<legend>' ) } ) );
+       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: $( '<div>' ) } ) );
        OO.ui.mixin.GroupElement.call( this, config );
 
        if ( config.help ) {
@@ -10158,10 +10162,14 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
                        icon: 'info'
                } );
 
+               $div = $( '<div>' );
+               if ( config.help instanceof OO.ui.HtmlSnippet ) {
+                       $div.html( config.help.toString() );
+               } else {
+                       $div.text( config.help );
+               }
                this.popupButtonWidget.getPopup().$body.append(
-                       $( '<div>' )
-                               .text( config.help )
-                               .addClass( 'oo-ui-fieldsetLayout-help-content' )
+                       $div.addClass( 'oo-ui-fieldsetLayout-help-content' )
                );
                this.$help = this.popupButtonWidget.$element;
        } else {
index 17bca7e..7dc6bef 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-12-06T23:32:53Z
  */
 ( function ( OO ) {
 
index 7fb36c4..baf8833 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index cb9660a..99a1f5e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-tool.oo-ui-widget-enabled {
   -webkit-transition: background-color 100ms;
index e17f511..e45ca29 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-12-06T23:32:53Z
  */
 ( function ( OO ) {
 
index d6ba00b..318bf82 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
index bf50532..a4db2a3 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
 .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon {
   position: absolute;
 }
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content :-moz-placeholder {
+  color: #72777d;
+  opacity: 1;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content ::-moz-placeholder {
+  color: #72777d;
+  opacity: 1;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content :-ms-input-placeholder {
+  color: #72777d;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content ::-webkit-input-placeholder {
+  color: #72777d;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content :placeholder-shown {
+  color: #72777d;
+}
 .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input {
   border: 0;
   line-height: 1.675;
index 6962c92..1185fc1 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-12-06T23:32:53Z
  */
 ( function ( OO ) {
 
@@ -4074,8 +4074,7 @@ OO.ui.CapsuleMultiselectWidget.prototype.onPopupFocusOut = function () {
        setTimeout( function () {
                if (
                        widget.isVisible() &&
-                       !OO.ui.contains( widget.$element[ 0 ], document.activeElement, true ) &&
-                       ( !widget.$autoCloseIgnore || !widget.$autoCloseIgnore.has( document.activeElement ).length )
+                       !OO.ui.contains( widget.$element.add( widget.$autoCloseIgnore ).get(), document.activeElement, true )
                ) {
                        widget.toggle( false );
                }
index 6258b84..ad0e7ab 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
   background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
index 359c469..ecc0004 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-12-06T23:32:57Z
  */
 .oo-ui-window {
   background: transparent;
index 8b614c6..b47b0c8 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-12-06T23:32:53Z
  */
 ( function ( OO ) {
 
@@ -1286,7 +1286,8 @@ OO.ui.WindowManager.prototype.getCurrentWindow = function () {
  *
  * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
  * @param {Object} [data] Window opening data
- * @param {jQuery} [data.$returnFocusTo] Element to which the window will return focus when closed.
+ * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when closed.
+ *  Defaults the current activeElement. If set to null, focus isn't changed on close.
  * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
  *  See {@link #event-opening 'opening' event}  for more information about `opening` promises.
  * @fires opening
@@ -1418,7 +1419,9 @@ OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {
                                                                manager.toggleGlobalEvents( false );
                                                                manager.toggleAriaIsolation( false );
                                                        }
-                                                       manager.$returnFocusTo[ 0 ].focus();
+                                                       if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) {
+                                                               manager.$returnFocusTo[ 0 ].focus();
+                                                       }
                                                        manager.closing = null;
                                                        manager.currentWindow = null;
                                                        closing.resolve( data );
index f5694a1..e6fa863 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 651cddf..80bbcaf 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 6b9e490..21efb82 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 11fcef7..4515405 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index cd4087e..3edb545 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index d168364..c97d770 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 7efe531..f110a04 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 765b8fe..6ff4a0e 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index c844449..7098f23 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 8c7b845..afdb9e5 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index e98012f..3779ae3 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index c545a49..059073f 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 39fdda5..5a70c5e 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index bac9768..61aec85 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 6a7c565..4666fd1 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 53460be..54b9f62 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.png differ
index abf656f..f01a779 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
 </svg>
index 000e529..f574a39 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.png differ
index b2b0179..3391d4e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <g id="cancel">
         <path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
     </g>
index 0cc9169..305f41d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.png differ
index 7e3dc53..059f0bd 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index 42311de..e16f042 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.png differ
index a9900c1..2cfa62e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
 </svg>
index 72d6a7b..29cd2b5 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.png differ
index 2811b25..2daea47 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
 </svg>
index 55a68db..441fe2d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-invert.png differ
index 663913a..548e136 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
-    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 3c-1.6 0-3-1.4-3-3s1.4-3 3-3 3 1.4 3 3-1.4 3-3 3z"/>
 </svg>
index c1676e6..2e7107d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png differ
index a9631cc..daf032a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
-    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 3c-1.6 0-3-1.4-3-3s1.4-3 3-3 3 1.4 3 3-1.4 3-3 3z"/>
 </svg>
index 536e77c..ddb1c5c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin.png differ
index f1fa246..26fb6b7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 3c-1.6 0-3-1.4-3-3s1.4-3 3-3 3 1.4 3 3-1.4 3-3 3z"/>
 </svg>
index 607354c..ee5de90 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-invert.png differ
index 43074af..cfa98d8 100644 (file)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
-    <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
-    <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+  <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4V4z"/>
+  <path d="M18.9 11c.1.3.1.7.1 1 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-3.9 3.1-7 7-7 .3 0 .7 0 1 .1V7h3.9l.1.1V11h1.9zM15 12c0-1.6-1.4-3-3-3s-3 1.4-3 3 1.4 3 3 3 3-1.4 3-3z"/>
 </svg>
+
index 2fcf2e1..036a31d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png differ
index 7dc09d4..7cf1509 100644 (file)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
-    <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
-    <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+  <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4V4z"/>
+  <path d="M18.9 11c.1.3.1.7.1 1 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-3.9 3.1-7 7-7 .3 0 .7 0 1 .1V7h3.9l.1.1V11h1.9zM15 12c0-1.6-1.4-3-3-3s-3 1.4-3 3 1.4 3 3 3 3-1.4 3-3z"/>
 </svg>
+
index 88160bc..d8c1691 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr.png differ
index d84970f..9d71335 100644 (file)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
-    <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+  <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4V4z"/>
+  <path d="M18.9 11c.1.3.1.7.1 1 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-3.9 3.1-7 7-7 .3 0 .7 0 1 .1V7h3.9l.1.1V11h1.9zM15 12c0-1.6-1.4-3-3-3s-3 1.4-3 3 1.4 3 3 3 3-1.4 3-3z"/>
 </svg>
+
index 6ea8226..934d5cc 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-invert.png differ
index 6a4af93..8b02ddb 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
-    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
-    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
+    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0"/>
+    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 4c1.65 0 3-1.35 3-3s-1.35-3-3-3-3 1.35-3 3 1.35 3 3 3z"/>
 </svg>
index 56b7924..be7c51e 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png differ
index 8108685..c920c8d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
-    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
-    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
+    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0"/>
+    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 4c1.65 0 3-1.35 3-3s-1.35-3-3-3-3 1.35-3 3 1.35 3 3 3z"/>
 </svg>
index 20aba25..7cc1f74 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl.png differ
index 8f35458..03c484a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
-    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
+    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0"/>
+    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 4c1.65 0 3-1.35 3-3s-1.35-3-3-3-3 1.35-3 3 1.35 3 3 3z"/>
 </svg>
index 55ab6c4..cf85c4d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.png differ
index 7048a40..0732f2e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index c1d2a66..c367e77 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.png differ
index 3ebc63b..59ad3f2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
 </svg>
index 8fb039c..2cb27f1 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.png differ
index 7ee7522..d45ebac 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
 </svg>
index 7c2786d..fac51b9 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.png differ
index a5f2721..5e6d205 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
     <path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
 </svg>
index 349227a..91d0358 100644 (file)
@@ -15,7 +15,7 @@
                        "color": "#36c"
                },
                "destructive": {
-                       "color": "#c33"
+                       "color": "#d33"
                },
                "warning": {
                        "color": "#ff5d00"
index 8c78b67..ae68fc4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.22.0
+ * QUnit 1.23.1
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-02-23T15:57Z
+ * Date: 2016-04-12T17:29Z
  */
 
 /** Font Family and Sizes */
index 84873ae..5df0822 100644 (file)
@@ -1,15 +1,15 @@
 /*!
- * QUnit 1.22.0
+ * QUnit 1.23.1
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-02-23T15:57Z
+ * Date: 2016-04-12T17:29Z
  */
 
-(function( global ) {
+( function( global ) {
 
 var QUnit = {};
 
@@ -27,7 +27,7 @@ var window = global.window;
 var defined = {
        document: window && window.document !== undefined,
        setTimeout: setTimeout !== undefined,
-       sessionStorage: (function() {
+       sessionStorage: ( function() {
                var x = "qunit-test-string";
                try {
                        sessionStorage.setItem( x, x );
@@ -46,7 +46,7 @@ var runStarted = false;
 var toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty;
 
-// returns a new Array with the elements that are in a but not in b
+// Returns a new Array with the elements that are in a but not in b
 function diff( a, b ) {
        var i, j,
                result = a.slice();
@@ -63,7 +63,7 @@ function diff( a, b ) {
        return result;
 }
 
-// from jquery.js
+// From jquery.js
 function inArray( elem, array ) {
        if ( array.indexOf ) {
                return array.indexOf( elem );
@@ -157,32 +157,6 @@ function is( type, obj ) {
        return QUnit.objectType( obj ) === type;
 }
 
-var getUrlParams = function() {
-       var i, param, name, value;
-       var urlParams = {};
-       var location = window.location;
-       var params = location.search.slice( 1 ).split( "&" );
-       var length = params.length;
-
-       for ( i = 0; i < length; i++ ) {
-               if ( params[ i ] ) {
-                       param = params[ i ].split( "=" );
-                       name = decodeURIComponent( param[ 0 ] );
-
-                       // allow just a key to turn on a flag, e.g., test.html?noglobals
-                       value = param.length === 1 ||
-                               decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
-                       if ( urlParams[ name ] ) {
-                               urlParams[ name ] = [].concat( urlParams[ name ], value );
-                       } else {
-                               urlParams[ name ] = value;
-                       }
-               }
-       }
-
-       return urlParams;
-};
-
 // Doesn't support IE6 to IE9, it will return undefined on these browsers
 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
 function extractStacktrace( e, offset ) {
@@ -212,12 +186,12 @@ function extractStacktrace( e, offset ) {
        // Support: Safari <=6 only
        } else if ( e.sourceURL ) {
 
-               // exclude useless self-reference for generated Error objects
+               // Exclude useless self-reference for generated Error objects
                if ( /qunit.js$/.test( e.sourceURL ) ) {
                        return;
                }
 
-               // for actual exceptions, this is useful
+               // For actual exceptions, this is useful
                return e.sourceURL + ":" + e.line;
        }
 }
@@ -244,53 +218,35 @@ function sourceFromStacktrace( offset ) {
  * `config` initialized at top of scope
  */
 var config = {
+
        // The queue of tests to run
        queue: [],
 
-       // block until document ready
+       // Block until document ready
        blocking: true,
 
-       // by default, run previously failed tests first
+       // By default, run previously failed tests first
        // very useful in combination with "Hide passed tests" checked
        reorder: true,
 
-       // by default, modify document.title when suite is done
+       // By default, modify document.title when suite is done
        altertitle: true,
 
        // HTML Reporter: collapse every test except the first failing test
        // If false, all failing tests will be expanded
        collapse: true,
 
-       // by default, scroll to top of the page when suite is done
+       // By default, scroll to top of the page when suite is done
        scrolltop: true,
 
-       // depth up-to which object will be dumped
+       // Depth up-to which object will be dumped
        maxDepth: 5,
 
-       // when enabled, all tests must call expect()
+       // When enabled, all tests must call expect()
        requireExpects: false,
 
-       // add checkboxes that are persisted in the query-string
-       // when enabled, the id is set to `true` as a `QUnit.config` property
-       urlConfig: [
-               {
-                       id: "hidepassed",
-                       label: "Hide passed tests",
-                       tooltip: "Only show tests and assertions that fail. Stored as query-strings."
-               },
-               {
-                       id: "noglobals",
-                       label: "Check for Globals",
-                       tooltip: "Enabling this will test if any test introduces new properties on the " +
-                               "global object (`window` in Browsers). Stored as query-strings."
-               },
-               {
-                       id: "notrycatch",
-                       label: "No try-catch",
-                       tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
-                               "exceptions in IE reasonable. Stored as query-strings."
-               }
-       ],
+       // Placeholder for user-configurable form-exposed URL parameters
+       urlConfig: [],
 
        // Set of all modules.
        modules: [],
@@ -307,27 +263,9 @@ var config = {
        callbacks: {}
 };
 
-var urlParams = defined.document ? getUrlParams() : {};
-
 // Push a loose unnamed module to the modules collection
 config.modules.push( config.currentModule );
 
-if ( urlParams.filter === true ) {
-       delete urlParams.filter;
-}
-
-// String search anywhere in moduleName+testName
-config.filter = urlParams.filter;
-
-config.testId = [];
-if ( urlParams.testId ) {
-       // Ensure that urlParams.testId is an array
-       urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
-       for (var i = 0; i < urlParams.testId.length; i++ ) {
-               config.testId.push( urlParams.testId[ i ] );
-       }
-}
-
 var loggingCallbacks = {};
 
 // Register logging callbacks
@@ -431,7 +369,7 @@ function verifyLoggingCallbacks() {
                                }
                                QUnit.pushFailure( error, filePath + ":" + linerNr );
                        } else {
-                               QUnit.test( "global failure", extend(function() {
+                               QUnit.test( "global failure", extend( function() {
                                        QUnit.pushFailure( error, filePath + ":" + linerNr );
                                }, { validTest: true } ) );
                        }
@@ -440,25 +378,23 @@ function verifyLoggingCallbacks() {
 
                return ret;
        };
-} )();
-
-QUnit.urlParams = urlParams;
+}() );
 
 // Figure out if we're running the tests from a server or not
 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
 
 // Expose the current QUnit version
-QUnit.version = "1.22.0";
+QUnit.version = "1.23.1";
 
 extend( QUnit, {
 
-       // call on start of module test to prepend name to all tests
+       // Call on start of module test to prepend name to all tests
        module: function( name, testEnvironment, executeNow ) {
                var module, moduleFns;
                var currentModule = config.currentModule;
 
                if ( arguments.length === 2 ) {
-                       if ( testEnvironment instanceof Function ) {
+                       if ( objectType( testEnvironment ) === "function" ) {
                                executeNow = testEnvironment;
                                testEnvironment = undefined;
                        }
@@ -482,7 +418,7 @@ extend( QUnit, {
                        afterEach: setHook( module, "afterEach" )
                };
 
-               if ( executeNow instanceof Function ) {
+               if ( objectType( executeNow ) === "function" ) {
                        config.moduleStack.push( module );
                        setCurrentModule( module );
                        executeNow.call( module.testEnvironment, moduleFns );
@@ -500,7 +436,8 @@ extend( QUnit, {
                        var module = {
                                name: moduleName,
                                parentModule: parentModule,
-                               tests: []
+                               tests: [],
+                               moduleId: generateHash( moduleName )
                        };
 
                        var env = {};
@@ -573,7 +510,7 @@ extend( QUnit, {
                                return;
                        }
 
-                       // throw an Error if start is called more often than stop
+                       // Throw an Error if start is called more often than stop
                        if ( config.current.semaphore < 0 ) {
                                config.current.semaphore = 0;
 
@@ -634,7 +571,7 @@ extend( QUnit, {
                offset = ( offset || 0 ) + 2;
                return sourceFromStacktrace( offset );
        }
-});
+} );
 
 registerLoggingCallbacks( QUnit );
 
@@ -657,17 +594,17 @@ function begin() {
 
                // Avoid unnecessary information by not logging modules' test environments
                for ( i = 0, l = config.modules.length; i < l; i++ ) {
-                       modulesLog.push({
+                       modulesLog.push( {
                                name: config.modules[ i ].name,
                                tests: config.modules[ i ].tests
-                       });
+                       } );
                }
 
                // The test run is officially beginning now
                runLoggingCallbacks( "begin", {
                        totalTests: Test.count,
                        modules: modulesLog
-               });
+               } );
        }
 
        config.blocking = false;
@@ -706,7 +643,7 @@ function pauseProcessing() {
 
        if ( config.testTimeout && defined.setTimeout ) {
                clearTimeout( config.timeout );
-               config.timeout = setTimeout(function() {
+               config.timeout = setTimeout( function() {
                        if ( config.current ) {
                                config.current.semaphore = 0;
                                QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
@@ -723,7 +660,7 @@ function resumeProcessing() {
 
        // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
        if ( defined.setTimeout ) {
-               setTimeout(function() {
+               setTimeout( function() {
                        if ( config.current && config.current.semaphore > 0 ) {
                                return;
                        }
@@ -752,7 +689,7 @@ function done() {
                        passed: config.moduleStats.all - config.moduleStats.bad,
                        total: config.moduleStats.all,
                        runtime: now() - config.moduleStats.started
-               });
+               } );
        }
        delete config.previousModule;
 
@@ -764,7 +701,7 @@ function done() {
                passed: passed,
                total: config.stats.all,
                runtime: runtime
-       });
+       } );
 }
 
 function setHook( module, hookName ) {
@@ -779,6 +716,7 @@ function setHook( module, hookName ) {
 
 var focused = false;
 var priorityCount = 0;
+var unitSampler;
 
 function Test( settings ) {
        var i, l;
@@ -801,10 +739,10 @@ function Test( settings ) {
 
        this.testId = generateHash( this.module.name, this.testName );
 
-       this.module.tests.push({
+       this.module.tests.push( {
                name: this.testName,
                testId: this.testId
-       });
+       } );
 
        if ( settings.skip ) {
 
@@ -840,14 +778,14 @@ Test.prototype = {
                                        passed: config.moduleStats.all - config.moduleStats.bad,
                                        total: config.moduleStats.all,
                                        runtime: now() - config.moduleStats.started
-                               });
+                               } );
                        }
                        config.previousModule = this.module;
                        config.moduleStats = { all: 0, bad: 0, started: now() };
                        runLoggingCallbacks( "moduleStart", {
                                name: this.module.name,
                                tests: this.module.tests
-                       });
+                       } );
                }
 
                config.current = this;
@@ -863,7 +801,7 @@ Test.prototype = {
                        name: this.testName,
                        module: this.module.name,
                        testId: this.testId
-               });
+               } );
 
                if ( !config.pollution ) {
                        saveGlobal();
@@ -892,7 +830,7 @@ Test.prototype = {
                        this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
                                this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
 
-                       // else next test will carry the responsibility
+                       // Else next test will carry the responsibility
                        saveGlobal();
 
                        // Restart the tests if they're blocking
@@ -1001,7 +939,7 @@ Test.prototype = {
 
                        // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
                        duration: this.runtime
-               });
+               } );
 
                // QUnit.reset() is deprecated and will be replaced for a new
                // fixture reset function on QUnit 2.0/2.1.
@@ -1021,8 +959,8 @@ Test.prototype = {
 
                function run() {
 
-                       // each of these can by async
-                       synchronize([
+                       // Each of these can by async
+                       synchronize( [
                                function() {
                                        test.before();
                                },
@@ -1040,19 +978,19 @@ Test.prototype = {
                                function() {
                                        test.finish();
                                }
-                       ]);
+                       ] );
                }
 
                // Prioritize previously failed tests, detected from sessionStorage
                priority = QUnit.config.reorder && defined.sessionStorage &&
                                +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
 
-               return synchronize( run, priority );
+               return synchronize( run, priority, config.seed );
        },
 
        pushResult: function( resultInfo ) {
 
-               // resultInfo = { result, actual, expected, message, negative }
+               // Destructure of resultInfo = { result, actual, expected, message, negative }
                var source,
                        details = {
                                module: this.module.name,
@@ -1076,10 +1014,10 @@ Test.prototype = {
 
                runLoggingCallbacks( "log", details );
 
-               this.assertions.push({
+               this.assertions.push( {
                        result: !!resultInfo.result,
                        message: resultInfo.message
-               });
+               } );
        },
 
        pushFailure: function( message, source, actual ) {
@@ -1104,10 +1042,10 @@ Test.prototype = {
 
                runLoggingCallbacks( "log", details );
 
-               this.assertions.push({
+               this.assertions.push( {
                        result: false,
                        message: message
-               });
+               } );
        },
 
        resolvePromise: function( promise, phase ) {
@@ -1126,7 +1064,7 @@ Test.prototype = {
                                                        " " + test.testName + ": " + ( error.message || error );
                                                test.pushFailure( message, extractStacktrace( error, 0 ) );
 
-                                               // else next test will carry the responsibility
+                                               // Else next test will carry the responsibility
                                                saveGlobal();
 
                                                // Unblock
@@ -1140,30 +1078,43 @@ Test.prototype = {
        valid: function() {
                var filter = config.filter,
                        regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
-                       module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+                       module = config.module && config.module.toLowerCase(),
                        fullName = ( this.module.name + ": " + this.testName );
 
-               function testInModuleChain( testModule ) {
+               function moduleChainNameMatch( testModule ) {
                        var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
                        if ( testModuleName === module ) {
                                return true;
                        } else if ( testModule.parentModule ) {
-                               return testInModuleChain( testModule.parentModule );
+                               return moduleChainNameMatch( testModule.parentModule );
                        } else {
                                return false;
                        }
                }
 
+               function moduleChainIdMatch( testModule ) {
+                       return inArray( testModule.moduleId, config.moduleId ) > -1 ||
+                               testModule.parentModule && moduleChainIdMatch( testModule.parentModule );
+               }
+
                // Internally-generated tests are always valid
                if ( this.callback && this.callback.validTest ) {
                        return true;
                }
 
-               if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+               if ( config.moduleId && config.moduleId.length > 0 &&
+                       !moduleChainIdMatch( this.module ) ) {
+
+                       return false;
+               }
+
+               if ( config.testId && config.testId.length > 0 &&
+                       inArray( this.testId, config.testId ) < 0 ) {
+
                        return false;
                }
 
-               if ( module && !testInModuleChain( this.module ) ) {
+               if ( module && !moduleChainNameMatch( this.module ) ) {
                        return false;
                }
 
@@ -1172,7 +1123,7 @@ Test.prototype = {
                }
 
                return regexFilter ?
-                       this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) :
+                       this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) :
                        this.stringFilter( filter, fullName );
        },
 
@@ -1260,8 +1211,9 @@ function generateHash( module, testName ) {
        return hex.slice( -8 );
 }
 
-function synchronize( callback, priority ) {
-       var last = !priority;
+function synchronize( callback, priority, seed ) {
+       var last = !priority,
+               index;
 
        if ( QUnit.objectType( callback ) === "array" ) {
                while ( callback.length ) {
@@ -1272,6 +1224,14 @@ function synchronize( callback, priority ) {
 
        if ( priority ) {
                config.queue.splice( priorityCount++, 0, callback );
+       } else if ( seed ) {
+               if ( !unitSampler ) {
+                       unitSampler = unitSamplerGenerator( seed );
+               }
+
+               // Insert into a random position after all priority items
+               index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) );
+               config.queue.splice( priorityCount + index, 0, callback );
        } else {
                config.queue.push( callback );
        }
@@ -1281,6 +1241,25 @@ function synchronize( callback, priority ) {
        }
 }
 
+function unitSamplerGenerator( seed ) {
+
+       // 32-bit xorshift, requires only a nonzero seed
+       // http://excamera.com/sphinx/article-xorshift.html
+       var sample = parseInt( generateHash( seed ), 16 ) || -1;
+       return function() {
+               sample ^= sample << 13;
+               sample ^= sample >>> 17;
+               sample ^= sample << 5;
+
+               // ECMAScript has no unsigned number type
+               if ( sample < 0 ) {
+                       sample += 0x100000000;
+               }
+
+               return sample / 0x100000000;
+       };
+}
+
 function saveGlobal() {
        config.pollution = [];
 
@@ -1288,7 +1267,7 @@ function saveGlobal() {
                for ( var key in global ) {
                        if ( hasOwn.call( global, key ) ) {
 
-                               // in Opera sometimes DOM element ids show up here, ignore them
+                               // In Opera sometimes DOM element ids show up here, ignore them
                                if ( /^qunit-test-output/.test( key ) ) {
                                        continue;
                                }
@@ -1337,12 +1316,12 @@ function test( testName, expected, callback, async ) {
                expected = null;
        }
 
-       newTest = new Test({
+       newTest = new Test( {
                testName: testName,
                expected: expected,
                async: async,
                callback: callback
-       });
+       } );
 
        newTest.queue();
 }
@@ -1351,10 +1330,10 @@ function test( testName, expected, callback, async ) {
 function skip( testName ) {
        if ( focused )  { return; }
 
-       var test = new Test({
+       var test = new Test( {
                testName: testName,
                skip: true
-       });
+       } );
 
        test.queue();
 }
@@ -1373,12 +1352,12 @@ function only( testName, expected, callback, async ) {
                expected = null;
        }
 
-       newTest = new Test({
+       newTest = new Test( {
                testName: testName,
                expected: expected,
                async: async,
                callback: callback
-       });
+       } );
 
        newTest.queue();
 }
@@ -1448,7 +1427,7 @@ QUnit.assert = Assert.prototype = {
 
        pushResult: function( resultInfo ) {
 
-               // resultInfo = { result, actual, expected, message, negative }
+               // Destructure of resultInfo = { result, actual, expected, message, negative }
                var assert = this,
                        currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
 
@@ -1594,7 +1573,7 @@ QUnit.assert = Assert.prototype = {
                currentTest.ignoreGlobalErrors = true;
                try {
                        block.call( currentTest.testEnvironment );
-               } catch (e) {
+               } catch ( e ) {
                        actual = e;
                }
                currentTest.ignoreGlobalErrors = false;
@@ -1602,30 +1581,30 @@ QUnit.assert = Assert.prototype = {
                if ( actual ) {
                        expectedType = QUnit.objectType( expected );
 
-                       // we don't want to validate thrown error
+                       // We don't want to validate thrown error
                        if ( !expected ) {
                                ok = true;
                                expectedOutput = null;
 
-                       // expected is a regexp
+                       // Expected is a regexp
                        } else if ( expectedType === "regexp" ) {
                                ok = expected.test( errorString( actual ) );
 
-                       // expected is a string
+                       // Expected is a string
                        } else if ( expectedType === "string" ) {
                                ok = expected === errorString( actual );
 
-                       // expected is a constructor, maybe an Error constructor
+                       // Expected is a constructor, maybe an Error constructor
                        } else if ( expectedType === "function" && actual instanceof expected ) {
                                ok = true;
 
-                       // expected is an Error object
+                       // Expected is an Error object
                        } else if ( expectedType === "object" ) {
                                ok = actual instanceof expected.constructor &&
                                        actual.name === expected.name &&
                                        actual.message === expected.message;
 
-                       // expected is a validation function which returns true if validation passed
+                       // Expected is a validation function which returns true if validation passed
                        } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
                                expectedOutput = null;
                                ok = true;
@@ -1643,10 +1622,10 @@ QUnit.assert = Assert.prototype = {
 
 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
 // Known to us are: Closure Compiler, Narwhal
-(function() {
+( function() {
        /*jshint sub:true */
-       Assert.prototype.raises = Assert.prototype[ "throws" ];
-}());
+       Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation
+}() );
 
 function errorString( error ) {
        var name, message,
@@ -1670,7 +1649,7 @@ function errorString( error ) {
 
 // Test for equality any JavaScript type.
 // Author: Philippe Rathé <prathe@gmail.com>
-QUnit.equiv = (function() {
+QUnit.equiv = ( function() {
 
        // Stack to decide between skip/abort functions
        var callers = [];
@@ -1766,7 +1745,8 @@ QUnit.equiv = (function() {
 
                        len = a.length;
                        if ( len !== b.length ) {
-                               // safe and faster
+
+                               // Safe and faster
                                return false;
                        }
 
@@ -1800,33 +1780,53 @@ QUnit.equiv = (function() {
                },
 
                "set": function( b, a ) {
-                       var aArray, bArray;
-
-                       aArray = [];
-                       a.forEach( function( v ) {
-                               aArray.push( v );
-                       });
-                       bArray = [];
-                       b.forEach( function( v ) {
-                               bArray.push( v );
-                       });
-
-                       return innerEquiv( bArray, aArray );
+                       var innerEq,
+                               outerEq = true;
+
+                       if ( a.size !== b.size ) {
+                               return false;
+                       }
+
+                       a.forEach( function( aVal ) {
+                               innerEq = false;
+
+                               b.forEach( function( bVal ) {
+                                       if ( innerEquiv( bVal, aVal ) ) {
+                                               innerEq = true;
+                                       }
+                               } );
+
+                               if ( !innerEq ) {
+                                       outerEq = false;
+                               }
+                       } );
+
+                       return outerEq;
                },
 
                "map": function( b, a ) {
-                       var aArray, bArray;
-
-                       aArray = [];
-                       a.forEach( function( v, k ) {
-                               aArray.push( [ k, v ] );
-                       });
-                       bArray = [];
-                       b.forEach( function( v, k ) {
-                               bArray.push( [ k, v ] );
-                       });
-
-                       return innerEquiv( bArray, aArray );
+                       var innerEq,
+                               outerEq = true;
+
+                       if ( a.size !== b.size ) {
+                               return false;
+                       }
+
+                       a.forEach( function( aVal, aKey ) {
+                               innerEq = false;
+
+                               b.forEach( function( bVal, bKey ) {
+                                       if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) {
+                                               innerEq = true;
+                                       }
+                               } );
+
+                               if ( !innerEq ) {
+                                       outerEq = false;
+                               }
+                       } );
+
+                       return outerEq;
                },
 
                "object": function( b, a ) {
@@ -1908,11 +1908,11 @@ QUnit.equiv = (function() {
        }
 
        return innerEquiv;
-}());
+}() );
 
 // Based on jsDump by Ariel Flesler
 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
-QUnit.dump = (function() {
+QUnit.dump = ( function() {
        function quote( str ) {
                return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
        }
@@ -1950,7 +1950,7 @@ QUnit.dump = (function() {
        var reName = /^function (\w+)/,
                dump = {
 
-                       // objType is used mostly internally, you can fix a (custom) type in advance
+                       // The objType is used mostly internally, you can fix a (custom) type in advance
                        parse: function( obj, objType, stack ) {
                                stack = stack || [];
                                var res, parser, parserType,
@@ -1994,7 +1994,7 @@ QUnit.dump = (function() {
                                        type = "node";
                                } else if (
 
-                                       // native arrays
+                                       // Native arrays
                                        toString.call( obj ) === "[object Array]" ||
 
                                        // NodeList objects
@@ -2010,10 +2010,12 @@ QUnit.dump = (function() {
                                }
                                return type;
                        },
+
                        separator: function() {
                                return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
                        },
-                       // extra can be a number, shortcut for increasing-calling-decreasing
+
+                       // Extra can be a number, shortcut for increasing-calling-decreasing
                        indent: function( extra ) {
                                if ( !this.multiline ) {
                                        return "";
@@ -2033,11 +2035,11 @@ QUnit.dump = (function() {
                        setParser: function( name, parser ) {
                                this.parsers[ name ] = parser;
                        },
+
                        // The next 3 are exposed so you can use them
                        quote: quote,
                        literal: literal,
                        join: join,
-                       //
                        depth: 1,
                        maxDepth: QUnit.config.maxDepth,
 
@@ -2054,13 +2056,13 @@ QUnit.dump = (function() {
                                "function": function( fn ) {
                                        var ret = "function",
 
-                                               // functions never have name in IE
+                                               // Functions never have name in IE
                                                name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
 
                                        if ( name ) {
                                                ret += " " + name;
                                        }
-                                       ret += "( ";
+                                       ret += "(";
 
                                        ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
                                        return join( ret, dump.parse( fn, "functionCode" ), "}" );
@@ -2131,7 +2133,7 @@ QUnit.dump = (function() {
                                        return ret + open + "/" + tag + close;
                                },
 
-                               // function calls it internally, it's the arguments part of the function
+                               // Function calls it internally, it's the arguments part of the function
                                functionArgs: function( fn ) {
                                        var args,
                                                l = fn.length;
@@ -2148,11 +2150,14 @@ QUnit.dump = (function() {
                                        }
                                        return " " + args.join( ", " ) + " ";
                                },
-                               // object calls it internally, the key part of an item in a map
+
+                               // Object calls it internally, the key part of an item in a map
                                key: quote,
-                               // function calls it internally, it's the content of the function
+
+                               // Function calls it internally, it's the content of the function
                                functionCode: "[code]",
-                               // node calls it internally, it's a html attribute value
+
+                               // Node calls it internally, it's a html attribute value
                                attribute: quote,
                                string: quote,
                                date: quote,
@@ -2160,23 +2165,26 @@ QUnit.dump = (function() {
                                number: literal,
                                "boolean": literal
                        },
-                       // if true, entities are escaped ( <, >, \t, space and \n )
+
+                       // If true, entities are escaped ( <, >, \t, space and \n )
                        HTML: false,
-                       // indentation unit
+
+                       // Indentation unit
                        indentChar: "  ",
-                       // if true, items in a collection, are separated by a \n, else just a space.
+
+                       // If true, items in a collection, are separated by a \n, else just a space.
                        multiline: true
                };
 
        return dump;
-}());
+}() );
 
-// back compat
+// Back compat
 QUnit.jsDump = QUnit.dump;
 
 // Deprecated
 // Extend assert methods to QUnit for Backwards compatibility
-(function() {
+( function() {
        var i,
                assertions = Assert.prototype;
 
@@ -2190,12 +2198,12 @@ QUnit.jsDump = QUnit.dump;
        for ( i in assertions ) {
                QUnit[ i ] = applyCurrent( assertions[ i ] );
        }
-})();
+}() );
 
 // For browser, export only select globals
 if ( defined.document ) {
 
-       (function() {
+       ( function() {
                var i, l,
                        keys = [
                                "test",
@@ -2221,7 +2229,7 @@ if ( defined.document ) {
                for ( i = 0, l = keys.length; i < l; i++ ) {
                        window[ keys[ i ] ] = QUnit[ keys[ i ] ];
                }
-       })();
+       }() );
 
        window.QUnit = QUnit;
 }
@@ -2246,1959 +2254,2081 @@ if ( typeof define === "function" && define.amd ) {
        QUnit.config.autostart = false;
 }
 
-/*
- * This file is a modified version of google-diff-match-patch's JavaScript implementation
- * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
- * modifications are licensed as more fully set forth in LICENSE.txt.
- *
- * The original source of google-diff-match-patch is attributable and licensed as follows:
- *
- * Copyright 2006 Google Inc.
- * https://code.google.com/p/google-diff-match-patch/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * More Info:
- *  https://code.google.com/p/google-diff-match-patch/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- */
-QUnit.diff = ( function() {
-       function DiffMatchPatch() {
-       }
+// Get a reference to the global object, like window in browsers
+}( ( function() {
+       return this;
+}() ) ) );
 
-       //  DIFF FUNCTIONS
+( function() {
 
-       /**
-        * The data structure representing a diff is an array of tuples:
-        * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
-        * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
-        */
-       var DIFF_DELETE = -1,
-               DIFF_INSERT = 1,
-               DIFF_EQUAL = 0;
+// Only interact with URLs via window.location
+var location = typeof window !== "undefined" && window.location;
+if ( !location ) {
+       return;
+}
 
-       /**
-        * Find the differences between two texts.  Simplifies the problem by stripping
-        * any common prefix or suffix off the texts before diffing.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {boolean=} optChecklines Optional speedup flag. If present and false,
-        *     then don't run a line-level diff first to identify the changed areas.
-        *     Defaults to true, which does a faster, slightly less optimal diff.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
-               var deadline, checklines, commonlength,
-                       commonprefix, commonsuffix, diffs;
+var urlParams = getUrlParams();
 
-               // The diff must be complete in up to 1 second.
-               deadline = ( new Date() ).getTime() + 1000;
+QUnit.urlParams = urlParams;
 
-               // Check for null inputs.
-               if ( text1 === null || text2 === null ) {
-                       throw new Error( "Null input. (DiffMain)" );
-               }
+// Match module/test by inclusion in an array
+QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
+QUnit.config.testId = [].concat( urlParams.testId || [] );
 
-               // Check for equality (speedup).
-               if ( text1 === text2 ) {
-                       if ( text1 ) {
-                               return [
-                                       [ DIFF_EQUAL, text1 ]
-                               ];
-                       }
-                       return [];
-               }
+// Exact case-insensitive match of the module name
+QUnit.config.module = urlParams.module;
 
-               if ( typeof optChecklines === "undefined" ) {
-                       optChecklines = true;
-               }
+// Regular expression or case-insenstive substring match against "moduleName: testName"
+QUnit.config.filter = urlParams.filter;
 
-               checklines = optChecklines;
+// Test order randomization
+if ( urlParams.seed === true ) {
 
-               // Trim off common prefix (speedup).
-               commonlength = this.diffCommonPrefix( text1, text2 );
-               commonprefix = text1.substring( 0, commonlength );
-               text1 = text1.substring( commonlength );
-               text2 = text2.substring( commonlength );
+       // Generate a random seed if the option is specified without a value
+       QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
+} else if ( urlParams.seed ) {
+       QUnit.config.seed = urlParams.seed;
+}
 
-               // Trim off common suffix (speedup).
-               commonlength = this.diffCommonSuffix( text1, text2 );
-               commonsuffix = text1.substring( text1.length - commonlength );
-               text1 = text1.substring( 0, text1.length - commonlength );
-               text2 = text2.substring( 0, text2.length - commonlength );
+// Add URL-parameter-mapped config values with UI form rendering data
+QUnit.config.urlConfig.push(
+       {
+               id: "hidepassed",
+               label: "Hide passed tests",
+               tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+       },
+       {
+               id: "noglobals",
+               label: "Check for Globals",
+               tooltip: "Enabling this will test if any test introduces new properties on the " +
+                       "global object (`window` in Browsers). Stored as query-strings."
+       },
+       {
+               id: "notrycatch",
+               label: "No try-catch",
+               tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+                       "exceptions in IE reasonable. Stored as query-strings."
+       }
+);
 
-               // Compute the diff on the middle block.
-               diffs = this.diffCompute( text1, text2, checklines, deadline );
+QUnit.begin( function() {
+       var i, option,
+               urlConfig = QUnit.config.urlConfig;
 
-               // Restore the prefix and suffix.
-               if ( commonprefix ) {
-                       diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
-               }
-               if ( commonsuffix ) {
-                       diffs.push( [ DIFF_EQUAL, commonsuffix ] );
-               }
-               this.diffCleanupMerge( diffs );
-               return diffs;
-       };
+       for ( i = 0; i < urlConfig.length; i++ ) {
 
-       /**
-        * Reduce the number of edits by eliminating operationally trivial equalities.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
-               var changes, equalities, equalitiesLength, lastequality,
-                       pointer, preIns, preDel, postIns, postDel;
-               changes = false;
-               equalities = []; // Stack of indices where equalities are found.
-               equalitiesLength = 0; // Keeping our own length var is faster in JS.
-               /** @type {?string} */
-               lastequality = null;
-               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
-               pointer = 0; // Index of current position.
-               // Is there an insertion operation before the last equality.
-               preIns = false;
-               // Is there a deletion operation before the last equality.
-               preDel = false;
-               // Is there an insertion operation after the last equality.
-               postIns = false;
-               // Is there a deletion operation after the last equality.
-               postDel = false;
-               while ( pointer < diffs.length ) {
+               // Options can be either strings or objects with nonempty "id" properties
+               option = QUnit.config.urlConfig[ i ];
+               if ( typeof option !== "string" ) {
+                       option = option.id;
+               }
 
-                       // Equality found.
-                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
-                               if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
+               if ( QUnit.config[ option ] === undefined ) {
+                       QUnit.config[ option ] = urlParams[ option ];
+               }
+       }
+} );
 
-                                       // Candidate found.
-                                       equalities[ equalitiesLength++ ] = pointer;
-                                       preIns = postIns;
-                                       preDel = postDel;
-                                       lastequality = diffs[ pointer ][ 1 ];
-                               } else {
+function getUrlParams() {
+       var i, param, name, value;
+       var urlParams = {};
+       var params = location.search.slice( 1 ).split( "&" );
+       var length = params.length;
 
-                                       // Not a candidate, and can never become one.
-                                       equalitiesLength = 0;
-                                       lastequality = null;
-                               }
-                               postIns = postDel = false;
+       for ( i = 0; i < length; i++ ) {
+               if ( params[ i ] ) {
+                       param = params[ i ].split( "=" );
+                       name = decodeURIComponent( param[ 0 ] );
 
-                       // An insertion or deletion.
+                       // Allow just a key to turn on a flag, e.g., test.html?noglobals
+                       value = param.length === 1 ||
+                               decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
+                       if ( urlParams[ name ] ) {
+                               urlParams[ name ] = [].concat( urlParams[ name ], value );
                        } else {
+                               urlParams[ name ] = value;
+                       }
+               }
+       }
 
-                               if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
-                                       postDel = true;
-                               } else {
-                                       postIns = true;
-                               }
+       return urlParams;
+}
 
-                               /*
-                                * Five types to be split:
-                                * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
-                                * <ins>A</ins>X<ins>C</ins><del>D</del>
-                                * <ins>A</ins><del>B</del>X<ins>C</ins>
-                                * <ins>A</del>X<ins>C</ins><del>D</del>
-                                * <ins>A</ins><del>B</del>X<del>C</del>
-                                */
-                               if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
-                                               ( ( lastequality.length < 2 ) &&
-                                               ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
+// Don't load the HTML Reporter on non-browser environments
+if ( typeof window === "undefined" || !window.document ) {
+       return;
+}
 
-                                       // Duplicate record.
-                                       diffs.splice(
-                                               equalities[ equalitiesLength - 1 ],
-                                               0,
-                                               [ DIFF_DELETE, lastequality ]
-                                       );
+// Deprecated QUnit.init - Ref #530
+// Re-initialize the configuration options
+QUnit.init = function() {
+       var config = QUnit.config;
 
-                                       // Change second copy to insert.
-                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
-                                       equalitiesLength--; // Throw away the equality we just deleted;
-                                       lastequality = null;
-                                       if ( preIns && preDel ) {
-                                               // No changes made which could affect previous entry, keep going.
-                                               postIns = postDel = true;
-                                               equalitiesLength = 0;
-                                       } else {
-                                               equalitiesLength--; // Throw away the previous equality.
-                                               pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
-                                               postIns = postDel = false;
-                                       }
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
+       config.stats = { all: 0, bad: 0 };
+       config.moduleStats = { all: 0, bad: 0 };
+       config.started = 0;
+       config.updateRate = 1000;
+       config.blocking = false;
+       config.autostart = true;
+       config.autorun = false;
+       config.filter = "";
+       config.queue = [];
 
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
-               }
-       };
+       appendInterface();
+};
 
-       /**
-        * Convert a diff array into a pretty HTML report.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        * @param {integer} string to be beautified.
-        * @return {string} HTML representation.
-        */
-       DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
-               var op, data, x,
-                       html = [];
-               for ( x = 0; x < diffs.length; x++ ) {
-                       op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
-                       data = diffs[ x ][ 1 ]; // Text of change.
-                       switch ( op ) {
-                       case DIFF_INSERT:
-                               html[ x ] = "<ins>" + data + "</ins>";
-                               break;
-                       case DIFF_DELETE:
-                               html[ x ] = "<del>" + data + "</del>";
-                               break;
-                       case DIFF_EQUAL:
-                               html[ x ] = "<span>" + data + "</span>";
-                               break;
+var config = QUnit.config,
+       document = window.document,
+       collapseNext = false,
+       hasOwn = Object.prototype.hasOwnProperty,
+       unfilteredUrl = setUrl( { filter: undefined, module: undefined,
+               moduleId: undefined, testId: undefined } ),
+       defined = {
+               sessionStorage: ( function() {
+                       var x = "qunit-test-string";
+                       try {
+                               sessionStorage.setItem( x, x );
+                               sessionStorage.removeItem( x );
+                               return true;
+                       } catch ( e ) {
+                               return false;
                        }
-               }
-               return html.join( "" );
-       };
+               }() )
+       },
+       modulesList = [];
 
-       /**
-        * Determine the common prefix of two strings.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the start of each
-        *     string.
-        */
-       DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
-               var pointermid, pointermax, pointermin, pointerstart;
-               // Quick check for common null cases.
-               if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
-                       return 0;
-               }
-               // Binary search.
-               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
-               pointermin = 0;
-               pointermax = Math.min( text1.length, text2.length );
-               pointermid = pointermax;
-               pointerstart = 0;
-               while ( pointermin < pointermid ) {
-                       if ( text1.substring( pointerstart, pointermid ) ===
-                                       text2.substring( pointerstart, pointermid ) ) {
-                               pointermin = pointermid;
-                               pointerstart = pointermin;
-                       } else {
-                               pointermax = pointermid;
-                       }
-                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
-               }
-               return pointermid;
-       };
+/**
+* Escape text for attribute or text content.
+*/
+function escapeText( s ) {
+       if ( !s ) {
+               return "";
+       }
+       s = s + "";
 
-       /**
-        * Determine the common suffix of two strings.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the end of each string.
-        */
-       DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
-               var pointermid, pointermax, pointermin, pointerend;
-               // Quick check for common null cases.
-               if ( !text1 ||
-                               !text2 ||
-                               text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
-                       return 0;
-               }
-               // Binary search.
-               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
-               pointermin = 0;
-               pointermax = Math.min( text1.length, text2.length );
-               pointermid = pointermax;
-               pointerend = 0;
-               while ( pointermin < pointermid ) {
-                       if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
-                                       text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
-                               pointermin = pointermid;
-                               pointerend = pointermin;
-                       } else {
-                               pointermax = pointermid;
-                       }
-                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+       // Both single quotes and double quotes (for attributes)
+       return s.replace( /['"<>&]/g, function( s ) {
+               switch ( s ) {
+               case "'":
+                       return "&#039;";
+               case "\"":
+                       return "&quot;";
+               case "<":
+                       return "&lt;";
+               case ">":
+                       return "&gt;";
+               case "&":
+                       return "&amp;";
                }
-               return pointermid;
-       };
-
-       /**
-        * Find the differences between two texts.  Assumes that the texts do not
-        * have any common prefix or suffix.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {boolean} checklines Speedup flag.  If false, then don't run a
-        *     line-level diff first to identify the changed areas.
-        *     If true, then run a faster, slightly less optimal diff.
-        * @param {number} deadline Time when the diff should be complete by.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
-               var diffs, longtext, shorttext, i, hm,
-                       text1A, text2A, text1B, text2B,
-                       midCommon, diffsA, diffsB;
+       } );
+}
 
-               if ( !text1 ) {
-                       // Just add some text (speedup).
-                       return [
-                               [ DIFF_INSERT, text2 ]
-                       ];
-               }
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+       if ( elem.addEventListener ) {
 
-               if ( !text2 ) {
-                       // Just delete some text (speedup).
-                       return [
-                               [ DIFF_DELETE, text1 ]
-                       ];
-               }
+               // Standards-based browsers
+               elem.addEventListener( type, fn, false );
+       } else if ( elem.attachEvent ) {
 
-               longtext = text1.length > text2.length ? text1 : text2;
-               shorttext = text1.length > text2.length ? text2 : text1;
-               i = longtext.indexOf( shorttext );
-               if ( i !== -1 ) {
-                       // Shorter text is inside the longer text (speedup).
-                       diffs = [
-                               [ DIFF_INSERT, longtext.substring( 0, i ) ],
-                               [ DIFF_EQUAL, shorttext ],
-                               [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
-                       ];
-                       // Swap insertions for deletions if diff is reversed.
-                       if ( text1.length > text2.length ) {
-                               diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
+               // Support: IE <9
+               elem.attachEvent( "on" + type, function() {
+                       var event = window.event;
+                       if ( !event.target ) {
+                               event.target = event.srcElement || document;
                        }
-                       return diffs;
-               }
 
-               if ( shorttext.length === 1 ) {
-                       // Single character string.
-                       // After the previous speedup, the character can't be an equality.
-                       return [
-                               [ DIFF_DELETE, text1 ],
-                               [ DIFF_INSERT, text2 ]
-                       ];
-               }
+                       fn.call( elem, event );
+               } );
+       }
+}
 
-               // Check to see if the problem can be split in two.
-               hm = this.diffHalfMatch( text1, text2 );
-               if ( hm ) {
-                       // A half-match was found, sort out the return data.
-                       text1A = hm[ 0 ];
-                       text1B = hm[ 1 ];
-                       text2A = hm[ 2 ];
-                       text2B = hm[ 3 ];
-                       midCommon = hm[ 4 ];
-                       // Send both pairs off for separate processing.
-                       diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
-                       diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
-                       // Merge the results.
-                       return diffsA.concat( [
-                               [ DIFF_EQUAL, midCommon ]
-                       ], diffsB );
-               }
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+       var i = elems.length;
+       while ( i-- ) {
+               addEvent( elems[ i ], type, fn );
+       }
+}
 
-               if ( checklines && text1.length > 100 && text2.length > 100 ) {
-                       return this.diffLineMode( text1, text2, deadline );
-               }
+function hasClass( elem, name ) {
+       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
+}
 
-               return this.diffBisect( text1, text2, deadline );
-       };
+function addClass( elem, name ) {
+       if ( !hasClass( elem, name ) ) {
+               elem.className += ( elem.className ? " " : "" ) + name;
+       }
+}
 
-       /**
-        * Do the two texts share a substring which is at least half the length of the
-        * longer text?
-        * This speedup can produce non-minimal diffs.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {Array.<string>} Five element Array, containing the prefix of
-        *     text1, the suffix of text1, the prefix of text2, the suffix of
-        *     text2 and the common middle.  Or null if there was no match.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
-               var longtext, shorttext, dmp,
-                       text1A, text2B, text2A, text1B, midCommon,
-                       hm1, hm2, hm;
+function toggleClass( elem, name, force ) {
+       if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) {
+               addClass( elem, name );
+       } else {
+               removeClass( elem, name );
+       }
+}
 
-               longtext = text1.length > text2.length ? text1 : text2;
-               shorttext = text1.length > text2.length ? text2 : text1;
-               if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
-                       return null; // Pointless.
+function removeClass( elem, name ) {
+       var set = " " + elem.className + " ";
+
+       // Class name may appear multiple times
+       while ( set.indexOf( " " + name + " " ) >= 0 ) {
+               set = set.replace( " " + name + " ", " " );
+       }
+
+       // Trim for prettiness
+       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
+}
+
+function id( name ) {
+       return document.getElementById && document.getElementById( name );
+}
+
+function getUrlConfigHtml() {
+       var i, j, val,
+               escaped, escapedTooltip,
+               selection = false,
+               urlConfig = config.urlConfig,
+               urlConfigHtml = "";
+
+       for ( i = 0; i < urlConfig.length; i++ ) {
+
+               // Options can be either strings or objects with nonempty "id" properties
+               val = config.urlConfig[ i ];
+               if ( typeof val === "string" ) {
+                       val = {
+                               id: val,
+                               label: val
+                       };
                }
-               dmp = this; // 'this' becomes 'window' in a closure.
 
-               /**
-                * Does a substring of shorttext exist within longtext such that the substring
-                * is at least half the length of longtext?
-                * Closure, but does not reference any external variables.
-                * @param {string} longtext Longer string.
-                * @param {string} shorttext Shorter string.
-                * @param {number} i Start index of quarter length substring within longtext.
-                * @return {Array.<string>} Five element Array, containing the prefix of
-                *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
-                *     of shorttext and the common middle.  Or null if there was no match.
-                * @private
-                */
-               function diffHalfMatchI( longtext, shorttext, i ) {
-                       var seed, j, bestCommon, prefixLength, suffixLength,
-                               bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
-                       // Start with a 1/4 length substring at position i as a seed.
-                       seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
-                       j = -1;
-                       bestCommon = "";
-                       while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
-                               prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
-                                       shorttext.substring( j ) );
-                               suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
-                                       shorttext.substring( 0, j ) );
-                               if ( bestCommon.length < suffixLength + prefixLength ) {
-                                       bestCommon = shorttext.substring( j - suffixLength, j ) +
-                                               shorttext.substring( j, j + prefixLength );
-                                       bestLongtextA = longtext.substring( 0, i - suffixLength );
-                                       bestLongtextB = longtext.substring( i + prefixLength );
-                                       bestShorttextA = shorttext.substring( 0, j - suffixLength );
-                                       bestShorttextB = shorttext.substring( j + prefixLength );
+               escaped = escapeText( val.id );
+               escapedTooltip = escapeText( val.tooltip );
+
+               if ( !val.value || typeof val.value === "string" ) {
+                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
+                               "' name='" + escaped + "' type='checkbox'" +
+                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
+                               ( config[ val.id ] ? " checked='checked'" : "" ) +
+                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
+                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
+               } else {
+                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
+                               "' title='" + escapedTooltip + "'>" + val.label +
+                               ": </label><select id='qunit-urlconfig-" + escaped +
+                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
+
+                       if ( QUnit.is( "array", val.value ) ) {
+                               for ( j = 0; j < val.value.length; j++ ) {
+                                       escaped = escapeText( val.value[ j ] );
+                                       urlConfigHtml += "<option value='" + escaped + "'" +
+                                               ( config[ val.id ] === val.value[ j ] ?
+                                                       ( selection = true ) && " selected='selected'" : "" ) +
+                                               ">" + escaped + "</option>";
                                }
-                       }
-                       if ( bestCommon.length * 2 >= longtext.length ) {
-                               return [ bestLongtextA, bestLongtextB,
-                                       bestShorttextA, bestShorttextB, bestCommon
-                               ];
                        } else {
-                               return null;
+                               for ( j in val.value ) {
+                                       if ( hasOwn.call( val.value, j ) ) {
+                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
+                                                       ( config[ val.id ] === j ?
+                                                               ( selection = true ) && " selected='selected'" : "" ) +
+                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
+                                       }
+                               }
+                       }
+                       if ( config[ val.id ] && !selection ) {
+                               escaped = escapeText( config[ val.id ] );
+                               urlConfigHtml += "<option value='" + escaped +
+                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
                        }
+                       urlConfigHtml += "</select>";
                }
+       }
 
-               // First check if the second quarter is the seed for a half-match.
-               hm1 = diffHalfMatchI( longtext, shorttext,
-                       Math.ceil( longtext.length / 4 ) );
-               // Check again based on the third quarter.
-               hm2 = diffHalfMatchI( longtext, shorttext,
-                       Math.ceil( longtext.length / 2 ) );
-               if ( !hm1 && !hm2 ) {
-                       return null;
-               } else if ( !hm2 ) {
-                       hm = hm1;
-               } else if ( !hm1 ) {
-                       hm = hm2;
-               } else {
-                       // Both matched.  Select the longest.
-                       hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
-               }
+       return urlConfigHtml;
+}
 
-               // A half-match was found, sort out the return data.
-               text1A, text1B, text2A, text2B;
-               if ( text1.length > text2.length ) {
-                       text1A = hm[ 0 ];
-                       text1B = hm[ 1 ];
-                       text2A = hm[ 2 ];
-                       text2B = hm[ 3 ];
-               } else {
-                       text2A = hm[ 0 ];
-                       text2B = hm[ 1 ];
-                       text1A = hm[ 2 ];
-                       text1B = hm[ 3 ];
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+       var updatedUrl, value, tests,
+               field = this,
+               params = {};
+
+       // Detect if field is a select menu or a checkbox
+       if ( "selectedIndex" in field ) {
+               value = field.options[ field.selectedIndex ].value || undefined;
+       } else {
+               value = field.checked ? ( field.defaultValue || true ) : undefined;
+       }
+
+       params[ field.name ] = value;
+       updatedUrl = setUrl( params );
+
+       // Check if we can apply the change without a page refresh
+       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+               QUnit.urlParams[ field.name ] = value;
+               config[ field.name ] = value || false;
+               tests = id( "qunit-tests" );
+               if ( tests ) {
+                       toggleClass( tests, "hidepass", value || false );
                }
-               midCommon = hm[ 4 ];
-               return [ text1A, text1B, text2A, text2B, midCommon ];
-       };
+               window.history.replaceState( null, "", updatedUrl );
+       } else {
+               window.location = updatedUrl;
+       }
+}
 
-       /**
-        * Do a quick line-level diff on both strings, then rediff the parts for
-        * greater accuracy.
-        * This speedup can produce non-minimal diffs.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} deadline Time when the diff should be complete by.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
-               var a, diffs, linearray, pointer, countInsert,
-                       countDelete, textInsert, textDelete, j;
-               // Scan the text on a line-by-line basis first.
-               a = this.diffLinesToChars( text1, text2 );
-               text1 = a.chars1;
-               text2 = a.chars2;
-               linearray = a.lineArray;
+function setUrl( params ) {
+       var key, arrValue, i,
+               querystring = "?",
+               location = window.location;
 
-               diffs = this.DiffMain( text1, text2, false, deadline );
+       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
 
-               // Convert the diff back to original text.
-               this.diffCharsToLines( diffs, linearray );
-               // Eliminate freak matches (e.g. blank lines)
-               this.diffCleanupSemantic( diffs );
+       for ( key in params ) {
 
-               // Rediff any replacement blocks, this time character-by-character.
-               // Add a dummy entry at the end.
-               diffs.push( [ DIFF_EQUAL, "" ] );
-               pointer = 0;
-               countDelete = 0;
-               countInsert = 0;
-               textDelete = "";
-               textInsert = "";
-               while ( pointer < diffs.length ) {
-                       switch ( diffs[ pointer ][ 0 ] ) {
-                       case DIFF_INSERT:
-                               countInsert++;
-                               textInsert += diffs[ pointer ][ 1 ];
-                               break;
-                       case DIFF_DELETE:
-                               countDelete++;
-                               textDelete += diffs[ pointer ][ 1 ];
-                               break;
-                       case DIFF_EQUAL:
-                               // Upon reaching an equality, check for prior redundancies.
-                               if ( countDelete >= 1 && countInsert >= 1 ) {
-                                       // Delete the offending records and add the merged ones.
-                                       diffs.splice( pointer - countDelete - countInsert,
-                                               countDelete + countInsert );
-                                       pointer = pointer - countDelete - countInsert;
-                                       a = this.DiffMain( textDelete, textInsert, false, deadline );
-                                       for ( j = a.length - 1; j >= 0; j-- ) {
-                                               diffs.splice( pointer, 0, a[ j ] );
-                                       }
-                                       pointer = pointer + a.length;
+               // Skip inherited or undefined properties
+               if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) {
+
+                       // Output a parameter for each value of this key (but usually just one)
+                       arrValue = [].concat( params[ key ] );
+                       for ( i = 0; i < arrValue.length; i++ ) {
+                               querystring += encodeURIComponent( key );
+                               if ( arrValue[ i ] !== true ) {
+                                       querystring += "=" + encodeURIComponent( arrValue[ i ] );
                                }
-                               countInsert = 0;
-                               countDelete = 0;
-                               textDelete = "";
-                               textInsert = "";
-                               break;
+                               querystring += "&";
                        }
-                       pointer++;
                }
-               diffs.pop(); // Remove the dummy entry at the end.
+       }
+       return location.protocol + "//" + location.host +
+               location.pathname + querystring.slice( 0, -1 );
+}
 
-               return diffs;
-       };
+function applyUrlParams() {
+       var selectedModule,
+               modulesList = id( "qunit-modulefilter" ),
+               filter = id( "qunit-filter-input" ).value;
 
-       /**
-        * Find the 'middle snake' of a diff, split the problem in two
-        * and return the recursively constructed diff.
-        * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} deadline Time at which to bail if not yet complete.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
-               var text1Length, text2Length, maxD, vOffset, vLength,
-                       v1, v2, x, delta, front, k1start, k1end, k2start,
-                       k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
-               // Cache the text lengths to prevent multiple calls.
-               text1Length = text1.length;
-               text2Length = text2.length;
-               maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
-               vOffset = maxD;
-               vLength = 2 * maxD;
-               v1 = new Array( vLength );
-               v2 = new Array( vLength );
-               // Setting all elements to -1 is faster in Chrome & Firefox than mixing
-               // integers and undefined.
-               for ( x = 0; x < vLength; x++ ) {
-                       v1[ x ] = -1;
-                       v2[ x ] = -1;
+       selectedModule = modulesList ?
+               decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
+               undefined;
+
+       window.location = setUrl( {
+               module: ( selectedModule === "" ) ? undefined : selectedModule,
+               filter: ( filter === "" ) ? undefined : filter,
+
+               // Remove moduleId and testId filters
+               moduleId: undefined,
+               testId: undefined
+       } );
+}
+
+function toolbarUrlConfigContainer() {
+       var urlConfigContainer = document.createElement( "span" );
+
+       urlConfigContainer.innerHTML = getUrlConfigHtml();
+       addClass( urlConfigContainer, "qunit-url-config" );
+
+       // For oldIE support:
+       // * Add handlers to the individual elements instead of the container
+       // * Use "click" instead of "change" for checkboxes
+       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+
+       return urlConfigContainer;
+}
+
+function toolbarLooseFilter() {
+       var filter = document.createElement( "form" ),
+               label = document.createElement( "label" ),
+               input = document.createElement( "input" ),
+               button = document.createElement( "button" );
+
+       addClass( filter, "qunit-filter" );
+
+       label.innerHTML = "Filter: ";
+
+       input.type = "text";
+       input.value = config.filter || "";
+       input.name = "filter";
+       input.id = "qunit-filter-input";
+
+       button.innerHTML = "Go";
+
+       label.appendChild( input );
+
+       filter.appendChild( label );
+       filter.appendChild( button );
+       addEvent( filter, "submit", function( ev ) {
+               applyUrlParams();
+
+               if ( ev && ev.preventDefault ) {
+                       ev.preventDefault();
                }
-               v1[ vOffset + 1 ] = 0;
-               v2[ vOffset + 1 ] = 0;
-               delta = text1Length - text2Length;
-               // If the total number of characters is odd, then the front path will collide
-               // with the reverse path.
-               front = ( delta % 2 !== 0 );
-               // Offsets for start and end of k loop.
-               // Prevents mapping of space beyond the grid.
-               k1start = 0;
-               k1end = 0;
-               k2start = 0;
-               k2end = 0;
-               for ( d = 0; d < maxD; d++ ) {
-                       // Bail out if deadline is reached.
-                       if ( ( new Date() ).getTime() > deadline ) {
-                               break;
-                       }
 
-                       // Walk the front path one step.
-                       for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
-                               k1Offset = vOffset + k1;
-                               if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
-                                       x1 = v1[ k1Offset + 1 ];
-                               } else {
-                                       x1 = v1[ k1Offset - 1 ] + 1;
-                               }
-                               y1 = x1 - k1;
-                               while ( x1 < text1Length && y1 < text2Length &&
-                                       text1.charAt( x1 ) === text2.charAt( y1 ) ) {
-                                       x1++;
-                                       y1++;
-                               }
-                               v1[ k1Offset ] = x1;
-                               if ( x1 > text1Length ) {
-                                       // Ran off the right of the graph.
-                                       k1end += 2;
-                               } else if ( y1 > text2Length ) {
-                                       // Ran off the bottom of the graph.
-                                       k1start += 2;
-                               } else if ( front ) {
-                                       k2Offset = vOffset + delta - k1;
-                                       if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
-                                               // Mirror x2 onto top-left coordinate system.
-                                               x2 = text1Length - v2[ k2Offset ];
-                                               if ( x1 >= x2 ) {
-                                                       // Overlap detected.
-                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
-                                               }
-                                       }
-                               }
-                       }
+               return false;
+       } );
+
+       return filter;
+}
+
+function toolbarModuleFilterHtml() {
+       var i,
+               moduleFilterHtml = "";
+
+       if ( !modulesList.length ) {
+               return false;
+       }
+
+       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
+               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
+               ">< All Modules ></option>";
+
+       for ( i = 0; i < modulesList.length; i++ ) {
+               moduleFilterHtml += "<option value='" +
+                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+                       ">" + escapeText( modulesList[ i ] ) + "</option>";
+       }
+       moduleFilterHtml += "</select>";
+
+       return moduleFilterHtml;
+}
+
+function toolbarModuleFilter() {
+       var toolbar = id( "qunit-testrunner-toolbar" ),
+               moduleFilter = document.createElement( "span" ),
+               moduleFilterHtml = toolbarModuleFilterHtml();
+
+       if ( !toolbar || !moduleFilterHtml ) {
+               return false;
+       }
+
+       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+       moduleFilter.innerHTML = moduleFilterHtml;
+
+       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+
+       toolbar.appendChild( moduleFilter );
+}
+
+function appendToolbar() {
+       var toolbar = id( "qunit-testrunner-toolbar" );
+
+       if ( toolbar ) {
+               toolbar.appendChild( toolbarUrlConfigContainer() );
+               toolbar.appendChild( toolbarLooseFilter() );
+               toolbarModuleFilter();
+       }
+}
+
+function appendHeader() {
+       var header = id( "qunit-header" );
+
+       if ( header ) {
+               header.innerHTML = "<a href='" + escapeText( unfilteredUrl ) + "'>" + header.innerHTML +
+                       "</a> ";
+       }
+}
+
+function appendBanner() {
+       var banner = id( "qunit-banner" );
+
+       if ( banner ) {
+               banner.className = "";
+       }
+}
+
+function appendTestResults() {
+       var tests = id( "qunit-tests" ),
+               result = id( "qunit-testresult" );
+
+       if ( result ) {
+               result.parentNode.removeChild( result );
+       }
+
+       if ( tests ) {
+               tests.innerHTML = "";
+               result = document.createElement( "p" );
+               result.id = "qunit-testresult";
+               result.className = "result";
+               tests.parentNode.insertBefore( result, tests );
+               result.innerHTML = "Running...<br />&#160;";
+       }
+}
+
+function storeFixture() {
+       var fixture = id( "qunit-fixture" );
+       if ( fixture ) {
+               config.fixture = fixture.innerHTML;
+       }
+}
+
+function appendFilteredTest() {
+       var testId = QUnit.config.testId;
+       if ( !testId || testId.length <= 0 ) {
+               return "";
+       }
+       return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
+               escapeText( testId.join( ", " ) ) +
+               " <a id='qunit-clearFilter' href='" +
+               escapeText( unfilteredUrl ) +
+               "'>Run all tests</a></div>";
+}
+
+function appendUserAgent() {
+       var userAgent = id( "qunit-userAgent" );
+
+       if ( userAgent ) {
+               userAgent.innerHTML = "";
+               userAgent.appendChild(
+                       document.createTextNode(
+                               "QUnit " + QUnit.version + "; " + navigator.userAgent
+                       )
+               );
+       }
+}
 
-                       // Walk the reverse path one step.
-                       for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
-                               k2Offset = vOffset + k2;
-                               if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
-                                       x2 = v2[ k2Offset + 1 ];
-                               } else {
-                                       x2 = v2[ k2Offset - 1 ] + 1;
-                               }
-                               y2 = x2 - k2;
-                               while ( x2 < text1Length && y2 < text2Length &&
-                                       text1.charAt( text1Length - x2 - 1 ) ===
-                                       text2.charAt( text2Length - y2 - 1 ) ) {
-                                       x2++;
-                                       y2++;
-                               }
-                               v2[ k2Offset ] = x2;
-                               if ( x2 > text1Length ) {
-                                       // Ran off the left of the graph.
-                                       k2end += 2;
-                               } else if ( y2 > text2Length ) {
-                                       // Ran off the top of the graph.
-                                       k2start += 2;
-                               } else if ( !front ) {
-                                       k1Offset = vOffset + delta - k2;
-                                       if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
-                                               x1 = v1[ k1Offset ];
-                                               y1 = vOffset + x1 - k1Offset;
-                                               // Mirror x2 onto top-left coordinate system.
-                                               x2 = text1Length - x2;
-                                               if ( x1 >= x2 ) {
-                                                       // Overlap detected.
-                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
-                                               }
-                                       }
-                               }
-                       }
+function appendInterface() {
+       var qunit = id( "qunit" );
+
+       if ( qunit ) {
+               qunit.innerHTML =
+                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+                       "<h2 id='qunit-banner'></h2>" +
+                       "<div id='qunit-testrunner-toolbar'></div>" +
+                       appendFilteredTest() +
+                       "<h2 id='qunit-userAgent'></h2>" +
+                       "<ol id='qunit-tests'></ol>";
+       }
+
+       appendHeader();
+       appendBanner();
+       appendTestResults();
+       appendUserAgent();
+       appendToolbar();
+}
+
+function appendTestsList( modules ) {
+       var i, l, x, z, test, moduleObj;
+
+       for ( i = 0, l = modules.length; i < l; i++ ) {
+               moduleObj = modules[ i ];
+
+               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+                       test = moduleObj.tests[ x ];
+
+                       appendTest( test.name, test.testId, moduleObj.name );
                }
-               // Diff took too long and hit the deadline or
-               // number of diffs equals number of characters, no commonality at all.
-               return [
-                       [ DIFF_DELETE, text1 ],
-                       [ DIFF_INSERT, text2 ]
-               ];
-       };
+       }
+}
 
-       /**
-        * Given the location of the 'middle snake', split the diff in two parts
-        * and recurse.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} x Index of split point in text1.
-        * @param {number} y Index of split point in text2.
-        * @param {number} deadline Time at which to bail if not yet complete.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
-               var text1a, text1b, text2a, text2b, diffs, diffsb;
-               text1a = text1.substring( 0, x );
-               text2a = text2.substring( 0, y );
-               text1b = text1.substring( x );
-               text2b = text2.substring( y );
+function appendTest( name, testId, moduleName ) {
+       var title, rerunTrigger, testBlock, assertList,
+               tests = id( "qunit-tests" );
 
-               // Compute both diffs serially.
-               diffs = this.DiffMain( text1a, text2a, false, deadline );
-               diffsb = this.DiffMain( text1b, text2b, false, deadline );
+       if ( !tests ) {
+               return;
+       }
 
-               return diffs.concat( diffsb );
-       };
+       title = document.createElement( "strong" );
+       title.innerHTML = getNameHtml( name, moduleName );
 
-       /**
-        * Reduce the number of edits by eliminating semantically trivial equalities.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
-               var changes, equalities, equalitiesLength, lastequality,
-                       pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
-                       lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
-               changes = false;
-               equalities = []; // Stack of indices where equalities are found.
-               equalitiesLength = 0; // Keeping our own length var is faster in JS.
-               /** @type {?string} */
-               lastequality = null;
-               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
-               pointer = 0; // Index of current position.
-               // Number of characters that changed prior to the equality.
-               lengthInsertions1 = 0;
-               lengthDeletions1 = 0;
-               // Number of characters that changed after the equality.
-               lengthInsertions2 = 0;
-               lengthDeletions2 = 0;
-               while ( pointer < diffs.length ) {
-                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
-                               equalities[ equalitiesLength++ ] = pointer;
-                               lengthInsertions1 = lengthInsertions2;
-                               lengthDeletions1 = lengthDeletions2;
-                               lengthInsertions2 = 0;
-                               lengthDeletions2 = 0;
-                               lastequality = diffs[ pointer ][ 1 ];
-                       } else { // An insertion or deletion.
-                               if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
-                                       lengthInsertions2 += diffs[ pointer ][ 1 ].length;
-                               } else {
-                                       lengthDeletions2 += diffs[ pointer ][ 1 ].length;
-                               }
-                               // Eliminate an equality that is smaller or equal to the edits on both
-                               // sides of it.
-                               if ( lastequality && ( lastequality.length <=
-                                               Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
-                                               ( lastequality.length <= Math.max( lengthInsertions2,
-                                                       lengthDeletions2 ) ) ) {
+       rerunTrigger = document.createElement( "a" );
+       rerunTrigger.innerHTML = "Rerun";
+       rerunTrigger.href = setUrl( { testId: testId } );
 
-                                       // Duplicate record.
-                                       diffs.splice(
-                                               equalities[ equalitiesLength - 1 ],
-                                               0,
-                                               [ DIFF_DELETE, lastequality ]
-                                       );
+       testBlock = document.createElement( "li" );
+       testBlock.appendChild( title );
+       testBlock.appendChild( rerunTrigger );
+       testBlock.id = "qunit-test-output-" + testId;
 
-                                       // Change second copy to insert.
-                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+       assertList = document.createElement( "ol" );
+       assertList.className = "qunit-assert-list";
 
-                                       // Throw away the equality we just deleted.
-                                       equalitiesLength--;
+       testBlock.appendChild( assertList );
 
-                                       // Throw away the previous equality (it needs to be reevaluated).
-                                       equalitiesLength--;
-                                       pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+       tests.appendChild( testBlock );
+}
 
-                                       // Reset the counters.
-                                       lengthInsertions1 = 0;
-                                       lengthDeletions1 = 0;
-                                       lengthInsertions2 = 0;
-                                       lengthDeletions2 = 0;
-                                       lastequality = null;
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
+// HTML Reporter initialization and load
+QUnit.begin( function( details ) {
+       var i, moduleObj, tests;
 
-               // Normalize the diff.
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
+       // Sort modules by name for the picker
+       for ( i = 0; i < details.modules.length; i++ ) {
+               moduleObj = details.modules[ i ];
+               if ( moduleObj.name ) {
+                       modulesList.push( moduleObj.name );
                }
+       }
+       modulesList.sort( function( a, b ) {
+               return a.localeCompare( b );
+       } );
 
-               // Find any overlaps between deletions and insertions.
-               // e.g: <del>abcxxx</del><ins>xxxdef</ins>
-               //   -> <del>abc</del>xxx<ins>def</ins>
-               // e.g: <del>xxxabc</del><ins>defxxx</ins>
-               //   -> <ins>def</ins>xxx<del>abc</del>
-               // Only extract an overlap if it is as big as the edit ahead or behind it.
-               pointer = 1;
-               while ( pointer < diffs.length ) {
-                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
-                                       diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
-                               deletion = diffs[ pointer - 1 ][ 1 ];
-                               insertion = diffs[ pointer ][ 1 ];
-                               overlapLength1 = this.diffCommonOverlap( deletion, insertion );
-                               overlapLength2 = this.diffCommonOverlap( insertion, deletion );
-                               if ( overlapLength1 >= overlapLength2 ) {
-                                       if ( overlapLength1 >= deletion.length / 2 ||
-                                                       overlapLength1 >= insertion.length / 2 ) {
-                                               // Overlap found.  Insert an equality and trim the surrounding edits.
-                                               diffs.splice(
-                                                       pointer,
-                                                       0,
-                                                       [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
-                                               );
-                                               diffs[ pointer - 1 ][ 1 ] =
-                                                       deletion.substring( 0, deletion.length - overlapLength1 );
-                                               diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
-                                               pointer++;
-                                       }
-                               } else {
-                                       if ( overlapLength2 >= deletion.length / 2 ||
-                                                       overlapLength2 >= insertion.length / 2 ) {
+       // Capture fixture HTML from the page
+       storeFixture();
 
-                                               // Reverse overlap found.
-                                               // Insert an equality and swap and trim the surrounding edits.
-                                               diffs.splice(
-                                                       pointer,
-                                                       0,
-                                                       [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
-                                               );
+       // Initialize QUnit elements
+       appendInterface();
+       appendTestsList( details.modules );
+       tests = id( "qunit-tests" );
+       if ( tests && config.hidepassed ) {
+               addClass( tests, "hidepass" );
+       }
+} );
+
+QUnit.done( function( details ) {
+       var i, key,
+               banner = id( "qunit-banner" ),
+               tests = id( "qunit-tests" ),
+               html = [
+                       "Tests completed in ",
+                       details.runtime,
+                       " milliseconds.<br />",
+                       "<span class='passed'>",
+                       details.passed,
+                       "</span> assertions of <span class='total'>",
+                       details.total,
+                       "</span> passed, <span class='failed'>",
+                       details.failed,
+                       "</span> failed."
+               ].join( "" );
+
+       if ( banner ) {
+               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+       }
+
+       if ( tests ) {
+               id( "qunit-testresult" ).innerHTML = html;
+       }
+
+       if ( config.altertitle && document.title ) {
 
-                                               diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
-                                               diffs[ pointer - 1 ][ 1 ] =
-                                                       insertion.substring( 0, insertion.length - overlapLength2 );
-                                               diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
-                                               diffs[ pointer + 1 ][ 1 ] =
-                                                       deletion.substring( overlapLength2 );
-                                               pointer++;
-                                       }
-                               }
-                               pointer++;
+               // Show ✖ for good, ✔ for bad suite result in title
+               // use escape sequences in case file gets loaded with non-utf-8-charset
+               document.title = [
+                       ( details.failed ? "\u2716" : "\u2714" ),
+                       document.title.replace( /^[\u2714\u2716] /i, "" )
+               ].join( " " );
+       }
+
+       // Clear own sessionStorage items if all tests passed
+       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
+               for ( i = 0; i < sessionStorage.length; i++ ) {
+                       key = sessionStorage.key( i++ );
+                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
+                               sessionStorage.removeItem( key );
                        }
-                       pointer++;
                }
-       };
+       }
 
-       /**
-        * Determine if the suffix of one string is the prefix of another.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the end of the first
-        *     string and the start of the second string.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
-               var text1Length, text2Length, textLength,
-                       best, length, pattern, found;
-               // Cache the text lengths to prevent multiple calls.
-               text1Length = text1.length;
-               text2Length = text2.length;
-               // Eliminate the null case.
-               if ( text1Length === 0 || text2Length === 0 ) {
-                       return 0;
-               }
-               // Truncate the longer string.
-               if ( text1Length > text2Length ) {
-                       text1 = text1.substring( text1Length - text2Length );
-               } else if ( text1Length < text2Length ) {
-                       text2 = text2.substring( 0, text1Length );
-               }
-               textLength = Math.min( text1Length, text2Length );
-               // Quick check for the worst case.
-               if ( text1 === text2 ) {
-                       return textLength;
-               }
+       // Scroll back to top to show results
+       if ( config.scrolltop && window.scrollTo ) {
+               window.scrollTo( 0, 0 );
+       }
+} );
 
-               // Start by looking for a single character match
-               // and increase length until no match is found.
-               // Performance analysis: https://neil.fraser.name/news/2010/11/04/
-               best = 0;
-               length = 1;
-               while ( true ) {
-                       pattern = text1.substring( textLength - length );
-                       found = text2.indexOf( pattern );
-                       if ( found === -1 ) {
-                               return best;
-                       }
-                       length += found;
-                       if ( found === 0 || text1.substring( textLength - length ) ===
-                                       text2.substring( 0, length ) ) {
-                               best = length;
-                               length++;
-                       }
-               }
-       };
+function getNameHtml( name, module ) {
+       var nameHtml = "";
 
-       /**
-        * Split two texts into an array of strings.  Reduce the texts to a string of
-        * hashes where each Unicode character represents one line.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
-        *     An object containing the encoded text1, the encoded text2 and
-        *     the array of unique strings.
-        *     The zeroth element of the array of unique strings is intentionally blank.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
-               var lineArray, lineHash, chars1, chars2;
-               lineArray = []; // e.g. lineArray[4] === 'Hello\n'
-               lineHash = {}; // e.g. lineHash['Hello\n'] === 4
+       if ( module ) {
+               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
+       }
 
-               // '\x00' is a valid character, but various debuggers don't like it.
-               // So we'll insert a junk entry to avoid generating a null character.
-               lineArray[ 0 ] = "";
+       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
 
-               /**
-                * Split a text into an array of strings.  Reduce the texts to a string of
-                * hashes where each Unicode character represents one line.
-                * Modifies linearray and linehash through being a closure.
-                * @param {string} text String to encode.
-                * @return {string} Encoded string.
-                * @private
-                */
-               function diffLinesToCharsMunge( text ) {
-                       var chars, lineStart, lineEnd, lineArrayLength, line;
-                       chars = "";
-                       // Walk the text, pulling out a substring for each line.
-                       // text.split('\n') would would temporarily double our memory footprint.
-                       // Modifying text would create many large strings to garbage collect.
-                       lineStart = 0;
-                       lineEnd = -1;
-                       // Keeping our own length variable is faster than looking it up.
-                       lineArrayLength = lineArray.length;
-                       while ( lineEnd < text.length - 1 ) {
-                               lineEnd = text.indexOf( "\n", lineStart );
-                               if ( lineEnd === -1 ) {
-                                       lineEnd = text.length - 1;
-                               }
-                               line = text.substring( lineStart, lineEnd + 1 );
-                               lineStart = lineEnd + 1;
+       return nameHtml;
+}
 
-                               if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
-                                                       ( lineHash[ line ] !== undefined ) ) {
-                                       chars += String.fromCharCode( lineHash[ line ] );
-                               } else {
-                                       chars += String.fromCharCode( lineArrayLength );
-                                       lineHash[ line ] = lineArrayLength;
-                                       lineArray[ lineArrayLength++ ] = line;
-                               }
-                       }
-                       return chars;
-               }
+QUnit.testStart( function( details ) {
+       var running, testBlock, bad;
 
-               chars1 = diffLinesToCharsMunge( text1 );
-               chars2 = diffLinesToCharsMunge( text2 );
-               return {
-                       chars1: chars1,
-                       chars2: chars2,
-                       lineArray: lineArray
-               };
-       };
+       testBlock = id( "qunit-test-output-" + details.testId );
+       if ( testBlock ) {
+               testBlock.className = "running";
+       } else {
 
-       /**
-        * Rehydrate the text in a diff from a string of line hashes to real lines of
-        * text.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        * @param {!Array.<string>} lineArray Array of unique strings.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
-               var x, chars, text, y;
-               for ( x = 0; x < diffs.length; x++ ) {
-                       chars = diffs[ x ][ 1 ];
-                       text = [];
-                       for ( y = 0; y < chars.length; y++ ) {
-                               text[ y ] = lineArray[ chars.charCodeAt( y ) ];
-                       }
-                       diffs[ x ][ 1 ] = text.join( "" );
-               }
-       };
+               // Report later registered tests
+               appendTest( details.name, details.testId, details.module );
+       }
 
-       /**
-        * Reorder and merge like edit sections.  Merge equalities.
-        * Any edit section can move as long as it doesn't cross an equality.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
-               var pointer, countDelete, countInsert, textInsert, textDelete,
-                       commonlength, changes, diffPointer, position;
-               diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
-               pointer = 0;
-               countDelete = 0;
-               countInsert = 0;
-               textDelete = "";
-               textInsert = "";
-               commonlength;
-               while ( pointer < diffs.length ) {
-                       switch ( diffs[ pointer ][ 0 ] ) {
-                       case DIFF_INSERT:
-                               countInsert++;
-                               textInsert += diffs[ pointer ][ 1 ];
-                               pointer++;
-                               break;
-                       case DIFF_DELETE:
-                               countDelete++;
-                               textDelete += diffs[ pointer ][ 1 ];
-                               pointer++;
-                               break;
-                       case DIFF_EQUAL:
-                               // Upon reaching an equality, check for prior redundancies.
-                               if ( countDelete + countInsert > 1 ) {
-                                       if ( countDelete !== 0 && countInsert !== 0 ) {
-                                               // Factor out any common prefixes.
-                                               commonlength = this.diffCommonPrefix( textInsert, textDelete );
-                                               if ( commonlength !== 0 ) {
-                                                       if ( ( pointer - countDelete - countInsert ) > 0 &&
-                                                                       diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
-                                                                       DIFF_EQUAL ) {
-                                                               diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
-                                                                       textInsert.substring( 0, commonlength );
-                                                       } else {
-                                                               diffs.splice( 0, 0, [ DIFF_EQUAL,
-                                                                       textInsert.substring( 0, commonlength )
-                                                               ] );
-                                                               pointer++;
-                                                       }
-                                                       textInsert = textInsert.substring( commonlength );
-                                                       textDelete = textDelete.substring( commonlength );
-                                               }
-                                               // Factor out any common suffixies.
-                                               commonlength = this.diffCommonSuffix( textInsert, textDelete );
-                                               if ( commonlength !== 0 ) {
-                                                       diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
-                                                                       commonlength ) + diffs[ pointer ][ 1 ];
-                                                       textInsert = textInsert.substring( 0, textInsert.length -
-                                                               commonlength );
-                                                       textDelete = textDelete.substring( 0, textDelete.length -
-                                                               commonlength );
-                                               }
-                                       }
-                                       // Delete the offending records and add the merged ones.
-                                       if ( countDelete === 0 ) {
-                                               diffs.splice( pointer - countInsert,
-                                                       countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
-                                       } else if ( countInsert === 0 ) {
-                                               diffs.splice( pointer - countDelete,
-                                                       countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
-                                       } else {
-                                               diffs.splice(
-                                                       pointer - countDelete - countInsert,
-                                                       countDelete + countInsert,
-                                                       [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
-                                               );
-                                       }
-                                       pointer = pointer - countDelete - countInsert +
-                                               ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
-                               } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
+       running = id( "qunit-testresult" );
+       if ( running ) {
+               bad = QUnit.config.reorder && defined.sessionStorage &&
+                       +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+
+               running.innerHTML = ( bad ?
+                       "Rerunning previously failed test: <br />" :
+                       "Running: <br />" ) +
+                       getNameHtml( details.name, details.module );
+       }
 
-                                       // Merge this equality with the previous one.
-                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
-                                       diffs.splice( pointer, 1 );
-                               } else {
-                                       pointer++;
-                               }
-                               countInsert = 0;
-                               countDelete = 0;
-                               textDelete = "";
-                               textInsert = "";
-                               break;
-                       }
-               }
-               if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
-                       diffs.pop(); // Remove the dummy entry at the end.
-               }
+} );
 
-               // Second pass: look for single edits surrounded on both sides by equalities
-               // which can be shifted sideways to eliminate an equality.
-               // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
-               changes = false;
-               pointer = 1;
+function stripHtml( string ) {
 
-               // Intentionally ignore the first and last element (don't need checking).
-               while ( pointer < diffs.length - 1 ) {
-                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
-                                       diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
+       // Strip tags, html entity and whitespaces
+       return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\&quot;/g, "" ).replace( /\s+/g, "" );
+}
 
-                               diffPointer = diffs[ pointer ][ 1 ];
-                               position = diffPointer.substring(
-                                       diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
-                               );
+QUnit.log( function( details ) {
+       var assertList, assertLi,
+               message, expected, actual, diff,
+               showDiff = false,
+               testItem = id( "qunit-test-output-" + details.testId );
 
-                               // This is a single edit surrounded by equalities.
-                               if ( position === diffs[ pointer - 1 ][ 1 ] ) {
+       if ( !testItem ) {
+               return;
+       }
 
-                                       // Shift the edit over the previous equality.
-                                       diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
-                                               diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
-                                                       diffs[ pointer - 1 ][ 1 ].length );
-                                       diffs[ pointer + 1 ][ 1 ] =
-                                               diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
-                                       diffs.splice( pointer - 1, 1 );
-                                       changes = true;
-                               } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
-                                               diffs[ pointer + 1 ][ 1 ] ) {
+       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
+       message = "<span class='test-message'>" + message + "</span>";
+       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
 
-                                       // Shift the edit over the next equality.
-                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
-                                       diffs[ pointer ][ 1 ] =
-                                               diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
-                                               diffs[ pointer + 1 ][ 1 ];
-                                       diffs.splice( pointer + 1, 1 );
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
-               // If shifts were made, the diff needs reordering and another shift sweep.
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
+       // The pushFailure doesn't provide details.expected
+       // when it calls, it's implicit to also not show expected and diff stuff
+       // Also, we need to check details.expected existence, as it can exist and be undefined
+       if ( !details.result && hasOwn.call( details, "expected" ) ) {
+               if ( details.negative ) {
+                       expected = "NOT " + QUnit.dump.parse( details.expected );
+               } else {
+                       expected = QUnit.dump.parse( details.expected );
                }
-       };
 
-       return function( o, n ) {
-               var diff, output, text;
-               diff = new DiffMatchPatch();
-               output = diff.DiffMain( o, n );
-               diff.diffCleanupEfficiency( output );
-               text = diff.diffPrettyHtml( output );
+               actual = QUnit.dump.parse( details.actual );
+               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
+                       escapeText( expected ) +
+                       "</pre></td></tr>";
 
-               return text;
-       };
-}() );
+               if ( actual !== expected ) {
 
-// Get a reference to the global object, like window in browsers
-}( (function() {
-       return this;
-})() ));
+                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
+                               escapeText( actual ) + "</pre></td></tr>";
 
-(function() {
+                       // Don't show diff if actual or expected are booleans
+                       if ( !( /^(true|false)$/.test( actual ) ) &&
+                                       !( /^(true|false)$/.test( expected ) ) ) {
+                               diff = QUnit.diff( expected, actual );
+                               showDiff = stripHtml( diff ).length !==
+                                       stripHtml( expected ).length +
+                                       stripHtml( actual ).length;
+                       }
 
-// Don't load the HTML Reporter on non-Browser environments
-if ( typeof window === "undefined" || !window.document ) {
-       return;
-}
+                       // Don't show diff if expected and actual are totally different
+                       if ( showDiff ) {
+                               message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
+                                       diff + "</pre></td></tr>";
+                       }
+               } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
+                               expected.indexOf( "[object Object]" ) !== -1 ) {
+                       message += "<tr class='test-message'><th>Message: </th><td>" +
+                               "Diff suppressed as the depth of object is more than current max depth (" +
+                               QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
+                               " run with a higher max depth or <a href='" +
+                               escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
+                               "Rerun</a> without max depth.</p></td></tr>";
+               } else {
+                       message += "<tr class='test-message'><th>Message: </th><td>" +
+                               "Diff suppressed as the expected and actual results have an equivalent" +
+                               " serialization</td></tr>";
+               }
 
-// Deprecated QUnit.init - Ref #530
-// Re-initialize the configuration options
-QUnit.init = function() {
-       var tests, banner, result, qunit,
-               config = QUnit.config;
+               if ( details.source ) {
+                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
+                               escapeText( details.source ) + "</pre></td></tr>";
+               }
 
-       config.stats = { all: 0, bad: 0 };
-       config.moduleStats = { all: 0, bad: 0 };
-       config.started = 0;
-       config.updateRate = 1000;
-       config.blocking = false;
-       config.autostart = true;
-       config.autorun = false;
-       config.filter = "";
-       config.queue = [];
+               message += "</table>";
 
-       // Return on non-browser environments
-       // This is necessary to not break on node tests
-       if ( typeof window === "undefined" ) {
-               return;
+       // This occurs when pushFailure is set and we have an extracted stack trace
+       } else if ( !details.result && details.source ) {
+               message += "<table>" +
+                       "<tr class='test-source'><th>Source: </th><td><pre>" +
+                       escapeText( details.source ) + "</pre></td></tr>" +
+                       "</table>";
        }
 
-       qunit = id( "qunit" );
-       if ( qunit ) {
-               qunit.innerHTML =
-                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-                       "<h2 id='qunit-banner'></h2>" +
-                       "<div id='qunit-testrunner-toolbar'></div>" +
-                       "<h2 id='qunit-userAgent'></h2>" +
-                       "<ol id='qunit-tests'></ol>";
-       }
+       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
 
-       tests = id( "qunit-tests" );
-       banner = id( "qunit-banner" );
-       result = id( "qunit-testresult" );
+       assertLi = document.createElement( "li" );
+       assertLi.className = details.result ? "pass" : "fail";
+       assertLi.innerHTML = message;
+       assertList.appendChild( assertLi );
+} );
 
-       if ( tests ) {
-               tests.innerHTML = "";
-       }
+QUnit.testDone( function( details ) {
+       var testTitle, time, testItem, assertList,
+               good, bad, testCounts, skipped, sourceName,
+               tests = id( "qunit-tests" );
 
-       if ( banner ) {
-               banner.className = "";
+       if ( !tests ) {
+               return;
        }
 
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
+       testItem = id( "qunit-test-output-" + details.testId );
 
-       if ( tests ) {
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
+       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+       good = details.passed;
+       bad = details.failed;
+
+       // Store result when possible
+       if ( config.reorder && defined.sessionStorage ) {
+               if ( bad ) {
+                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
+               } else {
+                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+               }
        }
-};
 
-var config = QUnit.config,
-       collapseNext = false,
-       hasOwn = Object.prototype.hasOwnProperty,
-       defined = {
-               document: window.document !== undefined,
-               sessionStorage: (function() {
-                       var x = "qunit-test-string";
-                       try {
-                               sessionStorage.setItem( x, x );
-                               sessionStorage.removeItem( x );
-                               return true;
-                       } catch ( e ) {
-                               return false;
-                       }
-               }())
-       },
-       modulesList = [];
+       if ( bad === 0 ) {
 
-/**
-* Escape text for attribute or text content.
-*/
-function escapeText( s ) {
-       if ( !s ) {
-               return "";
+               // Collapse the passing tests
+               addClass( assertList, "qunit-collapsed" );
+       } else if ( bad && config.collapse && !collapseNext ) {
+
+               // Skip collapsing the first failing test
+               collapseNext = true;
+       } else {
+
+               // Collapse remaining tests
+               addClass( assertList, "qunit-collapsed" );
        }
-       s = s + "";
 
-       // Both single quotes and double quotes (for attributes)
-       return s.replace( /['"<>&]/g, function( s ) {
-               switch ( s ) {
-               case "'":
-                       return "&#039;";
-               case "\"":
-                       return "&quot;";
-               case "<":
-                       return "&lt;";
-               case ">":
-                       return "&gt;";
-               case "&":
-                       return "&amp;";
-               }
-       });
-}
+       // The testItem.firstChild is the test name
+       testTitle = testItem.firstChild;
 
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
-       if ( elem.addEventListener ) {
+       testCounts = bad ?
+               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
+               "";
 
-               // Standards-based browsers
-               elem.addEventListener( type, fn, false );
-       } else if ( elem.attachEvent ) {
+       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
+               details.assertions.length + ")</b>";
+
+       if ( details.skipped ) {
+               testItem.className = "skipped";
+               skipped = document.createElement( "em" );
+               skipped.className = "qunit-skipped-label";
+               skipped.innerHTML = "skipped";
+               testItem.insertBefore( skipped, testTitle );
+       } else {
+               addEvent( testTitle, "click", function() {
+                       toggleClass( assertList, "qunit-collapsed" );
+               } );
 
-               // support: IE <9
-               elem.attachEvent( "on" + type, function() {
-                       var event = window.event;
-                       if ( !event.target ) {
-                               event.target = event.srcElement || document;
-                       }
+               testItem.className = bad ? "fail" : "pass";
 
-                       fn.call( elem, event );
-               });
+               time = document.createElement( "span" );
+               time.className = "runtime";
+               time.innerHTML = details.runtime + " ms";
+               testItem.insertBefore( time, assertList );
        }
-}
 
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
-       var i = elems.length;
-       while ( i-- ) {
-               addEvent( elems[ i ], type, fn );
+       // Show the source of the test when showing assertions
+       if ( details.source ) {
+               sourceName = document.createElement( "p" );
+               sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
+               addClass( sourceName, "qunit-source" );
+               if ( bad === 0 ) {
+                       addClass( sourceName, "qunit-collapsed" );
+               }
+               addEvent( testTitle, "click", function() {
+                       toggleClass( sourceName, "qunit-collapsed" );
+               } );
+               testItem.appendChild( sourceName );
        }
-}
+} );
 
-function hasClass( elem, name ) {
-       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
-}
+// Avoid readyState issue with phantomjs
+// Ref: #818
+var notPhantom = ( function( p ) {
+       return !( p && p.version && p.version.major > 0 );
+} )( window.phantom );
 
-function addClass( elem, name ) {
-       if ( !hasClass( elem, name ) ) {
-               elem.className += ( elem.className ? " " : "" ) + name;
-       }
+if ( notPhantom && document.readyState === "complete" ) {
+       QUnit.load();
+} else {
+       addEvent( window, "load", QUnit.load );
 }
 
-function toggleClass( elem, name ) {
-       if ( hasClass( elem, name ) ) {
-               removeClass( elem, name );
-       } else {
-               addClass( elem, name );
+/*
+ * This file is a modified version of google-diff-match-patch's JavaScript implementation
+ * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+ * modifications are licensed as more fully set forth in LICENSE.txt.
+ *
+ * The original source of google-diff-match-patch is attributable and licensed as follows:
+ *
+ * Copyright 2006 Google Inc.
+ * https://code.google.com/p/google-diff-match-patch/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More Info:
+ *  https://code.google.com/p/google-diff-match-patch/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ */
+QUnit.diff = ( function() {
+       function DiffMatchPatch() {
        }
-}
 
-function removeClass( elem, name ) {
-       var set = " " + elem.className + " ";
-
-       // Class name may appear multiple times
-       while ( set.indexOf( " " + name + " " ) >= 0 ) {
-               set = set.replace( " " + name + " ", " " );
-       }
+       //  DIFF FUNCTIONS
 
-       // trim for prettiness
-       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
-}
+       /**
+        * The data structure representing a diff is an array of tuples:
+        * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
+        * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
+        */
+       var DIFF_DELETE = -1,
+               DIFF_INSERT = 1,
+               DIFF_EQUAL = 0;
 
-function id( name ) {
-       return defined.document && document.getElementById && document.getElementById( name );
-}
+       /**
+        * Find the differences between two texts.  Simplifies the problem by stripping
+        * any common prefix or suffix off the texts before diffing.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+        *     then don't run a line-level diff first to identify the changed areas.
+        *     Defaults to true, which does a faster, slightly less optimal diff.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
+               var deadline, checklines, commonlength,
+                       commonprefix, commonsuffix, diffs;
 
-function getUrlConfigHtml() {
-       var i, j, val,
-               escaped, escapedTooltip,
-               selection = false,
-               len = config.urlConfig.length,
-               urlConfigHtml = "";
+               // The diff must be complete in up to 1 second.
+               deadline = ( new Date() ).getTime() + 1000;
 
-       for ( i = 0; i < len; i++ ) {
-               val = config.urlConfig[ i ];
-               if ( typeof val === "string" ) {
-                       val = {
-                               id: val,
-                               label: val
-                       };
+               // Check for null inputs.
+               if ( text1 === null || text2 === null ) {
+                       throw new Error( "Null input. (DiffMain)" );
                }
 
-               escaped = escapeText( val.id );
-               escapedTooltip = escapeText( val.tooltip );
-
-               if ( config[ val.id ] === undefined ) {
-                       config[ val.id ] = QUnit.urlParams[ val.id ];
+               // Check for equality (speedup).
+               if ( text1 === text2 ) {
+                       if ( text1 ) {
+                               return [
+                                       [ DIFF_EQUAL, text1 ]
+                               ];
+                       }
+                       return [];
                }
 
-               if ( !val.value || typeof val.value === "string" ) {
-                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' type='checkbox'" +
-                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
-                               ( config[ val.id ] ? " checked='checked'" : "" ) +
-                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
-               } else {
-                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label +
-                               ": </label><select id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
-
-                       if ( QUnit.is( "array", val.value ) ) {
-                               for ( j = 0; j < val.value.length; j++ ) {
-                                       escaped = escapeText( val.value[ j ] );
-                                       urlConfigHtml += "<option value='" + escaped + "'" +
-                                               ( config[ val.id ] === val.value[ j ] ?
-                                                       ( selection = true ) && " selected='selected'" : "" ) +
-                                               ">" + escaped + "</option>";
-                               }
-                       } else {
-                               for ( j in val.value ) {
-                                       if ( hasOwn.call( val.value, j ) ) {
-                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
-                                                       ( config[ val.id ] === j ?
-                                                               ( selection = true ) && " selected='selected'" : "" ) +
-                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
-                                       }
-                               }
-                       }
-                       if ( config[ val.id ] && !selection ) {
-                               escaped = escapeText( config[ val.id ] );
-                               urlConfigHtml += "<option value='" + escaped +
-                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
-                       }
-                       urlConfigHtml += "</select>";
+               if ( typeof optChecklines === "undefined" ) {
+                       optChecklines = true;
                }
-       }
 
-       return urlConfigHtml;
-}
+               checklines = optChecklines;
 
-// Handle "click" events on toolbar checkboxes and "change" for select menus.
-// Updates the URL with the new state of `config.urlConfig` values.
-function toolbarChanged() {
-       var updatedUrl, value,
-               field = this,
-               params = {};
+               // Trim off common prefix (speedup).
+               commonlength = this.diffCommonPrefix( text1, text2 );
+               commonprefix = text1.substring( 0, commonlength );
+               text1 = text1.substring( commonlength );
+               text2 = text2.substring( commonlength );
 
-       // Detect if field is a select menu or a checkbox
-       if ( "selectedIndex" in field ) {
-               value = field.options[ field.selectedIndex ].value || undefined;
-       } else {
-               value = field.checked ? ( field.defaultValue || true ) : undefined;
-       }
+               // Trim off common suffix (speedup).
+               commonlength = this.diffCommonSuffix( text1, text2 );
+               commonsuffix = text1.substring( text1.length - commonlength );
+               text1 = text1.substring( 0, text1.length - commonlength );
+               text2 = text2.substring( 0, text2.length - commonlength );
 
-       params[ field.name ] = value;
-       updatedUrl = setUrl( params );
+               // Compute the diff on the middle block.
+               diffs = this.diffCompute( text1, text2, checklines, deadline );
 
-       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
-               config[ field.name ] = value || false;
-               if ( value ) {
-                       addClass( id( "qunit-tests" ), "hidepass" );
-               } else {
-                       removeClass( id( "qunit-tests" ), "hidepass" );
+               // Restore the prefix and suffix.
+               if ( commonprefix ) {
+                       diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
                }
+               if ( commonsuffix ) {
+                       diffs.push( [ DIFF_EQUAL, commonsuffix ] );
+               }
+               this.diffCleanupMerge( diffs );
+               return diffs;
+       };
 
-               // It is not necessary to refresh the whole page
-               window.history.replaceState( null, "", updatedUrl );
-       } else {
-               window.location = updatedUrl;
-       }
-}
-
-function setUrl( params ) {
-       var key,
-               querystring = "?";
+       /**
+        * Reduce the number of edits by eliminating operationally trivial equalities.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
+               var changes, equalities, equalitiesLength, lastequality,
+                       pointer, preIns, preDel, postIns, postDel;
+               changes = false;
+               equalities = []; // Stack of indices where equalities are found.
+               equalitiesLength = 0; // Keeping our own length var is faster in JS.
+               /** @type {?string} */
+               lastequality = null;
 
-       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+               pointer = 0; // Index of current position.
 
-       for ( key in params ) {
-               if ( hasOwn.call( params, key ) ) {
-                       if ( params[ key ] === undefined ) {
-                               continue;
-                       }
-                       querystring += encodeURIComponent( key );
-                       if ( params[ key ] !== true ) {
-                               querystring += "=" + encodeURIComponent( params[ key ] );
-                       }
-                       querystring += "&";
-               }
-       }
-       return location.protocol + "//" + location.host +
-               location.pathname + querystring.slice( 0, -1 );
-}
+               // Is there an insertion operation before the last equality.
+               preIns = false;
 
-function applyUrlParams() {
-       var selectedModule,
-               modulesList = id( "qunit-modulefilter" ),
-               filter = id( "qunit-filter-input" ).value;
+               // Is there a deletion operation before the last equality.
+               preDel = false;
 
-       selectedModule = modulesList ?
-               decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
-               undefined;
+               // Is there an insertion operation after the last equality.
+               postIns = false;
 
-       window.location = setUrl({
-               module: ( selectedModule === "" ) ? undefined : selectedModule,
-               filter: ( filter === "" ) ? undefined : filter,
+               // Is there a deletion operation after the last equality.
+               postDel = false;
+               while ( pointer < diffs.length ) {
 
-               // Remove testId filter
-               testId: undefined
-       });
-}
+                       // Equality found.
+                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
+                               if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
 
-function toolbarUrlConfigContainer() {
-       var urlConfigContainer = document.createElement( "span" );
+                                       // Candidate found.
+                                       equalities[ equalitiesLength++ ] = pointer;
+                                       preIns = postIns;
+                                       preDel = postDel;
+                                       lastequality = diffs[ pointer ][ 1 ];
+                               } else {
 
-       urlConfigContainer.innerHTML = getUrlConfigHtml();
-       addClass( urlConfigContainer, "qunit-url-config" );
+                                       // Not a candidate, and can never become one.
+                                       equalitiesLength = 0;
+                                       lastequality = null;
+                               }
+                               postIns = postDel = false;
 
-       // For oldIE support:
-       // * Add handlers to the individual elements instead of the container
-       // * Use "click" instead of "change" for checkboxes
-       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
-       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+                       // An insertion or deletion.
+                       } else {
 
-       return urlConfigContainer;
-}
+                               if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
+                                       postDel = true;
+                               } else {
+                                       postIns = true;
+                               }
 
-function toolbarLooseFilter() {
-       var filter = document.createElement( "form" ),
-               label = document.createElement( "label" ),
-               input = document.createElement( "input" ),
-               button = document.createElement( "button" );
+                               /*
+                                * Five types to be split:
+                                * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+                                * <ins>A</ins>X<ins>C</ins><del>D</del>
+                                * <ins>A</ins><del>B</del>X<ins>C</ins>
+                                * <ins>A</del>X<ins>C</ins><del>D</del>
+                                * <ins>A</ins><del>B</del>X<del>C</del>
+                                */
+                               if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
+                                               ( ( lastequality.length < 2 ) &&
+                                               ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
 
-       addClass( filter, "qunit-filter" );
+                                       // Duplicate record.
+                                       diffs.splice(
+                                               equalities[ equalitiesLength - 1 ],
+                                               0,
+                                               [ DIFF_DELETE, lastequality ]
+                                       );
 
-       label.innerHTML = "Filter: ";
+                                       // Change second copy to insert.
+                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+                                       equalitiesLength--; // Throw away the equality we just deleted;
+                                       lastequality = null;
+                                       if ( preIns && preDel ) {
 
-       input.type = "text";
-       input.value = config.filter || "";
-       input.name = "filter";
-       input.id = "qunit-filter-input";
+                                               // No changes made which could affect previous entry, keep going.
+                                               postIns = postDel = true;
+                                               equalitiesLength = 0;
+                                       } else {
+                                               equalitiesLength--; // Throw away the previous equality.
+                                               pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+                                               postIns = postDel = false;
+                                       }
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
 
-       button.innerHTML = "Go";
+               if ( changes ) {
+                       this.diffCleanupMerge( diffs );
+               }
+       };
 
-       label.appendChild( input );
+       /**
+        * Convert a diff array into a pretty HTML report.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        * @param {integer} string to be beautified.
+        * @return {string} HTML representation.
+        */
+       DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
+               var op, data, x,
+                       html = [];
+               for ( x = 0; x < diffs.length; x++ ) {
+                       op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
+                       data = diffs[ x ][ 1 ]; // Text of change.
+                       switch ( op ) {
+                       case DIFF_INSERT:
+                               html[ x ] = "<ins>" + escapeText( data ) + "</ins>";
+                               break;
+                       case DIFF_DELETE:
+                               html[ x ] = "<del>" + escapeText( data ) + "</del>";
+                               break;
+                       case DIFF_EQUAL:
+                               html[ x ] = "<span>" + escapeText( data ) + "</span>";
+                               break;
+                       }
+               }
+               return html.join( "" );
+       };
 
-       filter.appendChild( label );
-       filter.appendChild( button );
-       addEvent( filter, "submit", function( ev ) {
-               applyUrlParams();
+       /**
+        * Determine the common prefix of two strings.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {number} The number of characters common to the start of each
+        *     string.
+        */
+       DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
+               var pointermid, pointermax, pointermin, pointerstart;
 
-               if ( ev && ev.preventDefault ) {
-                       ev.preventDefault();
+               // Quick check for common null cases.
+               if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
+                       return 0;
                }
 
-               return false;
-       });
+               // Binary search.
+               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+               pointermin = 0;
+               pointermax = Math.min( text1.length, text2.length );
+               pointermid = pointermax;
+               pointerstart = 0;
+               while ( pointermin < pointermid ) {
+                       if ( text1.substring( pointerstart, pointermid ) ===
+                                       text2.substring( pointerstart, pointermid ) ) {
+                               pointermin = pointermid;
+                               pointerstart = pointermin;
+                       } else {
+                               pointermax = pointermid;
+                       }
+                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+               }
+               return pointermid;
+       };
 
-       return filter;
-}
+       /**
+        * Determine the common suffix of two strings.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {number} The number of characters common to the end of each string.
+        */
+       DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
+               var pointermid, pointermax, pointermin, pointerend;
 
-function toolbarModuleFilterHtml() {
-       var i,
-               moduleFilterHtml = "";
+               // Quick check for common null cases.
+               if ( !text1 ||
+                               !text2 ||
+                               text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
+                       return 0;
+               }
 
-       if ( !modulesList.length ) {
-               return false;
-       }
+               // Binary search.
+               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+               pointermin = 0;
+               pointermax = Math.min( text1.length, text2.length );
+               pointermid = pointermax;
+               pointerend = 0;
+               while ( pointermin < pointermid ) {
+                       if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
+                                       text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
+                               pointermin = pointermid;
+                               pointerend = pointermin;
+                       } else {
+                               pointermax = pointermid;
+                       }
+                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+               }
+               return pointermid;
+       };
 
-       modulesList.sort(function( a, b ) {
-               return a.localeCompare( b );
-       });
+       /**
+        * Find the differences between two texts.  Assumes that the texts do not
+        * have any common prefix or suffix.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {boolean} checklines Speedup flag.  If false, then don't run a
+        *     line-level diff first to identify the changed areas.
+        *     If true, then run a faster, slightly less optimal diff.
+        * @param {number} deadline Time when the diff should be complete by.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
+               var diffs, longtext, shorttext, i, hm,
+                       text1A, text2A, text1B, text2B,
+                       midCommon, diffsA, diffsB;
 
-       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
-               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
-               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
-               ">< All Modules ></option>";
+               if ( !text1 ) {
 
-       for ( i = 0; i < modulesList.length; i++ ) {
-               moduleFilterHtml += "<option value='" +
-                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
-                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
-                       ">" + escapeText( modulesList[ i ] ) + "</option>";
-       }
-       moduleFilterHtml += "</select>";
+                       // Just add some text (speedup).
+                       return [
+                               [ DIFF_INSERT, text2 ]
+                       ];
+               }
 
-       return moduleFilterHtml;
-}
+               if ( !text2 ) {
 
-function toolbarModuleFilter() {
-       var toolbar = id( "qunit-testrunner-toolbar" ),
-               moduleFilter = document.createElement( "span" ),
-               moduleFilterHtml = toolbarModuleFilterHtml();
+                       // Just delete some text (speedup).
+                       return [
+                               [ DIFF_DELETE, text1 ]
+                       ];
+               }
+
+               longtext = text1.length > text2.length ? text1 : text2;
+               shorttext = text1.length > text2.length ? text2 : text1;
+               i = longtext.indexOf( shorttext );
+               if ( i !== -1 ) {
+
+                       // Shorter text is inside the longer text (speedup).
+                       diffs = [
+                               [ DIFF_INSERT, longtext.substring( 0, i ) ],
+                               [ DIFF_EQUAL, shorttext ],
+                               [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
+                       ];
 
-       if ( !toolbar || !moduleFilterHtml ) {
-               return false;
-       }
+                       // Swap insertions for deletions if diff is reversed.
+                       if ( text1.length > text2.length ) {
+                               diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
+                       }
+                       return diffs;
+               }
 
-       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
-       moduleFilter.innerHTML = moduleFilterHtml;
+               if ( shorttext.length === 1 ) {
 
-       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+                       // Single character string.
+                       // After the previous speedup, the character can't be an equality.
+                       return [
+                               [ DIFF_DELETE, text1 ],
+                               [ DIFF_INSERT, text2 ]
+                       ];
+               }
 
-       toolbar.appendChild( moduleFilter );
-}
+               // Check to see if the problem can be split in two.
+               hm = this.diffHalfMatch( text1, text2 );
+               if ( hm ) {
 
-function appendToolbar() {
-       var toolbar = id( "qunit-testrunner-toolbar" );
+                       // A half-match was found, sort out the return data.
+                       text1A = hm[ 0 ];
+                       text1B = hm[ 1 ];
+                       text2A = hm[ 2 ];
+                       text2B = hm[ 3 ];
+                       midCommon = hm[ 4 ];
 
-       if ( toolbar ) {
-               toolbar.appendChild( toolbarUrlConfigContainer() );
-               toolbar.appendChild( toolbarLooseFilter() );
-       }
-}
+                       // Send both pairs off for separate processing.
+                       diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
+                       diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
 
-function appendHeader() {
-       var header = id( "qunit-header" );
+                       // Merge the results.
+                       return diffsA.concat( [
+                               [ DIFF_EQUAL, midCommon ]
+                       ], diffsB );
+               }
 
-       if ( header ) {
-               header.innerHTML = "<a href='" +
-                       escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
-                       "'>" + header.innerHTML + "</a> ";
-       }
-}
+               if ( checklines && text1.length > 100 && text2.length > 100 ) {
+                       return this.diffLineMode( text1, text2, deadline );
+               }
 
-function appendBanner() {
-       var banner = id( "qunit-banner" );
+               return this.diffBisect( text1, text2, deadline );
+       };
 
-       if ( banner ) {
-               banner.className = "";
-       }
-}
+       /**
+        * Do the two texts share a substring which is at least half the length of the
+        * longer text?
+        * This speedup can produce non-minimal diffs.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {Array.<string>} Five element Array, containing the prefix of
+        *     text1, the suffix of text1, the prefix of text2, the suffix of
+        *     text2 and the common middle.  Or null if there was no match.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
+               var longtext, shorttext, dmp,
+                       text1A, text2B, text2A, text1B, midCommon,
+                       hm1, hm2, hm;
 
-function appendTestResults() {
-       var tests = id( "qunit-tests" ),
-               result = id( "qunit-testresult" );
+               longtext = text1.length > text2.length ? text1 : text2;
+               shorttext = text1.length > text2.length ? text2 : text1;
+               if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
+                       return null; // Pointless.
+               }
+               dmp = this; // 'this' becomes 'window' in a closure.
 
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
+               /**
+                * Does a substring of shorttext exist within longtext such that the substring
+                * is at least half the length of longtext?
+                * Closure, but does not reference any external variables.
+                * @param {string} longtext Longer string.
+                * @param {string} shorttext Shorter string.
+                * @param {number} i Start index of quarter length substring within longtext.
+                * @return {Array.<string>} Five element Array, containing the prefix of
+                *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
+                *     of shorttext and the common middle.  Or null if there was no match.
+                * @private
+                */
+               function diffHalfMatchI( longtext, shorttext, i ) {
+                       var seed, j, bestCommon, prefixLength, suffixLength,
+                               bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
 
-       if ( tests ) {
-               tests.innerHTML = "";
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
-       }
-}
+                       // Start with a 1/4 length substring at position i as a seed.
+                       seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
+                       j = -1;
+                       bestCommon = "";
+                       while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
+                               prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
+                                       shorttext.substring( j ) );
+                               suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
+                                       shorttext.substring( 0, j ) );
+                               if ( bestCommon.length < suffixLength + prefixLength ) {
+                                       bestCommon = shorttext.substring( j - suffixLength, j ) +
+                                               shorttext.substring( j, j + prefixLength );
+                                       bestLongtextA = longtext.substring( 0, i - suffixLength );
+                                       bestLongtextB = longtext.substring( i + prefixLength );
+                                       bestShorttextA = shorttext.substring( 0, j - suffixLength );
+                                       bestShorttextB = shorttext.substring( j + prefixLength );
+                               }
+                       }
+                       if ( bestCommon.length * 2 >= longtext.length ) {
+                               return [ bestLongtextA, bestLongtextB,
+                                       bestShorttextA, bestShorttextB, bestCommon
+                               ];
+                       } else {
+                               return null;
+                       }
+               }
 
-function storeFixture() {
-       var fixture = id( "qunit-fixture" );
-       if ( fixture ) {
-               config.fixture = fixture.innerHTML;
-       }
-}
+               // First check if the second quarter is the seed for a half-match.
+               hm1 = diffHalfMatchI( longtext, shorttext,
+                       Math.ceil( longtext.length / 4 ) );
 
-function appendFilteredTest() {
-       var testId = QUnit.config.testId;
-       if ( !testId || testId.length <= 0 ) {
-               return "";
-       }
-       return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
-               escapeText( testId.join(", ") ) +
-               " <a id='qunit-clearFilter' href='" +
-               escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
-               "'>" + "Run all tests" + "</a></div>";
-}
+               // Check again based on the third quarter.
+               hm2 = diffHalfMatchI( longtext, shorttext,
+                       Math.ceil( longtext.length / 2 ) );
+               if ( !hm1 && !hm2 ) {
+                       return null;
+               } else if ( !hm2 ) {
+                       hm = hm1;
+               } else if ( !hm1 ) {
+                       hm = hm2;
+               } else {
 
-function appendUserAgent() {
-       var userAgent = id( "qunit-userAgent" );
+                       // Both matched.  Select the longest.
+                       hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
+               }
 
-       if ( userAgent ) {
-               userAgent.innerHTML = "";
-               userAgent.appendChild(
-                       document.createTextNode(
-                               "QUnit " + QUnit.version + "; " + navigator.userAgent
-                       )
-               );
-       }
-}
+               // A half-match was found, sort out the return data.
+               text1A, text1B, text2A, text2B;
+               if ( text1.length > text2.length ) {
+                       text1A = hm[ 0 ];
+                       text1B = hm[ 1 ];
+                       text2A = hm[ 2 ];
+                       text2B = hm[ 3 ];
+               } else {
+                       text2A = hm[ 0 ];
+                       text2B = hm[ 1 ];
+                       text1A = hm[ 2 ];
+                       text1B = hm[ 3 ];
+               }
+               midCommon = hm[ 4 ];
+               return [ text1A, text1B, text2A, text2B, midCommon ];
+       };
 
-function appendTestsList( modules ) {
-       var i, l, x, z, test, moduleObj;
+       /**
+        * Do a quick line-level diff on both strings, then rediff the parts for
+        * greater accuracy.
+        * This speedup can produce non-minimal diffs.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {number} deadline Time when the diff should be complete by.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
+               var a, diffs, linearray, pointer, countInsert,
+                       countDelete, textInsert, textDelete, j;
 
-       for ( i = 0, l = modules.length; i < l; i++ ) {
-               moduleObj = modules[ i ];
+               // Scan the text on a line-by-line basis first.
+               a = this.diffLinesToChars( text1, text2 );
+               text1 = a.chars1;
+               text2 = a.chars2;
+               linearray = a.lineArray;
 
-               if ( moduleObj.name ) {
-                       modulesList.push( moduleObj.name );
-               }
+               diffs = this.DiffMain( text1, text2, false, deadline );
 
-               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
-                       test = moduleObj.tests[ x ];
+               // Convert the diff back to original text.
+               this.diffCharsToLines( diffs, linearray );
 
-                       appendTest( test.name, test.testId, moduleObj.name );
-               }
-       }
-}
+               // Eliminate freak matches (e.g. blank lines)
+               this.diffCleanupSemantic( diffs );
 
-function appendTest( name, testId, moduleName ) {
-       var title, rerunTrigger, testBlock, assertList,
-               tests = id( "qunit-tests" );
+               // Rediff any replacement blocks, this time character-by-character.
+               // Add a dummy entry at the end.
+               diffs.push( [ DIFF_EQUAL, "" ] );
+               pointer = 0;
+               countDelete = 0;
+               countInsert = 0;
+               textDelete = "";
+               textInsert = "";
+               while ( pointer < diffs.length ) {
+                       switch ( diffs[ pointer ][ 0 ] ) {
+                       case DIFF_INSERT:
+                               countInsert++;
+                               textInsert += diffs[ pointer ][ 1 ];
+                               break;
+                       case DIFF_DELETE:
+                               countDelete++;
+                               textDelete += diffs[ pointer ][ 1 ];
+                               break;
+                       case DIFF_EQUAL:
+
+                               // Upon reaching an equality, check for prior redundancies.
+                               if ( countDelete >= 1 && countInsert >= 1 ) {
 
-       if ( !tests ) {
-               return;
-       }
+                                       // Delete the offending records and add the merged ones.
+                                       diffs.splice( pointer - countDelete - countInsert,
+                                               countDelete + countInsert );
+                                       pointer = pointer - countDelete - countInsert;
+                                       a = this.DiffMain( textDelete, textInsert, false, deadline );
+                                       for ( j = a.length - 1; j >= 0; j-- ) {
+                                               diffs.splice( pointer, 0, a[ j ] );
+                                       }
+                                       pointer = pointer + a.length;
+                               }
+                               countInsert = 0;
+                               countDelete = 0;
+                               textDelete = "";
+                               textInsert = "";
+                               break;
+                       }
+                       pointer++;
+               }
+               diffs.pop(); // Remove the dummy entry at the end.
 
-       title = document.createElement( "strong" );
-       title.innerHTML = getNameHtml( name, moduleName );
+               return diffs;
+       };
 
-       rerunTrigger = document.createElement( "a" );
-       rerunTrigger.innerHTML = "Rerun";
-       rerunTrigger.href = setUrl({ testId: testId });
+       /**
+        * Find the 'middle snake' of a diff, split the problem in two
+        * and return the recursively constructed diff.
+        * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {number} deadline Time at which to bail if not yet complete.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
+               var text1Length, text2Length, maxD, vOffset, vLength,
+                       v1, v2, x, delta, front, k1start, k1end, k2start,
+                       k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
 
-       testBlock = document.createElement( "li" );
-       testBlock.appendChild( title );
-       testBlock.appendChild( rerunTrigger );
-       testBlock.id = "qunit-test-output-" + testId;
+               // Cache the text lengths to prevent multiple calls.
+               text1Length = text1.length;
+               text2Length = text2.length;
+               maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
+               vOffset = maxD;
+               vLength = 2 * maxD;
+               v1 = new Array( vLength );
+               v2 = new Array( vLength );
 
-       assertList = document.createElement( "ol" );
-       assertList.className = "qunit-assert-list";
+               // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+               // integers and undefined.
+               for ( x = 0; x < vLength; x++ ) {
+                       v1[ x ] = -1;
+                       v2[ x ] = -1;
+               }
+               v1[ vOffset + 1 ] = 0;
+               v2[ vOffset + 1 ] = 0;
+               delta = text1Length - text2Length;
 
-       testBlock.appendChild( assertList );
+               // If the total number of characters is odd, then the front path will collide
+               // with the reverse path.
+               front = ( delta % 2 !== 0 );
 
-       tests.appendChild( testBlock );
-}
+               // Offsets for start and end of k loop.
+               // Prevents mapping of space beyond the grid.
+               k1start = 0;
+               k1end = 0;
+               k2start = 0;
+               k2end = 0;
+               for ( d = 0; d < maxD; d++ ) {
 
-// HTML Reporter initialization and load
-QUnit.begin(function( details ) {
-       var qunit = id( "qunit" );
+                       // Bail out if deadline is reached.
+                       if ( ( new Date() ).getTime() > deadline ) {
+                               break;
+                       }
 
-       // Fixture is the only one necessary to run without the #qunit element
-       storeFixture();
+                       // Walk the front path one step.
+                       for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
+                               k1Offset = vOffset + k1;
+                               if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
+                                       x1 = v1[ k1Offset + 1 ];
+                               } else {
+                                       x1 = v1[ k1Offset - 1 ] + 1;
+                               }
+                               y1 = x1 - k1;
+                               while ( x1 < text1Length && y1 < text2Length &&
+                                       text1.charAt( x1 ) === text2.charAt( y1 ) ) {
+                                       x1++;
+                                       y1++;
+                               }
+                               v1[ k1Offset ] = x1;
+                               if ( x1 > text1Length ) {
 
-       if ( qunit ) {
-               qunit.innerHTML =
-                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-                       "<h2 id='qunit-banner'></h2>" +
-                       "<div id='qunit-testrunner-toolbar'></div>" +
-                       appendFilteredTest() +
-                       "<h2 id='qunit-userAgent'></h2>" +
-                       "<ol id='qunit-tests'></ol>";
-       }
+                                       // Ran off the right of the graph.
+                                       k1end += 2;
+                               } else if ( y1 > text2Length ) {
 
-       appendHeader();
-       appendBanner();
-       appendTestResults();
-       appendUserAgent();
-       appendToolbar();
-       appendTestsList( details.modules );
-       toolbarModuleFilter();
+                                       // Ran off the bottom of the graph.
+                                       k1start += 2;
+                               } else if ( front ) {
+                                       k2Offset = vOffset + delta - k1;
+                                       if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
 
-       if ( qunit && config.hidepassed ) {
-               addClass( qunit.lastChild, "hidepass" );
-       }
-});
+                                               // Mirror x2 onto top-left coordinate system.
+                                               x2 = text1Length - v2[ k2Offset ];
+                                               if ( x1 >= x2 ) {
 
-QUnit.done(function( details ) {
-       var i, key,
-               banner = id( "qunit-banner" ),
-               tests = id( "qunit-tests" ),
-               html = [
-                       "Tests completed in ",
-                       details.runtime,
-                       " milliseconds.<br />",
-                       "<span class='passed'>",
-                       details.passed,
-                       "</span> assertions of <span class='total'>",
-                       details.total,
-                       "</span> passed, <span class='failed'>",
-                       details.failed,
-                       "</span> failed."
-               ].join( "" );
+                                                       // Overlap detected.
+                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
+                                               }
+                                       }
+                               }
+                       }
 
-       if ( banner ) {
-               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
-       }
+                       // Walk the reverse path one step.
+                       for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
+                               k2Offset = vOffset + k2;
+                               if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
+                                       x2 = v2[ k2Offset + 1 ];
+                               } else {
+                                       x2 = v2[ k2Offset - 1 ] + 1;
+                               }
+                               y2 = x2 - k2;
+                               while ( x2 < text1Length && y2 < text2Length &&
+                                       text1.charAt( text1Length - x2 - 1 ) ===
+                                       text2.charAt( text2Length - y2 - 1 ) ) {
+                                       x2++;
+                                       y2++;
+                               }
+                               v2[ k2Offset ] = x2;
+                               if ( x2 > text1Length ) {
 
-       if ( tests ) {
-               id( "qunit-testresult" ).innerHTML = html;
-       }
+                                       // Ran off the left of the graph.
+                                       k2end += 2;
+                               } else if ( y2 > text2Length ) {
 
-       if ( config.altertitle && defined.document && document.title ) {
+                                       // Ran off the top of the graph.
+                                       k2start += 2;
+                               } else if ( !front ) {
+                                       k1Offset = vOffset + delta - k2;
+                                       if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
+                                               x1 = v1[ k1Offset ];
+                                               y1 = vOffset + x1 - k1Offset;
 
-               // show ✖ for good, ✔ for bad suite result in title
-               // use escape sequences in case file gets loaded with non-utf-8-charset
-               document.title = [
-                       ( details.failed ? "\u2716" : "\u2714" ),
-                       document.title.replace( /^[\u2714\u2716] /i, "" )
-               ].join( " " );
-       }
+                                               // Mirror x2 onto top-left coordinate system.
+                                               x2 = text1Length - x2;
+                                               if ( x1 >= x2 ) {
 
-       // clear own sessionStorage items if all tests passed
-       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
-               for ( i = 0; i < sessionStorage.length; i++ ) {
-                       key = sessionStorage.key( i++ );
-                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
-                               sessionStorage.removeItem( key );
+                                                       // Overlap detected.
+                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
+                                               }
+                                       }
+                               }
                        }
                }
-       }
-
-       // scroll back to top to show results
-       if ( config.scrolltop && window.scrollTo ) {
-               window.scrollTo( 0, 0 );
-       }
-});
 
-function getNameHtml( name, module ) {
-       var nameHtml = "";
+               // Diff took too long and hit the deadline or
+               // number of diffs equals number of characters, no commonality at all.
+               return [
+                       [ DIFF_DELETE, text1 ],
+                       [ DIFF_INSERT, text2 ]
+               ];
+       };
 
-       if ( module ) {
-               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
-       }
+       /**
+        * Given the location of the 'middle snake', split the diff in two parts
+        * and recurse.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {number} x Index of split point in text1.
+        * @param {number} y Index of split point in text2.
+        * @param {number} deadline Time at which to bail if not yet complete.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
+               var text1a, text1b, text2a, text2b, diffs, diffsb;
+               text1a = text1.substring( 0, x );
+               text2a = text2.substring( 0, y );
+               text1b = text1.substring( x );
+               text2b = text2.substring( y );
 
-       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
+               // Compute both diffs serially.
+               diffs = this.DiffMain( text1a, text2a, false, deadline );
+               diffsb = this.DiffMain( text1b, text2b, false, deadline );
 
-       return nameHtml;
-}
+               return diffs.concat( diffsb );
+       };
 
-QUnit.testStart(function( details ) {
-       var running, testBlock, bad;
+       /**
+        * Reduce the number of edits by eliminating semantically trivial equalities.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
+               var changes, equalities, equalitiesLength, lastequality,
+                       pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
+                       lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+               changes = false;
+               equalities = []; // Stack of indices where equalities are found.
+               equalitiesLength = 0; // Keeping our own length var is faster in JS.
+               /** @type {?string} */
+               lastequality = null;
 
-       testBlock = id( "qunit-test-output-" + details.testId );
-       if ( testBlock ) {
-               testBlock.className = "running";
-       } else {
+               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+               pointer = 0; // Index of current position.
 
-               // Report later registered tests
-               appendTest( details.name, details.testId, details.module );
-       }
+               // Number of characters that changed prior to the equality.
+               lengthInsertions1 = 0;
+               lengthDeletions1 = 0;
 
-       running = id( "qunit-testresult" );
-       if ( running ) {
-               bad = QUnit.config.reorder && defined.sessionStorage &&
-                       +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+               // Number of characters that changed after the equality.
+               lengthInsertions2 = 0;
+               lengthDeletions2 = 0;
+               while ( pointer < diffs.length ) {
+                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
+                               equalities[ equalitiesLength++ ] = pointer;
+                               lengthInsertions1 = lengthInsertions2;
+                               lengthDeletions1 = lengthDeletions2;
+                               lengthInsertions2 = 0;
+                               lengthDeletions2 = 0;
+                               lastequality = diffs[ pointer ][ 1 ];
+                       } else { // An insertion or deletion.
+                               if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
+                                       lengthInsertions2 += diffs[ pointer ][ 1 ].length;
+                               } else {
+                                       lengthDeletions2 += diffs[ pointer ][ 1 ].length;
+                               }
 
-               running.innerHTML = ( bad ?
-                       "Rerunning previously failed test: <br />" :
-                       "Running: <br />" ) +
-                       getNameHtml( details.name, details.module );
-       }
+                               // Eliminate an equality that is smaller or equal to the edits on both
+                               // sides of it.
+                               if ( lastequality && ( lastequality.length <=
+                                               Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
+                                               ( lastequality.length <= Math.max( lengthInsertions2,
+                                                       lengthDeletions2 ) ) ) {
 
-});
+                                       // Duplicate record.
+                                       diffs.splice(
+                                               equalities[ equalitiesLength - 1 ],
+                                               0,
+                                               [ DIFF_DELETE, lastequality ]
+                                       );
 
-function stripHtml( string ) {
-       // strip tags, html entity and whitespaces
-       return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
-}
+                                       // Change second copy to insert.
+                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
 
-QUnit.log(function( details ) {
-       var assertList, assertLi,
-               message, expected, actual, diff,
-               showDiff = false,
-               testItem = id( "qunit-test-output-" + details.testId );
+                                       // Throw away the equality we just deleted.
+                                       equalitiesLength--;
 
-       if ( !testItem ) {
-               return;
-       }
+                                       // Throw away the previous equality (it needs to be reevaluated).
+                                       equalitiesLength--;
+                                       pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
 
-       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
-       message = "<span class='test-message'>" + message + "</span>";
-       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
+                                       // Reset the counters.
+                                       lengthInsertions1 = 0;
+                                       lengthDeletions1 = 0;
+                                       lengthInsertions2 = 0;
+                                       lengthDeletions2 = 0;
+                                       lastequality = null;
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
 
-       // pushFailure doesn't provide details.expected
-       // when it calls, it's implicit to also not show expected and diff stuff
-       // Also, we need to check details.expected existence, as it can exist and be undefined
-       if ( !details.result && hasOwn.call( details, "expected" ) ) {
-               if ( details.negative ) {
-                       expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
-               } else {
-                       expected = escapeText( QUnit.dump.parse( details.expected ) );
+               // Normalize the diff.
+               if ( changes ) {
+                       this.diffCleanupMerge( diffs );
                }
 
-               actual = escapeText( QUnit.dump.parse( details.actual ) );
-               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
-                       expected +
-                       "</pre></td></tr>";
+               // Find any overlaps between deletions and insertions.
+               // e.g: <del>abcxxx</del><ins>xxxdef</ins>
+               //   -> <del>abc</del>xxx<ins>def</ins>
+               // e.g: <del>xxxabc</del><ins>defxxx</ins>
+               //   -> <ins>def</ins>xxx<del>abc</del>
+               // Only extract an overlap if it is as big as the edit ahead or behind it.
+               pointer = 1;
+               while ( pointer < diffs.length ) {
+                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
+                                       diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
+                               deletion = diffs[ pointer - 1 ][ 1 ];
+                               insertion = diffs[ pointer ][ 1 ];
+                               overlapLength1 = this.diffCommonOverlap( deletion, insertion );
+                               overlapLength2 = this.diffCommonOverlap( insertion, deletion );
+                               if ( overlapLength1 >= overlapLength2 ) {
+                                       if ( overlapLength1 >= deletion.length / 2 ||
+                                                       overlapLength1 >= insertion.length / 2 ) {
 
-               if ( actual !== expected ) {
+                                               // Overlap found.  Insert an equality and trim the surrounding edits.
+                                               diffs.splice(
+                                                       pointer,
+                                                       0,
+                                                       [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
+                                               );
+                                               diffs[ pointer - 1 ][ 1 ] =
+                                                       deletion.substring( 0, deletion.length - overlapLength1 );
+                                               diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
+                                               pointer++;
+                                       }
+                               } else {
+                                       if ( overlapLength2 >= deletion.length / 2 ||
+                                                       overlapLength2 >= insertion.length / 2 ) {
 
-                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
-                               actual + "</pre></td></tr>";
+                                               // Reverse overlap found.
+                                               // Insert an equality and swap and trim the surrounding edits.
+                                               diffs.splice(
+                                                       pointer,
+                                                       0,
+                                                       [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
+                                               );
 
-                       // Don't show diff if actual or expected are booleans
-                       if ( !( /^(true|false)$/.test( actual ) ) &&
-                                       !( /^(true|false)$/.test( expected ) ) ) {
-                               diff = QUnit.diff( expected, actual );
-                               showDiff = stripHtml( diff ).length !==
-                                       stripHtml( expected ).length +
-                                       stripHtml( actual ).length;
+                                               diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
+                                               diffs[ pointer - 1 ][ 1 ] =
+                                                       insertion.substring( 0, insertion.length - overlapLength2 );
+                                               diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
+                                               diffs[ pointer + 1 ][ 1 ] =
+                                                       deletion.substring( overlapLength2 );
+                                               pointer++;
+                                       }
+                               }
+                               pointer++;
                        }
+                       pointer++;
+               }
+       };
 
-                       // Don't show diff if expected and actual are totally different
-                       if ( showDiff ) {
-                               message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
-                                       diff + "</pre></td></tr>";
-                       }
-               } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
-                               expected.indexOf( "[object Object]" ) !== -1 ) {
-                       message += "<tr class='test-message'><th>Message: </th><td>" +
-                               "Diff suppressed as the depth of object is more than current max depth (" +
-                               QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
-                               " run with a higher max depth or <a href='" +
-                               escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
-                               "Rerun</a> without max depth.</p></td></tr>";
+       /**
+        * Determine if the suffix of one string is the prefix of another.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {number} The number of characters common to the end of the first
+        *     string and the start of the second string.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
+               var text1Length, text2Length, textLength,
+                       best, length, pattern, found;
+
+               // Cache the text lengths to prevent multiple calls.
+               text1Length = text1.length;
+               text2Length = text2.length;
+
+               // Eliminate the null case.
+               if ( text1Length === 0 || text2Length === 0 ) {
+                       return 0;
                }
 
-               if ( details.source ) {
-                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
-                               escapeText( details.source ) + "</pre></td></tr>";
+               // Truncate the longer string.
+               if ( text1Length > text2Length ) {
+                       text1 = text1.substring( text1Length - text2Length );
+               } else if ( text1Length < text2Length ) {
+                       text2 = text2.substring( 0, text1Length );
                }
+               textLength = Math.min( text1Length, text2Length );
 
-               message += "</table>";
+               // Quick check for the worst case.
+               if ( text1 === text2 ) {
+                       return textLength;
+               }
 
-       // this occurs when pushFailure is set and we have an extracted stack trace
-       } else if ( !details.result && details.source ) {
-               message += "<table>" +
-                       "<tr class='test-source'><th>Source: </th><td><pre>" +
-                       escapeText( details.source ) + "</pre></td></tr>" +
-                       "</table>";
-       }
+               // Start by looking for a single character match
+               // and increase length until no match is found.
+               // Performance analysis: https://neil.fraser.name/news/2010/11/04/
+               best = 0;
+               length = 1;
+               while ( true ) {
+                       pattern = text1.substring( textLength - length );
+                       found = text2.indexOf( pattern );
+                       if ( found === -1 ) {
+                               return best;
+                       }
+                       length += found;
+                       if ( found === 0 || text1.substring( textLength - length ) ===
+                                       text2.substring( 0, length ) ) {
+                               best = length;
+                               length++;
+                       }
+               }
+       };
 
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+       /**
+        * Split two texts into an array of strings.  Reduce the texts to a string of
+        * hashes where each Unicode character represents one line.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
+        *     An object containing the encoded text1, the encoded text2 and
+        *     the array of unique strings.
+        *     The zeroth element of the array of unique strings is intentionally blank.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
+               var lineArray, lineHash, chars1, chars2;
+               lineArray = []; // E.g. lineArray[4] === 'Hello\n'
+               lineHash = {};  // E.g. lineHash['Hello\n'] === 4
 
-       assertLi = document.createElement( "li" );
-       assertLi.className = details.result ? "pass" : "fail";
-       assertLi.innerHTML = message;
-       assertList.appendChild( assertLi );
-});
+               // '\x00' is a valid character, but various debuggers don't like it.
+               // So we'll insert a junk entry to avoid generating a null character.
+               lineArray[ 0 ] = "";
 
-QUnit.testDone(function( details ) {
-       var testTitle, time, testItem, assertList,
-               good, bad, testCounts, skipped, sourceName,
-               tests = id( "qunit-tests" );
+               /**
+                * Split a text into an array of strings.  Reduce the texts to a string of
+                * hashes where each Unicode character represents one line.
+                * Modifies linearray and linehash through being a closure.
+                * @param {string} text String to encode.
+                * @return {string} Encoded string.
+                * @private
+                */
+               function diffLinesToCharsMunge( text ) {
+                       var chars, lineStart, lineEnd, lineArrayLength, line;
+                       chars = "";
 
-       if ( !tests ) {
-               return;
-       }
+                       // Walk the text, pulling out a substring for each line.
+                       // text.split('\n') would would temporarily double our memory footprint.
+                       // Modifying text would create many large strings to garbage collect.
+                       lineStart = 0;
+                       lineEnd = -1;
 
-       testItem = id( "qunit-test-output-" + details.testId );
+                       // Keeping our own length variable is faster than looking it up.
+                       lineArrayLength = lineArray.length;
+                       while ( lineEnd < text.length - 1 ) {
+                               lineEnd = text.indexOf( "\n", lineStart );
+                               if ( lineEnd === -1 ) {
+                                       lineEnd = text.length - 1;
+                               }
+                               line = text.substring( lineStart, lineEnd + 1 );
+                               lineStart = lineEnd + 1;
 
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+                               if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
+                                                       ( lineHash[ line ] !== undefined ) ) {
+                                       chars += String.fromCharCode( lineHash[ line ] );
+                               } else {
+                                       chars += String.fromCharCode( lineArrayLength );
+                                       lineHash[ line ] = lineArrayLength;
+                                       lineArray[ lineArrayLength++ ] = line;
+                               }
+                       }
+                       return chars;
+               }
 
-       good = details.passed;
-       bad = details.failed;
+               chars1 = diffLinesToCharsMunge( text1 );
+               chars2 = diffLinesToCharsMunge( text2 );
+               return {
+                       chars1: chars1,
+                       chars2: chars2,
+                       lineArray: lineArray
+               };
+       };
 
-       // store result when possible
-       if ( config.reorder && defined.sessionStorage ) {
-               if ( bad ) {
-                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
-               } else {
-                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+       /**
+        * Rehydrate the text in a diff from a string of line hashes to real lines of
+        * text.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        * @param {!Array.<string>} lineArray Array of unique strings.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
+               var x, chars, text, y;
+               for ( x = 0; x < diffs.length; x++ ) {
+                       chars = diffs[ x ][ 1 ];
+                       text = [];
+                       for ( y = 0; y < chars.length; y++ ) {
+                               text[ y ] = lineArray[ chars.charCodeAt( y ) ];
+                       }
+                       diffs[ x ][ 1 ] = text.join( "" );
                }
-       }
+       };
 
-       if ( bad === 0 ) {
+       /**
+        * Reorder and merge like edit sections.  Merge equalities.
+        * Any edit section can move as long as it doesn't cross an equality.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
+               var pointer, countDelete, countInsert, textInsert, textDelete,
+                       commonlength, changes, diffPointer, position;
+               diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
+               pointer = 0;
+               countDelete = 0;
+               countInsert = 0;
+               textDelete = "";
+               textInsert = "";
+               commonlength;
+               while ( pointer < diffs.length ) {
+                       switch ( diffs[ pointer ][ 0 ] ) {
+                       case DIFF_INSERT:
+                               countInsert++;
+                               textInsert += diffs[ pointer ][ 1 ];
+                               pointer++;
+                               break;
+                       case DIFF_DELETE:
+                               countDelete++;
+                               textDelete += diffs[ pointer ][ 1 ];
+                               pointer++;
+                               break;
+                       case DIFF_EQUAL:
 
-               // Collapse the passing tests
-               addClass( assertList, "qunit-collapsed" );
-       } else if ( bad && config.collapse && !collapseNext ) {
+                               // Upon reaching an equality, check for prior redundancies.
+                               if ( countDelete + countInsert > 1 ) {
+                                       if ( countDelete !== 0 && countInsert !== 0 ) {
 
-               // Skip collapsing the first failing test
-               collapseNext = true;
-       } else {
+                                               // Factor out any common prefixes.
+                                               commonlength = this.diffCommonPrefix( textInsert, textDelete );
+                                               if ( commonlength !== 0 ) {
+                                                       if ( ( pointer - countDelete - countInsert ) > 0 &&
+                                                                       diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
+                                                                       DIFF_EQUAL ) {
+                                                               diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
+                                                                       textInsert.substring( 0, commonlength );
+                                                       } else {
+                                                               diffs.splice( 0, 0, [ DIFF_EQUAL,
+                                                                       textInsert.substring( 0, commonlength )
+                                                               ] );
+                                                               pointer++;
+                                                       }
+                                                       textInsert = textInsert.substring( commonlength );
+                                                       textDelete = textDelete.substring( commonlength );
+                                               }
 
-               // Collapse remaining tests
-               addClass( assertList, "qunit-collapsed" );
-       }
+                                               // Factor out any common suffixies.
+                                               commonlength = this.diffCommonSuffix( textInsert, textDelete );
+                                               if ( commonlength !== 0 ) {
+                                                       diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
+                                                                       commonlength ) + diffs[ pointer ][ 1 ];
+                                                       textInsert = textInsert.substring( 0, textInsert.length -
+                                                               commonlength );
+                                                       textDelete = textDelete.substring( 0, textDelete.length -
+                                                               commonlength );
+                                               }
+                                       }
 
-       // testItem.firstChild is the test name
-       testTitle = testItem.firstChild;
+                                       // Delete the offending records and add the merged ones.
+                                       if ( countDelete === 0 ) {
+                                               diffs.splice( pointer - countInsert,
+                                                       countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
+                                       } else if ( countInsert === 0 ) {
+                                               diffs.splice( pointer - countDelete,
+                                                       countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
+                                       } else {
+                                               diffs.splice(
+                                                       pointer - countDelete - countInsert,
+                                                       countDelete + countInsert,
+                                                       [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
+                                               );
+                                       }
+                                       pointer = pointer - countDelete - countInsert +
+                                               ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
+                               } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
 
-       testCounts = bad ?
-               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
-               "";
+                                       // Merge this equality with the previous one.
+                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
+                                       diffs.splice( pointer, 1 );
+                               } else {
+                                       pointer++;
+                               }
+                               countInsert = 0;
+                               countDelete = 0;
+                               textDelete = "";
+                               textInsert = "";
+                               break;
+                       }
+               }
+               if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
+                       diffs.pop(); // Remove the dummy entry at the end.
+               }
 
-       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
-               details.assertions.length + ")</b>";
+               // Second pass: look for single edits surrounded on both sides by equalities
+               // which can be shifted sideways to eliminate an equality.
+               // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
+               changes = false;
+               pointer = 1;
 
-       if ( details.skipped ) {
-               testItem.className = "skipped";
-               skipped = document.createElement( "em" );
-               skipped.className = "qunit-skipped-label";
-               skipped.innerHTML = "skipped";
-               testItem.insertBefore( skipped, testTitle );
-       } else {
-               addEvent( testTitle, "click", function() {
-                       toggleClass( assertList, "qunit-collapsed" );
-               });
+               // Intentionally ignore the first and last element (don't need checking).
+               while ( pointer < diffs.length - 1 ) {
+                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
+                                       diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
 
-               testItem.className = bad ? "fail" : "pass";
+                               diffPointer = diffs[ pointer ][ 1 ];
+                               position = diffPointer.substring(
+                                       diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
+                               );
 
-               time = document.createElement( "span" );
-               time.className = "runtime";
-               time.innerHTML = details.runtime + " ms";
-               testItem.insertBefore( time, assertList );
-       }
+                               // This is a single edit surrounded by equalities.
+                               if ( position === diffs[ pointer - 1 ][ 1 ] ) {
 
-       // Show the source of the test when showing assertions
-       if ( details.source ) {
-               sourceName = document.createElement( "p" );
-               sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
-               addClass( sourceName, "qunit-source" );
-               if ( bad === 0 ) {
-                       addClass( sourceName, "qunit-collapsed" );
+                                       // Shift the edit over the previous equality.
+                                       diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
+                                               diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
+                                                       diffs[ pointer - 1 ][ 1 ].length );
+                                       diffs[ pointer + 1 ][ 1 ] =
+                                               diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
+                                       diffs.splice( pointer - 1, 1 );
+                                       changes = true;
+                               } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
+                                               diffs[ pointer + 1 ][ 1 ] ) {
+
+                                       // Shift the edit over the next equality.
+                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
+                                       diffs[ pointer ][ 1 ] =
+                                               diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
+                                               diffs[ pointer + 1 ][ 1 ];
+                                       diffs.splice( pointer + 1, 1 );
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
                }
-               addEvent( testTitle, "click", function() {
-                       toggleClass( sourceName, "qunit-collapsed" );
-               });
-               testItem.appendChild( sourceName );
-       }
-});
 
-if ( defined.document ) {
+               // If shifts were made, the diff needs reordering and another shift sweep.
+               if ( changes ) {
+                       this.diffCleanupMerge( diffs );
+               }
+       };
 
-       // Avoid readyState issue with phantomjs
-       // Ref: #818
-       var notPhantom = ( function( p ) {
-               return !( p && p.version && p.version.major > 0 );
-       } )( window.phantom );
+       return function( o, n ) {
+               var diff, output, text;
+               diff = new DiffMatchPatch();
+               output = diff.DiffMain( o, n );
+               diff.diffCleanupEfficiency( output );
+               text = diff.diffPrettyHtml( output );
 
-       if ( notPhantom && document.readyState === "complete" ) {
-               QUnit.load();
-       } else {
-               addEvent( window, "load", QUnit.load );
-       }
-} else {
-       config.pageLoaded = true;
-       config.autorun = true;
-}
+               return text;
+       };
+}() );
 
-})();
+}() );
index 0bfa8f3..c3d39da 100644 (file)
@@ -12,7 +12,6 @@
  *             $('#textbox').expandableField();
  *
  * Options:
- *
  */
 ( function ( $ ) {
 
index 5b08f95..cad530b 100644 (file)
@@ -10,8 +10,8 @@
 }
 
 #pagehistory li.selected {
-       background-color: #f9f9f9;
-       border: 1px dashed #aaa;
+       background-color: #f8f9fa;
+       border: 1px dashed #a2a9b1;
 }
 
 .mw-history-revisionactions {
index d5520a1..1f0365e 100644 (file)
@@ -46,8 +46,8 @@
  */
 #filetoc {
        text-align: center;
-       border: 1px solid #aaa;
-       background-color: #f9f9f9;
+       border: 1px solid #a2a9b1;
+       background-color: #f8f9fa;
        padding: 5px;
        font-size: 95%;
        margin-bottom: 0.5em;
 
 .mw_metadata td,
 .mw_metadata th {
-       border: 1px solid #aaa;
+       border: 1px solid #a2a9b1;
        padding-left: 5px;
        padding-right: 5px;
 }
 
 .mw_metadata th {
-       background-color: #f9f9f9;
+       background-color: #f8f9fa;
 }
 
 .mw_metadata td {
index 1192650..83277cb 100644 (file)
                return valueParts.join( options.decimal );
        }
 
+       /**
+        * Helper function to flip transformation tables.
+        *
+        * @param {...Object} Transformation tables
+        * @return {Object}
+        */
+       function flipTransform() {
+               var i, key, table, flipped = {};
+
+               // Ensure we strip thousand separators. This might be overwritten.
+               flipped[ ',' ] = '';
+
+               for ( i = 0; i < arguments.length; i++ ) {
+                       table = arguments[ i ];
+                       for ( key in table ) {
+                               if ( table.hasOwnProperty( key ) ) {
+                                       // The thousand separator should be deleted
+                                       flipped[ table[ key ] ] = key === ',' ? '' : key;
+                               }
+                       }
+               }
+
+               return flipped;
+       }
+
        $.extend( mw.language, {
 
                /**
                 * @return {number|string} Formatted number
                 */
                convertNumber: function ( num, integer ) {
-                       var i, tmp, transformTable, numberString, convertedNumber, pattern;
-
-                       pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
-                               'digitGroupingPattern' ) || '#,##0.###';
+                       var transformTable, digitTransformTable, separatorTransformTable,
+                               i, numberString, convertedNumber, pattern;
 
-                       // Set the target transform table:
-                       transformTable = mw.language.getDigitTransformTable();
-
-                       if ( !transformTable ) {
+                       // Quick shortcut for plain numbers
+                       if ( integer && parseInt( num, 10 ) === num ) {
                                return num;
                        }
 
-                       // Check if the 'restore' to Latin number flag is set:
+                       // Load the transformation tables (can be empty)
+                       digitTransformTable = mw.language.getDigitTransformTable();
+                       separatorTransformTable = mw.language.getSeparatorTransformTable();
+
                        if ( integer ) {
-                               if ( parseInt( num, 10 ) === num ) {
-                                       return num;
-                               }
-                               tmp = [];
-                               for ( i in transformTable ) {
-                                       tmp[ transformTable[ i ] ] = i;
-                               }
-                               transformTable = tmp;
+                               // Reverse the digit transformation tables if we are doing unformatting
+                               transformTable = flipTransform( separatorTransformTable, digitTransformTable );
                                numberString = String( num );
                        } else {
-                               // Ignore transform table if wgTranslateNumerals is false
-                               if ( !mw.config.get( 'wgTranslateNumerals' ) ) {
-                                       transformTable = [];
+                               // This check being here means that digits can still be unformatted
+                               // even if we do not produce them. This seems sane behavior.
+                               if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+                                       transformTable = digitTransformTable;
                                }
+
+                               // Commaying is more complex, so we handle it here separately.
+                               // When unformatting, we just use separatorTransformTable.
+                               pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
+                                       'digitGroupingPattern' ) || '#,##0.###';
                                numberString = mw.language.commafy( num, pattern );
                        }
 
-                       convertedNumber = '';
-                       for ( i = 0; i < numberString.length; i++ ) {
-                               if ( transformTable[ numberString[ i ] ] ) {
-                                       convertedNumber += transformTable[ numberString[ i ] ];
-                               } else {
-                                       convertedNumber += numberString[ i ];
+                       if ( transformTable ) {
+                               convertedNumber = '';
+                               for ( i = 0; i < numberString.length; i++ ) {
+                                       if ( transformTable.hasOwnProperty( numberString[ i ] ) ) {
+                                               convertedNumber += transformTable[ numberString[ i ] ];
+                                       } else {
+                                               convertedNumber += numberString[ i ];
+                                       }
                                }
+                       } else {
+                               convertedNumber = numberString;
                        }
-                       return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
+
+                       if ( integer ) {
+                               // Parse string to integer. This loses decimals!
+                               convertedNumber = parseInt( convertedNumber, 10 );
+                       }
+
+                       return convertedNumber;
                },
 
                /**
index 1522de1..de2cd5b 100644 (file)
@@ -124,6 +124,16 @@ span.comment {
        font-family: serif;
 }
 
+/* Underline preference */
+
+.mw-underline-always a {
+       text-decoration: underline;
+}
+
+.mw-underline-never a {
+       text-decoration: none;
+}
+
 /**
  * rev_deleted stuff
  */
@@ -343,8 +353,8 @@ a.new {
  */
 table.wikitable {
        margin: 1em 0;
-       background-color: #f9f9f9;
-       border: 1px solid #aaa;
+       background-color: #f8f9fa;
+       border: 1px solid #a2a9b1;
        border-collapse: collapse;
        color: #000;
 }
@@ -353,13 +363,13 @@ table.wikitable > tr > th,
 table.wikitable > tr > td,
 table.wikitable > * > tr > th,
 table.wikitable > * > tr > td {
-       border: 1px solid #aaa;
+       border: 1px solid #a2a9b1;
        padding: 0.2em 0.4em;
 }
 
 table.wikitable > tr > th,
 table.wikitable > * > tr > th {
-       background-color: #f2f2f2;
+       background-color: #eaecf0;
        text-align: center;
 }
 
index 676ecca..9688f1f 100644 (file)
@@ -26,9 +26,9 @@
 // Orange; for contextual use of returning to a past action
 @colorRegressive: #ff5d00;
 // Red; for contextual use of a negative action of high severity
-@colorDestructive: #c33;
-@colorDestructiveHighlight: #e53939;
-@colorDestructiveActive: #873636;
+@colorDestructive: #d33;
+@colorDestructiveHighlight: #ff4242;
+@colorDestructiveActive: #b32424;
 // Orange; for contextual use of a potentially negative action of medium severity
 @colorMediumSevere: #ff5d00;
 // Yellow; for contextual use of a potentially negative action of low severity
index 558fd4c..b069d4a 100644 (file)
@@ -99,7 +99,6 @@
         * @param {mw.Title} title Title being posted to
         * @param {mw.Api} api mw.Api instance that the instance should use
         * @return {mw.messagePoster.MessagePoster}
-        *
         */
        MessagePosterFactory.prototype.createForContentModel = function ( contentModel, title, api ) {
                return new this.contentModelToClass[ contentModel ]( title, api );
index d4c93fd..e1df01d 100644 (file)
@@ -10,8 +10,8 @@
 .toc,
 .mw-warning,
 .toccolours {
-       border: 1px solid #aaa;
-       background-color: #f9f9f9;
+       border: 1px solid #a2a9b1;
+       background-color: #f8f9fa;
        padding: 5px;
        font-size: 95%;
 }
@@ -147,9 +147,9 @@ div.thumb {
 }
 
 div.thumbinner {
-       border: 1px solid #ccc;
+       border: 1px solid #c8ccd1;
        padding: 3px;
-       background-color: #f9f9f9;
+       background-color: #f8f9fa;
        font-size: 94%;
        text-align: center;
        /* new block formatting context,
@@ -158,7 +158,7 @@ div.thumbinner {
 }
 
 html .thumbimage {
-       border: 1px solid #ccc;
+       border: 1px solid #c8ccd1;
 }
 
 html .thumbcaption {
@@ -199,7 +199,7 @@ div.magnify a {
 }
 
 img.thumbborder {
-       border: 1px solid #ddd;
+       border: 1px solid #eaecf0;
 }
 
 /* Directionality-specific styles for thumbnails - their positioning depends on content language */
index 9c52b2a..33ceb48 100644 (file)
@@ -204,8 +204,8 @@ pre, code, tt, kbd, samp, .mw-code {
 
 code {
        color: #000;
-       background-color: #f9f9f9;
-       border: 1px solid #ddd;
+       background-color: #f8f9fa;
+       border: 1px solid #eaecf0;
        border-radius: 2px;
        padding: 1px 4px;
 }
@@ -213,8 +213,8 @@ code {
 pre,
 .mw-code {
        color: #000;
-       background-color: #f9f9f9;
-       border: 1px solid #ddd;
+       background-color: #f8f9fa;
+       border: 1px solid #eaecf0;
        padding: 1em;
        /* Wrap lines in overflow. T2260, T103780 */
        white-space: pre-wrap;
index 4ca2096..7dbcd4d 100644 (file)
@@ -8,8 +8,8 @@
 
 /* Categories */
 .catlinks {
-       border: 1px solid #aaa;
-       background-color: #f9f9f9;
+       border: 1px solid #a2a9b1;
+       background-color: #f8f9fa;
        padding: 5px;
        margin-top: 1em;
        clear: both;
index d2015d8..43321fe 100644 (file)
@@ -51,9 +51,7 @@
                tokenWidget: {
                        alertTokenError: function ( code, error ) {
                                windowManager.openWindow( 'errorAlert', {
-                                       title: mw.message(
-                                               'apisandbox-results-fixtoken-fail', this.paramInfo.tokentype
-                                       ).parse(),
+                                       title: Util.parseMsg( 'apisandbox-results-fixtoken-fail', this.paramInfo.tokentype ),
                                        message: error,
                                        actions: [
                                                {
        };
 
        /**
-        * @class mw.special.ApiSandbox.Utils
+        * @class mw.special.ApiSandbox.Util
         * @private
         */
        Util = {
                                                } else {
                                                        n = +value;
                                                        return !isNaN( n ) && isFinite( n ) &&
-                                                               // eslint-disable-next-line no-bitwise
-                                                               ( n | 0 ) === n &&
+                                                               Math.floor( n ) === n &&
                                                                n >= pi.min && n <= pi.apiSandboxMax;
                                                }
                                        } );
                },
 
                /**
-                * Parse an HTML string, adding target="_blank" to any links
+                * Parse an HTML string and call Util.fixupHTML()
                 *
                 * @param {string} html HTML to parse
                 * @return {jQuery}
                 */
                parseHTML: function ( html ) {
                        var $ret = $( $.parseHTML( html ) );
-                       $ret.filter( 'a' ).add( $ret.find( 'a' ) )
+                       return Util.fixupHTML( $ret );
+               },
+
+               /**
+                * Parse an i18n message and call Util.fixupHTML()
+                *
+                * @param {string} key Key of message to get
+                * @param {...Mixed} parameters Values for $N replacements
+                * @return {jQuery}
+                */
+               parseMsg: function () {
+                       var $ret = mw.message.apply( mw.message, arguments ).parseDom();
+                       return Util.fixupHTML( $ret );
+               },
+
+               /**
+                * Fix HTML for ApiSandbox display
+                *
+                * Fixes are:
+                * - Add target="_blank" to any links
+                *
+                * @param {jQuery} $html DOM to process
+                * @return {jQuery}
+                */
+               fixupHTML: function ( $html ) {
+                       $html.filter( 'a' ).add( $html.find( 'a' ) )
                                .filter( '[href]:not([target])' )
                                .attr( 'target', '_blank' );
-                       return $ret;
+                       return $html;
                }
        };
 
 
                        $content
                                .empty()
-                               .append( $( '<p>' ).append( mw.message( 'apisandbox-intro' ).parse() ) )
+                               .append( $( '<p>' ).append( Util.parseMsg( 'apisandbox-intro' ) ) )
                                .append(
                                        $( '<div>', { id: 'mw-apisandbox-ui' } )
                                                .append( $toolbar )
                        $.when.apply( $, deferreds ).done( function () {
                                if ( $.inArray( false, arguments ) !== -1 ) {
                                        windowManager.openWindow( 'errorAlert', {
-                                               title: mw.message( 'apisandbox-submit-invalid-fields-title' ).parse(),
-                                               message: mw.message( 'apisandbox-submit-invalid-fields-message' ).parse(),
+                                               title: Util.parseMsg( 'apisandbox-submit-invalid-fields-title' ),
+                                               message: Util.parseMsg( 'apisandbox-submit-invalid-fields-message' ),
                                                actions: [
                                                        {
                                                                action: 'accept',
                                                                readOnly: true,
                                                                value: mw.util.wikiScript( 'api' ) + '?' + query
                                                        } ), {
-                                                               label: mw.message( 'apisandbox-request-url-label' ).parse()
+                                                               label: Util.parseMsg( 'apisandbox-request-url-label' )
                                                        }
                                                ).$element,
                                                $result
                                                        if ( data.status && data.status !== 200 ) {
                                                                $( '<div>' )
                                                                        .addClass( 'api-pretty-header api-pretty-status' )
-                                                                       .append(
-                                                                               mw.message( 'api-format-prettyprint-status', data.status, data.statustext ).parse()
-                                                                       )
+                                                                       .append( Util.parseMsg( 'api-format-prettyprint-status', data.status, data.statustext ) )
                                                                        .appendTo( $result );
                                                        }
                                                        $result.append( Util.parseHTML( data.html ) );
                                                                                framed: false,
                                                                                icon: 'info',
                                                                                popup: {
-                                                                                       $content: $( '<div>' ).append( mw.message( 'apisandbox-continue-help' ).parse() ),
+                                                                                       $content: $( '<div>' ).append( Util.parseMsg( 'apisandbox-continue-help' ) ),
                                                                                        padded: true
                                                                                }
                                                                        } ).$element
 
                                if ( that.widgets[ name ] !== undefined ) {
                                        windowManager.openWindow( 'errorAlert', {
-                                               title: mw.message(
-                                                       'apisandbox-dynamic-error-exists', name
-                                               ).parse(),
+                                               title: Util.parseMsg( 'apisandbox-dynamic-error-exists', name ),
                                                actions: [
                                                        {
                                                                action: 'accept',
                                                                        dl.append( $( '<dd>', {
                                                                                addClass: 'info',
                                                                                append: [
-                                                                                       Util.parseHTML( mw.message(
+                                                                                       Util.parseMsg(
                                                                                                'api-help-param-limit2', pi.parameters[ i ].max, pi.parameters[ i ].highmax
-                                                                                       ).parse() ),
+                                                                                       ),
                                                                                        ' ',
-                                                                                       Util.parseHTML( mw.message( 'apisandbox-param-limit' ).parse() )
+                                                                                       Util.parseMsg( 'apisandbox-param-limit' )
                                                                                ]
                                                                        } ) );
                                                                } else {
                                                                        dl.append( $( '<dd>', {
                                                                                addClass: 'info',
                                                                                append: [
-                                                                                       Util.parseHTML( mw.message(
-                                                                                               'api-help-param-limit', pi.parameters[ i ].max
-                                                                                       ).parse() ),
+                                                                                       Util.parseMsg( 'api-help-param-limit', pi.parameters[ i ].max ),
                                                                                        ' ',
-                                                                                       Util.parseHTML( mw.message( 'apisandbox-param-limit' ).parse() )
+                                                                                       Util.parseMsg( 'apisandbox-param-limit' )
                                                                                ]
                                                                        } ) );
                                                                }
                                                                if ( tmp !== '' ) {
                                                                        dl.append( $( '<dd>', {
                                                                                addClass: 'info',
-                                                                               append: Util.parseHTML( mw.message(
+                                                                               append: Util.parseMsg(
                                                                                        'api-help-param-integer-' + tmp,
                                                                                        Util.apiBool( pi.parameters[ i ].multi ) ? 2 : 1,
                                                                                        pi.parameters[ i ].min, pi.parameters[ i ].max
-                                                                               ).parse() )
+                                                                               )
                                                                        } ) );
                                                                }
                                                                break;
                                        items.push( new OO.ui.FieldLayout(
                                                new OO.ui.Widget( {} ).toggle( false ), {
                                                        align: 'top',
-                                                       label: Util.parseHTML( mw.message( 'apisandbox-no-parameters' ).parse() )
+                                                       label: Util.parseMsg( 'apisandbox-no-parameters' )
                                                }
                                        ) );
                                }
index bdd45bc..29c0fea 100644 (file)
@@ -21,7 +21,6 @@
                        $checkboxes.prop( 'disabled', isAllNS );
                },
 
-               /** */
                init: function () {
                        $select = $( '#namespace' );
                        $checkboxes = $( '#nsassociated, #nsinvert' );
index 5191f92..ebe9ed9 100644 (file)
@@ -53,9 +53,9 @@
        font-size: 97%;
 }
 .mw-search-profile-tabs {
-       background-color: #f3f3f3;
+       background-color: #f8f9fa;
        margin-top: 1em;
-       border: 1px solid #c0c0c0;
+       border: 1px solid #c8ccd1;
 }
 .search-types {
        float: left;
@@ -76,7 +76,7 @@
        padding: 0.5em;
 }
 .search-types .current a {
-       color: #333;
+       color: #222;
        cursor: default;
 }
 .search-types .current a:hover {
@@ -86,7 +86,7 @@
        float: right;
        padding: 0.5em;
        padding-right: 0.75em;
-       color: #666;
+       color: #54595d;
        font-size: 95%;
 }
 #mw-search-top-table div.oo-ui-actionFieldLayout {
@@ -96,8 +96,8 @@
 #mw-searchoptions {
        margin: 0;
        padding: 0.5em 0.75em 0.75em 0.75em;
-       background-color: #f9f9f9;
-       border: 1px solid #c0c0c0;
+       background-color: #f8f9fa;
+       border: 1px solid #c8ccd1;
        border-top-width: 0;
 }
 #mw-searchoptions legend {
 }
 #mw-searchoptions .divider {
        clear: both;
-       border-bottom: 1px solid #ddd;
+       border-bottom: 1px solid #eaecf0;
        padding-top: 0.5em;
        margin-bottom: 0.5em;
 }
 #mw-search-interwiki {
        float: right;
        width: 18em;
-       border: 1px solid #aaa;
+       border: 1px solid #a2a9b1;
        margin-top: 2ex;
 }
 .searchalttitle,
        font-size: 97%;
        text-align: left;
        padding: 0.15em 0.15em 0.2em 0.2em;
-       background-color: #ececec;
-       border-top: 1px solid #bbb;
+       background-color: #eaecf0;
+       border-top: 1px solid #c8ccd1;
 }
 .searchdidyoumean {
        font-size: 127%;
        margin-top: 0.8em;
        /* Note that this color won't affect the link, as desired. */
-       color: #c00;
+       color: #d33;
 }
index a35f4d1..bfe2c1c 100644 (file)
@@ -1,15 +1,62 @@
 /*!
  * JavaScript for Special:Watchlist
- *
- * This script is only loaded, if the user opt-in a setting in Special:Preferences,
- * that the watchlist should be automatically reloaded, when a filter option is
- * changed in the header form.
  */
-jQuery( function ( $ ) {
-       // add a listener on all form elements in the header form
-       $( '#mw-watchlist-form input, #mw-watchlist-form select' ).on( 'change', function () {
-               // submit the form, when one of the input fields was changed
-               $( '#mw-watchlist-form' ).submit();
+( function ( mw, $, OO ) {
+       $( function () {
+               var $resetForm = $( '#mw-watchlist-resetbutton' ),
+                       $progressBar = new OO.ui.ProgressBarWidget( { progress: false } ).$element;
+
+               $progressBar.css( {
+                       visibility: 'hidden',
+                       position: 'absolute',
+                       width: '100%'
+               } );
+               $resetForm.append( $progressBar );
+
+               // If the user wants to reset their watchlist, use an API call to do so (no reload required)
+               // Adapted from a user script by User:NQ of English Wikipedia
+               // (User:NQ/WatchlistResetConfirm.js)
+               $resetForm.submit( function ( event ) {
+                       event.preventDefault();
+
+                       OO.ui.confirm( mw.msg( 'watchlist-mark-all-visited' ) ).done( function ( confirmed ) {
+                               var $button;
+
+                               if ( confirmed ) {
+                                       // Disable reset button to prevent multiple requests and show progress bar
+                                       $button = $resetForm.find( 'input[name=mw-watchlist-reset-submit]' ).prop( 'disabled', true );
+                                       $progressBar.css( 'visibility', 'visible' );
+
+                                       // Use action=setnotificationtimestamp to mark all as visited,
+                                       // then set all watchlist lines accordingly
+                                       new mw.Api().postWithToken( 'csrf', {
+                                               formatversion: 2,
+                                               action: 'setnotificationtimestamp',
+                                               entirewatchlist: true
+                                       } ).done( function () {
+                                               $button.css( 'visibility', 'hidden' );
+                                               $progressBar.css( 'visibility', 'hidden' );
+                                               $( '.mw-changeslist-line-watched' )
+                                                       .removeClass( 'mw-changeslist-line-watched' )
+                                                       .addClass( 'mw-changeslist-line-not-watched' );
+                                       } ).fail( function () {
+                                               // On error, fall back to server-side reset
+                                               // First remove this submit listener and then re-submit the form
+                                               $resetForm.off( 'submit' ).submit();
+                                       } );
+
+                               }
+                       } );
+               } );
+
+               // if the user wishes to reload the watchlist whenever a filter changes
+               if ( mw.user.options.get( 'watchlistreloadautomatically' ) ) {
+                       // add a listener on all form elements in the header form
+                       $( '#mw-watchlist-form input, #mw-watchlist-form select' ).on( 'change', function () {
+                               // submit the form when one of the input fields is modified
+                               $( '#mw-watchlist-form' ).submit();
+                       } );
+               }
        } );
 
-} );
+}( mediaWiki, jQuery, OO ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/broken-image.png b/resources/src/mediawiki.widgets/MediaSearch/broken-image.png
new file mode 100644 (file)
index 0000000..f5be958
Binary files /dev/null and b/resources/src/mediawiki.widgets/MediaSearch/broken-image.png differ
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsProvider.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsProvider.js
new file mode 100644 (file)
index 0000000..dd07b92
--- /dev/null
@@ -0,0 +1,229 @@
+/*!
+ * MediaWiki Widgets - APIResultsProvider class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
+ */
+( function ( $, mw ) {
+
+       /**
+        * API Results Provider object.
+        *
+        * @class
+        * @mixins OO.EventEmitter
+        *
+        * @constructor
+        * @param {string} apiurl The URL to the api
+        * @param {Object} [config] Configuration options
+        * @cfg {number} fetchLimit The default number of results to fetch
+        * @cfg {string} lang The language of the API
+        * @cfg {number} offset Initial offset, if relevant, to call results from
+        * @cfg {Object} ajaxSettings The settings for the ajax call
+        * @cfg {Object} staticParams The data parameters that are static and should
+        *  always be sent to the API request, as opposed to user parameters.
+        * @cfg {Object} userParams Initial user parameters to be sent as data to
+        *  the API request. These can change per request, like the search query term
+        *  or sizing parameters for images, etc.
+        */
+       mw.widgets.APIResultsProvider = function MwWidgetsAPIResultsProvider( apiurl, config ) {
+               config = config || {};
+
+               this.setAPIurl( apiurl );
+               this.setDefaultFetchLimit( config.fetchLimit || 30 );
+               this.setLang( config.lang );
+               this.setOffset( config.offset || 0 );
+               this.setAjaxSettings( config.ajaxSettings || {} );
+
+               this.staticParams = config.staticParams || {};
+               this.userParams = config.userParams || {};
+
+               this.toggleDepleted( false );
+
+               // Mixin constructors
+               OO.EventEmitter.call( this );
+       };
+
+       /* Setup */
+       OO.mixinClass( mw.widgets.APIResultsProvider, OO.EventEmitter );
+
+       /* Methods */
+
+       /**
+        * Get results from the source
+        *
+        * @param {number} howMany Number of results to ask for
+        * @return {jQuery.Promise} Promise that is resolved into an array
+        * of available results, or is rejected if no results are available.
+        */
+       mw.widgets.APIResultsProvider.prototype.getResults = function () {
+               var xhr,
+                       deferred = $.Deferred(),
+                       allParams = $.extend( {}, this.getStaticParams(), this.getUserParams() );
+
+               xhr = $.getJSON( this.getAPIurl(), allParams )
+                       .done( function ( data ) {
+                               if (
+                                       $.type( data ) !== 'array' ||
+                                       (
+                                               $.type( data ) === 'array' &&
+                                               data.length === 0
+                                       )
+                               ) {
+                                       deferred.resolve();
+                               } else {
+                                       deferred.resolve( data );
+                               }
+                       } );
+               return deferred.promise( { abort: xhr.abort } );
+       };
+
+       /**
+        * Set API url
+        *
+        * @param {string} apiurl API url
+        */
+       mw.widgets.APIResultsProvider.prototype.setAPIurl = function ( apiurl ) {
+               this.apiurl = apiurl;
+       };
+
+       /**
+        * Set api url
+        *
+        * @return {string} API url
+        */
+       mw.widgets.APIResultsProvider.prototype.getAPIurl = function () {
+               return this.apiurl;
+       };
+
+       /**
+        * Get the static, non-changing data parameters sent to the API
+        *
+        * @return {Object} Data parameters
+        */
+       mw.widgets.APIResultsProvider.prototype.getStaticParams = function () {
+               return this.staticParams;
+       };
+
+       /**
+        * Get the user-inputted dynamic data parameters sent to the API
+        *
+        * @return {Object} Data parameters
+        */
+       mw.widgets.APIResultsProvider.prototype.getUserParams = function () {
+               return this.userParams;
+       };
+
+       /**
+        * Set the data parameters sent to the API
+        *
+        * @param {Object} params User defined data parameters
+        */
+       mw.widgets.APIResultsProvider.prototype.setUserParams = function ( params ) {
+               // Asymmetrically compare (params is subset of this.userParams)
+               if ( !OO.compare( params, this.userParams, true ) ) {
+                       this.userParams = $.extend( {}, this.userParams, params );
+                       this.reset();
+               }
+       };
+
+       /**
+        * Reset the provider
+        */
+       mw.widgets.APIResultsProvider.prototype.reset = function () {
+               // Reset offset
+               this.setOffset( 0 );
+               // Reset depleted status
+               this.toggleDepleted( false );
+       };
+
+       /**
+        * Get fetch limit or 'page' size. This is the number
+        * of results per request.
+        *
+        * @return {number} limit
+        */
+       mw.widgets.APIResultsProvider.prototype.getDefaultFetchLimit = function () {
+               return this.limit;
+       };
+
+       /**
+        * Set limit
+        *
+        * @param {number} limit Default number of results to fetch from the API
+        */
+       mw.widgets.APIResultsProvider.prototype.setDefaultFetchLimit = function ( limit ) {
+               this.limit = limit;
+       };
+
+       /**
+        * Get provider API language
+        *
+        * @return {string} Provider API language
+        */
+       mw.widgets.APIResultsProvider.prototype.getLang = function () {
+               return this.lang;
+       };
+
+       /**
+        * Set provider API language
+        *
+        * @param {string} lang Provider API language
+        */
+       mw.widgets.APIResultsProvider.prototype.setLang = function ( lang ) {
+               this.lang = lang;
+       };
+
+       /**
+        * Get result offset
+        *
+        * @return {number} Offset Results offset for the upcoming request
+        */
+       mw.widgets.APIResultsProvider.prototype.getOffset = function () {
+               return this.offset;
+       };
+
+       /**
+        * Set result offset
+        *
+        * @param {number} offset Results offset for the upcoming request
+        */
+       mw.widgets.APIResultsProvider.prototype.setOffset = function ( offset ) {
+               this.offset = offset;
+       };
+
+       /**
+        * Check whether the provider is depleted and has no more results
+        * to hand off.
+        *
+        * @return {boolean} The provider is depleted
+        */
+       mw.widgets.APIResultsProvider.prototype.isDepleted = function () {
+               return this.depleted;
+       };
+
+       /**
+        * Toggle depleted state
+        *
+        * @param {boolean} isDepleted The provider is depleted
+        */
+       mw.widgets.APIResultsProvider.prototype.toggleDepleted = function ( isDepleted ) {
+               this.depleted = isDepleted !== undefined ? isDepleted : !this.depleted;
+       };
+
+       /**
+        * Get the default ajax settings
+        *
+        * @return {Object} Ajax settings
+        */
+       mw.widgets.APIResultsProvider.prototype.getAjaxSettings = function () {
+               return this.ajaxSettings;
+       };
+
+       /**
+        * Get the default ajax settings
+        *
+        * @param {Object} settings Ajax settings
+        */
+       mw.widgets.APIResultsProvider.prototype.setAjaxSettings = function ( settings ) {
+               this.ajaxSettings = settings;
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsQueue.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsQueue.js
new file mode 100644 (file)
index 0000000..3bc1d51
--- /dev/null
@@ -0,0 +1,224 @@
+/*!
+ * MediaWiki Widgets - APIResultsQueue class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
+ */
+( function ( $, mw ) {
+
+       /**
+        * API Results Queue object.
+        *
+        * @class
+        * @mixins OO.EventEmitter
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {number} limit The default number of results to fetch
+        * @cfg {number} threshold The default number of extra results
+        *  that the queue should always strive to have on top of the
+        *  individual requests for items.
+        */
+       mw.widgets.APIResultsQueue = function MwWidgetsAPIResultsQueue( config ) {
+               config = config || {};
+
+               this.fileRepoPromise = null;
+               this.providers = [];
+               this.providerPromises = [];
+               this.queue = [];
+
+               this.params = {};
+
+               this.limit = config.limit || 20;
+               this.setThreshold( config.threshold || 10 );
+
+               // Mixin constructors
+               OO.EventEmitter.call( this );
+       };
+
+       /* Setup */
+       OO.mixinClass( mw.widgets.APIResultsQueue, OO.EventEmitter );
+
+       /* Methods */
+
+       /**
+        * Set up the queue and its resources.
+        * This should be overridden if there are any setup steps to perform.
+        *
+        * @return {jQuery.Promise} Promise that resolves when the resources
+        *  are set up. Note: The promise must have an .abort() functionality.
+        */
+       mw.widgets.APIResultsQueue.prototype.setup = function () {
+               return $.Deferred().resolve().promise( { abort: $.noop } );
+       };
+
+       /**
+        * Get items from the queue
+        *
+        * @param {number} [howMany] How many items to retrieve. Defaults to the
+        *  default limit supplied on initialization.
+        * @return {jQuery.Promise} Promise that resolves into an array of items.
+        */
+       mw.widgets.APIResultsQueue.prototype.get = function ( howMany ) {
+               var fetchingPromise = null,
+                       me = this;
+
+               howMany = howMany || this.limit;
+
+               // Check if the queue has enough items
+               if ( this.queue.length < howMany + this.threshold ) {
+                       // Call for more results
+                       fetchingPromise = this.queryProviders( howMany + this.threshold )
+                               .then( function ( items ) {
+                                       // Add to the queue
+                                       me.queue = me.queue.concat.apply( me.queue, items );
+                               } );
+               }
+
+               return $.when( fetchingPromise )
+                       .then( function () {
+                               return me.queue.splice( 0, howMany );
+                       } );
+
+       };
+
+       /**
+        * Get results from all providers
+        *
+        * @param {number} [howMany] How many items to retrieve. Defaults to the
+        *  default limit supplied on initialization.
+        * @return {jQuery.Promise} Promise that is resolved into an array
+        *  of fetched items. Note: The promise must have an .abort() functionality.
+        */
+       mw.widgets.APIResultsQueue.prototype.queryProviders = function ( howMany ) {
+               var i, len,
+                       queue = this;
+
+               // Make sure there are resources set up
+               return this.setup()
+                       .then( function () {
+                               // Abort previous requests
+                               for ( i = 0, len = queue.providerPromises.length; i < len; i++ ) {
+                                       queue.providerPromises[ i ].abort();
+                               }
+                               queue.providerPromises = [];
+                               // Set up the query to all providers
+                               for ( i = 0, len = queue.providers.length; i < len; i++ ) {
+                                       if ( !queue.providers[ i ].isDepleted() ) {
+                                               queue.providerPromises.push(
+                                                       queue.providers[ i ].getResults( howMany )
+                                               );
+                                       }
+                               }
+
+                               return $.when.apply( $, queue.providerPromises )
+                                       .then( Array.prototype.concat.bind( [] ) );
+                       } );
+       };
+
+       /**
+        * Set the search query for all the providers.
+        *
+        * This also makes sure to abort any previous promises.
+        *
+        * @param {Object} params API search parameters
+        */
+       mw.widgets.APIResultsQueue.prototype.setParams = function ( params ) {
+               var i, len;
+               if ( !OO.compare( params, this.params, true ) ) {
+                       this.reset();
+                       this.params = $.extend( this.params, params );
+                       // Reset queue
+                       this.queue = [];
+                       // Reset promises
+                       for ( i = 0, len = this.providerPromises.length; i < len; i++ ) {
+                               this.providerPromises[ i ].abort();
+                       }
+                       // Change queries
+                       for ( i = 0, len = this.providers.length; i < len; i++ ) {
+                               this.providers[ i ].setUserParams( this.params );
+                       }
+               }
+       };
+
+       /**
+        * Reset the queue and all its providers
+        */
+       mw.widgets.APIResultsQueue.prototype.reset = function () {
+               var i, len;
+               // Reset queue
+               this.queue = [];
+               // Reset promises
+               for ( i = 0, len = this.providerPromises.length; i < len; i++ ) {
+                       this.providerPromises[ i ].abort();
+               }
+               // Change queries
+               for ( i = 0, len = this.providers.length; i < len; i++ ) {
+                       this.providers[ i ].reset();
+               }
+       };
+
+       /**
+        * Get the data parameters sent to the API
+        *
+        * @return {Object} params API search parameters
+        */
+       mw.widgets.APIResultsQueue.prototype.getParams = function () {
+               return this.params;
+       };
+
+       /**
+        * Set the providers
+        *
+        * @param {mw.widgets.APIResultsProvider[]} providers An array of providers
+        */
+       mw.widgets.APIResultsQueue.prototype.setProviders = function ( providers ) {
+               this.providers = providers;
+       };
+
+       /**
+        * Add a provider to the group
+        *
+        * @param {mw.widgets.APIResultsProvider} provider A provider object
+        */
+       mw.widgets.APIResultsQueue.prototype.addProvider = function ( provider ) {
+               this.providers.push( provider );
+       };
+
+       /**
+        * Set the providers
+        *
+        * @return {mw.widgets.APIResultsProvider[]} providers An array of providers
+        */
+       mw.widgets.APIResultsQueue.prototype.getProviders = function () {
+               return this.providers;
+       };
+
+       /**
+        * Get the queue size
+        *
+        * @return {number} Queue size
+        */
+       mw.widgets.APIResultsQueue.prototype.getQueueSize = function () {
+               return this.queue.length;
+       };
+
+       /**
+        * Set queue threshold
+        *
+        * @param {number} threshold Queue threshold, below which we will
+        *  request more items
+        */
+       mw.widgets.APIResultsQueue.prototype.setThreshold = function ( threshold ) {
+               this.threshold = threshold;
+       };
+
+       /**
+        * Get queue threshold
+        *
+        * @return {number} threshold Queue threshold, below which we will
+        *  request more items
+        */
+       mw.widgets.APIResultsQueue.prototype.getThreshold = function () {
+               return this.threshold;
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceProvider.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceProvider.js
new file mode 100644 (file)
index 0000000..d767109
--- /dev/null
@@ -0,0 +1,322 @@
+/*!
+ * MediaWiki Widgets - MediaResourceProvider class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * MediaWiki media resource provider.
+        *
+        * @class
+        * @extends mw.widgets.APIResultsProvider
+        *
+        * @constructor
+        * @param {string} apiurl The API url
+        * @param {Object} [config] Configuration options
+        * @cfg {string} [scriptDirUrl] The url of the API script
+        */
+       mw.widgets.MediaResourceProvider = function MwWidgetsMediaResourceProvider( apiurl, config ) {
+               config = config || {};
+
+               // Parent constructor
+               mw.widgets.MediaResourceProvider.super.call( this, apiurl, config );
+
+               // Fetching configuration
+               this.scriptDirUrl = config.scriptDirUrl;
+               this.isLocal = config.local !== undefined;
+
+               if ( this.isLocal ) {
+                       this.setAPIurl( mw.util.wikiScript( 'api' ) );
+               } else {
+                       // If 'apiurl' is set, use that. Otherwise, build the url
+                       // from scriptDirUrl and /api.php suffix
+                       this.setAPIurl( this.getAPIurl() || ( this.scriptDirUrl + '/api.php' ) );
+               }
+
+               this.siteInfoPromise = null;
+               this.thumbSizes = [];
+               this.imageSizes = [];
+       };
+
+       /* Inheritance */
+       OO.inheritClass( mw.widgets.MediaResourceProvider, mw.widgets.APIResultsProvider );
+
+       /* Methods */
+
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.MediaResourceProvider.prototype.getStaticParams = function () {
+               return $.extend(
+                       {},
+                       // Parent method
+                       mw.widgets.MediaResourceProvider.super.prototype.getStaticParams.call( this ),
+                       {
+                               action: 'query',
+                               iiprop: 'dimensions|url|mediatype|extmetadata|timestamp|user',
+                               iiextmetadatalanguage: this.getLang(),
+                               prop: 'imageinfo'
+                       }
+               );
+       };
+
+       /**
+        * Initialize the source and get the site info.
+        *
+        * Connect to the api url and retrieve the siteinfo parameters
+        * that are required for fetching results.
+        *
+        * @return {jQuery.Promise} Promise that resolves when the class
+        * properties are set.
+        */
+       mw.widgets.MediaResourceProvider.prototype.loadSiteInfo = function () {
+               var provider = this;
+
+               if ( !this.siteInfoPromise ) {
+                       this.siteInfoPromise = new mw.Api().get( {
+                               action: 'query',
+                               meta: 'siteinfo'
+                       } )
+                               .then( function ( data ) {
+                                       provider.setImageSizes( data.query.general.imagelimits || [] );
+                                       provider.setThumbSizes( data.query.general.thumblimits || [] );
+                                       provider.setUserParams( {
+                                               // Standard width per resource
+                                               iiurlwidth: provider.getStandardWidth()
+                                       } );
+                               } );
+               }
+               return this.siteInfoPromise;
+       };
+
+       /**
+        * Override parent method and get results from the source
+        *
+        * @param {number} [howMany] The number of items to pull from the API
+        * @return {jQuery.Promise} Promise that is resolved into an array
+        * of available results, or is rejected if no results are available.
+        */
+       mw.widgets.MediaResourceProvider.prototype.getResults = function ( howMany ) {
+               var xhr,
+                       aborted = false,
+                       provider = this;
+
+               return this.loadSiteInfo()
+                       .then( function () {
+                               if ( aborted ) {
+                                       return $.Deferred().reject();
+                               }
+                               xhr = provider.fetchAPIresults( howMany );
+                               return xhr;
+                       } )
+                       .then(
+                               function ( results ) {
+                                       if ( !results || results.length === 0 ) {
+                                               provider.toggleDepleted( true );
+                                               return [];
+                                       }
+                                       return results;
+                               },
+                               // Process failed, return an empty promise
+                               function () {
+                                       provider.toggleDepleted( true );
+                                       return $.Deferred().resolve( [] );
+                               }
+                       )
+                       .promise( { abort: function () {
+                               aborted = true;
+                               if ( xhr ) {
+                                       xhr.abort();
+                               }
+                       } } );
+       };
+
+       /**
+        * Get continuation API data
+        *
+        * @param {number} howMany The number of results to retrieve
+        * @return {Object} API request data
+        */
+       mw.widgets.MediaResourceProvider.prototype.getContinueData = function () {
+               return {};
+       };
+
+       /**
+        * Set continuation data for the next page
+        *
+        * @param {Object} continueData Continuation data
+        */
+       mw.widgets.MediaResourceProvider.prototype.setContinue = function () {
+       };
+
+       /**
+        * Sort the results
+        *
+        * @param {Object[]} results API results
+        * @return {Object[]} Sorted results
+        */
+       mw.widgets.MediaResourceProvider.prototype.sort = function ( results ) {
+               return results;
+       };
+
+       /**
+        * Call the API for search results.
+        *
+        * @param {number} howMany The number of results to retrieve
+        * @return {jQuery.Promise} Promise that resolves with an array of objects that contain
+        *  the fetched data.
+        */
+       mw.widgets.MediaResourceProvider.prototype.fetchAPIresults = function ( howMany ) {
+               var xhr, api,
+                       provider = this;
+
+               if ( !this.isValid() ) {
+                       return $.Deferred().reject().promise( { abort: $.noop } );
+               }
+
+               api = this.isLocal ? new mw.Api() : new mw.ForeignApi( this.getAPIurl(), { anonymous: true } );
+               xhr = api.get( $.extend( {}, this.getStaticParams(), this.getUserParams(), this.getContinueData( howMany ) ) );
+               return xhr
+                       .then( function ( data ) {
+                               var page, newObj, raw,
+                                       results = [];
+
+                               if ( data.error ) {
+                                       provider.toggleDepleted( true );
+                                       return [];
+                               }
+
+                               if ( data.continue ) {
+                                       // Update the offset for next time
+                                       provider.setContinue( data.continue );
+                               } else {
+                                       // This is the last available set of results. Mark as depleted!
+                                       provider.toggleDepleted( true );
+                               }
+
+                               // If the source returned no results, it will not have a
+                               // query property
+                               if ( data.query ) {
+                                       raw = data.query.pages;
+                                       if ( raw ) {
+                                               // Strip away the page ids
+                                               for ( page in raw ) {
+                                                       if ( !raw[ page ].imageinfo ) {
+                                                               // The search may give us pages that belong to the File:
+                                                               // namespace but have no files in them, either because
+                                                               // they were deleted or imported wrongly, or just started
+                                                               // as pages. In that case, the response will not include
+                                                               // imageinfo. Skip those files.
+                                                               continue;
+                                                       }
+                                                       newObj = raw[ page ].imageinfo[ 0 ];
+                                                       newObj.title = raw[ page ].title;
+                                                       newObj.index = raw[ page ].index;
+                                                       results.push( newObj );
+                                               }
+                                       }
+                               }
+                               return provider.sort( results );
+                       } )
+                       .promise( { abort: xhr.abort } );
+       };
+
+       /**
+        * Set name
+        *
+        * @param {string} name
+        */
+       mw.widgets.MediaResourceProvider.prototype.setName = function ( name ) {
+               this.name = name;
+       };
+
+       /**
+        * Get name
+        *
+        * @return {string} name
+        */
+       mw.widgets.MediaResourceProvider.prototype.getName = function () {
+               return this.name;
+       };
+
+       /**
+        * Get standard width, based on the provider source's thumb sizes.
+        *
+        * @return {number|undefined} fetchWidth
+        */
+       mw.widgets.MediaResourceProvider.prototype.getStandardWidth = function () {
+               return ( this.thumbSizes && this.thumbSizes[ this.thumbSizes.length - 1 ] ) ||
+                       ( this.imageSizes && this.imageSizes[ 0 ] ) ||
+                       // Fall back on a number
+                       300;
+       };
+
+       /**
+        * Get prop
+        *
+        * @return {string} prop
+        */
+       mw.widgets.MediaResourceProvider.prototype.getFetchProp = function () {
+               return this.fetchProp;
+       };
+
+       /**
+        * Set prop
+        *
+        * @param {string} prop
+        */
+       mw.widgets.MediaResourceProvider.prototype.setFetchProp = function ( prop ) {
+               this.fetchProp = prop;
+       };
+
+       /**
+        * Set thumb sizes
+        *
+        * @param {number[]} sizes Available thumbnail sizes
+        */
+       mw.widgets.MediaResourceProvider.prototype.setThumbSizes = function ( sizes ) {
+               this.thumbSizes = sizes;
+       };
+
+       /**
+        * Set image sizes
+        *
+        * @param {number[]} sizes Available image sizes
+        */
+       mw.widgets.MediaResourceProvider.prototype.setImageSizes = function ( sizes ) {
+               this.imageSizes = sizes;
+       };
+
+       /**
+        * Get thumb sizes
+        *
+        * @return {number[]} sizes Available thumbnail sizes
+        */
+       mw.widgets.MediaResourceProvider.prototype.getThumbSizes = function () {
+               return this.thumbSizes;
+       };
+
+       /**
+        * Get image sizes
+        *
+        * @return {number[]} sizes Available image sizes
+        */
+       mw.widgets.MediaResourceProvider.prototype.getImageSizes = function () {
+               return this.imageSizes;
+       };
+
+       /**
+        * Check if this source is valid.
+        *
+        * @return {boolean} Source is valid
+        */
+       mw.widgets.MediaResourceProvider.prototype.isValid = function () {
+               return this.isLocal ||
+                       // If we don't have either 'apiurl' or 'scriptDirUrl'
+                       // the source is invalid, and we will skip it
+                       this.apiurl !== undefined ||
+                       this.scriptDirUrl !== undefined;
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceQueue.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceQueue.js
new file mode 100644 (file)
index 0000000..34fa44b
--- /dev/null
@@ -0,0 +1,68 @@
+/*!
+ * MediaWiki Widgets - MediaResourceQueue class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * MediaWiki media resource queue.
+        *
+        * @class
+        * @extends mw.widgets.APIResultsQueue
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {number} maxHeight The maximum height of the media, used in the
+        *  search call to the API.
+        */
+       mw.widgets.MediaResourceQueue = function MwWidgetsMediaResourceQueue( config ) {
+               config = config || {};
+
+               // Parent constructor
+               mw.widgets.MediaResourceQueue.super.call( this, config );
+
+               this.maxHeight = config.maxHeight || 200;
+       };
+
+       /* Inheritance */
+       OO.inheritClass( mw.widgets.MediaResourceQueue, mw.widgets.APIResultsQueue );
+
+       /**
+        * Fetch the file repos.
+        *
+        * @return {jQuery.Promise} Promise that resolves when the resources are set up
+        */
+       mw.widgets.MediaResourceQueue.prototype.getFileRepos = function () {
+               var defaultSource = [ {
+                       url: mw.util.wikiScript( 'api' ),
+                       local: ''
+               } ];
+
+               if ( !this.fileRepoPromise ) {
+                       this.fileRepoPromise = new mw.Api().get( {
+                               action: 'query',
+                               meta: 'filerepoinfo'
+                       } ).then(
+                               function ( resp ) {
+                                       return resp.query && resp.query.repos || defaultSource;
+                               },
+                               function () {
+                                       return $.Deferred().resolve( defaultSource );
+                               }
+                       );
+               }
+
+               return this.fileRepoPromise;
+       };
+
+       /**
+        * Get image maximum height
+        *
+        * @return {string} Image max height
+        */
+       mw.widgets.MediaResourceQueue.prototype.getMaxHeight = function () {
+               return this.maxHeight;
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.css b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.css
new file mode 100644 (file)
index 0000000..bc752b5
--- /dev/null
@@ -0,0 +1,89 @@
+/*!
+ * MediaWiki Widgets - MediaResultWidget styles.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+.mw-widget-mediaResultWidget {
+       display: inline-block;
+       position: relative;
+       padding: 0;
+       margin: 2px;
+       overflow: hidden;
+       box-sizing: border-box;
+       text-align: center;
+}
+
+.mw-widget-mediaResultWidget-error {
+       background-color: #f3f3f3;
+}
+
+.mw-widget-mediaResultWidget-thumbnail {
+       opacity: 0;
+       display: inline-block;
+       /* stylelint-disable no-unsupported-browser-features */
+       -webkit-transition: opacity 400ms;
+       -moz-transition: opacity 400ms;
+       transition: opacity 400ms;
+       /* stylelint-enable no-unsupported-browser-features */
+}
+
+.mw-widget-mediaResultWidget-done .mw-widget-mediaResultWidget-thumbnail,
+.mw-widget-mediaResultWidget-error .mw-widget-mediaResultWidget-thumbnail {
+       opacity: 1;
+}
+
+.mw-widget-mediaResultWidget-crop {
+       background-size: cover; /* stylelint-disable-line no-unsupported-browser-features */
+       background-position: center center;
+}
+
+.mw-widget-mediaResultWidget-overlay {
+       position: absolute;
+       top: 0;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       box-shadow: inset 0 0 0 1px #ccc;
+}
+
+.mw-widget-mediaResultWidget.oo-ui-optionWidget-highlighted,
+.mw-widget-mediaResultWidget.oo-ui-optionWidget-selected {
+       box-shadow: 0 0 0.3em #a7dcff, 0 0 0 #fff;
+}
+
+.mw-widget-mediaResultWidget-error .mw-widget-mediaResultWidget-thumbnail {
+       /* @embed */
+       background-image: url( broken-image.png );
+       background-size: auto; /* stylelint-disable-line no-unsupported-browser-features */
+       background-position: center center;
+       background-repeat: no-repeat;
+}
+
+.mw-widget-mediaResultWidget .mw-widget-mediaResultWidget-nameLabel {
+       position: absolute;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       overflow: hidden;
+       padding: 0.5em;
+       color: #fff;
+       text-shadow: 1px 1px #000; /* stylelint-disable-line no-unsupported-browser-features */
+       line-height: 1.125em;
+       background-color: rgba( 0, 0, 0, 0.5 );
+       text-overflow: ellipsis;
+       text-align: left;
+}
+
+.mw-widget-mediaResultWidget.oo-ui-optionWidget-highlighted .mw-widget-mediaResultWidget-nameLabel {
+       background-color: rgba( 0, 0, 0, 0.75 );
+}
+
+.mw-widget-mediaResultWidget.oo-ui-optionWidget-selected .mw-widget-mediaResultWidget-nameLabel {
+       background-color: #000;
+}
+
+.mw-widget-mediaSearchWidget-noresults {
+       padding-top: 1em;
+}
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.js
new file mode 100644 (file)
index 0000000..7607e84
--- /dev/null
@@ -0,0 +1,274 @@
+/*!
+ * MediaWiki Widgets - MediaResultWidget class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * Creates an mw.widgets.MediaResultWidget object.
+        *
+        * @class
+        * @extends OO.ui.OptionWidget
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {number} [rowHeight] Height of the row this result is part of
+        * @cfg {number} [maxRowWidth] A limit for the width of the row this
+        *  result is a part of.
+        * @cfg {number} [minWidth] Minimum width for the result
+        * @cfg {number} [maxWidth] Maximum width for the result
+        */
+       mw.widgets.MediaResultWidget = function MwWidgetsMediaResultWidget( config ) {
+               // Configuration initialization
+               config = config || {};
+
+               // Parent constructor
+               mw.widgets.MediaResultWidget.super.call( this, config );
+
+               // Properties
+               this.setRowHeight( config.rowHeight || 150 );
+               this.maxRowWidth = config.maxRowWidth || 500;
+               this.minWidth = config.minWidth || this.maxRowWidth / 5;
+               this.maxWidth = config.maxWidth || this.maxRowWidth * 2 / 3;
+
+               this.imageDimensions = {};
+
+               this.isAudio = this.data.mediatype === 'AUDIO';
+
+               // Store the thumbnail url
+               this.thumbUrl = this.data.thumburl;
+               this.src = null;
+               this.row = null;
+
+               this.$thumb = $( '<img>' )
+                       .addClass( 'mw-widget-mediaResultWidget-thumbnail' )
+                       .on( {
+                               load: this.onThumbnailLoad.bind( this ),
+                               error: this.onThumbnailError.bind( this )
+                       } );
+               this.$overlay = $( '<div>' )
+                       .addClass( 'mw-widget-mediaResultWidget-overlay' );
+
+               this.calculateSizing( this.data );
+
+               // Get wiki default thumbnail size
+               this.defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' )
+                       .defaultUserOptions.defaultthumbsize;
+
+               // Initialization
+               this.setLabel( new mw.Title( this.data.title ).getNameText() );
+               this.$label.addClass( 'mw-widget-mediaResultWidget-nameLabel' );
+
+               this.$element
+                       .addClass( 'mw-widget-mediaResultWidget ve-ui-texture-pending' )
+                       .prepend( this.$thumb, this.$overlay );
+       };
+
+       /* Inheritance */
+
+       OO.inheritClass( mw.widgets.MediaResultWidget, OO.ui.OptionWidget );
+
+       /* Static methods */
+
+       // Copied from ve.dm.MWImageNode
+       mw.widgets.MediaResultWidget.static.resizeToBoundingBox = function ( imageDimensions, boundingBox ) {
+               var newDimensions = OO.copy( imageDimensions ),
+                       scale = Math.min(
+                               boundingBox.height / imageDimensions.height,
+                               boundingBox.width / imageDimensions.width
+                       );
+
+               if ( scale < 1 ) {
+                       // Scale down
+                       newDimensions = {
+                               width: Math.floor( newDimensions.width * scale ),
+                               height: Math.floor( newDimensions.height * scale )
+                       };
+               }
+               return newDimensions;
+       };
+
+       /* Methods */
+       /** */
+       mw.widgets.MediaResultWidget.prototype.onThumbnailLoad = function () {
+               this.$thumb.first().addClass( 've-ui-texture-transparency' );
+               this.$element
+                       .addClass( 'mw-widget-mediaResultWidget-done' )
+                       .removeClass( 've-ui-texture-pending' );
+       };
+
+       /** */
+       mw.widgets.MediaResultWidget.prototype.onThumbnailError = function () {
+               this.$thumb.last()
+                       .css( 'background-image', '' )
+                       .addClass( 've-ui-texture-alert' );
+               this.$element
+                       .addClass( 'mw-widget-mediaResultWidget-error' )
+                       .removeClass( 've-ui-texture-pending' );
+       };
+
+       /**
+        * Resize the thumbnail and wrapper according to row height and bounding boxes, if given.
+        *
+        * @param {Object} originalDimensions Original image dimensions with width and height values
+        * @param {Object} [boundingBox] Specific bounding box, if supplied
+        */
+       mw.widgets.MediaResultWidget.prototype.calculateSizing = function ( originalDimensions, boundingBox ) {
+               var wrapperPadding,
+                       imageDimensions = {};
+
+               boundingBox = boundingBox || {};
+
+               if ( this.isAudio ) {
+                       // HACK: We are getting the wrong information from the
+                       // API about audio files. Set their thumbnail to square 120px
+                       imageDimensions = {
+                               width: 120,
+                               height: 120
+                       };
+               } else {
+                       // Get the image within the bounding box
+                       imageDimensions = this.constructor.static.resizeToBoundingBox(
+                               // Image original dimensions
+                               {
+                                       width: originalDimensions.width || originalDimensions.thumbwidth,
+                                       height: originalDimensions.height || originalDimensions.thumbwidth
+                               },
+                               // Bounding box
+                               {
+                                       width: boundingBox.width || this.getImageMaxWidth(),
+                                       height: boundingBox.height || this.getRowHeight()
+                               }
+                       );
+               }
+               this.imageDimensions = imageDimensions;
+               // Set the thumbnail size
+               this.$thumb.css( this.imageDimensions );
+
+               // Set the box size
+               wrapperPadding = this.calculateWrapperPadding( this.imageDimensions );
+               this.$element.css( wrapperPadding );
+       };
+
+       /**
+        * Replace the empty .src attribute of the image with the
+        * actual src.
+        */
+       mw.widgets.MediaResultWidget.prototype.lazyLoad = function () {
+               if ( !this.hasSrc() ) {
+                       this.src = this.thumbUrl;
+                       this.$thumb.attr( 'src', this.thumbUrl );
+               }
+       };
+
+       /**
+        * Retrieve the store dimensions object
+        *
+        * @return {Object} Thumb dimensions
+        */
+       mw.widgets.MediaResultWidget.prototype.getDimensions = function () {
+               return this.dimensions;
+       };
+
+       /**
+        * Resize thumbnail and element according to the resize factor
+        *
+        * @param {number} resizeFactor The resizing factor for the image
+        */
+       mw.widgets.MediaResultWidget.prototype.resizeThumb = function ( resizeFactor ) {
+               var boundingBox,
+                       imageOriginalWidth = this.imageDimensions.width,
+                       wrapperWidth = this.$element.width();
+               // Set the new row height
+               this.setRowHeight( Math.ceil( this.getRowHeight() * resizeFactor ) );
+
+               boundingBox = {
+                       width: Math.ceil( this.imageDimensions.width * resizeFactor ),
+                       height: this.getRowHeight()
+               };
+
+               this.calculateSizing( this.data, boundingBox );
+
+               // We need to adjust the wrapper this time to fit the "perfect"
+               // dimensions, regardless of how small the image is
+               if ( imageOriginalWidth < wrapperWidth ) {
+                       boundingBox.width = wrapperWidth * resizeFactor;
+               }
+               this.$element.css( this.calculateWrapperPadding( boundingBox ) );
+       };
+
+       /**
+        * Adjust the wrapper padding for small images
+        *
+        * @param {Object} thumbDimensions Thumbnail dimensions
+        * @return {Object} Css styling for the wrapper
+        */
+       mw.widgets.MediaResultWidget.prototype.calculateWrapperPadding = function ( thumbDimensions ) {
+               var css = {
+                       height: this.rowHeight,
+                       width: thumbDimensions.width,
+                       lineHeight: this.getRowHeight() + 'px'
+               };
+
+               // Check if the image is too thin so we can make a bit of space around it
+               if ( thumbDimensions.width < this.minWidth ) {
+                       css.width = this.minWidth;
+               }
+
+               return css;
+       };
+
+       /**
+        * Set the row height for all size calculations
+        *
+        * @return {number} rowHeight Row height
+        */
+       mw.widgets.MediaResultWidget.prototype.getRowHeight = function () {
+               return this.rowHeight;
+       };
+
+       /**
+        * Set the row height for all size calculations
+        *
+        * @param {number} rowHeight Row height
+        */
+       mw.widgets.MediaResultWidget.prototype.setRowHeight = function ( rowHeight ) {
+               this.rowHeight = rowHeight;
+       };
+
+       mw.widgets.MediaResultWidget.prototype.setImageMaxWidth = function ( width ) {
+               this.maxWidth = width;
+       };
+       mw.widgets.MediaResultWidget.prototype.getImageMaxWidth = function () {
+               return this.maxWidth;
+       };
+
+       /**
+        * Set the row this result is in.
+        *
+        * @param {number} row Row number
+        */
+       mw.widgets.MediaResultWidget.prototype.setRow = function ( row ) {
+               this.row = row;
+       };
+
+       /**
+        * Get the row this result is in.
+        *
+        * @return {number} row Row number
+        */
+       mw.widgets.MediaResultWidget.prototype.getRow = function () {
+               return this.row;
+       };
+
+       /**
+        * Check if the image has a src attribute already
+        *
+        * @return {boolean} Thumbnail has its source attribute set
+        */
+       mw.widgets.MediaResultWidget.prototype.hasSrc = function () {
+               return !!this.src;
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchProvider.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchProvider.js
new file mode 100644 (file)
index 0000000..a46d911
--- /dev/null
@@ -0,0 +1,69 @@
+/*!
+ * MediaWiki Widgets - MediaSearchProvider class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * MediaWiki media search provider.
+        *
+        * @class
+        * @extends mw.widgets.MediaResourceProvider
+        *
+        * @constructor
+        * @param {string} apiurl The API url
+        * @param {Object} [config] Configuration options
+        */
+       mw.widgets.MediaSearchProvider = function MwWidgetsMediaSearchProvider( apiurl, config ) {
+               config = config || {};
+
+               config.staticParams = $.extend( {
+                       generator: 'search',
+                       gsrnamespace: mw.config.get( 'wgNamespaceIds' ).file
+               }, config.staticParams );
+
+               // Parent constructor
+               mw.widgets.MediaSearchProvider.super.call( this, apiurl, config );
+       };
+
+       /* Inheritance */
+       OO.inheritClass( mw.widgets.MediaSearchProvider, mw.widgets.MediaResourceProvider );
+
+       /* Methods */
+
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.MediaSearchProvider.prototype.getContinueData = function ( howMany ) {
+               return {
+                       gsroffset: this.getOffset(),
+                       gsrlimit: howMany || this.getDefaultFetchLimit()
+               };
+       };
+
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.MediaSearchProvider.prototype.setContinue = function ( continueData ) {
+               // Update the offset for next time
+               this.setOffset( continueData.gsroffset );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.MediaSearchProvider.prototype.sort = function ( results ) {
+               return results.sort( function ( a, b ) {
+                       return a.index - b.index;
+               } );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.MediaSearchProvider.prototype.isValid = function () {
+               return this.getUserParams().gsrsearch && mw.widgets.MediaSearchProvider.super.prototype.isValid.call( this );
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchQueue.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchQueue.js
new file mode 100644 (file)
index 0000000..7ee98bb
--- /dev/null
@@ -0,0 +1,82 @@
+/*!
+ * MediaWiki Widgets - MediaSearchQueue class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * MediaWiki media resource queue.
+        *
+        * @class
+        * @extends mw.widgets.MediaResourceQueue
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {number} maxHeight The maximum height of the media, used in the
+        *  search call to the API.
+        */
+       mw.widgets.MediaSearchQueue = function MwWidgetsMediaSearchQueue( config ) {
+               config = config || {};
+
+               // Parent constructor
+               mw.widgets.MediaSearchQueue.super.call( this, config );
+
+               this.searchQuery = '';
+       };
+
+       /* Inheritance */
+       OO.inheritClass( mw.widgets.MediaSearchQueue, mw.widgets.MediaResourceQueue );
+
+       /**
+        * Override parent method to set up the providers according to
+        * the file repos
+        *
+        * @return {jQuery.Promise} Promise that resolves when the resources are set up
+        */
+       mw.widgets.MediaSearchQueue.prototype.setup = function () {
+               var i, len,
+                       queue = this;
+
+               return this.getFileRepos().then( function ( sources ) {
+                       if ( queue.providers.length === 0 ) {
+                               // Set up the providers
+                               for ( i = 0, len = sources.length; i < len; i++ ) {
+                                       queue.providers.push( new mw.widgets.MediaSearchProvider(
+                                               sources[ i ].apiurl,
+                                               {
+                                                       name: sources[ i ].name,
+                                                       local: sources[ i ].local,
+                                                       scriptDirUrl: sources[ i ].scriptDirUrl,
+                                                       userParams: {
+                                                               gsrsearch: queue.getSearchQuery()
+                                                       },
+                                                       staticParams: {
+                                                               iiurlheight: queue.getMaxHeight()
+                                                       }
+                                               } )
+                                       );
+                               }
+                       }
+               } );
+       };
+
+       /**
+        * Set the search query
+        *
+        * @param {string} searchQuery API search query
+        */
+       mw.widgets.MediaSearchQueue.prototype.setSearchQuery = function ( searchQuery ) {
+               this.setParams( { gsrsearch: searchQuery } );
+       };
+
+       /**
+        * Get the search query
+        *
+        * @return {string} API search query
+        */
+       mw.widgets.MediaSearchQueue.prototype.getSearchQuery = function () {
+               return this.getParams().gsrsearch;
+       };
+}( jQuery, mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.css b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.css
new file mode 100644 (file)
index 0000000..3d28ef8
--- /dev/null
@@ -0,0 +1,10 @@
+/*!
+ * MediaWiki Widgets - MediaSearchWidget styles.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+.mw-widget-mediaSearchWidget .oo-ui-searchWidget-query .oo-ui-inputWidget {
+       max-width: none;
+}
diff --git a/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js b/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js
new file mode 100644 (file)
index 0000000..c6938e8
--- /dev/null
@@ -0,0 +1,462 @@
+/*!
+ * MediaWiki Widgets - MediaSearchWidget class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * Creates an mw.widgets.MediaSearchWidget object.
+        *
+        * @class
+        * @extends OO.ui.SearchWidget
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @param {number} [size] Vertical size of thumbnails
+        */
+       mw.widgets.MediaSearchWidget = function MwWidgetsMediaSearchWidget( config ) {
+               // Configuration initialization
+               config = $.extend( {
+                       placeholder: mw.msg( 'mw-widgets-mediasearch-input-placeholder' )
+               }, config );
+
+               // Parent constructor
+               mw.widgets.MediaSearchWidget.super.call( this, config );
+
+               // Properties
+               this.providers = {};
+               this.lastQueryValue = '';
+               this.searchQueue = new mw.widgets.MediaSearchQueue( {
+                       limit: this.constructor.static.limit,
+                       threshold: this.constructor.static.threshold
+               } );
+
+               this.queryTimeout = null;
+               this.itemCache = {};
+               this.promises = [];
+               this.lang = config.lang || 'en';
+               this.$panels = config.$panels;
+
+               this.externalLinkUrlProtocolsRegExp = new RegExp(
+                       '^(' + mw.config.get( 'wgUrlProtocols' ) + ')',
+                       'i'
+               );
+
+               // Masonry fit properties
+               this.rows = [];
+               this.rowHeight = config.rowHeight || 200;
+               this.layoutQueue = [];
+               this.numItems = 0;
+               this.currentItemCache = [];
+
+               this.resultsSize = {};
+
+               this.selected = null;
+
+               this.noItemsMessage = new OO.ui.LabelWidget( {
+                       label: mw.msg( 'mw-widgets-mediasearch-noresults' ),
+                       classes: [ 'mw-widget-mediaSearchWidget-noresults' ]
+               } );
+               this.noItemsMessage.toggle( false );
+
+               // Events
+               this.$results.on( 'scroll', this.onResultsScroll.bind( this ) );
+               this.$query.append( this.noItemsMessage.$element );
+               this.results.connect( this, {
+                       add: 'onResultsAdd',
+                       remove: 'onResultsRemove'
+               } );
+
+               this.resizeHandler = OO.ui.debounce( this.afterResultsResize.bind( this ), 500 );
+
+               // Initialization
+               this.$element.addClass( 'mw-widget-mediaSearchWidget' );
+       };
+
+       /* Inheritance */
+
+       OO.inheritClass( mw.widgets.MediaSearchWidget, OO.ui.SearchWidget );
+
+       /* Static properties */
+
+       mw.widgets.MediaSearchWidget.static.limit = 10;
+
+       mw.widgets.MediaSearchWidget.static.threshold = 5;
+
+       /* Methods */
+
+       /**
+        * Respond to window resize and check if the result display should
+        * be updated.
+        */
+       mw.widgets.MediaSearchWidget.prototype.afterResultsResize = function () {
+               var items = this.currentItemCache;
+
+               if (
+                       items.length > 0 &&
+                       (
+                               this.resultsSize.width !== this.$results.width() ||
+                               this.resultsSize.height !== this.$results.height()
+                       )
+               ) {
+                       this.resetRows();
+                       this.itemCache = {};
+                       this.processQueueResults( items );
+                       if ( this.results.getItems().length > 0 ) {
+                               this.lazyLoadResults();
+                       }
+
+                       // Cache the size
+                       this.resultsSize = {
+                               width: this.$results.width(),
+                               height: this.$results.height()
+                       };
+               }
+       };
+
+       /**
+        * Teardown the widget; disconnect the window resize event.
+        */
+       mw.widgets.MediaSearchWidget.prototype.teardown = function () {
+               $( window ).off( 'resize', this.resizeHandler );
+       };
+
+       /**
+        * Setup the widget; activate the resize event.
+        */
+       mw.widgets.MediaSearchWidget.prototype.setup = function () {
+               $( window ).on( 'resize', this.resizeHandler );
+       };
+
+       /**
+        * Query all sources for media.
+        *
+        * @method
+        */
+       mw.widgets.MediaSearchWidget.prototype.queryMediaQueue = function () {
+               var search = this,
+                       value = this.getQueryValue();
+
+               if ( value === '' ) {
+                       return;
+               }
+
+               this.query.pushPending();
+               search.noItemsMessage.toggle( false );
+
+               this.searchQueue.setSearchQuery( value );
+               this.searchQueue.get( this.constructor.static.limit )
+                       .then( function ( items ) {
+                               if ( items.length > 0 ) {
+                                       search.processQueueResults( items );
+                                       search.currentItemCache = search.currentItemCache.concat( items );
+                               }
+
+                               search.query.popPending();
+                               search.noItemsMessage.toggle( search.results.getItems().length === 0 );
+                               if ( search.results.getItems().length > 0 ) {
+                                       search.lazyLoadResults();
+                               }
+
+                       } );
+       };
+
+       /**
+        * Process the media queue giving more items
+        *
+        * @method
+        * @param {Object[]} items Given items by the media queue
+        */
+       mw.widgets.MediaSearchWidget.prototype.processQueueResults = function ( items ) {
+               var i, len, title,
+                       resultWidgets = [],
+                       inputSearchQuery = this.getQueryValue(),
+                       queueSearchQuery = this.searchQueue.getSearchQuery();
+
+               if ( inputSearchQuery === '' || queueSearchQuery !== inputSearchQuery ) {
+                       return;
+               }
+
+               for ( i = 0, len = items.length; i < len; i++ ) {
+                       title = new mw.Title( items[ i ].title ).getMainText();
+                       // Do not insert duplicates
+                       if ( !Object.prototype.hasOwnProperty.call( this.itemCache, title ) ) {
+                               this.itemCache[ title ] = true;
+                               resultWidgets.push(
+                                       new mw.widgets.MediaResultWidget( {
+                                               data: items[ i ],
+                                               rowHeight: this.rowHeight,
+                                               maxWidth: this.results.$element.width() / 3,
+                                               minWidth: 30,
+                                               rowWidth: this.results.$element.width()
+                                       } )
+                               );
+                       }
+               }
+               this.results.addItems( resultWidgets );
+
+       };
+
+       /**
+        * Get the sanitized query value from the input
+        *
+        * @return {string} Query value
+        */
+       mw.widgets.MediaSearchWidget.prototype.getQueryValue = function () {
+               var queryValue = this.query.getValue().trim();
+
+               if ( queryValue.match( this.externalLinkUrlProtocolsRegExp ) ) {
+                       queryValue = queryValue.match( /.+\/([^\/]+)/ )[ 1 ];
+               }
+               return queryValue;
+       };
+
+       /**
+        * Handle search value change
+        *
+        * @param {string} value New value
+        */
+       mw.widgets.MediaSearchWidget.prototype.onQueryChange = function () {
+               // Get the sanitized query value
+               var queryValue = this.getQueryValue();
+
+               if ( queryValue === this.lastQueryValue ) {
+                       return;
+               }
+
+               // Parent method
+               mw.widgets.MediaSearchWidget.super.prototype.onQueryChange.apply( this, arguments );
+
+               // Reset
+               this.itemCache = {};
+               this.currentItemCache = [];
+               this.resetRows();
+
+               // Empty the results queue
+               this.layoutQueue = [];
+
+               // Change resource queue query
+               this.searchQueue.setSearchQuery( queryValue );
+               this.lastQueryValue = queryValue;
+
+               // Queue
+               clearTimeout( this.queryTimeout );
+               this.queryTimeout = setTimeout( this.queryMediaQueue.bind( this ), 350 );
+       };
+
+       /**
+        * Handle results scroll events.
+        *
+        * @param {jQuery.Event} e Scroll event
+        */
+       mw.widgets.MediaSearchWidget.prototype.onResultsScroll = function () {
+               var position = this.$results.scrollTop() + this.$results.outerHeight(),
+                       threshold = this.results.$element.outerHeight() - this.rowHeight * 3;
+
+               // Check if we need to ask for more results
+               if ( !this.query.isPending() && position > threshold ) {
+                       this.queryMediaQueue();
+               }
+
+               this.lazyLoadResults();
+       };
+
+       /**
+        * Lazy-load the images that are visible.
+        */
+       mw.widgets.MediaSearchWidget.prototype.lazyLoadResults = function () {
+               var i, elementTop,
+                       items = this.results.getItems(),
+                       resultsScrollTop = this.$results.scrollTop(),
+                       position = resultsScrollTop + this.$results.outerHeight();
+
+               // Lazy-load results
+               for ( i = 0; i < items.length; i++ ) {
+                       elementTop = items[ i ].$element.position().top;
+                       if ( elementTop <= position && !items[ i ].hasSrc() ) {
+                               // Load the image
+                               items[ i ].lazyLoad();
+                       }
+               }
+       };
+
+       /**
+        * Reset all the rows; destroy the jQuery elements and reset
+        * the rows array.
+        */
+       mw.widgets.MediaSearchWidget.prototype.resetRows = function () {
+               var i, len;
+
+               for ( i = 0, len = this.rows.length; i < len; i++ ) {
+                       this.rows[ i ].$element.remove();
+               }
+
+               this.rows = [];
+               this.itemCache = {};
+       };
+
+       /**
+        * Find an available row at the end. Either we will need to create a new
+        * row or use the last available row if it isn't full.
+        *
+        * @return {number} Row index
+        */
+       mw.widgets.MediaSearchWidget.prototype.getAvailableRow = function () {
+               var row;
+
+               if ( this.rows.length === 0 ) {
+                       row = 0;
+               } else {
+                       row = this.rows.length - 1;
+               }
+
+               if ( !this.rows[ row ] ) {
+                       // Create new row
+                       this.rows[ row ] = {
+                               isFull: false,
+                               width: 0,
+                               items: [],
+                               $element: $( '<div>' )
+                                       .addClass( 'mw-widget-mediaResultWidget-row' )
+                                       .css( {
+                                               overflow: 'hidden'
+                                       } )
+                                       .data( 'row', row )
+                                       .attr( 'data-full', false )
+                       };
+                       // Append to results
+                       this.results.$element.append( this.rows[ row ].$element );
+               } else if ( this.rows[ row ].isFull ) {
+                       row++;
+                       // Create new row
+                       this.rows[ row ] = {
+                               isFull: false,
+                               width: 0,
+                               items: [],
+                               $element: $( '<div>' )
+                                       .addClass( 'mw-widget-mediaResultWidget-row' )
+                                       .css( {
+                                               overflow: 'hidden'
+                                       } )
+                                       .data( 'row', row )
+                                       .attr( 'data-full', false )
+                       };
+                       // Append to results
+                       this.results.$element.append( this.rows[ row ].$element );
+               }
+
+               return row;
+       };
+
+       /**
+        * Respond to add results event in the results widget.
+        * Override the way SelectWidget and GroupElement append the items
+        * into the group so we can append them in groups of rows.
+        *
+        * @param {mw.widgets.MediaResultWidget[]} items An array of item elements
+        */
+       mw.widgets.MediaSearchWidget.prototype.onResultsAdd = function ( items ) {
+               var search = this;
+
+               // Add method to a queue; this queue will only run when the widget
+               // is visible
+               this.layoutQueue.push( function () {
+                       var i, j, ilen, jlen, itemWidth, row, effectiveWidth,
+                               resizeFactor,
+                               maxRowWidth = search.results.$element.width() - 15;
+
+                       // Go over the added items
+                       row = search.getAvailableRow();
+                       for ( i = 0, ilen = items.length; i < ilen; i++ ) {
+                               itemWidth = items[ i ].$element.outerWidth( true );
+
+                               // Add items to row until it is full
+                               if ( search.rows[ row ].width + itemWidth >= maxRowWidth ) {
+                                       // Mark this row as full
+                                       search.rows[ row ].isFull = true;
+                                       search.rows[ row ].$element.attr( 'data-full', true );
+
+                                       // Find the resize factor
+                                       effectiveWidth = search.rows[ row ].width;
+                                       resizeFactor = maxRowWidth / effectiveWidth;
+
+                                       search.rows[ row ].$element.attr( 'data-effectiveWidth', effectiveWidth );
+                                       search.rows[ row ].$element.attr( 'data-resizeFactor', resizeFactor );
+                                       search.rows[ row ].$element.attr( 'data-row', row );
+
+                                       // Resize all images in the row to fit the width
+                                       for ( j = 0, jlen = search.rows[ row ].items.length; j < jlen; j++ ) {
+                                               search.rows[ row ].items[ j ].resizeThumb( resizeFactor );
+                                       }
+
+                                       // find another row
+                                       row = search.getAvailableRow();
+                               }
+
+                               // Add the cumulative
+                               search.rows[ row ].width += itemWidth;
+
+                               // Store reference to the item and to the row
+                               search.rows[ row ].items.push( items[ i ] );
+                               items[ i ].setRow( row );
+
+                               // Append the item
+                               search.rows[ row ].$element.append( items[ i ].$element );
+                       }
+
+                       // If we have less than 4 rows, call for more images
+                       if ( search.rows.length < 4 ) {
+                               search.queryMediaQueue();
+                       }
+               } );
+               this.runLayoutQueue();
+       };
+
+       /**
+        * Run layout methods from the queue only if the element is visible.
+        */
+       mw.widgets.MediaSearchWidget.prototype.runLayoutQueue = function () {
+               var i, len;
+
+               if ( this.$element.is( ':visible' ) ) {
+                       for ( i = 0, len = this.layoutQueue.length; i < len; i++ ) {
+                               this.layoutQueue.pop()();
+                       }
+               }
+       };
+
+       /**
+        * Respond to removing results event in the results widget.
+        * Clear the relevant rows.
+        *
+        * @param {OO.ui.OptionWidget[]} items Removed items
+        */
+       mw.widgets.MediaSearchWidget.prototype.onResultsRemove = function ( items ) {
+               if ( items.length > 0 ) {
+                       // In the case of the media search widget, if any items are removed
+                       // all are removed (new search)
+                       this.resetRows();
+                       this.currentItemCache = [];
+               }
+       };
+
+       /**
+        * Set language for the search results.
+        *
+        * @param {string} lang Language
+        */
+       mw.widgets.MediaSearchWidget.prototype.setLang = function ( lang ) {
+               this.lang = lang;
+       };
+
+       /**
+        * Get language for the search results.
+        *
+        * @return {string} lang Language
+        */
+       mw.widgets.MediaSearchWidget.prototype.getLang = function () {
+               return this.lang;
+       };
+}( jQuery, mediaWiki ) );
index 39bee7c..2ac75c5 100755 (executable)
@@ -78,7 +78,7 @@
         * @inheritdoc mw.widgets.TitleWidget
         */
        mw.widgets.SearchInputWidget.prototype.getSuggestionsPromise = function () {
-               var api = new mw.Api(),
+               var api = this.getApi(),
                        promise,
                        self = this;
 
index e1e50ea..0e5e0c5 100644 (file)
@@ -6,16 +6,6 @@
  */
 ( function ( $, mw ) {
 
-       var interwikiPrefixesPromise = new mw.Api().get( {
-               action: 'query',
-               meta: 'siteinfo',
-               siprop: 'interwikimap'
-       } ).then( function ( data ) {
-               return $.map( data.query.interwikimap, function ( interwiki ) {
-                       return interwiki.prefix;
-               } );
-       } );
-
        /**
         * Mixin for title widgets
         *
@@ -36,6 +26,7 @@
         * @cfg {boolean} [validateTitle=true] Whether the input must be a valid title (if set to true,
         *  the widget will marks itself red for invalid inputs, including an empty query).
         * @cfg {Object} [cache] Result cache which implements a 'set' method, taking keyed values as an argument
+        * @cfg {mw.Api} [api] API object to use, creates a default mw.Api instance if not specified
         */
        mw.widgets.TitleWidget = function MwWidgetsTitleWidget( config ) {
                // Config initialization
@@ -56,6 +47,7 @@
                this.excludeCurrentPage = !!config.excludeCurrentPage;
                this.validateTitle = config.validateTitle !== undefined ? config.validateTitle : true;
                this.cache = config.cache;
+               this.api = config.api || new mw.Api();
 
                // Initialization
                this.$element.addClass( 'mw-widget-titleWidget' );
 
        OO.initClass( mw.widgets.TitleWidget );
 
+       /* Static properties */
+
+       mw.widgets.TitleWidget.static.interwikiPrefixesPromiseCache = {};
+
        /* Methods */
 
        /**
                this.namespace = namespace;
        };
 
+       mw.widgets.TitleWidget.prototype.getInterwikiPrefixesPromise = function () {
+               var api = this.getApi(),
+                       cache = this.constructor.static.interwikiPrefixesPromiseCache,
+                       key = api.defaults.ajax.url;
+               if ( !cache.hasOwnProperty( key ) ) {
+                       cache[ key ] = api.get( {
+                               action: 'query',
+                               meta: 'siteinfo',
+                               siprop: 'interwikimap'
+                       } ).then( function ( data ) {
+                               return $.map( data.query.interwikimap, function ( interwiki ) {
+                                       return interwiki.prefix;
+                               } );
+                       } );
+               }
+               return cache[ key ];
+       };
+
        /**
         * Get a promise which resolves with an API repsonse for suggested
         * links for the current query.
         */
        mw.widgets.TitleWidget.prototype.getSuggestionsPromise = function () {
                var req,
+                       api = this.getApi(),
                        query = this.getQueryValue(),
                        widget = this,
                        promiseAbortObject = { abort: function () {
                        } };
 
                if ( mw.Title.newFromText( query ) ) {
-                       return interwikiPrefixesPromise.then( function ( interwikiPrefixes ) {
+                       return this.getInterwikiPrefixesPromise().then( function ( interwikiPrefixes ) {
                                var params,
                                        interwiki = query.substring( 0, query.indexOf( ':' ) );
                                if (
                                                params.prop.push( 'pageterms' );
                                                params.wbptterms = 'description';
                                        }
-                                       req = new mw.Api().get( params );
+                                       req = api.get( params );
                                        promiseAbortObject.abort = req.abort.bind( req ); // TODO ew
                                        return req.then( function ( ret ) {
                                                if ( ret.query === undefined ) {
-                                                       ret = new mw.Api().get( { action: 'query', titles: query } );
+                                                       ret = api.get( { action: 'query', titles: query } );
                                                        promiseAbortObject.abort = ret.abort.bind( ret );
                                                }
                                                return ret;
                }
        };
 
+       /**
+        * Get the API object for title requests
+        *
+        * @return {mw.Api} MediaWiki API
+        */
+       mw.widgets.TitleWidget.prototype.getApi = function () {
+               return this.api;
+       };
+
        /**
         * Get option widgets from the server response
         *
index b9e05c3..b9db059 100644 (file)
                                        } else if ( result.error ) {
                                                code = result.error.code === undefined ? 'unknown' : result.error.code;
                                                apiDeferred.reject( code, result, result, jqXHR );
+                                       } else if ( result.errors ) {
+                                               code = result.errors[ 0 ].code === undefined ? 'unknown' : result.errors[ 0 ].code;
+                                               apiDeferred.reject( code, result, result, jqXHR );
                                        } else {
                                                apiDeferred.resolve( result, jqXHR );
                                        }
                                                        } );
                                                }
 
-                                               // Different error, pass on to let caller handle the error code
-                                               return this;
+                                               // Let caller handle the error code
+                                               return $.Deferred().rejectWith( this, arguments );
                                        }
                                );
                        } ).promise( { abort: function () {
                        promiseGroup = promises[ this.defaults.ajax.url ];
                        d = promiseGroup && promiseGroup[ type + 'Token' ];
 
+                       if ( !promiseGroup ) {
+                               promiseGroup = promises[ this.defaults.ajax.url ] = {};
+                       }
+
                        if ( !d ) {
                                apiPromise = this.get( {
                                        action: 'query',
                                                // Clear promise. Do not cache errors.
                                                delete promiseGroup[ type + 'Token' ];
 
-                                               // Pass on to allow the caller to handle the error
-                                               return this;
+                                               // Let caller handle the error code
+                                               return $.Deferred().rejectWith( this, arguments );
                                        } )
                                        // Attach abort handler
                                        .promise( { abort: apiPromise.abort } );
 
                                // Store deferred now so that we can use it again even if it isn't ready yet
-                               if ( !promiseGroup ) {
-                                       promiseGroup = promises[ this.defaults.ajax.url ] = {};
-                               }
                                promiseGroup[ type + 'Token' ] = d;
                        }
 
index 687b475..92722b5 100644 (file)
@@ -19,7 +19,6 @@
         *  parameter)
         * @return {string} return.done.watch.title Full pagename
         * @return {boolean} return.done.watch.watched Whether the page is now watched or unwatched
-        * @return {string} return.done.watch.message Parsed HTML of the confirmational interface message
         */
        function doWatchInternal( pages, addParams ) {
                // XXX: Parameter addParams is undocumented because we inherit this
index 926f8c5..e1287db 100644 (file)
         * @ignore
         */
        function init() {
-               var offset, $window = $( window );
+               var offset,
+                       isFloating = false;
 
                $area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
                        // Pause auto-hide timers when the mouse is in the notification area.
                $area.hide();
 
                function updateAreaMode() {
-                       var isFloating = $window.scrollTop() > offset.top;
+                       var shouldFloat = window.pageYOffset > offset.top;
+                       if ( isFloating === shouldFloat ) {
+                               return;
+                       }
+                       isFloating = shouldFloat;
                        $area
                                .toggleClass( 'mw-notification-area-floating', isFloating )
                                .toggleClass( 'mw-notification-area-layout', !isFloating );
                }
 
-               $window.on( 'scroll', updateAreaMode );
+               $( window ).on( 'scroll', updateAreaMode );
 
                // Initial mode
                updateAreaMode();
index a9d17ff..20f8efb 100644 (file)
@@ -1,65 +1,91 @@
 ( function ( mw ) {
        'use strict';
 
-       /**
-        * Library for storing device specific information. It should be used for storing simple
-        * strings and is not suitable for storing large chunks of data.
-        *
-        * @class mw.storage
-        * @singleton
-        */
-       mw.storage = {
-
-               localStorage: ( function () {
-                       // Catch exceptions to avoid fatal in Chrome's "Block data storage" mode
-                       // which throws when accessing the localStorage property itself, as opposed
-                       // to the standard behaviour of throwing on getItem/setItem. (T148998)
+       // Catch exceptions to avoid fatal in Chrome's "Block data storage" mode
+       // which throws when accessing the localStorage property itself, as opposed
+       // to the standard behaviour of throwing on getItem/setItem. (T148998)
+       var
+               localStorage = ( function () {
                        try {
                                return window.localStorage;
                        } catch ( e ) {}
                }() ),
-
-               /**
-                * Retrieve value from device storage.
-                *
-                * @param {string} key Key of item to retrieve
-                * @return {string|boolean} False when localStorage not available, otherwise string
-                */
-               get: function ( key ) {
+               sessionStorage = ( function () {
                        try {
-                               return mw.storage.localStorage.getItem( key );
+                               return window.sessionStorage;
                        } catch ( e ) {}
-                       return false;
-               },
+               }() );
 
-               /**
-                 * Set a value in device storage.
-                 *
-                 * @param {string} key Key name to store under
-                 * @param {string} value Value to be stored
-                 * @return {boolean} Whether the save succeeded or not
-                 */
-               set: function ( key, value ) {
-                       try {
-                               mw.storage.localStorage.setItem( key, value );
-                               return true;
-                       } catch ( e ) {}
-                       return false;
-               },
+       /**
+        * A wrapper for an HTML5 Storage interface (`localStorage` or `sessionStorage`)
+        * that is safe to call on all browsers.
+        *
+        * @class mw.SafeStorage
+        * @private
+        */
 
-               /**
-                 * Remove a value from device storage.
-                 *
-                 * @param {string} key Key of item to remove
-                 * @return {boolean} Whether the save succeeded or not
-                 */
-               remove: function ( key ) {
-                       try {
-                               mw.storage.localStorage.removeItem( key );
-                               return true;
-                       } catch ( e ) {}
-                       return false;
-               }
+       /**
+        * @ignore
+        * @param {Object|undefined} store The Storage instance to wrap around
+        */
+       function SafeStorage( store ) {
+               this.store = store;
+       }
+
+       /**
+        * Retrieve value from device storage.
+        *
+        * @param {string} key Key of item to retrieve
+        * @return {string|boolean} False when localStorage not available, otherwise string
+        */
+       SafeStorage.prototype.get = function ( key ) {
+               try {
+                       return this.store.getItem( key );
+               } catch ( e ) {}
+               return false;
+       };
+
+       /**
+         * Set a value in device storage.
+         *
+         * @param {string} key Key name to store under
+         * @param {string} value Value to be stored
+         * @return {boolean} Whether the save succeeded or not
+         */
+       SafeStorage.prototype.set = function ( key, value ) {
+               try {
+                       this.store.setItem( key, value );
+                       return true;
+               } catch ( e ) {}
+               return false;
+       };
+
+       /**
+         * Remove a value from device storage.
+         *
+         * @param {string} key Key of item to remove
+         * @return {boolean} Whether the save succeeded or not
+         */
+       SafeStorage.prototype.remove = function ( key ) {
+               try {
+                       this.store.removeItem( key );
+                       return true;
+               } catch ( e ) {}
+               return false;
        };
 
+       /**
+        * @class
+        * @singleton
+        * @extends mw.SafeStorage
+        */
+       mw.storage = new SafeStorage( localStorage );
+
+       /**
+        * @class
+        * @singleton
+        * @extends mw.SafeStorage
+        */
+       mw.storage.session = new SafeStorage( sessionStorage );
+
 }( mediaWiki ) );
index 474d541..b7a9132 100644 (file)
@@ -28,8 +28,8 @@ li.gallerycaption {
 
 li.gallerybox div.thumb {
        text-align: center;
-       border: 1px solid #ccc;
-       background-color: #f9f9f9;
+       border: 1px solid #c8ccd1;
+       background-color: #f8f9fa;
        margin: 2px;
 }
 
index cb46b11..d94b158 100644 (file)
@@ -30,6 +30,7 @@
                        $spinner = $.createSpinner( { size: 'small', type: 'inline' } );
                        $link.hide().after( $spinner );
 
+                       // @todo: data.messageHtml is no more. Convert to using errorformat=html.
                        api = new mw.Api();
                        api.rollback( page, user )
                                .then( function ( data ) {
index b860dbd..f880e6a 100644 (file)
 
                        api[ action ]( title )
                                .done( function ( watchResponse ) {
-                                       var otherAction = action === 'watch' ? 'unwatch' : 'watch';
+                                       var mwTitle, message, otherAction = action === 'watch' ? 'unwatch' : 'watch';
 
-                                       mw.notify( $.parseHTML( watchResponse.message ), {
+                                       mwTitle = mw.Title.newFromText( title );
+                                       if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) {
+                                               message = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
+                                       } else {
+                                               message = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
+                                       }
+
+                                       mw.notify( mw.message( message, title ).parseDom(), {
                                                tag: 'watch-self'
                                        } );
 
index 82a00bc..20818d2 100644 (file)
@@ -75,7 +75,7 @@ function isCompatible( str ) {
                // support in the modern run-time.
                // Note: Please extend the regex instead of adding new ones
                !(
-                       ua.match( /webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass|^Mozilla\/5\.0 .+ Gecko\/$/ ) ||
+                       ua.match( /webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass|^Mozilla\/5\.0 .+ Gecko\/$|googleweblight/ ) ||
                        ua.match( /PlayStation/i )
                )
        );
index 66df315..b67c9ab 100644 (file)
@@ -29,11 +29,13 @@ $wgAutoloadClasses += [
        # tests/common
        'TestSetup' => "$testDir/common/TestSetup.php",
 
+       # tests/integration
+       'MWHttpRequestTestCase' => "$testDir/integration/includes/http/MWHttpRequestTestCase.php",
+
        # tests/parser
        'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
        'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
        'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
-       'TestRecorder' => "$testDir/parser/TestRecorder.php",
        'MultiTestRecorder' => "$testDir/parser/MultiTestRecorder.php",
        'ParserTestMockParser' => "$testDir/parser/ParserTestMockParser.php",
        'ParserTestRunner' => "$testDir/parser/ParserTestRunner.php",
diff --git a/tests/integration/includes/http/CurlHttpRequestTest.php b/tests/integration/includes/http/CurlHttpRequestTest.php
new file mode 100644 (file)
index 0000000..04f80f4
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+class CurlHttpRequestTest extends MWHttpRequestTestCase {
+       protected static $httpEngine = 'curl';
+}
diff --git a/tests/integration/includes/http/MWHttpRequestTestCase.php b/tests/integration/includes/http/MWHttpRequestTestCase.php
new file mode 100644 (file)
index 0000000..4fd1cde
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+
+class MWHttpRequestTestCase extends PHPUnit_Framework_TestCase {
+       protected static $httpEngine;
+       protected $oldHttpEngine;
+
+       public function setUp() {
+               parent::setUp();
+               $this->oldHttpEngine = Http::$httpEngine;
+               Http::$httpEngine = static::$httpEngine;
+
+               try {
+                       $request = MWHttpRequest::factory( 'null:' );
+               } catch ( DomainException $e ) {
+                       $this->markTestSkipped( static::$httpEngine . ' engine not supported' );
+               }
+
+               if ( static::$httpEngine === 'php' ) {
+                       $this->assertInstanceOf( PhpHttpRequest::class, $request );
+               } else {
+                       $this->assertInstanceOf( CurlHttpRequest::class, $request );
+               }
+       }
+
+       public function tearDown() {
+               parent::tearDown();
+               Http::$httpEngine = $this->oldHttpEngine;
+       }
+
+       // --------------------
+
+       public function testIsRedirect() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/get' );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertFalse( $request->isRedirect() );
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/redirect/1' );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertTrue( $request->isRedirect() );
+       }
+
+       public function testgetFinalUrl() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/redirect/3' );
+               if ( !$request->canFollowRedirects() ) {
+                       $this->markTestSkipped( 'cannot follow redirects' );
+               }
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertNotSame( 'http://httpbin.org/get', $request->getFinalUrl() );
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/redirect/3', [ 'followRedirects'
+                       => true ] );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertSame( 'http://httpbin.org/get', $request->getFinalUrl() );
+               $this->assertResponseFieldValue( 'url', 'http://httpbin.org/get', $request );
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/redirect/3', [ 'followRedirects'
+               => true ] );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertSame( 'http://httpbin.org/get', $request->getFinalUrl() );
+               $this->assertResponseFieldValue( 'url', 'http://httpbin.org/get', $request );
+
+               if ( static::$httpEngine === 'curl' ) {
+                       $this->markTestIncomplete( 'maxRedirects seems to be ignored by CurlHttpRequest' );
+                       return;
+               }
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/redirect/3', [ 'followRedirects'
+               => true, 'maxRedirects' => 1 ] );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertNotSame( 'http://httpbin.org/get', $request->getFinalUrl() );
+       }
+
+       public function testSetCookie() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/cookies' );
+               $request->setCookie( 'foo', 'bar' );
+               $request->setCookie( 'foo2', 'bar2', [ 'domain' => 'example.com' ] );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertResponseFieldValue( 'cookies', [ 'foo' => 'bar' ], $request );
+       }
+
+       public function testSetCookieJar() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/cookies' );
+               $cookieJar = new CookieJar();
+               $cookieJar->setCookie( 'foo', 'bar', [ 'domain' => 'httpbin.org' ] );
+               $cookieJar->setCookie( 'foo2', 'bar2', [ 'domain' => 'example.com' ] );
+               $request->setCookieJar( $cookieJar );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertResponseFieldValue( 'cookies', [ 'foo' => 'bar' ], $request );
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/cookies/set?foo=bar' );
+               $cookieJar = new CookieJar();
+               $request->setCookieJar( $cookieJar );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertHasCookie( 'foo', 'bar', $request->getCookieJar() );
+
+               $this->markTestIncomplete( 'CookieJar does not handle deletion' );
+               return;
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/cookies/delete?foo' );
+               $cookieJar = new CookieJar();
+               $cookieJar->setCookie( 'foo', 'bar', [ 'domain' => 'httpbin.org' ] );
+               $cookieJar->setCookie( 'foo2', 'bar2', [ 'domain' => 'httpbin.org' ] );
+               $request->setCookieJar( $cookieJar );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertNotHasCookie( 'foo', $request->getCookieJar() );
+               $this->assertHasCookie( 'foo2', 'bar2', $request->getCookieJar() );
+       }
+
+       public function testGetResponseHeaders() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/response-headers?Foo=bar' );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $headers = array_change_key_case( $request->getResponseHeaders(), CASE_LOWER );
+               $this->assertArrayHasKey( 'foo', $headers );
+               $this->assertSame( $request->getResponseHeader( 'Foo' ), 'bar' );
+       }
+
+       public function testSetHeader() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/headers' );
+               $request->setHeader( 'Foo', 'bar' );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertResponseFieldValue( [ 'headers', 'Foo' ], 'bar', $request );
+       }
+
+       public function testGetStatus() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/status/418' );
+               $status = $request->execute();
+               $this->assertFalse( $status->isOK() );
+               $this->assertSame( $request->getStatus(), 418 );
+       }
+
+       public function testSetUserAgent() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/user-agent' );
+               $request->setUserAgent( 'foo' );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertResponseFieldValue( 'user-agent', 'foo', $request );
+       }
+
+       public function testSetData() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/post', [ 'method' => 'POST' ] );
+               $request->setData( [ 'foo' => 'bar', 'foo2' => 'bar2' ] );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertResponseFieldValue( 'form', [ 'foo' => 'bar', 'foo2' => 'bar2' ], $request );
+       }
+
+       public function testSetCallback() {
+               if ( static::$httpEngine === 'php' ) {
+                       $this->markTestIncomplete( 'PhpHttpRequest does not use setCallback()' );
+                       return;
+               }
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/ip' );
+               $data = '';
+               $request->setCallback( function ( $fh, $content ) use ( &$data ) {
+                       $data .= $content;
+                       return strlen( $content );
+               } );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $data = json_decode( $data, true );
+               $this->assertInternalType( 'array', $data );
+               $this->assertArrayHasKey( 'origin', $data );
+       }
+
+       public function testBasicAuthentication() {
+               $request = MWHttpRequest::factory( 'http://httpbin.org/basic-auth/user/pass', [
+                       'username' => 'user',
+                       'password' => 'pass',
+               ] );
+               $status = $request->execute();
+               $this->assertTrue( $status->isGood() );
+               $this->assertResponseFieldValue( 'authenticated', true, $request );
+
+               $request = MWHttpRequest::factory( 'http://httpbin.org/basic-auth/user/pass', [
+                       'username' => 'user',
+                       'password' => 'wrongpass',
+               ] );
+               $status = $request->execute();
+               $this->assertFalse( $status->isOK() );
+               $this->assertSame( 401, $request->getStatus() );
+       }
+
+       // --------------------
+
+       /**
+        * Verifies that the request was successful, returned valid JSON and the given field of that
+        * JSON data is as expected.
+        * @param string|string[] $key Path to the data in the response object
+        * @param mixed $expectedValue
+        * @param MWHttpRequest $response
+        */
+       protected function assertResponseFieldValue( $key, $expectedValue, MWHttpRequest $response ) {
+               $this->assertSame( 200, $response->getStatus(), 'response status is not 200' );
+               $data = json_decode( $response->getContent(), true );
+               $this->assertInternalType( 'array', $data, 'response is not JSON' );
+               $keyPath = '';
+               foreach ( (array)$key as $keySegment ) {
+                       $keyPath .= ( $keyPath ? '.' : '' ) . $keySegment;
+                       $this->assertArrayHasKey( $keySegment, $data, $keyPath . ' not found' );
+                       $data = $data[$keySegment];
+               }
+               $this->assertSame( $expectedValue, $data );
+       }
+
+       /**
+        * Asserts that the cookie jar has the given cookie with the given value.
+        * @param string $expectedName Cookie name
+        * @param string $expectedValue Cookie value
+        * @param CookieJar $cookieJar
+        */
+       protected function assertHasCookie( $expectedName, $expectedValue, CookieJar $cookieJar ) {
+               $cookieJar = TestingAccessWrapper::newFromObject( $cookieJar );
+               $cookies = array_change_key_case( $cookieJar->cookie, CASE_LOWER );
+               $this->assertArrayHasKey( strtolower( $expectedName ), $cookies );
+               $cookie = TestingAccessWrapper::newFromObject(
+                       $cookies[strtolower( $expectedName )] );
+               $this->assertSame( $expectedValue, $cookie->value );
+       }
+
+       /**
+        * Asserts that the cookie jar does not have the given cookie.
+        * @param string $expectedName Cookie name
+        * @param CookieJar $cookieJar
+        */
+       protected function assertNotHasCookie( $name, CookieJar $cookieJar ) {
+               $cookieJar = TestingAccessWrapper::newFromObject( $cookieJar );
+               $this->assertArrayNotHasKey( strtolower( $name ),
+                       array_change_key_case( $cookieJar->cookie, CASE_LOWER ) );
+       }
+}
+
diff --git a/tests/integration/includes/http/PhpHttpRequestTest.php b/tests/integration/includes/http/PhpHttpRequestTest.php
new file mode 100644 (file)
index 0000000..d0222a5
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+class PhpHttpRequestTest extends MWHttpRequestTestCase {
+       protected static $httpEngine = 'php';
+}
index 5b17eac..07d50a8 100644 (file)
@@ -271,6 +271,12 @@ Template:EmptyTRWithHTMLAttrTest
 </table>
 !!endarticle
 
+!! article
+Template:CircularRef
+!! text
+<ref>{{CircularRef}}</ref>
+!! endarticle
+
 ###
 ### Basic tests
 ###
@@ -280,6 +286,15 @@ Blank input
 !! html
 !! end
 
+!! test
+CircularRef
+!! wikitext
+{{CircularRef}}
+<references />
+!! html/parsoid
+<p><span about="#mwt1" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Transclusion  mw:Extension/ref" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"CircularRef","href":"./Template:CircularRef"},"params":{},"i":0}}]}'><a href="#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></p>
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">Error: Expansion loop detected at <a data-parsoid='{"a":{"href":null},"sa":{"href":"Template:CircularRef"}}'>Template:CircularRef</a></span></li></ol>
+!! end
 
 !! test
 Simple paragraph
@@ -2727,6 +2742,10 @@ Templates: Handle comments in the target
 <!-- should be ignored -->
 |foo}}
 
+{{echo
+<!-- should be ignored and spaces on next line should not trip us up (T147742) -->
+  |foo}}
+
 {{echo<!-- should be ignored -->
 |foo}}
 
@@ -2738,10 +2757,13 @@ Templates: Handle comments in the target
 </p><p>foo
 </p><p>foo
 </p><p>foo
+</p><p>foo
 </p>
 !! html/parsoid
 <p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo\n&lt;!-- should be ignored -->\n","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</p>
 
+<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo\n&lt;!-- should be ignored and spaces on next line should not trip us up (T147742) -->\n  ","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</p>
+
 <p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo&lt;!-- should be ignored -->\n","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</p>
 
 <p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo&lt;!-- should be ignored -->","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}}]}'>foo</p>
@@ -3686,7 +3708,6 @@ Definition and unordered list using wiki syntax nested in unordered list using h
 !! end
 
 !! test
-
 Definition list with empty definition and following paragraph
 !! wikitext
 ; term:
@@ -7596,8 +7617,6 @@ Link with multiple pipes
 </p>
 !! end
 
-# Note that parsoid does not munge anchor text; all non-space
-# characters are valid in HTML5 ids.
 !! test
 Anchor containing a #. (bug 63430)
 !! wikitext
@@ -7606,7 +7625,7 @@ Anchor containing a #. (bug 63430)
 <p><a href="/wiki/Main_Page#And.23Link" title="Main Page">Main Page#And#Link</a>
 </p>
 !! html/parsoid
-<p><a rel="mw:WikiLink" href="./Main_Page#And%23Link" title="Main Page">Main Page#And#Link</a></p>
+<p><a rel="mw:WikiLink" href="./Main_Page#And.23Link" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#And.23Link"},"sa":{"href":"Main Page#And#Link"}}'>Main Page#And#Link</a></p>
 !! end
 
 !! test
@@ -7721,18 +7740,16 @@ Link containing % as a double hex sequence interpreted to hex sequence
 </p>
 !!end
 
-# note that parsoid does not munge anchor text; all non-space
-# characters are valid in HTML5 anchors.
+## Example for such a section: == < ==
 !! test
 Link containing "#<" and "#>" % as a hex sequences- these are valid section anchors
-Example for such a section: == < ==
 !! wikitext
 [[%23%3c]][[%23%3e]]
 !! html/php
 <p><a href="#.3C">#&lt;</a><a href="#.3E">#&gt;</a>
 </p>
 !! html/parsoid
-<p><a rel="mw:WikiLink" href="./Main_Page#%3C" title="Main Page">#&lt;</a><a rel="mw:WikiLink" href="./Main_Page#%3E" title="Main Page">#></a></p>
+<p><a rel="mw:WikiLink" href="./Main_Page#.3C" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#.3C"},"sa":{"href":"%23%3c"}}'>#&lt;</a><a rel="mw:WikiLink" href="./Main_Page#.3E" title="Main Page" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#.3E"},"sa":{"href":"%23%3e"}}'>#></a></p>
 !! end
 
 !! test
@@ -8252,7 +8269,7 @@ Link with angle bracket after anchor
 <p><a href="/wiki/Foo#.3Cbar.3E" title="Foo">Foo#&lt;bar&gt;</a>
 </p>
 !! html/parsoid
-<p><a rel="mw:WikiLink" href="./Foo#%3Cbar%3E" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#%3Cbar%3E"},"sa":{"href":"Foo#&lt;bar>"}}'>Foo#&lt;bar></a></p>
+<p><a rel="mw:WikiLink" href="./Foo#.3Cbar.3E" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#.3Cbar.3E"},"sa":{"href":"Foo#&lt;bar>"}}'>Foo#&lt;bar></a></p>
 !! end
 
 ###
@@ -8310,6 +8327,23 @@ Interwiki link with fragment (bug 2130)
 </p>
 !! end
 
+!! test
+Link scenarios with escaped fragments
+!! wikitext
+[[#Is this great?]]
+[[Foo#Is this great?]]
+[[meatball:Foo#Is this great?]]
+!! html/php
+<p><a href="#Is_this_great.3F">#Is this great?</a>
+<a href="/wiki/Foo#Is_this_great.3F" title="Foo">Foo#Is this great?</a>
+<a href="http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F" class="extiw" title="meatball:Foo">meatball:Foo#Is this great?</a>
+</p>
+!! html/parsoid
+<p><a rel="mw:WikiLink" href="./Main_Page#Is_this_great.3F" data-parsoid='{"stx":"simple","a":{"href":"./Main_Page#Is_this_great.3F"},"sa":{"href":"#Is this great?"}}'>#Is this great?</a>
+<a rel="mw:WikiLink" href="./Foo#Is_this_great.3F" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo#Is_this_great.3F"},"sa":{"href":"Foo#Is this great?"}}'>Foo#Is this great?</a>
+<a rel="mw:ExtLink" href="http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F" title="meatball:Foo" data-parsoid='{"stx":"simple","a":{"href":"http://www.usemod.com/cgi-bin/mb.pl?Foo#Is_this_great.3F"},"sa":{"href":"meatball:Foo#Is this great?"},"isIW":true}'>meatball:Foo#Is this great?</a></p>
+!! end
+
 # Ideally the wikipedia: prefix here should be proto-relative too
 # [CSA]: this is kind of a bogus test, as the PHP parser test doesn't
 # define the 'en' prefix, and originally the test used 'wikipedia',
@@ -10374,7 +10408,6 @@ language=de
 </p>
 !! end
 
-
 !! test
 Urlencode
 !! wikitext
@@ -10382,7 +10415,7 @@ Urlencode
 {{urlencode:hi world?!|WIKI}}
 {{urlencode:hi world?!|PATH}}
 {{urlencode:hi world?!|QUERY}}
-!! html
+!! html/php
 <p>hi+world%3F%21
 hi_world%3F!
 hi%20world%3F%21
@@ -15929,7 +15962,7 @@ parsoid=wt2html,html2html
 <div id="title.3D">HTML rocks</div>
 
 !! html/parsoid
-<div id="title=" data-parsoid='{"stx":"html"}'>HTML rocks</div>
+<div id="title.3D" data-parsoid='{"stx":"html"}'>HTML rocks</div>
 !! end
 
 !! test
@@ -17107,9 +17140,11 @@ Table not started</td></tr></table>
 Sanitizer: Escaping of spaces, multibyte characters, colons & other stuff in id=""
 !! wikitext
 <span id="æ: v">byte</span>[[#æ: v|backlink]]
-!! html
+!! html/php
 <p><span id=".C3.A6:_v">byte</span><a href="#.C3.A6:_v">backlink</a>
 </p>
+!! html/parsoid
+<p><span id=".C3.A6:_v" data-parsoid='{"stx":"html","a":{"id":".C3.A6:_v"},"sa":{"id":"æ: v"}}'>byte</span><a rel="mw:WikiLink" href="./Main_Page#.C3.A6:_v" data-parsoid='{"stx":"piped","a":{"href":"./Main_Page#.C3.A6:_v"},"sa":{"href":"#æ: v"}}'>backlink</a></p>
 !! end
 
 # In HTML5, the restrictions are that id must contain at least one character,
@@ -18858,8 +18893,14 @@ title=[[Parser test]]
 !! end
 ### Note: Above tests excludes the "{{NUMBEROFADMINS}}" magic word because it generates a MySQL error when included.
 
+## Parsoid thinks the "centre" here is a property, not a caption.
 !! test
 Gallery
+!! options
+parsoid={
+  "modes": ["wt2html"],
+  "nativeGallery": true
+}
 !! wikitext
 <gallery>
 image1.png |
@@ -18871,7 +18912,7 @@ image4    |300px| centre
 [[x|xx]]]]
 * image6
 </gallery>
-!! html
+!! html/php
 <ul class="gallery mw-gallery-traditional">
                <li class="gallerybox" style="width: 155px"><div style="width: 155px">
                        <div class="thumb" style="height: 150px;">Image1.png</div>
@@ -18909,19 +18950,94 @@ image4    |300px| centre
                </div></li>
 </ul>
 
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">image1.png </span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">image2.gif</span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">image3</span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">image4    </span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;"> image5.svg</span></div><div class="gallerytext"> <a rel="mw:ExtLink" href="http://///////">http://///////</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">* image6</span></div><div class="gallerytext"></div></li>
+</ul>
 !! end
 
 !! test
-Gallery (with options)
+Gallery (with options, html)
+!! options
+parsoid={
+  "modes": ["wt2html", "html2html"],
+  "nativeGallery": true
+}
 !! wikitext
-<gallery widths='70px' heights='40px' perrow='2' caption='Foo [[Main Page]]' >
+<gallery widths="70px" heights="40px" perrow="2" caption="Foo [[Main Page]]">
 File:Nonexistent.jpg|caption
 File:Nonexistent.jpg
 image:foobar.jpg|some '''caption''' [[Main Page]]
 image:foobar.jpg
 image:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.
 </gallery>
-!! html
+!! html/php
+<ul class="gallery mw-gallery-traditional" style="max-width: 226px;_width: 226px;">
+       <li class='gallerycaption'>Foo <a href="/wiki/Main_Page" title="Main Page">Main Page</a></li>
+               <li class="gallerybox" style="width: 105px"><div style="width: 105px">
+                       <div class="thumb" style="height: 70px;">Nonexistent.jpg</div>
+                       <div class="gallerytext">
+<p>caption
+</p>
+                       </div>
+               </div></li>
+               <li class="gallerybox" style="width: 105px"><div style="width: 105px">
+                       <div class="thumb" style="height: 70px;">Nonexistent.jpg</div>
+                       <div class="gallerytext">
+                       </div>
+               </div></li>
+               <li class="gallerybox" style="width: 105px"><div style="width: 105px">
+                       <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/105px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/140px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+<p>some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a>
+</p>
+                       </div>
+               </div></li>
+               <li class="gallerybox" style="width: 105px"><div style="width: 105px">
+                       <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/105px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/140px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+                       </div>
+               </div></li>
+               <li class="gallerybox" style="width: 105px"><div style="width: 105px">
+                       <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a foo-bar." src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/105px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/140px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+<p>blabla.
+</p>
+                       </div>
+               </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" style="max-width: 226px; _width: 226px;" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{"widths":"70px","heights":"40px","perrow":"2"},"body":{}}'>
+<li class="gallerycaption">Foo <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext">caption</div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img alt="This is a foo-bar." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">blabla.</div></li>
+</ul>
+!! end
+
+!! test
+Gallery (with options, extsrc)
+!! options
+parsoid={
+  "nativeGallery": false
+}
+!! wikitext
+<gallery widths="70px" heights="40px" perrow="2" caption="Foo [[Main Page]]">
+File:Nonexistent.jpg|caption
+File:Nonexistent.jpg
+image:foobar.jpg|some '''caption''' [[Main Page]]
+image:foobar.jpg
+image:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.
+</gallery>
+!! html/php
 <ul class="gallery mw-gallery-traditional" style="max-width: 226px;_width: 226px;">
        <li class='gallerycaption'>Foo <a href="/wiki/Main_Page" title="Main Page">Main Page</a></li>
                <li class="gallerybox" style="width: 105px"><div style="width: 105px">
@@ -18957,17 +19073,31 @@ image:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.
                </div></li>
 </ul>
 
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" style="max-width: 226px; _width: 226px;" typeof="mw:Extension/gallery" about="#mwt3" data-parsoid='{}' data-mw='{"name":"gallery","attrs":{"widths":"70px","heights":"40px","perrow":"2","caption":"Foo [[Main Page]]"},"body":{"extsrc":"\nFile:Nonexistent.jpg|caption\nFile:Nonexistent.jpg\nimage:foobar.jpg|some &#39;&#39;&#39;caption&#39;&#39;&#39; [[Main Page]]\nimage:foobar.jpg\nimage:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla.\n"}}'>
+<li class="gallerycaption">Foo <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext">caption</div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 105px;"><div class="thumb" style="width: 100px; height: 70px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img alt="This is a foo-bar." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="8" width="70"/></a></span></div><div class="gallerytext">blabla.</div></li>
+</ul>
 !! end
 
 !! test
 Gallery with link that has fragment
+!! options
+parsoid={
+  "modes": ["wt2html", "html2html"],
+  "nativeGallery": true
+}
 !! wikitext
 <gallery>
 image:foobar.jpg|link=Main_Page
 image:foobar.jpg|link=Main_Page#section
 image:foobar.jpg|link=Main Page#section|caption
 </gallery>
-!! html
+!! html/php
 <ul class="gallery mw-gallery-traditional">
                <li class="gallerybox" style="width: 155px"><div style="width: 155px">
                        <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/Main_Page"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
@@ -18988,16 +19118,56 @@ image:foobar.jpg|link=Main Page#section|caption
                </div></li>
 </ul>
 
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./Main_Page#section"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./Main_Page#section"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext">caption</div></li>
+</ul>
+!! end
+
+## Whoops, Parsoid shouldn't be parsing templates in the attribute caption!
+!! test
+Gallery with template inside caption
+!! options
+parsoid={
+  "nativeGallery": true
+}
+!! wikitext
+<gallery caption="{{echo|hi}}">
+File:Foobar.jpg|{{echo|ho}}
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+       <li class='gallerycaption'>{{echo|hi}}</li>
+               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
+                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+<p>ho
+</p>
+                       </div>
+               </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt6" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerycaption"><span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"hi"}},"i":0}}]}'>hi</span></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><span about="#mwt5" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"ho"}},"i":0}}]}'>ho</span></div></li>
+</ul>
 !! end
 
 !! test
 Gallery with wikitext inside caption
+!! options
+parsoid={
+  "nativeGallery": true
+}
 !! wikitext
 <gallery>
-File:foobar.jpg|[[File:foobar.jpg|20px|desc|alt=inneralt]]|alt=galleryalt
-File:foobar.jpg|{{Test|unamedParam|alt=param}}|alt=galleryalt
+File:Foobar.jpg|alt=galleryalt|[[File:Foobar.jpg|alt=inneralt|20x20px|desc]]
+File:Foobar.jpg|alt=galleryalt|{{Test|unamedParam|alt=param}}
 </gallery>
-!! html
+!! html/php
 <ul class="gallery mw-gallery-traditional">
                <li class="gallerybox" style="width: 155px"><div style="width: 155px">
                        <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
@@ -19015,18 +19185,27 @@ File:foobar.jpg|{{Test|unamedParam|alt=param}}|alt=galleryalt
                </div></li>
 </ul>
 
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt6" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><span typeof="mw:Image" data-mw='{"caption":"desc"}'><a href="./File:Foobar.jpg"><img alt="inneralt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="2" width="20"/></a></span></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><span about="#mwt4" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"Test","href":"./Template:Test"},"params":{"1":{"wt":"unamedParam"},"alt":{"wt":"param"}},"i":0}}]}'>This is a test template</span></div></li>
+</ul>
 !! end
 
 !! test
-gallery (with showfilename option)
+Gallery (with showfilename option)
+!! options
+parsoid={
+  "nativeGallery": true
+}
 !! wikitext
-<gallery showfilename>
+<gallery showfilename="">
 File:Nonexistent.jpg|caption
 File:Nonexistent.jpg
-image:foobar.jpg|some '''caption''' [[Main Page]]
+File:Foobar.jpg|some '''caption''' [[Main Page]]
 File:Foobar.jpg
 </gallery>
-!! html
+!! html/php
 <ul class="gallery mw-gallery-traditional">
                <li class="gallerybox" style="width: 155px"><div style="width: 155px">
                        <div class="thumb" style="height: 150px;">Nonexistent.jpg</div>
@@ -19060,10 +19239,23 @@ some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a>
                </div></li>
 </ul>
 
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt3" data-mw='{"name":"gallery","attrs":{"showfilename":""},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext"><a href="./File:Nonexistent.jpg" class="galleryfilename galleryfilename-truncate" title="File:Nonexistent.jpg">File:Nonexistent.jpg</a>caption</div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext"><a href="./File:Nonexistent.jpg" class="galleryfilename galleryfilename-truncate" title="File:Nonexistent.jpg">File:Nonexistent.jpg</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><a href="./File:Foobar.jpg" class="galleryfilename galleryfilename-truncate" title="File:Foobar.jpg">File:Foobar.jpg</a>some <b>caption</b> <a rel="mw:WikiLink" href="./Main_Page" title="Main Page">Main Page</a></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"><a href="./File:Foobar.jpg" class="galleryfilename galleryfilename-truncate" title="File:Foobar.jpg">File:Foobar.jpg</a></div></li>
+</ul>
 !! end
 
+## Should Parsoid be preserving these variations?
 !! test
 Gallery (with namespace-less filenames)
+!! options
+parsoid={
+  "modes": ["wt2html", "html2html"],
+  "nativeGallery": true
+}
 !! wikitext
 <gallery>
 File:Nonexistent.jpg
@@ -19071,7 +19263,7 @@ Nonexistent.jpg
 image:foobar.jpg
 foobar.jpg
 </gallery>
-!! html
+!! html/php
 <ul class="gallery mw-gallery-traditional">
                <li class="gallerybox" style="width: 155px"><div style="width: 155px">
                        <div class="thumb" style="height: 150px;">Nonexistent.jpg</div>
@@ -19095,6 +19287,115 @@ foobar.jpg
                </div></li>
 </ul>
 
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">File:Nonexistent.jpg</span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span style="vertical-align: middle; display: inline-block;">Nonexistent.jpg</span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+</ul>
+!! end
+
+!! test
+Gallery override link with WikiLink (T36852)
+!! options
+parsoid={
+  "nativeGallery": true
+}
+!! wikitext
+<gallery>
+File:Foobar.jpg|alt=galleryalt|link=InterWikiLink
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
+                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+                       </div>
+               </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-parsoid='{"dsr":[0,70,2,2]}' data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./InterWikiLink"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+</ul>
+!! end
+
+!! test
+Gallery override link with absolute external link (T36852)
+!! options
+parsoid={
+  "nativeGallery": true
+}
+!! wikitext
+<gallery>
+File:Foobar.jpg|alt=galleryalt|link=http://www.example.org
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
+                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+                       </div>
+               </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="http://www.example.org"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+</ul>
+!! end
+
+!! test
+Gallery override link with malicious javascript (T36852)
+!! options
+parsoid={
+  "modes": ["wt2html", "html2html"],
+  "nativeGallery": true
+}
+!! wikitext
+<gallery>
+File:Foobar.jpg|alt=galleryalt|link=" onclick="alert('malicious javascript code!');
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
+                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/%22_onclick%3D%22alert(%27malicious_javascript_code!%27);"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+                       </div>
+               </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./&quot;_onclick=&quot;alert('malicious_javascript_code!');"><img alt="galleryalt" resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext"></div></li>
+</ul>
+!! end
+
+!! test
+Gallery with invalid title as link (T45964)
+!! options
+parsoid={
+  "modes": ["wt2html", "html2html"],
+  "nativeGallery": true
+}
+!! wikitext
+<gallery>
+File:Foobar.jpg|link=<
+</gallery>
+!! html/php
+<ul class="gallery mw-gallery-traditional">
+               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
+                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
+                       <div class="gallerytext">
+                       </div>
+               </div></li>
+</ul>
+
+!! html/parsoid
+<ul class="gallery mw-gallery-traditional" typeof="mw:Extension/gallery" about="#mwt2" data-mw='{"name":"gallery","attrs":{},"body":{}}'>
+<li class="gallerybox" style="width: 155px;"><div class="thumb" style="width: 150px; height: 150px;"><span style="display: inline-block; height: 100%; vertical-align: middle;"></span><span typeof="mw:Image" style="vertical-align: middle; display: inline-block;"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="14" width="120"/></a></span></div><div class="gallerytext">link=&lt;</div></li>
+</ul>
 !! end
 
 !! test
@@ -20954,7 +21255,7 @@ Edit comment with link with more than one pipe (T99346)
 comment
 !! wikitext
 [[Main Page|Many|pipes]]
-!! html
+!! html/php
 <a href="/wiki/Main_Page" title="Main Page">Many|pipes</a>
 !! end
 
@@ -20964,7 +21265,7 @@ Complex edit comment with link with more than one pipe (T99346)
 comment
 !! wikitext
 Created page with "<noinclude>[[Category:Requests for permissions/Bot|{{subst:#titleparts:{{subst:PAGENAME}}|1|3}}]]</noinclude> === [[User:MineoBot|]] 8=== {{Request for permissions/links|Mineo..."
-!! html
+!! html/php
 Created page with &quot;&lt;noinclude&gt;<a href="/index.php?title=Category:Requests_for_permissions/Bot&amp;action=edit&amp;redlink=1" class="new" title="Category:Requests for permissions/Bot (page does not exist)">{{subst:#titleparts:{{subst:PAGENAME}}|1|3}}</a>&lt;/noinclude&gt; === <a href="/index.php?title=User:MineoBot&amp;action=edit&amp;redlink=1" class="new" title="User:MineoBot (page does not exist)">User:MineoBot</a> 8=== {{Request for permissions/links|Mineo...&quot;
 !! end
 
@@ -21765,7 +22066,7 @@ Strip marker in urlencode
 {{urlencode:x<nowiki/>y|wiki}}
 {{urlencode:x<nowiki/>y|path}}
 {{urlencode:x<pre id="one">two</pre>y}}
-!! html
+!! html/php
 <p>xy
 xy
 xy
@@ -21903,81 +22204,7 @@ Ignore pipe between table row attributes
 
 !! end
 
-!!test
-Gallery override link with WikiLink (bug 34852)
-!! wikitext
-<gallery>
-File:foobar.jpg|caption|alt=galleryalt|link=InterWikiLink
-</gallery>
-!! html
-<ul class="gallery mw-gallery-traditional">
-               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
-                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
-                       <div class="gallerytext">
-<p>caption
-</p>
-                       </div>
-               </div></li>
-</ul>
-
-!! end
-
-!!test
-Gallery override link with absolute external link (bug 34852)
-!! wikitext
-<gallery>
-File:foobar.jpg|caption|alt=galleryalt|link=http://www.example.org
-</gallery>
-!! html
-<ul class="gallery mw-gallery-traditional">
-               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
-                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
-                       <div class="gallerytext">
-<p>caption
-</p>
-                       </div>
-               </div></li>
-</ul>
-
-!! end
-
-!!test
-Gallery override link with malicious javascript (bug 34852)
-!! wikitext
-<gallery>
-File:foobar.jpg|caption|alt=galleryalt|link=" onclick="alert('malicious javascript code!');
-</gallery>
-!! html
-<ul class="gallery mw-gallery-traditional">
-               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
-                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/%22_onclick%3D%22alert(%27malicious_javascript_code!%27);"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
-                       <div class="gallerytext">
-<p>caption
-</p>
-                       </div>
-               </div></li>
-</ul>
-
-!! end
-
-!!test
-Gallery with invalid title as link (bug 43964)
-!! wikitext
-<gallery>
-File:foobar.jpg|link=<
-</gallery>
-!! html
-<ul class="gallery mw-gallery-traditional">
-               <li class="gallerybox" style="width: 155px"><div style="width: 155px">
-                       <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/240px-Foobar.jpg 2x" /></a></div></div>
-                       <div class="gallerytext">
-                       </div>
-               </div></li>
-</ul>
-
-!! end
-
-!!test
+!! test
 Language parser function
 !! wikitext
 {{#language:ar}}
@@ -22394,19 +22621,20 @@ Ref: 13. ref-tags are not SOL-transparent and block indent-pres
 </ol>
 !!end
 
-!!test
+## Roundtripping fails because of nowiki'ing
+!! test
 Ref: 14. A nested ref-tag should be emitted as plain text
+!! options
+parsoid=wt2html
 !! wikitext
 <ref>foo <ref>bar</ref> baz</ref>
 
 <references />
 !! html/parsoid
-<p><span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="#cite_note-1"><span class="mw-reflink-text">[1]</span></a></span>
-</p>
-<ol class="mw-references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'>
-<li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text">foo &lt;ref>bar&lt;/ref> baz</span></li>
-</ol>
-!!end
+<p><span about="#mwt2" class="mw-ref" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span> baz&lt;/ref></p>
+
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo &lt;ref>bar</span></li></ol>
+!! end
 
 !!test
 Ref: 15. ref-tags with identical names should get identical indexes
@@ -22725,24 +22953,6 @@ foo
 foo<ol class="mw-references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'></ol>
 !! end
 
-#### ----------------------------------------------------------------
-#### Parsoid-only testing of Parsoid's impl of LST
-#### Not implemented yet, see
-#### https://www.mediawiki.org/wiki/Parsoid/HTML_based_LST
-#### ----------------------------------------------------------------
-
-!! test
-LST Sections: 1. Simple section start and end
-!! options
-parsoid={ "suppressErrors": true }
-!! wikitext
-<section begin="2011-05-16" />
-<section end="2014-04-10 (MW 1.23wmf22)" />
-!! html/parsoid
-<p><meta typeof="mw:Extension/LabeledSectionTransclusion/begin" content="2011-05-16"/>
-<meta typeof="mw:Extension/LabeledSectionTransclusion/end" content="2014-04-10 (MW 1.23wmf22)"/></p>
-!! end
-
 #--------- Test stripping of empty nodes in template content ----------
 !!test
 Empty LI and TR nodes should be stripped from template content
@@ -26395,6 +26605,93 @@ parsoid=html2wt,wt2wt
 </table>
 !! end
 
+!! test
+T149209: WTS: Handle newlines in table cells properly
+!! options
+parsoid=html2wt
+!! html/parsoid
+<table>
+<tbody>
+<tr><td>a
+b
+</td><td data-parsoid='{"stx_v":"row"}'>c</td></tr>
+<tr><td><p>x</p>
+</td><td data-parsoid='{"stx_v":"row", "startTagSrc": "{{!}}{{!}}"}'>y</td></tr>
+</tbody></table>
+<table>
+<tbody>
+<tr><th>a
+b
+</th><th data-parsoid='{"stx_v":"row"}'>c</th></tr>
+<tr><th><p>x</h>
+</th><th data-parsoid='{"stx_v":"row"}'>y</th></tr>
+</tbody></table>
+!! wikitext
+{|
+|a
+b
+|c
+|-
+|x
+{{!}}y
+|}
+{|
+!a
+b
+!c
+|-
+!x
+!y
+|}
+!! end
+
+!! test
+T149209: Selser: Handle newlines in table cells properly
+!! options
+parsoid={
+  "modes": ["selser"],
+  "changes": [
+    [ "#h1", "html", "a\nb\n" ],
+    [ "#h2", "html", "a\nb\n" ],
+    [ "#c1", "html", "a\nb\n" ],
+    [ "#c2", "html", "<p>a</p>" ],
+    [ "#c3", "html", "<p>a</p>" ]
+  ]
+}
+!! wikitext
+{|
+! id="h1" |edit-me!!1
+|-
+! id="h2" |edit-me||2
+|-
+| id="c1" |edit-me||3
+|-
+| id="c2" |edit-me||4
+|-
+| id="c3" |edit-me||p||q||r
+|}
+!! wikitext/edited
+{|
+! id="h1" |a
+b
+!1
+|-
+! id="h2" |a
+b
+!2
+|-
+| id="c1" |a
+b
+|3
+|-
+| id="c2" |a
+|4
+|-
+| id="c3" |a
+|p||q||r
+|}
+!! end
+
 !! test
 HTML id attribute with Parsoid-like element ids should not be serialized to wikitext
 !! options
diff --git a/tests/phan/bin/phan b/tests/phan/bin/phan
new file mode 100755 (executable)
index 0000000..bed8c69
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# Note that this isn't loaded in via composer because then composer can
+# only be run with php7.0
+if [ ! -f "$PHAN" ]; then
+       echo "The environment variable PHAN must point to the 'phan' file"
+       echo "in a checkout of https://github.com/etsy/phan.git"
+       exit 1
+fi
+
+cd "$(dirname "$0")"
+
+# Root directory of project
+export ROOT="$(git rev-parse --show-toplevel)"
+
+# Phan's issues directory
+export ISSUES="${ROOT}/tests/phan/issues"
+
+# Go to the root of this git repo
+cd "$ROOT"
+
+# Get the current hash of HEAD
+export REV="$(git rev-parse HEAD)"
+
+# Destination for issues found
+export RUN="${ISSUES}/issues-${REV}"
+
+# Run the analysis, emitting output to the
+# issues file.
+php7.0 $PHAN \
+       --project-root-directory "$ROOT" \
+       --config-file "$ROOT/tests/phan/config.php" \
+       --output "$RUN" \
+       "${@}"
+
+
+cat "${RUN}" | php "$ROOT/tests/phan/bin/postprocess-phan.php" "${@}" > /tmp/phan.$$
+EXIT_CODE="$?"
+mv /tmp/phan.$$ "${RUN}"
+
+# Re-link the latest file
+rm -f "${ISSUES}/latest"
+ln -s "${RUN}" "${ISSUES}/latest"
+
+# Output any issues that were found
+cat "${RUN}"
+
+exit $EXIT_CODE
diff --git a/tests/phan/bin/postprocess-phan.php b/tests/phan/bin/postprocess-phan.php
new file mode 100644 (file)
index 0000000..3e80598
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+abstract class Suppressor {
+       /**
+        * @param string $input
+        * @return bool do errors remain
+        */
+       abstract public function suppress( $input );
+
+       /**
+        * @param string[] $source
+        * @param string $type
+        * @param int $lineno
+        * @return bool
+        */
+       protected function isSuppressed( array $source, $type, $lineno ) {
+               return $lineno > 0 && preg_match(
+                       "|/\*\* @suppress {$type} |",
+                       $source[$lineno - 1]
+               );
+       }
+}
+
+class TextSuppressor extends Suppressor {
+       /**
+        * @param string $input
+        * @return bool do errors remain
+        */
+       public function suppress( $input ) {
+               $hasErrors = false;
+               $errors = [];
+               foreach ( explode( "\n", $input ) as $error ) {
+                       if ( empty( $error ) ) {
+                               continue;
+                       }
+                       if ( !preg_match( '/^(.*):(\d+) (Phan\w+) (.*)$/', $error, $matches ) ) {
+                               echo "Failed to parse line: $error\n";
+                               continue;
+                       }
+                       list( $source, $file, $lineno, $type, $message ) = $matches;
+                       $errors[$file][] = [
+                               'orig' => $error,
+                               // convert from 1 indexed to 0 indexed
+                               'lineno' => $lineno - 1,
+                               'type' => $type,
+                       ];
+               }
+               foreach ( $errors  as $file => $fileErrors ) {
+                       $source = file( $file );
+                       foreach ( $fileErrors as $error ) {
+                               if ( !$this->isSuppressed( $source, $error['type'], $error['lineno'] ) ) {
+                                       echo $error['orig'], "\n";
+                                       $hasErrors = true;
+                               }
+                       }
+               }
+
+               return $hasErrors;
+       }
+}
+
+class CheckStyleSuppressor extends Suppressor {
+       /**
+        * @param string $input
+        * @return bool True do errors remain
+        */
+       public function suppress( $input ) {
+               $dom = new DOMDocument();
+               $dom->loadXML( $input );
+               $hasErrors = false;
+               // DOMNodeList's are "live", convert to an array so it works as expected
+               $files = [];
+               foreach ( $dom->getElementsByTagName( 'file' ) as $file ) {
+                       $files[] = $file;
+               }
+               foreach ( $files as $file ) {
+                       $errors = [];
+                       foreach ( $file->getElementsByTagName( 'error' ) as $error ) {
+                               $errors[] = $error;
+                       }
+                       $source = file( $file->getAttribute( 'name' ) );
+                       $fileHasErrors = false;
+                       foreach ( $errors as $error ) {
+                               $lineno = $error->getAttribute( 'line' ) - 1;
+                               $type = $error->getAttribute( 'source' );
+                               if ( $this->isSuppressed( $source, $type, $lineno ) ) {
+                                       $error->parentNode->removeChild( $error );
+                               } else {
+                                       $fileHasErrors = true;
+                                       $hasErrors = true;
+                               }
+                       }
+                       if ( !$fileHasErrors ) {
+                               $file->parentNode->removeChild( $file );
+                       }
+               }
+               echo $dom->saveXML();
+
+               return $hasErrors;
+       }
+}
+
+class NoopSuppressor extends Suppressor {
+       private $mode;
+
+       public function __construct( $mode ) {
+               $this->mode = $mode;
+       }
+       public function suppress( $input ) {
+               echo "Unsupported output mode: {$this->mode}\n$input";
+               return true;
+       }
+}
+
+$opt = getopt( "m:", [ "output-mode:" ] );
+// if provided multiple times getopt returns an array
+if ( isset( $opt['m'] ) ) {
+       $mode = $opt['m'];
+} elseif ( isset( $mode['output-mode'] ) ) {
+       $mode = $opt['output-mode'];
+} else {
+       $mode = 'text';
+}
+if ( is_array( $mode ) ) {
+       // If an option is passed multiple times getopt returns an
+       // array. Just take the last one.
+       $mode = end( $mode );
+}
+
+switch ( $mode ) {
+case 'text':
+       $suppressor = new TextSuppressor();
+       break;
+case 'checkstyle':
+       $suppressor = new CheckStyleSuppressor();
+       break;
+default:
+       $suppressor = new NoopSuppressor( $mode );
+}
+
+$input = file_get_contents( 'php://stdin' );
+$hasErrors = $suppressor->suppress( $input );
+
+if ( $hasErrors ) {
+       exit( 1 );
+}
diff --git a/tests/phan/config.php b/tests/phan/config.php
new file mode 100644 (file)
index 0000000..7fc069d
--- /dev/null
@@ -0,0 +1,470 @@
+<?php
+
+use \Phan\Config;
+
+// If xdebug is enabled, we need to increase the nesting level for phan
+ini_set( 'xdebug.max_nesting_level', 1000 );
+
+/**
+ * This configuration will be read and overlayed on top of the
+ * default configuration. Command line arguments will be applied
+ * after this file is read.
+ *
+ * @see src/Phan/Config.php
+ * See Config for all configurable options.
+ *
+ * A Note About Paths
+ * ==================
+ *
+ * Files referenced from this file should be defined as
+ *
+ * ```
+ *   Config::projectPath('relative_path/to/file')
+ * ```
+ *
+ * where the relative path is relative to the root of the
+ * project which is defined as either the working directory
+ * of the phan executable or a path passed in via the CLI
+ * '-d' flag.
+ */
+return [
+       /**
+        * A list of individual files to include in analysis
+        * with a path relative to the root directory of the
+        * project. directory_list won't find .inc files so
+        * we augment it here.
+        */
+       'file_list' => [
+               'maintenance/7zip.inc',
+               'maintenance/backupPrefetch.inc',
+               'maintenance/commandLine.inc',
+               'maintenance/sqlite.inc',
+               'maintenance/userOptions.inc',
+               'maintenance/backup.inc',
+               'maintenance/cleanupTable.inc',
+               'maintenance/importImages.inc',
+               'maintenance/userDupes.inc',
+               'maintenance/language/checkLanguage.inc',
+               'maintenance/language/languages.inc',
+       ],
+
+       /**
+        * A list of directories that should be parsed for class and
+        * method information. After excluding the directories
+        * defined in exclude_analysis_directory_list, the remaining
+        * files will be statically analyzed for errors.
+        *
+        * Thus, both first-party and third-party code being used by
+        * your application should be included in this list.
+        */
+       'directory_list' => [
+               'includes/',
+               'languages/',
+               'maintenance/',
+               'mw-config/',
+               'resources/',
+               'skins/',
+               'vendor/',
+               'tests/phan/stubs/',
+       ],
+
+       /**
+        * A file list that defines files that will be excluded
+        * from parsing and analysis and will not be read at all.
+        *
+        * This is useful for excluding hopelessly unanalyzable
+        * files that can't be removed for whatever reason.
+        */
+       'exclude_file_list' => function_exists( 'xcache_get' ) ? [] : [
+               // References xcache which probably isn't installed
+               'includes/libs/objectcache/XCacheBagOStuff.php'
+       ],
+
+       /**
+        * A list of directories holding code that we want
+        * to parse, but not analyze. Also works for individual
+        * files.
+        */
+       "exclude_analysis_directory_list" => [
+               'vendor/',
+               'tests/phan/stubs/',
+               // The referenced classes are not available in vendor, only when
+               // included from composer.
+               'includes/composer/',
+               // Directly references classes that only exist in Translate extension
+               'maintenance/language/',
+               // External class
+               'includes/libs/jsminplus.php',
+               // separate repositories
+               'skins/'
+       ],
+
+       /**
+        * Backwards Compatibility Checking. This is slow
+        * and expensive, but you should consider running
+        * it before upgrading your version of PHP to a
+        * new version that has backward compatibility
+        * breaks.
+        */
+       'backward_compatibility_checks' => false,
+
+       /**
+        * A set of fully qualified class-names for which
+        * a call to parent::__construct() is required
+        */
+       'parent_constructor_required' => [
+       ],
+
+       /**
+        * Run a quick version of checks that takes less
+        * time at the cost of not running as thorough
+        * an analysis. You should consider setting this
+        * to true only when you wish you had more issues
+        * to fix in your code base.
+        *
+        * In quick-mode the scanner doesn't rescan a function
+        * or a method's code block every time a call is seen.
+        * This means that the problem here won't be detected:
+        *
+        * ```php
+        * <?php
+        * function test($arg):int {
+        *      return $arg;
+        * }
+        * test("abc");
+        * ```
+        *
+        * This would normally generate:
+        *
+        * ```sh
+        * test.php:3 TypeError return string but `test()` is declared to return int
+        * ```
+        *
+        * The initial scan of the function's code block has no
+        * type information for `$arg`. It isn't until we see
+        * the call and rescan test()'s code block that we can
+        * detect that it is actually returning the passed in
+        * `string` instead of an `int` as declared.
+        */
+       'quick_mode' => false,
+
+       /**
+        * By default, Phan will not analyze all node types
+        * in order to save time. If this config is set to true,
+        * Phan will dig deeper into the AST tree and do an
+        * analysis on all nodes, possibly finding more issues.
+        *
+        * See \Phan\Analysis::shouldVisit for the set of skipped
+        * nodes.
+        */
+       'should_visit_all_nodes' => true,
+
+       /**
+        * If enabled, check all methods that override a
+        * parent method to make sure its signature is
+        * compatible with the parent's. This check
+        * can add quite a bit of time to the analysis.
+        */
+       'analyze_signature_compatibility' => true,
+
+       // Emit all issues. They are then supressed via
+       // suppress_issue_types, rather than a minimum
+       // severity.
+       "minimum_severity" => 0,
+
+       /**
+        * If true, missing properties will be created when
+        * they are first seen. If false, we'll report an
+        * error message if there is an attempt to write
+        * to a class property that wasn't explicitly
+        * defined.
+        */
+       'allow_missing_properties' => false,
+
+       /**
+        * Allow null to be cast as any type and for any
+        * type to be cast to null. Setting this to false
+        * will cut down on false positives.
+        */
+       'null_casts_as_any_type' => true,
+
+       /**
+        * If enabled, scalars (int, float, bool, string, null)
+        * are treated as if they can cast to each other.
+        *
+        * MediaWiki is pretty lax and uses many scalar
+        * types interchangably.
+        */
+       'scalar_implicit_cast' => true,
+
+       /**
+        * If true, seemingly undeclared variables in the global
+        * scope will be ignored. This is useful for projects
+        * with complicated cross-file globals that you have no
+        * hope of fixing.
+        */
+       'ignore_undeclared_variables_in_global_scope' => false,
+
+       /**
+        * Set to true in order to attempt to detect dead
+        * (unreferenced) code. Keep in mind that the
+        * results will only be a guess given that classes,
+        * properties, constants and methods can be referenced
+        * as variables (like `$class->$property` or
+        * `$class->$method()`) in ways that we're unable
+        * to make sense of.
+        */
+       'dead_code_detection' => false,
+
+       /**
+        * If true, the dead code detection rig will
+        * prefer false negatives (not report dead code) to
+        * false positives (report dead code that is not
+        * actually dead) which is to say that the graph of
+        * references will create too many edges rather than
+        * too few edges when guesses have to be made about
+        * what references what.
+        */
+       'dead_code_detection_prefer_false_negative' => true,
+
+       /**
+        * If disabled, Phan will not read docblock type
+        * annotation comments (such as for @return, @param,
+        * @var, @suppress, @deprecated) and only rely on
+        * types expressed in code.
+        */
+       'read_type_annotations' => true,
+
+       /**
+        * If a file path is given, the code base will be
+        * read from and written to the given location in
+        * order to attempt to save some work from being
+        * done. Only changed files will get analyzed if
+        * the file is read
+        */
+       'stored_state_file_path' => null,
+
+       /**
+        * Set to true in order to ignore issue suppression.
+        * This is useful for testing the state of your code, but
+        * unlikely to be useful outside of that.
+        */
+       'disable_suppression' => false,
+
+       /**
+        * If set to true, we'll dump the AST instead of
+        * analyzing files
+        */
+       'dump_ast' => false,
+
+       /**
+        * If set to a string, we'll dump the fully qualified lowercase
+        * function and method signatures instead of analyzing files.
+        */
+       'dump_signatures_file' => null,
+
+       /**
+        * If true (and if stored_state_file_path is set) we'll
+        * look at the list of files passed in and expand the list
+        * to include files that depend on the given files
+        */
+       'expand_file_list' => false,
+
+       // Include a progress bar in the output
+       'progress_bar' => false,
+
+       /**
+        * The probability of actually emitting any progress
+        * bar update. Setting this to something very low
+        * is good for reducing network IO and filling up
+        * your terminal's buffer when running phan on a
+        * remote host.
+        */
+       'progress_bar_sample_rate' => 0.005,
+
+       /**
+        * The number of processes to fork off during the analysis
+        * phase.
+        */
+       'processes' => 1,
+
+       /**
+        * Add any issue types (such as 'PhanUndeclaredMethod')
+        * to this black-list to inhibit them from being reported.
+        */
+       'suppress_issue_types' => [
+               // approximate error count: 8
+               "PhanDeprecatedClass",
+               // approximate error count: 441
+               "PhanDeprecatedFunction",
+               // approximate error count: 24
+               "PhanDeprecatedProperty",
+               // approximate error count: 12
+               "PhanParamReqAfterOpt",
+               // approximate error count: 748
+               "PhanParamSignatureMismatch",
+               // approximate error count: 7
+               "PhanParamSignatureMismatchInternal",
+               // approximate error count: 1
+               "PhanParamSpecial1",
+               // approximate error count: 2
+               "PhanParamTooFew",
+               // approximate error count: 308
+               "PhanParamTooMany",
+               // approximate error count: 3
+               "PhanParamTooManyInternal",
+               // approximate error count: 1
+               "PhanRedefineFunction",
+               // approximate error count: 1
+               "PhanRedefineFunctionInternal",
+               // approximate error count: 2
+               "PhanTraitParentReference",
+               // approximate error count: 4
+               "PhanTypeArraySuspicious",
+               // approximate error count: 4
+               "PhanTypeComparisonFromArray",
+               // approximate error count: 3
+               "PhanTypeInvalidRightOperand",
+               // approximate error count: 563
+               "PhanTypeMismatchArgument",
+               // approximate error count: 39
+               "PhanTypeMismatchArgumentInternal",
+               // approximate error count: 4
+               "PhanTypeMismatchDefault",
+               // approximate error count: 16
+               "PhanTypeMismatchForeach",
+               // approximate error count: 63
+               "PhanTypeMismatchProperty",
+               // approximate error count: 95
+               "PhanTypeMismatchReturn",
+               // approximate error count: 16
+               "PhanTypeMissingReturn",
+               // approximate error count: 5
+               "PhanTypeNonVarPassByRef",
+               // approximate error count: 27
+               "PhanUndeclaredConstant",
+               // approximate error count: 185
+               "PhanUndeclaredMethod",
+               // approximate error count: 1342
+               "PhanUndeclaredProperty",
+               // approximate error count: 9
+               "PhanUndeclaredStaticMethod",
+               // approximate error count: 79
+               "PhanUndeclaredVariable",
+       ],
+
+       /**
+        * If empty, no filter against issues types will be applied.
+        * If this white-list is non-empty, only issues within the list
+        * will be emitted by Phan.
+        */
+       'whitelist_issue_types' => [
+               // 'PhanAccessMethodPrivate',
+               // 'PhanAccessMethodProtected',
+               // 'PhanAccessNonStaticToStatic',
+               // 'PhanAccessPropertyPrivate',
+               // 'PhanAccessPropertyProtected',
+               // 'PhanAccessSignatureMismatch',
+               // 'PhanAccessSignatureMismatchInternal',
+               // 'PhanAccessStaticToNonStatic',
+               // 'PhanCompatibleExpressionPHP7',
+               // 'PhanCompatiblePHP7',
+               // 'PhanContextNotObject',
+               // 'PhanDeprecatedClass',
+               // 'PhanDeprecatedFunction',
+               // 'PhanDeprecatedProperty',
+               // 'PhanEmptyFile',
+               // 'PhanNonClassMethodCall',
+               // 'PhanNoopArray',
+               // 'PhanNoopClosure',
+               // 'PhanNoopConstant',
+               // 'PhanNoopProperty',
+               // 'PhanNoopVariable',
+               // 'PhanParamRedefined',
+               // 'PhanParamReqAfterOpt',
+               // 'PhanParamSignatureMismatch',
+               // 'PhanParamSignatureMismatchInternal',
+               // 'PhanParamSpecial1',
+               // 'PhanParamSpecial2',
+               // 'PhanParamSpecial3',
+               // 'PhanParamSpecial4',
+               // 'PhanParamTooFew',
+               // 'PhanParamTooFewInternal',
+               // 'PhanParamTooMany',
+               // 'PhanParamTooManyInternal',
+               // 'PhanParamTypeMismatch',
+               // 'PhanParentlessClass',
+               // 'PhanRedefineClass',
+               // 'PhanRedefineClassInternal',
+               // 'PhanRedefineFunction',
+               // 'PhanRedefineFunctionInternal',
+               // 'PhanStaticCallToNonStatic',
+               // 'PhanSyntaxError',
+               // 'PhanTraitParentReference',
+               // 'PhanTypeArrayOperator',
+               // 'PhanTypeArraySuspicious',
+               // 'PhanTypeComparisonFromArray',
+               // 'PhanTypeComparisonToArray',
+               // 'PhanTypeConversionFromArray',
+               // 'PhanTypeInstantiateAbstract',
+               // 'PhanTypeInstantiateInterface',
+               // 'PhanTypeInvalidLeftOperand',
+               // 'PhanTypeInvalidRightOperand',
+               // 'PhanTypeMismatchArgument',
+               // 'PhanTypeMismatchArgumentInternal',
+               // 'PhanTypeMismatchDefault',
+               // 'PhanTypeMismatchForeach',
+               // 'PhanTypeMismatchProperty',
+               // 'PhanTypeMismatchReturn',
+               // 'PhanTypeMissingReturn',
+               // 'PhanTypeNonVarPassByRef',
+               // 'PhanTypeParentConstructorCalled',
+               // 'PhanTypeVoidAssignment',
+               // 'PhanUnanalyzable',
+               // 'PhanUndeclaredClass',
+               // 'PhanUndeclaredClassCatch',
+               // 'PhanUndeclaredClassConstant',
+               // 'PhanUndeclaredClassInstanceof',
+               // 'PhanUndeclaredClassMethod',
+               // 'PhanUndeclaredClassReference',
+               // 'PhanUndeclaredConstant',
+               // 'PhanUndeclaredExtendedClass',
+               // 'PhanUndeclaredFunction',
+               // 'PhanUndeclaredInterface',
+               // 'PhanUndeclaredMethod',
+               // 'PhanUndeclaredProperty',
+               // 'PhanUndeclaredStaticMethod',
+               // 'PhanUndeclaredStaticProperty',
+               // 'PhanUndeclaredTrait',
+               // 'PhanUndeclaredTypeParameter',
+               // 'PhanUndeclaredTypeProperty',
+               // 'PhanUndeclaredVariable',
+               // 'PhanUnreferencedClass',
+               // 'PhanUnreferencedConstant',
+               // 'PhanUnreferencedMethod',
+               // 'PhanUnreferencedProperty',
+               // 'PhanVariableUseClause',
+       ],
+
+       /**
+        * Override to hardcode existence and types of (non-builtin) globals in the global scope.
+        * Class names must be prefixed with '\\'.
+        * (E.g. ['_FOO' => '\\FooClass', 'page' => '\\PageClass', 'userId' => 'int'])
+        */
+       'globals_type_map' => [
+               'IP' => 'string',
+       ],
+
+       // Emit issue messages with markdown formatting
+       'markdown_issue_messages' => false,
+
+       /**
+        * Enable or disable support for generic templated
+        * class types.
+        */
+       'generic_types_enabled' => true,
+
+       // A list of plugin files to execute
+       'plugins' => [
+       ],
+];
diff --git a/tests/phan/issues/.gitkeep b/tests/phan/issues/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/phan/stubs/README b/tests/phan/stubs/README
new file mode 100644 (file)
index 0000000..c458ab5
--- /dev/null
@@ -0,0 +1,3 @@
+These stubs describe how code that is not available at analysis time should be
+used. No implementations are necessary, just define the classes and their
+methods and use phpdoc to describe what arguments are allowed.
diff --git a/tests/phan/stubs/hhvm.php b/tests/phan/stubs/hhvm.php
new file mode 100644 (file)
index 0000000..8ffcdfb
--- /dev/null
@@ -0,0 +1,27 @@
+<?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
+ */
+
+// @codingStandardsIgnoreFile
+
+/**
+ * @param callable $callback
+ * @param mixed ...$parameters
+ */
+function register_postsend_function( $callback ) {
+}
+
diff --git a/tests/phan/stubs/mail.php b/tests/phan/stubs/mail.php
new file mode 100644 (file)
index 0000000..e906cdb
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+// @codingStandardsIgnoreFile
+
+/**
+ * Minimal set of classes necessary for UserMailer to be happy. Types
+ * taken from documentation at pear.php.net.
+ */
+class PEAR {
+       /**
+        * @param mixed $data
+        * @return bool
+        */
+       public static function isError( $data ) {
+       }
+}
+
+class PEAR_Error {
+       /**
+        * @return string
+        */
+       public function getMessage() {
+       }
+}
+
+class Mail {
+       /**
+        * @param string $driver
+        * @param array $params
+        * @return self
+        */
+       static public function factory( $driver, array $params = [] ) {
+       }
+
+       /**
+        * @param mixed $recipients
+        * @param array $headers
+        * @param string $body
+        * @return bool|PEAR_Error
+        */
+       public function send( $recipients, array $headers, $body ) {
+       }
+}
+
+class Mail_smtp extends Mail {
+}
+
+class Mail_mime {
+       /**
+        * @param mixed $params
+        */
+       public function __construct( $params = [] ) {
+       }
+
+       /**
+        * @param string $data
+        * @param bool $isfile
+        * @param bool $append
+        * @return bool|PEAR_Error
+        */
+       public function setTXTBody( $data, $isfile = false, $append = false ) {
+       }
+
+       /**
+        * @param string $data
+        * @param bool $isfile
+        * @return bool|PEAR_Error
+        */
+       public function setHTMLBody( $data, $isfile = false ) {
+       }
+
+       /**
+        * @param array|null $parms
+        * @param mixed $filename
+        * @param bool $skip_head
+        * @return string|bool|PEAR_Error
+        */
+       public function get( $params = null, $filename = null, $skip_head = false ) {
+       }
+
+       /**
+        * @param array|null $xtra_headers
+        * @param bool $overwrite
+        * @param bool $skip_content
+        * @return array
+        */
+       public function headers( array $xtra_headers = null, $overwrite = false, $skip_content = false ) {
+       }
+}
diff --git a/tests/phan/stubs/wikidiff.php b/tests/phan/stubs/wikidiff.php
new file mode 100644 (file)
index 0000000..9bd5d8d
--- /dev/null
@@ -0,0 +1,28 @@
+<?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
+ */
+
+// @codingStandardsIgnoreFile
+
+/**
+ * @param string $text1
+ * @param string $text2
+ * @param int $numContextLines
+ * @return string
+ */
+function wikidiff2_do_diff( $text1, $text2, $numContextLines ) {
+}
index db1df5c..9745f5b 100644 (file)
@@ -582,6 +582,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
        /**
         * Make sure MediaWikiTestCase extending classes have called their
         * parent setUp method
+        *
+        * With strict coverage activated in PHP_CodeCoverage, this test would be
+        * marked as risky without the following annotation (T152923).
+        * @coversNothing
         */
        final public function testMediaWikiTestCaseParentSetupCalled() {
                $this->assertArrayHasKey( 'setUp', $this->called,
index baa481e..45cfdbf 100644 (file)
@@ -11,14 +11,23 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
        const BLANK_VERSION = '09p30q0';
 
        /**
-        * @param string $lang
-        * @param string $dir
+        * @param array|string $options Language code or options array
+        * - string 'lang' Language code
+        * - string 'dir' Language direction (ltr or rtl)
         * @return ResourceLoaderContext
         */
-       protected function getResourceLoaderContext( $lang = 'en', $dir = 'ltr' ) {
+       protected function getResourceLoaderContext( $options = [] ) {
+               if ( is_string( $options ) ) {
+                       // Back-compat for extension tests
+                       $options = [ 'lang' => $options ];
+               }
+               $options += [
+                       'lang' => 'en',
+                       'dir' => 'ltr',
+               ];
                $resourceLoader = new ResourceLoader();
                $request = new FauxRequest( [
-                               'lang' => $lang,
+                               'lang' => $options['lang'],
                                'modules' => 'startup',
                                'only' => 'scripts',
                                'skin' => 'vector',
@@ -28,7 +37,7 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
                        ->setConstructorArgs( [ $resourceLoader, $request ] )
                        ->setMethods( [ 'getDirection' ] )
                        ->getMock();
-               $ctx->method( 'getDirection' )->willReturn( $dir );
+               $ctx->method( 'getDirection' )->willReturn( $options['dir'] );
                return $ctx;
        }
 
diff --git a/tests/phpunit/data/localisationcache/ba.json b/tests/phpunit/data/localisationcache/ba.json
new file mode 100644 (file)
index 0000000..59b8912
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "present-ba": "ba"
+}
index 27600cd..39cce86 100644 (file)
@@ -1,5 +1,5 @@
 {
-       "present-uk": "en",
+       "present-ba": "en",
        "present-ru": "en",
        "present-en": "en"
 }
index 79e1444..c9f89d3 100644 (file)
@@ -1,4 +1,4 @@
 {
-       "present-uk": "ru",
+       "present-ba": "ru",
        "present-ru": "ru"
 }
diff --git a/tests/phpunit/data/localisationcache/uk.json b/tests/phpunit/data/localisationcache/uk.json
deleted file mode 100644 (file)
index f63ce5d..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-       "present-uk": "uk"
-}
index dcaf1f7..0c853e0 100644 (file)
@@ -23,8 +23,6 @@ class FormOptionsExposed extends FormOptions {
  * Test class for FormOptions initialization
  * Ensure the FormOptions::add() does what we want it to do.
  *
- * Generated by PHPUnit on 2011-02-28 at 20:46:27.
- *
  * Copyright © 2011, Antoine Musso
  *
  * @author Antoine Musso
index e491d61..2ee8b98 100644 (file)
@@ -10,7 +10,6 @@
 
 /**
  * Test class for FormOptions methods.
- * Generated by PHPUnit on 2011-02-28 at 20:46:27.
  *
  * Copyright © 2011, Antoine Musso
  *
index cbe2e2f..09c1040 100644 (file)
@@ -28,7 +28,6 @@ class WfUrlencodeTest extends MediaWikiTestCase {
        /**
         * Internal helper that actually run the test.
         * Called by the public methods testEncodingUrlWith...()
-        *
         */
        private function verifyEncodingFor( $server, $input, $expectations ) {
                $expected = $this->extractExpect( $server, $expectations );
diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php
deleted file mode 100644 (file)
index c3804c6..0000000
+++ /dev/null
@@ -1,534 +0,0 @@
-<?php
-
-/**
- * @group Http
- */
-class HttpTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider cookieDomains
-        * @covers Cookie::validateCookieDomain
-        */
-       public function testValidateCookieDomain( $expected, $domain, $origin = null ) {
-               if ( $origin ) {
-                       $ok = Cookie::validateCookieDomain( $domain, $origin );
-                       $msg = "$domain against origin $origin";
-               } else {
-                       $ok = Cookie::validateCookieDomain( $domain );
-                       $msg = "$domain";
-               }
-               $this->assertEquals( $expected, $ok, $msg );
-       }
-
-       public static function cookieDomains() {
-               return [
-                       [ false, "org" ],
-                       [ false, ".org" ],
-                       [ true, "wikipedia.org" ],
-                       [ true, ".wikipedia.org" ],
-                       [ false, "co.uk" ],
-                       [ false, ".co.uk" ],
-                       [ false, "gov.uk" ],
-                       [ false, ".gov.uk" ],
-                       [ true, "supermarket.uk" ],
-                       [ false, "uk" ],
-                       [ false, ".uk" ],
-                       [ false, "127.0.0." ],
-                       [ false, "127." ],
-                       [ false, "127.0.0.1." ],
-                       [ true, "127.0.0.1" ],
-                       [ false, "333.0.0.1" ],
-                       [ true, "example.com" ],
-                       [ false, "example.com." ],
-                       [ true, ".example.com" ],
-
-                       [ true, ".example.com", "www.example.com" ],
-                       [ false, "example.com", "www.example.com" ],
-                       [ true, "127.0.0.1", "127.0.0.1" ],
-                       [ false, "127.0.0.1", "localhost" ],
-               ];
-       }
-
-       /**
-        * Test Http::isValidURI()
-        * @bug 27854 : Http::isValidURI is too lax
-        * @dataProvider provideURI
-        * @covers Http::isValidURI
-        */
-       public function testIsValidUri( $expect, $URI, $message = '' ) {
-               $this->assertEquals(
-                       $expect,
-                       (bool)Http::isValidURI( $URI ),
-                       $message
-               );
-       }
-
-       /**
-        * @covers Http::getProxy
-        */
-       public function testGetProxy() {
-               $this->setMwGlobals( 'wgHTTPProxy', 'proxy.domain.tld' );
-               $this->assertEquals(
-                       'proxy.domain.tld',
-                       Http::getProxy()
-               );
-       }
-
-       /**
-        * Feeds URI to test a long regular expression in Http::isValidURI
-        */
-       public static function provideURI() {
-               /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
-               return [
-                       [ false, '¿non sens before!! http://a', 'Allow anything before URI' ],
-
-                       # (http|https) - only two schemes allowed
-                       [ true, 'http://www.example.org/' ],
-                       [ true, 'https://www.example.org/' ],
-                       [ true, 'http://www.example.org', 'URI without directory' ],
-                       [ true, 'http://a', 'Short name' ],
-                       [ true, 'http://étoile', 'Allow UTF-8 in hostname' ], # 'étoile' is french for 'star'
-                       [ false, '\\host\directory', 'CIFS share' ],
-                       [ false, 'gopher://host/dir', 'Reject gopher scheme' ],
-                       [ false, 'telnet://host', 'Reject telnet scheme' ],
-
-                       # :\/\/ - double slashes
-                       [ false, 'http//example.org', 'Reject missing colon in protocol' ],
-                       [ false, 'http:/example.org', 'Reject missing slash in protocol' ],
-                       [ false, 'http:example.org', 'Must have two slashes' ],
-                       # Following fail since hostname can be made of anything
-                       [ false, 'http:///example.org', 'Must have exactly two slashes, not three' ],
-
-                       # (\w+:{0,1}\w*@)? - optional user:pass
-                       [ true, 'http://user@host', 'Username provided' ],
-                       [ true, 'http://user:@host', 'Username provided, no password' ],
-                       [ true, 'http://user:pass@host', 'Username and password provided' ],
-
-                       # (\S+) - host part is made of anything not whitespaces
-                       // commented these out in order to remove @group Broken
-                       // @todo are these valid tests? if so, fix Http::isValidURI so it can handle them
-                       // [ false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ],
-                       // [ false, 'http://exam:ple.org/', 'hostname can not use colons!' ],
-
-                       # (:[0-9]+)? - port number
-                       [ true, 'http://example.org:80/' ],
-                       [ true, 'https://example.org:80/' ],
-                       [ true, 'http://example.org:443/' ],
-                       [ true, 'https://example.org:443/' ],
-
-                       # Part after the hostname is / or / with something else
-                       [ true, 'http://example/#' ],
-                       [ true, 'http://example/!' ],
-                       [ true, 'http://example/:' ],
-                       [ true, 'http://example/.' ],
-                       [ true, 'http://example/?' ],
-                       [ true, 'http://example/+' ],
-                       [ true, 'http://example/=' ],
-                       [ true, 'http://example/&' ],
-                       [ true, 'http://example/%' ],
-                       [ true, 'http://example/@' ],
-                       [ true, 'http://example/-' ],
-                       [ true, 'http://example//' ],
-                       [ true, 'http://example/&' ],
-
-                       # Fragment
-                       [ true, 'http://exam#ple.org', ], # This one is valid, really!
-                       [ true, 'http://example.org:80#anchor' ],
-                       [ true, 'http://example.org/?id#anchor' ],
-                       [ true, 'http://example.org/?#anchor' ],
-
-                       [ false, 'http://a ¿non !!sens after', 'Allow anything after URI' ],
-               ];
-       }
-
-       /**
-        * Warning:
-        *
-        * These tests are for code that makes use of an artifact of how CURL
-        * handles header reporting on redirect pages, and will need to be
-        * rewritten when bug 29232 is taken care of (high-level handling of
-        * HTTP redirects).
-        */
-       public function testRelativeRedirections() {
-               $h = MWHttpRequestTester::factory( 'http://oldsite/file.ext', [], __METHOD__ );
-
-               # Forge a Location header
-               $h->setRespHeaders( 'location', [
-                               'http://newsite/file.ext',
-                               '/newfile.ext',
-                       ]
-               );
-               # Verify we correctly fix the Location
-               $this->assertEquals(
-                       'http://newsite/newfile.ext',
-                       $h->getFinalUrl(),
-                       "Relative file path Location: interpreted as full URL"
-               );
-
-               $h->setRespHeaders( 'location', [
-                               'https://oldsite/file.ext'
-                       ]
-               );
-               $this->assertEquals(
-                       'https://oldsite/file.ext',
-                       $h->getFinalUrl(),
-                       "Location to the HTTPS version of the site"
-               );
-
-               $h->setRespHeaders( 'location', [
-                               '/anotherfile.ext',
-                               'http://anotherfile/hoster.ext',
-                               'https://anotherfile/hoster.ext'
-                       ]
-               );
-               $this->assertEquals(
-                       'https://anotherfile/hoster.ext',
-                       $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!" )
-               );
-       }
-
-       /**
-        * Constant values are from PHP 5.3.28 using cURL 7.24.0
-        * @see https://secure.php.net/manual/en/curl.constants.php
-        *
-        * All constant values are present so that developers don’t need to remember
-        * to add them if added at a later date. The commented out constants were
-        * not found anywhere in the MediaWiki core code.
-        *
-        * Commented out constants that were not available in:
-        * HipHop VM 3.3.0 (rel)
-        * Compiler: heads/master-0-g08810d920dfff59e0774cf2d651f92f13a637175
-        * Repo schema: 3214fc2c684a4520485f715ee45f33f2182324b1
-        * Extension API: 20140829
-        *
-        * Commented out constants that were removed in PHP 5.6.0
-        *
-        * @covers CurlHttpRequest::execute
-        */
-       public function provideCurlConstants() {
-               return [
-                       [ 'CURLAUTH_ANY' ],
-                       [ 'CURLAUTH_ANYSAFE' ],
-                       [ 'CURLAUTH_BASIC' ],
-                       [ 'CURLAUTH_DIGEST' ],
-                       [ 'CURLAUTH_GSSNEGOTIATE' ],
-                       [ 'CURLAUTH_NTLM' ],
-                       // [ 'CURLCLOSEPOLICY_CALLBACK' ], // removed in PHP 5.6.0
-                       // [ 'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' ], // removed in PHP 5.6.0
-                       // [ 'CURLCLOSEPOLICY_LEAST_TRAFFIC' ], // removed in PHP 5.6.0
-                       // [ 'CURLCLOSEPOLICY_OLDEST' ], // removed in PHP 5.6.0
-                       // [ 'CURLCLOSEPOLICY_SLOWEST' ], // removed in PHP 5.6.0
-                       [ 'CURLE_ABORTED_BY_CALLBACK' ],
-                       [ 'CURLE_BAD_CALLING_ORDER' ],
-                       [ 'CURLE_BAD_CONTENT_ENCODING' ],
-                       [ 'CURLE_BAD_FUNCTION_ARGUMENT' ],
-                       [ 'CURLE_BAD_PASSWORD_ENTERED' ],
-                       [ 'CURLE_COULDNT_CONNECT' ],
-                       [ 'CURLE_COULDNT_RESOLVE_HOST' ],
-                       [ 'CURLE_COULDNT_RESOLVE_PROXY' ],
-                       [ 'CURLE_FAILED_INIT' ],
-                       [ 'CURLE_FILESIZE_EXCEEDED' ],
-                       [ 'CURLE_FILE_COULDNT_READ_FILE' ],
-                       [ 'CURLE_FTP_ACCESS_DENIED' ],
-                       [ 'CURLE_FTP_BAD_DOWNLOAD_RESUME' ],
-                       [ 'CURLE_FTP_CANT_GET_HOST' ],
-                       [ 'CURLE_FTP_CANT_RECONNECT' ],
-                       [ 'CURLE_FTP_COULDNT_GET_SIZE' ],
-                       [ 'CURLE_FTP_COULDNT_RETR_FILE' ],
-                       [ 'CURLE_FTP_COULDNT_SET_ASCII' ],
-                       [ 'CURLE_FTP_COULDNT_SET_BINARY' ],
-                       [ 'CURLE_FTP_COULDNT_STOR_FILE' ],
-                       [ 'CURLE_FTP_COULDNT_USE_REST' ],
-                       [ 'CURLE_FTP_PORT_FAILED' ],
-                       [ 'CURLE_FTP_QUOTE_ERROR' ],
-                       [ 'CURLE_FTP_SSL_FAILED' ],
-                       [ 'CURLE_FTP_USER_PASSWORD_INCORRECT' ],
-                       [ 'CURLE_FTP_WEIRD_227_FORMAT' ],
-                       [ 'CURLE_FTP_WEIRD_PASS_REPLY' ],
-                       [ 'CURLE_FTP_WEIRD_PASV_REPLY' ],
-                       [ 'CURLE_FTP_WEIRD_SERVER_REPLY' ],
-                       [ 'CURLE_FTP_WEIRD_USER_REPLY' ],
-                       [ 'CURLE_FTP_WRITE_ERROR' ],
-                       [ 'CURLE_FUNCTION_NOT_FOUND' ],
-                       [ 'CURLE_GOT_NOTHING' ],
-                       [ 'CURLE_HTTP_NOT_FOUND' ],
-                       [ 'CURLE_HTTP_PORT_FAILED' ],
-                       [ 'CURLE_HTTP_POST_ERROR' ],
-                       [ 'CURLE_HTTP_RANGE_ERROR' ],
-                       [ 'CURLE_LDAP_CANNOT_BIND' ],
-                       [ 'CURLE_LDAP_INVALID_URL' ],
-                       [ 'CURLE_LDAP_SEARCH_FAILED' ],
-                       [ 'CURLE_LIBRARY_NOT_FOUND' ],
-                       [ 'CURLE_MALFORMAT_USER' ],
-                       [ 'CURLE_OBSOLETE' ],
-                       [ 'CURLE_OK' ],
-                       [ 'CURLE_OPERATION_TIMEOUTED' ],
-                       [ 'CURLE_OUT_OF_MEMORY' ],
-                       [ 'CURLE_PARTIAL_FILE' ],
-                       [ 'CURLE_READ_ERROR' ],
-                       [ 'CURLE_RECV_ERROR' ],
-                       [ 'CURLE_SEND_ERROR' ],
-                       [ 'CURLE_SHARE_IN_USE' ],
-                       // [ 'CURLE_SSH' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLE_SSL_CACERT' ],
-                       [ 'CURLE_SSL_CERTPROBLEM' ],
-                       [ 'CURLE_SSL_CIPHER' ],
-                       [ 'CURLE_SSL_CONNECT_ERROR' ],
-                       [ 'CURLE_SSL_ENGINE_NOTFOUND' ],
-                       [ 'CURLE_SSL_ENGINE_SETFAILED' ],
-                       [ 'CURLE_SSL_PEER_CERTIFICATE' ],
-                       [ 'CURLE_TELNET_OPTION_SYNTAX' ],
-                       [ 'CURLE_TOO_MANY_REDIRECTS' ],
-                       [ 'CURLE_UNKNOWN_TELNET_OPTION' ],
-                       [ 'CURLE_UNSUPPORTED_PROTOCOL' ],
-                       [ 'CURLE_URL_MALFORMAT' ],
-                       [ 'CURLE_URL_MALFORMAT_USER' ],
-                       [ 'CURLE_WRITE_ERROR' ],
-                       [ 'CURLFTPAUTH_DEFAULT' ],
-                       [ 'CURLFTPAUTH_SSL' ],
-                       [ 'CURLFTPAUTH_TLS' ],
-                       // [ 'CURLFTPMETHOD_MULTICWD' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLFTPMETHOD_NOCWD' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLFTPMETHOD_SINGLECWD' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLFTPSSL_ALL' ],
-                       [ 'CURLFTPSSL_CONTROL' ],
-                       [ 'CURLFTPSSL_NONE' ],
-                       [ 'CURLFTPSSL_TRY' ],
-                       // [ 'CURLINFO_CERTINFO' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLINFO_CONNECT_TIME' ],
-                       [ 'CURLINFO_CONTENT_LENGTH_DOWNLOAD' ],
-                       [ 'CURLINFO_CONTENT_LENGTH_UPLOAD' ],
-                       [ 'CURLINFO_CONTENT_TYPE' ],
-                       [ 'CURLINFO_EFFECTIVE_URL' ],
-                       [ 'CURLINFO_FILETIME' ],
-                       [ 'CURLINFO_HEADER_OUT' ],
-                       [ 'CURLINFO_HEADER_SIZE' ],
-                       [ 'CURLINFO_HTTP_CODE' ],
-                       [ 'CURLINFO_NAMELOOKUP_TIME' ],
-                       [ 'CURLINFO_PRETRANSFER_TIME' ],
-                       [ 'CURLINFO_PRIVATE' ],
-                       [ 'CURLINFO_REDIRECT_COUNT' ],
-                       [ 'CURLINFO_REDIRECT_TIME' ],
-                       // [ 'CURLINFO_REDIRECT_URL' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLINFO_REQUEST_SIZE' ],
-                       [ 'CURLINFO_SIZE_DOWNLOAD' ],
-                       [ 'CURLINFO_SIZE_UPLOAD' ],
-                       [ 'CURLINFO_SPEED_DOWNLOAD' ],
-                       [ 'CURLINFO_SPEED_UPLOAD' ],
-                       [ 'CURLINFO_SSL_VERIFYRESULT' ],
-                       [ 'CURLINFO_STARTTRANSFER_TIME' ],
-                       [ 'CURLINFO_TOTAL_TIME' ],
-                       [ 'CURLMSG_DONE' ],
-                       [ 'CURLM_BAD_EASY_HANDLE' ],
-                       [ 'CURLM_BAD_HANDLE' ],
-                       [ 'CURLM_CALL_MULTI_PERFORM' ],
-                       [ 'CURLM_INTERNAL_ERROR' ],
-                       [ 'CURLM_OK' ],
-                       [ 'CURLM_OUT_OF_MEMORY' ],
-                       [ 'CURLOPT_AUTOREFERER' ],
-                       [ 'CURLOPT_BINARYTRANSFER' ],
-                       [ 'CURLOPT_BUFFERSIZE' ],
-                       [ 'CURLOPT_CAINFO' ],
-                       [ 'CURLOPT_CAPATH' ],
-                       // [ 'CURLOPT_CERTINFO' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLOPT_CLOSEPOLICY' ], // removed in PHP 5.6.0
-                       [ 'CURLOPT_CONNECTTIMEOUT' ],
-                       [ 'CURLOPT_CONNECTTIMEOUT_MS' ],
-                       [ 'CURLOPT_COOKIE' ],
-                       [ 'CURLOPT_COOKIEFILE' ],
-                       [ 'CURLOPT_COOKIEJAR' ],
-                       [ 'CURLOPT_COOKIESESSION' ],
-                       [ 'CURLOPT_CRLF' ],
-                       [ 'CURLOPT_CUSTOMREQUEST' ],
-                       [ 'CURLOPT_DNS_CACHE_TIMEOUT' ],
-                       [ 'CURLOPT_DNS_USE_GLOBAL_CACHE' ],
-                       [ 'CURLOPT_EGDSOCKET' ],
-                       [ 'CURLOPT_ENCODING' ],
-                       [ 'CURLOPT_FAILONERROR' ],
-                       [ 'CURLOPT_FILE' ],
-                       [ 'CURLOPT_FILETIME' ],
-                       [ 'CURLOPT_FOLLOWLOCATION' ],
-                       [ 'CURLOPT_FORBID_REUSE' ],
-                       [ 'CURLOPT_FRESH_CONNECT' ],
-                       [ 'CURLOPT_FTPAPPEND' ],
-                       [ 'CURLOPT_FTPLISTONLY' ],
-                       [ 'CURLOPT_FTPPORT' ],
-                       [ 'CURLOPT_FTPSSLAUTH' ],
-                       [ 'CURLOPT_FTP_CREATE_MISSING_DIRS' ],
-                       // [ 'CURLOPT_FTP_FILEMETHOD' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLOPT_FTP_SKIP_PASV_IP' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLOPT_FTP_SSL' ],
-                       [ 'CURLOPT_FTP_USE_EPRT' ],
-                       [ 'CURLOPT_FTP_USE_EPSV' ],
-                       [ 'CURLOPT_HEADER' ],
-                       [ 'CURLOPT_HEADERFUNCTION' ],
-                       [ 'CURLOPT_HTTP200ALIASES' ],
-                       [ 'CURLOPT_HTTPAUTH' ],
-                       [ 'CURLOPT_HTTPGET' ],
-                       [ 'CURLOPT_HTTPHEADER' ],
-                       [ 'CURLOPT_HTTPPROXYTUNNEL' ],
-                       [ 'CURLOPT_HTTP_VERSION' ],
-                       [ 'CURLOPT_INFILE' ],
-                       [ 'CURLOPT_INFILESIZE' ],
-                       [ 'CURLOPT_INTERFACE' ],
-                       [ 'CURLOPT_IPRESOLVE' ],
-                       // [ 'CURLOPT_KEYPASSWD' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLOPT_KRB4LEVEL' ],
-                       [ 'CURLOPT_LOW_SPEED_LIMIT' ],
-                       [ 'CURLOPT_LOW_SPEED_TIME' ],
-                       [ 'CURLOPT_MAXCONNECTS' ],
-                       [ 'CURLOPT_MAXREDIRS' ],
-                       // [ 'CURLOPT_MAX_RECV_SPEED_LARGE' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLOPT_MAX_SEND_SPEED_LARGE' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLOPT_NETRC' ],
-                       [ 'CURLOPT_NOBODY' ],
-                       [ 'CURLOPT_NOPROGRESS' ],
-                       [ 'CURLOPT_NOSIGNAL' ],
-                       [ 'CURLOPT_PORT' ],
-                       [ 'CURLOPT_POST' ],
-                       [ 'CURLOPT_POSTFIELDS' ],
-                       [ 'CURLOPT_POSTQUOTE' ],
-                       [ 'CURLOPT_POSTREDIR' ],
-                       [ 'CURLOPT_PRIVATE' ],
-                       [ 'CURLOPT_PROGRESSFUNCTION' ],
-                       // [ 'CURLOPT_PROTOCOLS' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLOPT_PROXY' ],
-                       [ 'CURLOPT_PROXYAUTH' ],
-                       [ 'CURLOPT_PROXYPORT' ],
-                       [ 'CURLOPT_PROXYTYPE' ],
-                       [ 'CURLOPT_PROXYUSERPWD' ],
-                       [ 'CURLOPT_PUT' ],
-                       [ 'CURLOPT_QUOTE' ],
-                       [ 'CURLOPT_RANDOM_FILE' ],
-                       [ 'CURLOPT_RANGE' ],
-                       [ 'CURLOPT_READDATA' ],
-                       [ 'CURLOPT_READFUNCTION' ],
-                       // [ 'CURLOPT_REDIR_PROTOCOLS' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLOPT_REFERER' ],
-                       [ 'CURLOPT_RESUME_FROM' ],
-                       [ 'CURLOPT_RETURNTRANSFER' ],
-                       // [ 'CURLOPT_SSH_AUTH_TYPES' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLOPT_SSH_PRIVATE_KEYFILE' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLOPT_SSH_PUBLIC_KEYFILE' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLOPT_SSLCERT' ],
-                       [ 'CURLOPT_SSLCERTPASSWD' ],
-                       [ 'CURLOPT_SSLCERTTYPE' ],
-                       [ 'CURLOPT_SSLENGINE' ],
-                       [ 'CURLOPT_SSLENGINE_DEFAULT' ],
-                       [ 'CURLOPT_SSLKEY' ],
-                       [ 'CURLOPT_SSLKEYPASSWD' ],
-                       [ 'CURLOPT_SSLKEYTYPE' ],
-                       [ 'CURLOPT_SSLVERSION' ],
-                       [ 'CURLOPT_SSL_CIPHER_LIST' ],
-                       [ 'CURLOPT_SSL_VERIFYHOST' ],
-                       [ 'CURLOPT_SSL_VERIFYPEER' ],
-                       [ 'CURLOPT_STDERR' ],
-                       [ 'CURLOPT_TCP_NODELAY' ],
-                       [ 'CURLOPT_TIMECONDITION' ],
-                       [ 'CURLOPT_TIMEOUT' ],
-                       [ 'CURLOPT_TIMEOUT_MS' ],
-                       [ 'CURLOPT_TIMEVALUE' ],
-                       [ 'CURLOPT_TRANSFERTEXT' ],
-                       [ 'CURLOPT_UNRESTRICTED_AUTH' ],
-                       [ 'CURLOPT_UPLOAD' ],
-                       [ 'CURLOPT_URL' ],
-                       [ 'CURLOPT_USERAGENT' ],
-                       [ 'CURLOPT_USERPWD' ],
-                       [ 'CURLOPT_VERBOSE' ],
-                       [ 'CURLOPT_WRITEFUNCTION' ],
-                       [ 'CURLOPT_WRITEHEADER' ],
-                       // [ 'CURLPROTO_ALL' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_DICT' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_FILE' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_FTP' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_FTPS' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_HTTP' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_HTTPS' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_LDAP' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_LDAPS' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_SCP' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_SFTP' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_TELNET' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLPROTO_TFTP' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLPROXY_HTTP' ],
-                       // [ 'CURLPROXY_SOCKS4' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLPROXY_SOCKS5' ],
-                       // [ 'CURLSSH_AUTH_DEFAULT' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLSSH_AUTH_HOST' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLSSH_AUTH_KEYBOARD' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLSSH_AUTH_NONE' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLSSH_AUTH_PASSWORD' ], // not present in HHVM 3.3.0-dev
-                       // [ 'CURLSSH_AUTH_PUBLICKEY' ], // not present in HHVM 3.3.0-dev
-                       [ 'CURLVERSION_NOW' ],
-                       [ 'CURL_HTTP_VERSION_1_0' ],
-                       [ 'CURL_HTTP_VERSION_1_1' ],
-                       [ 'CURL_HTTP_VERSION_NONE' ],
-                       [ 'CURL_IPRESOLVE_V4' ],
-                       [ 'CURL_IPRESOLVE_V6' ],
-                       [ 'CURL_IPRESOLVE_WHATEVER' ],
-                       [ 'CURL_NETRC_IGNORED' ],
-                       [ 'CURL_NETRC_OPTIONAL' ],
-                       [ 'CURL_NETRC_REQUIRED' ],
-                       [ 'CURL_TIMECOND_IFMODSINCE' ],
-                       [ 'CURL_TIMECOND_IFUNMODSINCE' ],
-                       [ 'CURL_TIMECOND_LASTMOD' ],
-                       [ 'CURL_VERSION_IPV6' ],
-                       [ 'CURL_VERSION_KERBEROS4' ],
-                       [ 'CURL_VERSION_LIBZ' ],
-                       [ 'CURL_VERSION_SSL' ],
-               ];
-       }
-
-       /**
-        * Added this test based on an issue experienced with HHVM 3.3.0-dev
-        * where it did not define a cURL constant.
-        *
-        * @bug 70570
-        * @dataProvider provideCurlConstants
-        */
-       public function testCurlConstants( $value ) {
-               $this->assertTrue( defined( $value ), $value . ' not defined' );
-       }
-}
-
-/**
- * Class to let us overwrite MWHttpRequest respHeaders variable
- */
-class MWHttpRequestTester extends MWHttpRequest {
-       // function derived from the MWHttpRequest factory function but
-       // returns appropriate tester class here
-       public static function factory( $url, $options = null, $caller = __METHOD__ ) {
-               if ( !Http::$httpEngine ) {
-                       Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
-               } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
-                               'Http::$httpEngine is set to "curl"' );
-               }
-
-               switch ( Http::$httpEngine ) {
-                       case 'curl':
-                               return new CurlHttpRequestTester( $url, $options, $caller );
-                       case 'php':
-                               if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
-                                       throw new MWException( __METHOD__ .
-                                               ': allow_url_fopen needs to be enabled for pure PHP HTTP requests to work. '
-                                                       . 'If possible, curl should be used instead. See http://php.net/curl.' );
-                               }
-
-                               return new PhpHttpRequestTester( $url, $options, $caller );
-                       default:
-               }
-       }
-}
-
-class CurlHttpRequestTester extends CurlHttpRequest {
-       function setRespHeaders( $name, $value ) {
-               $this->respHeaders[$name] = $value;
-       }
-}
-
-class PhpHttpRequestTester extends PhpHttpRequest {
-       function setRespHeaders( $name, $value ) {
-               $this->respHeaders[$name] = $value;
-       }
-}
index 24db445..092d57b 100644 (file)
@@ -7,10 +7,8 @@
 
 /**
  * Test class for MWNamespace.
- * Generated by PHPUnit on 2011-02-20 at 21:01:55.
  * @todo covers tags
  * @todo FIXME: this test file is a mess
- *
  */
 class MWNamespaceTest extends MediaWikiTestCase {
        protected function setUp() {
index e8afb4c..2d45c2e 100644 (file)
@@ -51,6 +51,10 @@ class MessageTest extends MediaWikiLangTestCase {
                                [],
                                [],
                        ],
+                       [
+                               [],
+                               [ [] ],
+                       ],
                        [
                                [ 'foo' ],
                                [ 'foo' ],
@@ -68,19 +72,37 @@ class MessageTest extends MediaWikiLangTestCase {
                                [ [ 'baz', 'foo' ] ],
                        ],
                        [
-                               [ 'baz', 'foo' ],
+                               [ Message::rawParam( 'baz' ) ],
+                               [ Message::rawParam( 'baz' ) ],
+                       ],
+                       [
+                               [ Message::rawParam( 'baz' ), 'foo' ],
+                               [ Message::rawParam( 'baz' ), 'foo' ],
+                       ],
+                       [
+                               [ Message::rawParam( 'baz' ) ],
+                               [ [ Message::rawParam( 'baz' ) ] ],
+                       ],
+                       [
+                               [ Message::rawParam( 'baz' ), 'foo' ],
+                               [ [ Message::rawParam( 'baz' ), 'foo' ] ],
+                       ],
+
+                       // Test handling of erroneous input, to detect if it changes
+                       [
+                               [ [ 'baz', 'foo' ], 'hhh' ],
                                [ [ 'baz', 'foo' ], 'hhh' ],
                        ],
                        [
-                               [ 'baz', 'foo' ],
+                               [ [ 'baz', 'foo' ], 'hhh', [ 'ahahahahha' ] ],
                                [ [ 'baz', 'foo' ], 'hhh', [ 'ahahahahha' ] ],
                        ],
                        [
-                               [ 'baz', 'foo' ],
+                               [ [ 'baz', 'foo' ], [ 'ahahahahha' ] ],
                                [ [ 'baz', 'foo' ], [ 'ahahahahha' ] ],
                        ],
                        [
-                               [ 'baz' ],
+                               [ [ 'baz' ], [ 'ahahahahha' ] ],
                                [ [ 'baz' ], [ 'ahahahahha' ] ],
                        ],
                ];
@@ -512,6 +534,108 @@ class MessageTest extends MediaWikiLangTestCase {
                );
        }
 
+       public static function provideListParam() {
+               $lang = Language::factory( 'de' );
+               $msg1 = new Message( 'mainpage', [], $lang );
+               $msg2 = new RawMessage( "''link''", [], $lang );
+
+               return [
+                       'Simple comma list' => [
+                               [ 'a', 'b', 'c' ],
+                               'comma',
+                               'text',
+                               'a, b, c'
+                       ],
+
+                       'Simple semicolon list' => [
+                               [ 'a', 'b', 'c' ],
+                               'semicolon',
+                               'text',
+                               'a; b; c'
+                       ],
+
+                       'Simple pipe list' => [
+                               [ 'a', 'b', 'c' ],
+                               'pipe',
+                               'text',
+                               'a | b | c'
+                       ],
+
+                       'Simple text list' => [
+                               [ 'a', 'b', 'c' ],
+                               'text',
+                               'text',
+                               'a, b and c'
+                       ],
+
+                       'Empty list' => [
+                               [],
+                               'comma',
+                               'text',
+                               ''
+                       ],
+
+                       'List with all "before" params, ->text()' => [
+                               [ "''link''", Message::numParam( 12345678 ) ],
+                               'semicolon',
+                               'text',
+                               '\'\'link\'\'; 12,345,678'
+                       ],
+
+                       'List with all "before" params, ->parse()' => [
+                               [ "''link''", Message::numParam( 12345678 ) ],
+                               'semicolon',
+                               'parse',
+                               '<i>link</i>; 12,345,678'
+                       ],
+
+                       'List with all "after" params, ->text()' => [
+                               [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
+                               'semicolon',
+                               'text',
+                               'Main Page; \'\'link\'\'; [[foo]]'
+                       ],
+
+                       'List with all "after" params, ->parse()' => [
+                               [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
+                               'semicolon',
+                               'parse',
+                               'Main Page; <i>link</i>; [[foo]]'
+                       ],
+
+                       'List with both "before" and "after" params, ->text()' => [
+                               [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
+                               'semicolon',
+                               'text',
+                               'Main Page; \'\'link\'\'; [[foo]]; \'\'link\'\'; 12,345,678'
+                       ],
+
+                       'List with both "before" and "after" params, ->parse()' => [
+                               [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
+                               'semicolon',
+                               'parse',
+                               'Main Page; <i>link</i>; [[foo]]; <i>link</i>; 12,345,678'
+                       ],
+               ];
+       }
+
+       /**
+        * @covers Message::listParam
+        * @covers Message::extractParam
+        * @covers Message::formatListParam
+        * @dataProvider provideListParam
+        */
+       public function testListParam( $list, $type, $format, $expect ) {
+               $lang = Language::factory( 'en' );
+
+               $msg = new RawMessage( '$1' );
+               $msg->params( [ Message::listParam( $list, $type ) ] );
+               $this->assertEquals(
+                       $expect,
+                       $msg->inLanguage( $lang )->$format()
+               );
+       }
+
        /**
         * @covers Message::extractParam
         */
index 6269872..c3faf0d 100644 (file)
@@ -7,7 +7,6 @@
  * @group Output
  *
  * @todo factor tests in this class into providers and test methods
- *
  */
 class OutputPageTest extends MediaWikiTestCase {
        const SCREEN_MEDIA_QUERY = 'screen and (min-width: 982px)';
index bc43709..c5a7e04 100644 (file)
@@ -129,11 +129,11 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                                'results' => [
                                        'Special:ActiveUsers',
                                        'Special:AllMessages',
-                                       'Special:AllMyFiles',
+                                       'Special:AllMyUploads',
                                ],
                                // Third result when testing offset
                                'offsetresult' => [
-                                       'Special:AllMyUploads',
+                                       'Special:AllPages',
                                ],
                        ] ],
                        [ [
@@ -146,7 +146,7 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                                ],
                                // Third result when testing offset
                                'offsetresult' => [
-                                       'Special:UncategorizedImages',
+                                       'Special:UncategorizedPages',
                                ],
                        ] ],
                        [ [
index 93687df..bdec0a5 100644 (file)
@@ -1233,7 +1233,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
                        ->with( 'watchlisttoken' )
                        ->willReturn( '0123456789abcdef' );
 
-               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+               $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
                $queryService->getWatchedItemsWithRecentChangeInfo(
                        $user,
                        [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => $token ]
index dbd1299..18ff1f4 100644 (file)
@@ -305,17 +305,6 @@ class XmlTest extends MediaWikiTestCase {
                );
        }
 
-       /**
-        * @covers Xml::escapeJsString
-        */
-       public function testEscapeJsStringSpecialChars() {
-               $this->assertEquals(
-                       '\\\\\r\n',
-                       Xml::escapeJsString( "\\\r\n" ),
-                       'escapeJsString() with special characters'
-               );
-       }
-
        /**
         * @covers Xml::encodeJsVar
         */
index 8b75d56..96f3e44 100644 (file)
@@ -20,7 +20,7 @@ class ApiBaseTest extends ApiTestCase {
        }
 
        /**
-        * @expectedException UsageException
+        * @expectedException ApiUsageException
         * @covers ApiBase::requireOnlyOneParameter
         */
        public function testRequireOnlyOneParameterZero() {
@@ -32,7 +32,7 @@ class ApiBaseTest extends ApiTestCase {
        }
 
        /**
-        * @expectedException UsageException
+        * @expectedException ApiUsageException
         * @covers ApiBase::requireOnlyOneParameter
         */
        public function testRequireOnlyOneParameterTrue() {
@@ -58,10 +58,10 @@ class ApiBaseTest extends ApiTestCase {
                $context->setRequest( new FauxRequest( $input !== null ? [ 'foo' => $input ] : [] ) );
                $wrapper->mMainModule = new ApiMain( $context );
 
-               if ( $expected instanceof UsageException ) {
+               if ( $expected instanceof ApiUsageException ) {
                        try {
                                $wrapper->getParameterFromSettings( 'foo', $paramSettings, true );
-                       } catch ( UsageException $ex ) {
+                       } catch ( ApiUsageException $ex ) {
                                $this->assertEquals( $expected, $ex );
                        }
                } else {
@@ -73,9 +73,7 @@ class ApiBaseTest extends ApiTestCase {
 
        public static function provideGetParameterFromSettings() {
                $warnings = [
-                       'The value passed for \'foo\' contains invalid or non-normalized data. Textual data should ' .
-                       'be valid, NFC-normalized Unicode without C0 control characters other than ' .
-                       'HT (\\t), LF (\\n), and CR (\\r).'
+                       [ 'apiwarn-badutf8', 'foo' ],
                ];
 
                $c0 = '';
@@ -96,7 +94,7 @@ class ApiBaseTest extends ApiTestCase {
                        'String param, required, empty' => [
                                '',
                                [ ApiBase::PARAM_DFLT => 'default', ApiBase::PARAM_REQUIRED => true ],
-                               new UsageException( 'The foo parameter must be set', 'nofoo' ),
+                               ApiUsageException::newWithMessage( null, [ 'apierror-missingparam', 'foo' ] ),
                                []
                        ],
                        'Multi-valued parameter' => [
@@ -126,4 +124,48 @@ class ApiBaseTest extends ApiTestCase {
                ];
        }
 
+       public function testErrorArrayToStatus() {
+               $mock = new MockApi();
+
+               // Sanity check empty array
+               $expect = Status::newGood();
+               $this->assertEquals( $expect, $mock->errorArrayToStatus( [] ) );
+
+               // No blocked $user, so no special block handling
+               $expect = Status::newGood();
+               $expect->fatal( 'blockedtext' );
+               $expect->fatal( 'autoblockedtext' );
+               $expect->fatal( 'mainpage' );
+               $expect->fatal( 'parentheses', 'foobar' );
+               $this->assertEquals( $expect, $mock->errorArrayToStatus( [
+                       [ 'blockedtext' ],
+                       [ 'autoblockedtext' ],
+                       'mainpage',
+                       [ 'parentheses', 'foobar' ],
+               ] ) );
+
+               // Has a blocked $user, so special block handling
+               $user = $this->getMutableTestUser()->getUser();
+               $block = new \Block( [
+                       'address' => $user->getName(),
+                       'user' => $user->getID(),
+                       'reason' => __METHOD__,
+                       'expiry' => time() + 100500,
+               ] );
+               $block->insert();
+               $blockinfo = [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ];
+
+               $expect = Status::newGood();
+               $expect->fatal( ApiMessage::create( 'apierror-blocked', 'blocked', $blockinfo ) );
+               $expect->fatal( ApiMessage::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
+               $expect->fatal( 'mainpage' );
+               $expect->fatal( 'parentheses', 'foobar' );
+               $this->assertEquals( $expect, $mock->errorArrayToStatus( [
+                       [ 'blockedtext' ],
+                       [ 'autoblockedtext' ],
+                       'mainpage',
+                       [ 'parentheses', 'foobar' ],
+               ], $user ) );
+       }
+
 }
index d2dccf9..832a113 100644 (file)
@@ -13,6 +13,14 @@ class ApiBlockTest extends ApiTestCase {
                $this->doLogin();
        }
 
+       protected function tearDown() {
+               $block = Block::newFromTarget( 'UTApiBlockee' );
+               if ( !is_null( $block ) ) {
+                       $block->delete();
+               }
+               parent::tearDown();
+       }
+
        protected function getTokens() {
                return $this->getTokenList( self::$users['sysop'] );
        }
@@ -65,8 +73,37 @@ class ApiBlockTest extends ApiTestCase {
        }
 
        /**
-        * @expectedException UsageException
-        * @expectedExceptionMessage The token parameter must be set
+        * Block by user ID
+        */
+       public function testMakeNormalBlockId() {
+               $tokens = $this->getTokens();
+               $user = User::newFromName( 'UTApiBlockee' );
+
+               if ( !$user->getId() ) {
+                       $this->markTestIncomplete( "The user UTApiBlockee does not exist." );
+               }
+
+               if ( !array_key_exists( 'blocktoken', $tokens ) ) {
+                       $this->markTestIncomplete( "No block token found" );
+               }
+
+               $data = $this->doApiRequest( [
+                       'action' => 'block',
+                       'userid' => $user->getId(),
+                       'reason' => 'Some reason',
+                       'token' => $tokens['blocktoken'] ], null, false, self::$users['sysop']->getUser() );
+
+               $block = Block::newFromTarget( 'UTApiBlockee' );
+
+               $this->assertTrue( !is_null( $block ), 'Block is valid.' );
+               $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
+               $this->assertEquals( 'Some reason', $block->mReason );
+               $this->assertEquals( 'infinity', $block->mExpiry );
+       }
+
+       /**
+        * @expectedException ApiUsageException
+        * @expectedExceptionMessage The "token" parameter must be set
         */
        public function testBlockingActionWithNoToken() {
                $this->doApiRequest(
index 3ad16d1..bb4ea75 100644 (file)
@@ -160,10 +160,8 @@ class ApiContinuationManagerTest extends MediaWikiTestCase {
                try {
                        self::getManager( 'foo', $allModules, [ 'mock1', 'mock2' ] );
                        $this->fail( 'Expected exception not thrown' );
-               } catch ( UsageException $ex ) {
-                       $this->assertSame(
-                               'Invalid continue param. You should pass the original value returned by the previous query',
-                               $ex->getMessage(),
+               } catch ( ApiUsageException $ex ) {
+                       $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'badcontinue' ),
                                'Expected exception'
                        );
                }
index 02d0a0d..0ffcbca 100644 (file)
@@ -195,9 +195,9 @@ class ApiEditPageTest extends ApiTestCase {
                                'section' => '9999',
                                'text' => 'text',
                        ] );
-                       $this->fail( "Should have raised a UsageException" );
-               } catch ( UsageException $e ) {
-                       $this->assertEquals( 'nosuchsection', $e->getCodeString() );
+                       $this->fail( "Should have raised an ApiUsageException" );
+               } catch ( ApiUsageException $e ) {
+                       $this->assertTrue( self::apiExceptionHasCode( $e, 'nosuchsection' ) );
                }
        }
 
@@ -333,8 +333,8 @@ class ApiEditPageTest extends ApiTestCase {
                        ], null, self::$users['sysop']->getUser() );
 
                        $this->fail( 'redirect-appendonly error expected' );
-               } catch ( UsageException $ex ) {
-                       $this->assertEquals( 'redirect-appendonly', $ex->getCodeString() );
+               } catch ( ApiUsageException $ex ) {
+                       $this->assertTrue( self::apiExceptionHasCode( $ex, 'redirect-appendonly' ) );
                }
        }
 
@@ -369,8 +369,8 @@ class ApiEditPageTest extends ApiTestCase {
                        ], null, self::$users['sysop']->getUser() );
 
                        $this->fail( 'edit conflict expected' );
-               } catch ( UsageException $ex ) {
-                       $this->assertEquals( 'editconflict', $ex->getCodeString() );
+               } catch ( ApiUsageException $ex ) {
+                       $this->assertTrue( self::apiExceptionHasCode( $ex, 'editconflict' ) );
                }
        }
 
@@ -474,7 +474,7 @@ class ApiEditPageTest extends ApiTestCase {
 
        public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
                $this->setExpectedException(
-                       'UsageException',
+                       'ApiUsageException',
                        'Direct editing via API is not supported for content model ' .
                                'testing used by Dummy:ApiEditPageTest_nonTextPageEdit'
                );
index d13b00b..1b7f6bf 100644 (file)
@@ -5,6 +5,30 @@
  */
 class ApiErrorFormatterTest extends MediaWikiLangTestCase {
 
+       /**
+        * @covers ApiErrorFormatter
+        */
+       public function testErrorFormatterBasics() {
+               $result = new ApiResult( 8388608 );
+               $formatter = new ApiErrorFormatter( $result, Language::factory( 'de' ), 'wikitext', false );
+               $this->assertSame( 'de', $formatter->getLanguage()->getCode() );
+
+               $formatter->addMessagesFromStatus( null, Status::newGood() );
+               $this->assertSame(
+                       [ ApiResult::META_TYPE => 'assoc' ],
+                       $result->getResultData()
+               );
+
+               $this->assertSame( [], $formatter->arrayFromStatus( Status::newGood() ) );
+
+               $wrappedFormatter = TestingAccessWrapper::newFromObject( $formatter );
+               $this->assertSame(
+                       'Blah "kbd" <X> 😊',
+                       $wrappedFormatter->stripMarkup( 'Blah <kbd>kbd</kbd> <b>&lt;X&gt;</b> &#x1f60a;' ),
+                       'stripMarkup'
+               );
+       }
+
        /**
         * @covers ApiErrorFormatter
         * @dataProvider provideErrorFormatter
@@ -22,7 +46,7 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
 
                $formatter->addWarning( 'string', 'mainpage' );
                $formatter->addError( 'err', 'mainpage' );
-               $this->assertSame( $expect1, $result->getResultData(), 'Simple test' );
+               $this->assertEquals( $expect1, $result->getResultData(), 'Simple test' );
 
                $result->reset();
                $formatter->addWarning( 'foo', 'mainpage' );
@@ -35,6 +59,17 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                $formatter->addError( 'errWithData', $msg2 );
                $this->assertSame( $expect2, $result->getResultData(), 'Complex test' );
 
+               $this->assertEquals(
+                       $this->removeModuleTag( $expect2['warnings'][2] ),
+                       $formatter->formatMessage( $msg1 ),
+                       'formatMessage test 1'
+               );
+               $this->assertEquals(
+                       $this->removeModuleTag( $expect2['warnings'][3] ),
+                       $formatter->formatMessage( $msg2 ),
+                       'formatMessage test 2'
+               );
+
                $result->reset();
                $status = Status::newGood();
                $status->warning( 'mainpage' );
@@ -47,245 +82,256 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                $this->assertSame( $expect3, $result->getResultData(), 'Status test' );
 
                $this->assertSame(
-                       $expect3['errors']['status'],
+                       array_map( [ $this, 'removeModuleTag' ], $expect3['errors'] ),
                        $formatter->arrayFromStatus( $status, 'error' ),
                        'arrayFromStatus test for error'
                );
                $this->assertSame(
-                       $expect3['warnings']['status'],
+                       array_map( [ $this, 'removeModuleTag' ], $expect3['warnings'] ),
                        $formatter->arrayFromStatus( $status, 'warning' ),
                        'arrayFromStatus test for warning'
                );
        }
 
+       private function removeModuleTag( $s ) {
+               if ( is_array( $s ) ) {
+                       unset( $s['module'] );
+               }
+               return $s;
+       }
+
        public static function provideErrorFormatter() {
-               $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain();
-               $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain();
-               $mainpageText = wfMessage( 'mainpage' )->inLanguage( 'de' )->text();
-               $parensText = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'de' )->text();
+               $mainpageText = wfMessage( 'mainpage' )->inLanguage( 'de' )->useDatabase( false )->text();
+               $parensText = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'de' )
+                       ->useDatabase( false )->text();
+               $mainpageHTML = wfMessage( 'mainpage' )->inLanguage( 'en' )->parse();
+               $parensHTML = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'en' )->parse();
                $C = ApiResult::META_CONTENT;
                $I = ApiResult::META_INDEXED_TAG_NAME;
+               $overriddenData = [ 'overriddenData' => true, ApiResult::META_TYPE => 'assoc' ];
 
                return [
-                       [ 'wikitext', 'de', true,
+                       $tmp = [ 'wikitext', 'de', false,
                                [
                                        'errors' => [
-                                               'err' => [
-                                                       [ 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ],
-                                                       $I => 'error',
-                                               ],
+                                               [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'err', $C => 'text' ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'string' => [
-                                                       [ 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ],
-                                                       $I => 'warning',
-                                               ],
+                                               [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'string', $C => 'text' ],
+                                               $I => 'warning',
                                        ],
                                ],
                                [
                                        'errors' => [
-                                               'errWithData' => [
-                                                       [ 'code' => 'overriddenCode', 'text' => $mainpageText,
-                                                               'overriddenData' => true, $C => 'text' ],
-                                                       $I => 'error',
-                                               ],
+                                               [ 'code' => 'overriddenCode', 'text' => $mainpageText,
+                                                       'data' => $overriddenData, 'module' => 'errWithData', $C => 'text' ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'messageWithData' => [
-                                                       [ 'code' => 'overriddenCode', 'text' => $mainpageText,
-                                                               'overriddenData' => true, $C => 'text' ],
-                                                       $I => 'warning',
-                                               ],
-                                               'message' => [
-                                                       [ 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ],
-                                                       $I => 'warning',
-                                               ],
-                                               'foo' => [
-                                                       [ 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ],
-                                                       [ 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ],
-                                                       $I => 'warning',
-                                               ],
+                                               [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'foo', $C => 'text' ],
+                                               [ 'code' => 'parentheses', 'text' => $parensText, 'module' => 'foo', $C => 'text' ],
+                                               [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'message', $C => 'text' ],
+                                               [ 'code' => 'overriddenCode', 'text' => $mainpageText,
+                                                       'data' => $overriddenData, 'module' => 'messageWithData', $C => 'text' ],
+                                               $I => 'warning',
                                        ],
                                ],
                                [
                                        'errors' => [
-                                               'status' => [
-                                                       [ 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ],
-                                                       [ 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ],
-                                                       $I => 'error',
-                                               ],
+                                               [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'status', $C => 'text' ],
+                                               [ 'code' => 'parentheses', 'text' => $parensText, 'module' => 'status', $C => 'text' ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'status' => [
-                                                       [ 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ],
-                                                       [ 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ],
-                                                       [ 'code' => 'overriddenCode', 'text' => $mainpageText,
-                                                               'overriddenData' => true, $C => 'text' ],
-                                                       $I => 'warning',
-                                               ],
+                                               [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'status', $C => 'text' ],
+                                               [ 'code' => 'parentheses', 'text' => $parensText, 'module' => 'status', $C => 'text' ],
+                                               [ 'code' => 'overriddenCode', 'text' => $mainpageText,
+                                                       'data' => $overriddenData, 'module' => 'status', $C => 'text' ],
+                                               $I => 'warning',
+                                       ],
+                               ],
+                       ],
+                       [ 'plaintext' ] + $tmp, // For these messages, plaintext and wikitext are the same
+                       [ 'html', 'en', true,
+                               [
+                                       'errors' => [
+                                               [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'err', $C => 'html' ],
+                                               $I => 'error',
+                                       ],
+                                       'warnings' => [
+                                               [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'string', $C => 'html' ],
+                                               $I => 'warning',
+                                       ],
+                               ],
+                               [
+                                       'errors' => [
+                                               [ 'code' => 'overriddenCode', 'html' => $mainpageHTML,
+                                                       'data' => $overriddenData, 'module' => 'errWithData', $C => 'html' ],
+                                               $I => 'error',
+                                       ],
+                                       'warnings' => [
+                                               [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'foo', $C => 'html' ],
+                                               [ 'code' => 'parentheses', 'html' => $parensHTML, 'module' => 'foo', $C => 'html' ],
+                                               [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'message', $C => 'html' ],
+                                               [ 'code' => 'overriddenCode', 'html' => $mainpageHTML,
+                                                       'data' => $overriddenData, 'module' => 'messageWithData', $C => 'html' ],
+                                               $I => 'warning',
+                                       ],
+                               ],
+                               [
+                                       'errors' => [
+                                               [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'status', $C => 'html' ],
+                                               [ 'code' => 'parentheses', 'html' => $parensHTML, 'module' => 'status', $C => 'html' ],
+                                               $I => 'error',
+                                       ],
+                                       'warnings' => [
+                                               [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'status', $C => 'html' ],
+                                               [ 'code' => 'parentheses', 'html' => $parensHTML, 'module' => 'status', $C => 'html' ],
+                                               [ 'code' => 'overriddenCode', 'html' => $mainpageHTML,
+                                                       'data' => $overriddenData, 'module' => 'status', $C => 'html' ],
+                                               $I => 'warning',
                                        ],
                                ],
                        ],
                        [ 'raw', 'fr', true,
                                [
                                        'errors' => [
-                                               'err' => [
-                                                       [
-                                                               'code' => 'mainpage',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ]
-                                                       ],
-                                                       $I => 'error',
+                                               [
+                                                       'code' => 'mainpage',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'module' => 'err',
                                                ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'string' => [
-                                                       [
-                                                               'code' => 'mainpage',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ]
-                                                       ],
-                                                       $I => 'warning',
+                                               [
+                                                       'code' => 'mainpage',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'module' => 'string',
                                                ],
+                                               $I => 'warning',
                                        ],
                                ],
                                [
                                        'errors' => [
-                                               'errWithData' => [
-                                                       [
-                                                               'code' => 'overriddenCode',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ],
-                                                               'overriddenData' => true
-                                                       ],
-                                                       $I => 'error',
+                                               [
+                                                       'code' => 'overriddenCode',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'data' => $overriddenData,
+                                                       'module' => 'errWithData',
                                                ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'messageWithData' => [
-                                                       [
-                                                               'code' => 'overriddenCode',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ],
-                                                               'overriddenData' => true
-                                                       ],
-                                                       $I => 'warning',
+                                               [
+                                                       'code' => 'mainpage',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'module' => 'foo',
+                                               ],
+                                               [
+                                                       'code' => 'parentheses',
+                                                       'key' => 'parentheses',
+                                                       'params' => [ 'foobar', $I => 'param' ],
+                                                       'module' => 'foo',
                                                ],
-                                               'message' => [
-                                                       [
-                                                               'code' => 'mainpage',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ]
-                                                       ],
-                                                       $I => 'warning',
+                                               [
+                                                       'code' => 'mainpage',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'module' => 'message',
                                                ],
-                                               'foo' => [
-                                                       [
-                                                               'code' => 'mainpage',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ]
-                                                       ],
-                                                       [
-                                                               'code' => 'parentheses',
-                                                               'key' => 'parentheses',
-                                                               'params' => [ 'foobar', $I => 'param' ]
-                                                       ],
-                                                       $I => 'warning',
+                                               [
+                                                       'code' => 'overriddenCode',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'data' => $overriddenData,
+                                                       'module' => 'messageWithData',
                                                ],
+                                               $I => 'warning',
                                        ],
                                ],
                                [
                                        'errors' => [
-                                               'status' => [
-                                                       [
-                                                               'code' => 'mainpage',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ]
-                                                       ],
-                                                       [
-                                                               'code' => 'parentheses',
-                                                               'key' => 'parentheses',
-                                                               'params' => [ 'foobar', $I => 'param' ]
-                                                       ],
-                                                       $I => 'error',
+                                               [
+                                                       'code' => 'mainpage',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'module' => 'status',
+                                               ],
+                                               [
+                                                       'code' => 'parentheses',
+                                                       'key' => 'parentheses',
+                                                       'params' => [ 'foobar', $I => 'param' ],
+                                                       'module' => 'status',
                                                ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'status' => [
-                                                       [
-                                                               'code' => 'mainpage',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ]
-                                                       ],
-                                                       [
-                                                               'code' => 'parentheses',
-                                                               'key' => 'parentheses',
-                                                               'params' => [ 'foobar', $I => 'param' ]
-                                                       ],
-                                                       [
-                                                               'code' => 'overriddenCode',
-                                                               'key' => 'mainpage',
-                                                               'params' => [ $I => 'param' ],
-                                                               'overriddenData' => true
-                                                       ],
-                                                       $I => 'warning',
+                                               [
+                                                       'code' => 'mainpage',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'module' => 'status',
                                                ],
+                                               [
+                                                       'code' => 'parentheses',
+                                                       'key' => 'parentheses',
+                                                       'params' => [ 'foobar', $I => 'param' ],
+                                                       'module' => 'status',
+                                               ],
+                                               [
+                                                       'code' => 'overriddenCode',
+                                                       'key' => 'mainpage',
+                                                       'params' => [ $I => 'param' ],
+                                                       'data' => $overriddenData,
+                                                       'module' => 'status',
+                                               ],
+                                               $I => 'warning',
                                        ],
                                ],
                        ],
                        [ 'none', 'fr', true,
                                [
                                        'errors' => [
-                                               'err' => [
-                                                       [ 'code' => 'mainpage' ],
-                                                       $I => 'error',
-                                               ],
+                                               [ 'code' => 'mainpage', 'module' => 'err' ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'string' => [
-                                                       [ 'code' => 'mainpage' ],
-                                                       $I => 'warning',
-                                               ],
+                                               [ 'code' => 'mainpage', 'module' => 'string' ],
+                                               $I => 'warning',
                                        ],
                                ],
                                [
                                        'errors' => [
-                                               'errWithData' => [
-                                                       [ 'code' => 'overriddenCode', 'overriddenData' => true ],
-                                                       $I => 'error',
-                                               ],
+                                               [ 'code' => 'overriddenCode', 'data' => $overriddenData,
+                                                       'module' => 'errWithData' ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'messageWithData' => [
-                                                       [ 'code' => 'overriddenCode', 'overriddenData' => true ],
-                                                       $I => 'warning',
-                                               ],
-                                               'message' => [
-                                                       [ 'code' => 'mainpage' ],
-                                                       $I => 'warning',
-                                               ],
-                                               'foo' => [
-                                                       [ 'code' => 'mainpage' ],
-                                                       [ 'code' => 'parentheses' ],
-                                                       $I => 'warning',
-                                               ],
+                                               [ 'code' => 'mainpage', 'module' => 'foo' ],
+                                               [ 'code' => 'parentheses', 'module' => 'foo' ],
+                                               [ 'code' => 'mainpage', 'module' => 'message' ],
+                                               [ 'code' => 'overriddenCode', 'data' => $overriddenData,
+                                                       'module' => 'messageWithData' ],
+                                               $I => 'warning',
                                        ],
                                ],
                                [
                                        'errors' => [
-                                               'status' => [
-                                                       [ 'code' => 'mainpage' ],
-                                                       [ 'code' => 'parentheses' ],
-                                                       $I => 'error',
-                                               ],
+                                               [ 'code' => 'mainpage', 'module' => 'status' ],
+                                               [ 'code' => 'parentheses', 'module' => 'status' ],
+                                               $I => 'error',
                                        ],
                                        'warnings' => [
-                                               'status' => [
-                                                       [ 'code' => 'mainpage' ],
-                                                       [ 'code' => 'parentheses' ],
-                                                       [ 'code' => 'overriddenCode', 'overriddenData' => true ],
-                                                       $I => 'warning',
-                                               ],
+                                               [ 'code' => 'mainpage', 'module' => 'status' ],
+                                               [ 'code' => 'parentheses', 'module' => 'status' ],
+                                               [ 'code' => 'overriddenCode', 'data' => $overriddenData, 'module' => 'status' ],
+                                               $I => 'warning',
                                        ],
                                ],
                        ],
@@ -302,7 +348,14 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                $result = new ApiResult( 8388608 );
                $formatter = new ApiErrorFormatter_BackCompat( $result );
 
+               $this->assertSame( 'en', $formatter->getLanguage()->getCode() );
+
+               $this->assertSame( [], $formatter->arrayFromStatus( Status::newGood() ) );
+
                $formatter->addWarning( 'string', 'mainpage' );
+               $formatter->addWarning( 'raw',
+                       new RawMessage( 'Blah <kbd>kbd</kbd> <b>&lt;X&gt;</b> &#x1f61e;' )
+               );
                $formatter->addError( 'err', 'mainpage' );
                $this->assertSame( [
                        'error' => [
@@ -310,6 +363,10 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                                'info' => $mainpagePlain,
                        ],
                        'warnings' => [
+                               'raw' => [
+                                       'warnings' => 'Blah "kbd" <X> 😞',
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ],
                                'string' => [
                                        'warnings' => $mainpagePlain,
                                        ApiResult::META_CONTENT => 'warnings',
@@ -321,12 +378,13 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                $result->reset();
                $formatter->addWarning( 'foo', 'mainpage' );
                $formatter->addWarning( 'foo', 'mainpage' );
-               $formatter->addWarning( 'foo', [ 'parentheses', 'foobar' ] );
+               $formatter->addWarning( 'xxx+foo', [ 'parentheses', 'foobar' ] );
                $msg1 = wfMessage( 'mainpage' );
                $formatter->addWarning( 'message', $msg1 );
                $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', [ 'overriddenData' => true ] );
                $formatter->addWarning( 'messageWithData', $msg2 );
                $formatter->addError( 'errWithData', $msg2 );
+               $formatter->addWarning( null, 'mainpage' );
                $this->assertSame( [
                        'error' => [
                                'code' => 'overriddenCode',
@@ -334,6 +392,10 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                                'overriddenData' => true,
                        ],
                        'warnings' => [
+                               'unknown' => [
+                                       'warnings' => $mainpagePlain,
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ],
                                'messageWithData' => [
                                        'warnings' => $mainpagePlain,
                                        ApiResult::META_CONTENT => 'warnings',
@@ -350,6 +412,22 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                        ApiResult::META_TYPE => 'assoc',
                ], $result->getResultData(), 'Complex test' );
 
+               $this->assertSame(
+                       [
+                               'code' => 'mainpage',
+                               'info' => 'Main Page',
+                       ],
+                       $formatter->formatMessage( $msg1 )
+               );
+               $this->assertSame(
+                       [
+                               'code' => 'overriddenCode',
+                               'info' => 'Main Page',
+                               'overriddenData' => true,
+                       ],
+                       $formatter->formatMessage( $msg2 )
+               );
+
                $result->reset();
                $status = Status::newGood();
                $status->warning( 'mainpage' );
@@ -377,14 +455,16 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                $this->assertSame(
                        [
                                [
-                                       'type' => 'error',
                                        'message' => 'mainpage',
-                                       'params' => [ $I => 'param' ]
+                                       'params' => [ $I => 'param' ],
+                                       'code' => 'mainpage',
+                                       'type' => 'error',
                                ],
                                [
-                                       'type' => 'error',
                                        'message' => 'parentheses',
-                                       'params' => [ 'foobar', $I => 'param' ]
+                                       'params' => [ 'foobar', $I => 'param' ],
+                                       'code' => 'parentheses',
+                                       'type' => 'error',
                                ],
                                $I => 'error',
                        ],
@@ -394,24 +474,28 @@ class ApiErrorFormatterTest extends MediaWikiLangTestCase {
                $this->assertSame(
                        [
                                [
-                                       'type' => 'warning',
                                        'message' => 'mainpage',
-                                       'params' => [ $I => 'param' ]
+                                       'params' => [ $I => 'param' ],
+                                       'code' => 'mainpage',
+                                       'type' => 'warning',
                                ],
                                [
-                                       'type' => 'warning',
                                        'message' => 'parentheses',
-                                       'params' => [ 'foobar', $I => 'param' ]
+                                       'params' => [ 'foobar', $I => 'param' ],
+                                       'code' => 'parentheses',
+                                       'type' => 'warning',
                                ],
                                [
                                        'message' => 'mainpage',
                                        'params' => [ $I => 'param' ],
-                                       'type' => 'warning'
+                                       'code' => 'mainpage',
+                                       'type' => 'warning',
                                ],
                                [
                                        'message' => 'mainpage',
                                        'params' => [ $I => 'param' ],
-                                       'type' => 'warning'
+                                       'code' => 'overriddenCode',
+                                       'type' => 'warning',
                                ],
                                $I => 'warning',
                        ],
index c111949..c9a3428 100644 (file)
@@ -53,8 +53,8 @@ class ApiMainTest extends ApiTestCase {
                                'assert' => $assert,
                        ], null, null, $user );
                        $this->assertFalse( $error ); // That no error was expected
-               } catch ( UsageException $e ) {
-                       $this->assertEquals( $e->getCodeString(), $error );
+               } catch ( ApiUsageException $e ) {
+                       $this->assertTrue( self::apiExceptionHasCode( $e, $error ) );
                }
        }
 
@@ -76,8 +76,8 @@ class ApiMainTest extends ApiTestCase {
                                'assertuser' => $user->getName() . 'X',
                        ], null, null, $user );
                        $this->fail( 'Expected exception not thrown' );
-               } catch ( UsageException $e ) {
-                       $this->assertEquals( $e->getCodeString(), 'assertnameduserfailed' );
+               } catch ( ApiUsageException $e ) {
+                       $this->assertTrue( self::apiExceptionHasCode( $e, 'assertnameduserfailed' ) );
                }
        }
 
@@ -305,4 +305,274 @@ class ApiMainTest extends ApiTestCase {
                $main = new ApiMain( new FauxRequest( [ 'action' => 'query', 'meta' => 'siteinfo' ] ) );
                $this->assertTrue( $main->lacksSameOriginSecurity(), 'Hook, should lack security' );
        }
+
+       /**
+        * Test proper creation of the ApiErrorFormatter
+        * @covers ApiMain::__construct
+        * @dataProvider provideApiErrorFormatterCreation
+        * @param array $request Request parameters
+        * @param array $expect Expected data
+        *  - uselang: ApiMain language
+        *  - class: ApiErrorFormatter class
+        *  - lang: ApiErrorFormatter language
+        *  - format: ApiErrorFormatter format
+        *  - usedb: ApiErrorFormatter use-database flag
+        */
+       public function testApiErrorFormatterCreation( array $request, array $expect ) {
+               $context = new RequestContext();
+               $context->setRequest( new FauxRequest( $request ) );
+               $context->setLanguage( 'ru' );
+
+               $main = new ApiMain( $context );
+               $formatter = $main->getErrorFormatter();
+               $wrappedFormatter = TestingAccessWrapper::newFromObject( $formatter );
+
+               $this->assertSame( $expect['uselang'], $main->getLanguage()->getCode() );
+               $this->assertInstanceOf( $expect['class'], $formatter );
+               $this->assertSame( $expect['lang'], $formatter->getLanguage()->getCode() );
+               $this->assertSame( $expect['format'], $wrappedFormatter->format );
+               $this->assertSame( $expect['usedb'], $wrappedFormatter->useDB );
+       }
+
+       public static function provideApiErrorFormatterCreation() {
+               global $wgContLang;
+
+               return [
+                       'Default (BC)' => [ [], [
+                               'uselang' => 'ru',
+                               'class' => ApiErrorFormatter_BackCompat::class,
+                               'lang' => 'en',
+                               'format' => 'none',
+                               'usedb' => false,
+                       ] ],
+                       'BC ignores fields' => [ [ 'errorlang' => 'de', 'errorsuselocal' => 1 ], [
+                               'uselang' => 'ru',
+                               'class' => ApiErrorFormatter_BackCompat::class,
+                               'lang' => 'en',
+                               'format' => 'none',
+                               'usedb' => false,
+                       ] ],
+                       'Explicit BC' => [ [ 'errorformat' => 'bc' ], [
+                               'uselang' => 'ru',
+                               'class' => ApiErrorFormatter_BackCompat::class,
+                               'lang' => 'en',
+                               'format' => 'none',
+                               'usedb' => false,
+                       ] ],
+                       'Basic' => [ [ 'errorformat' => 'wikitext' ], [
+                               'uselang' => 'ru',
+                               'class' => ApiErrorFormatter::class,
+                               'lang' => 'ru',
+                               'format' => 'wikitext',
+                               'usedb' => false,
+                       ] ],
+                       'Follows uselang' => [ [ 'uselang' => 'fr', 'errorformat' => 'plaintext' ], [
+                               'uselang' => 'fr',
+                               'class' => ApiErrorFormatter::class,
+                               'lang' => 'fr',
+                               'format' => 'plaintext',
+                               'usedb' => false,
+                       ] ],
+                       'Explicitly follows uselang' => [
+                               [ 'uselang' => 'fr', 'errorlang' => 'uselang', 'errorformat' => 'plaintext' ],
+                               [
+                                       'uselang' => 'fr',
+                                       'class' => ApiErrorFormatter::class,
+                                       'lang' => 'fr',
+                                       'format' => 'plaintext',
+                                       'usedb' => false,
+                               ]
+                       ],
+                       'uselang=content' => [
+                               [ 'uselang' => 'content', 'errorformat' => 'plaintext' ],
+                               [
+                                       'uselang' => $wgContLang->getCode(),
+                                       'class' => ApiErrorFormatter::class,
+                                       'lang' => $wgContLang->getCode(),
+                                       'format' => 'plaintext',
+                                       'usedb' => false,
+                               ]
+                       ],
+                       'errorlang=content' => [
+                               [ 'errorlang' => 'content', 'errorformat' => 'plaintext' ],
+                               [
+                                       'uselang' => 'ru',
+                                       'class' => ApiErrorFormatter::class,
+                                       'lang' => $wgContLang->getCode(),
+                                       'format' => 'plaintext',
+                                       'usedb' => false,
+                               ]
+                       ],
+                       'Explicit parameters' => [
+                               [ 'errorlang' => 'de', 'errorformat' => 'html', 'errorsuselocal' => 1 ],
+                               [
+                                       'uselang' => 'ru',
+                                       'class' => ApiErrorFormatter::class,
+                                       'lang' => 'de',
+                                       'format' => 'html',
+                                       'usedb' => true,
+                               ]
+                       ],
+                       'Explicit parameters override uselang' => [
+                               [ 'errorlang' => 'de', 'uselang' => 'fr', 'errorformat' => 'raw' ],
+                               [
+                                       'uselang' => 'fr',
+                                       'class' => ApiErrorFormatter::class,
+                                       'lang' => 'de',
+                                       'format' => 'raw',
+                                       'usedb' => false,
+                               ]
+                       ],
+                       'Bogus language doesn\'t explode' => [
+                               [ 'errorlang' => '<bogus1>', 'uselang' => '<bogus2>', 'errorformat' => 'none' ],
+                               [
+                                       'uselang' => 'en',
+                                       'class' => ApiErrorFormatter::class,
+                                       'lang' => 'en',
+                                       'format' => 'none',
+                                       'usedb' => false,
+                               ]
+                       ],
+                       'Bogus format doesn\'t explode' => [ [ 'errorformat' => 'bogus' ], [
+                               'uselang' => 'ru',
+                               'class' => ApiErrorFormatter_BackCompat::class,
+                               'lang' => 'en',
+                               'format' => 'none',
+                               'usedb' => false,
+                       ] ],
+               ];
+       }
+
+       /**
+        * @covers ApiMain::errorMessagesFromException
+        * @covers ApiMain::substituteResultWithError
+        * @dataProvider provideExceptionErrors
+        * @param Exception $exception
+        * @param array $expectReturn
+        * @param array $expectResult
+        */
+       public function testExceptionErrors( $error, $expectReturn, $expectResult ) {
+               $context = new RequestContext();
+               $context->setRequest( new FauxRequest( [ 'errorformat' => 'plaintext' ] ) );
+               $context->setLanguage( 'en' );
+               $context->setConfig( new MultiConfig( [
+                       new HashConfig( [ 'ShowHostnames' => true, 'ShowSQLErrors' => false ] ),
+                       $context->getConfig()
+               ] ) );
+
+               $main = new ApiMain( $context );
+               $main->addWarning( new RawMessage( 'existing warning' ), 'existing-warning' );
+               $main->addError( new RawMessage( 'existing error' ), 'existing-error' );
+
+               $ret = TestingAccessWrapper::newFromObject( $main )->substituteResultWithError( $error );
+               $this->assertSame( $expectReturn, $ret );
+
+               // PHPUnit sometimes adds some SplObjectStorage garbage to the arrays,
+               // so let's try ->assertEquals().
+               $this->assertEquals(
+                       $expectResult,
+                       $main->getResult()->getResultData( [], [ 'Strip' => 'all' ] )
+               );
+       }
+
+       // Not static so $this->getMock() can be used
+       public function provideExceptionErrors() {
+               $reqId = WebRequest::getRequestId();
+               $doclink = wfExpandUrl( wfScript( 'api' ) );
+
+               $ex = new InvalidArgumentException( 'Random exception' );
+               $trace = wfMessage( 'api-exception-trace',
+                       get_class( $ex ),
+                       $ex->getFile(),
+                       $ex->getLine(),
+                       MWExceptionHandler::getRedactedTraceAsString( $ex )
+               )->inLanguage( 'en' )->useDatabase( false )->text();
+
+               $dbex = new DBQueryError( $this->getMock( 'IDatabase' ), 'error', 1234, 'SELECT 1', __METHOD__ );
+               $dbtrace = wfMessage( 'api-exception-trace',
+                       get_class( $dbex ),
+                       $dbex->getFile(),
+                       $dbex->getLine(),
+                       MWExceptionHandler::getRedactedTraceAsString( $dbex )
+               )->inLanguage( 'en' )->useDatabase( false )->text();
+
+               $apiEx1 = new ApiUsageException( null,
+                       StatusValue::newFatal( new ApiRawMessage( 'An error', 'sv-error1' ) ) );
+               TestingAccessWrapper::newFromObject( $apiEx1 )->modulePath = 'foo+bar';
+               $apiEx1->getStatusValue()->warning( new ApiRawMessage( 'A warning', 'sv-warn1' ) );
+               $apiEx1->getStatusValue()->warning( new ApiRawMessage( 'Another warning', 'sv-warn2' ) );
+               $apiEx1->getStatusValue()->fatal( new ApiRawMessage( 'Another error', 'sv-error2' ) );
+
+               return [
+                       [
+                               $ex,
+                               [ 'existing-error', 'internal_api_error_InvalidArgumentException' ],
+                               [
+                                       'warnings' => [
+                                               [ 'code' => 'existing-warning', 'text' => 'existing warning', 'module' => 'main' ],
+                                       ],
+                                       'errors' => [
+                                               [ 'code' => 'existing-error', 'text' => 'existing error', 'module' => 'main' ],
+                                               [
+                                                       'code' => 'internal_api_error_InvalidArgumentException',
+                                                       'text' => "[$reqId] Exception caught: Random exception",
+                                               ]
+                                       ],
+                                       'trace' => $trace,
+                                       'servedby' => wfHostname(),
+                               ]
+                       ],
+                       [
+                               $dbex,
+                               [ 'existing-error', 'internal_api_error_DBQueryError' ],
+                               [
+                                       'warnings' => [
+                                               [ 'code' => 'existing-warning', 'text' => 'existing warning', 'module' => 'main' ],
+                                       ],
+                                       'errors' => [
+                                               [ 'code' => 'existing-error', 'text' => 'existing error', 'module' => 'main' ],
+                                               [
+                                                       'code' => 'internal_api_error_DBQueryError',
+                                                       'text' => "[$reqId] Database query error.",
+                                               ]
+                                       ],
+                                       'trace' => $dbtrace,
+                                       'servedby' => wfHostname(),
+                               ]
+                       ],
+                       [
+                               new UsageException( 'Usage exception!', 'ue', 0, [ 'foo' => 'bar' ] ),
+                               [ 'existing-error', 'ue' ],
+                               [
+                                       'warnings' => [
+                                               [ 'code' => 'existing-warning', 'text' => 'existing warning', 'module' => 'main' ],
+                                       ],
+                                       'errors' => [
+                                               [ 'code' => 'existing-error', 'text' => 'existing error', 'module' => 'main' ],
+                                               [ 'code' => 'ue', 'text' => "Usage exception!", 'data' => [ 'foo' => 'bar' ] ]
+                                       ],
+                                       'docref' => "See $doclink for API usage.",
+                                       'servedby' => wfHostname(),
+                               ]
+                       ],
+                       [
+                               $apiEx1,
+                               [ 'existing-error', 'sv-error1', 'sv-error2' ],
+                               [
+                                       'warnings' => [
+                                               [ 'code' => 'existing-warning', 'text' => 'existing warning', 'module' => 'main' ],
+                                               [ 'code' => 'sv-warn1', 'text' => 'A warning', 'module' => 'foo+bar' ],
+                                               [ 'code' => 'sv-warn2', 'text' => 'Another warning', 'module' => 'foo+bar' ],
+                                       ],
+                                       'errors' => [
+                                               [ 'code' => 'existing-error', 'text' => 'existing error', 'module' => 'main' ],
+                                               [ 'code' => 'sv-error1', 'text' => 'An error', 'module' => 'foo+bar' ],
+                                               [ 'code' => 'sv-error2', 'text' => 'Another error', 'module' => 'foo+bar' ],
+                                       ],
+                                       'docref' => "See $doclink for API usage.",
+                                       'servedby' => wfHostname(),
+                               ]
+                       ],
+               ];
+       }
 }
index 8764b41..e405b3b 100644 (file)
@@ -23,6 +23,56 @@ class ApiMessageTest extends MediaWikiTestCase {
                );
        }
 
+       /**
+        * @covers ApiMessageTrait
+        */
+       public function testCodeDefaults() {
+               $msg = new ApiMessage( 'foo' );
+               $this->assertSame( 'foo', $msg->getApiCode() );
+
+               $msg = new ApiMessage( 'apierror-bar' );
+               $this->assertSame( 'bar', $msg->getApiCode() );
+
+               $msg = new ApiMessage( 'apiwarn-baz' );
+               $this->assertSame( 'baz', $msg->getApiCode() );
+
+               // BC case
+               $msg = new ApiMessage( 'actionthrottledtext' );
+               $this->assertSame( 'ratelimited', $msg->getApiCode() );
+
+               $msg = new ApiMessage( [ 'apierror-missingparam', 'param' ] );
+               $this->assertSame( 'noparam', $msg->getApiCode() );
+       }
+
+       /**
+        * @covers ApiMessageTrait
+        * @dataProvider provideInvalidCode
+        * @param mixed $code
+        */
+       public function testInvalidCode( $code ) {
+               $msg = new ApiMessage( 'foo' );
+               try {
+                       $msg->setApiCode( $code );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertTrue( true );
+               }
+
+               try {
+                       new ApiMessage( 'foo', $code );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertTrue( true );
+               }
+       }
+
+       public static function provideInvalidCode() {
+               return [
+                       [ '' ],
+                       [ 42 ],
+               ];
+       }
+
        /**
         * @covers ApiMessage
         * @covers ApiMessageTrait
@@ -105,14 +155,32 @@ class ApiMessageTest extends MediaWikiTestCase {
         * @covers ApiMessage::create
         */
        public function testApiMessageCreate() {
-               $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( new Message( 'mainpage' ) ) );
-               $this->assertInstanceOf( 'ApiRawMessage', ApiMessage::create( new RawMessage( 'mainpage' ) ) );
-               $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( 'mainpage' ) );
+               $this->assertInstanceOf( ApiMessage::class, ApiMessage::create( new Message( 'mainpage' ) ) );
+               $this->assertInstanceOf(
+                       ApiRawMessage::class, ApiMessage::create( new RawMessage( 'mainpage' ) )
+               );
+               $this->assertInstanceOf( ApiMessage::class, ApiMessage::create( 'mainpage' ) );
+
+               $msg = new ApiMessage( [ 'parentheses', 'foobar' ] );
+               $msg2 = new Message( 'parentheses', [ 'foobar' ] );
 
-               $msg = new ApiMessage( 'mainpage' );
                $this->assertSame( $msg, ApiMessage::create( $msg ) );
+               $this->assertEquals( $msg, ApiMessage::create( $msg2 ) );
+               $this->assertEquals( $msg, ApiMessage::create( [ 'parentheses', 'foobar' ] ) );
+               $this->assertEquals( $msg,
+                       ApiMessage::create( [ 'message' => 'parentheses', 'params' => [ 'foobar' ] ] )
+               );
+               $this->assertSame( $msg,
+                       ApiMessage::create( [ 'message' => $msg, 'params' => [ 'xxx' ] ] )
+               );
+               $this->assertEquals( $msg,
+                       ApiMessage::create( [ 'message' => $msg2, 'params' => [ 'xxx' ] ] )
+               );
+               $this->assertSame( $msg,
+                       ApiMessage::create( [ 'message' => $msg ] )
+               );
 
-               $msg = new ApiRawMessage( 'mainpage' );
+               $msg = new ApiRawMessage( [ 'parentheses', 'foobar' ] );
                $this->assertSame( $msg, ApiMessage::create( $msg ) );
        }
 
index 0a577c1..ef70626 100644 (file)
@@ -30,7 +30,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $this->mUserMock->expects( $this->any() )
                        ->method( 'getEffectiveGroups' )->will( $this->returnValue( [ '*', 'user' ] ) );
                $this->mUserMock->expects( $this->any() )
-                       ->method( 'isAllowed' )->will( $this->returnValue( true ) );
+                       ->method( 'isAllowedAny' )->will( $this->returnValue( true ) );
 
                // Set up callback for User::getOptionKinds
                $this->mUserMock->expects( $this->any() )
@@ -146,7 +146,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
        }
 
        /**
-        * @expectedException UsageException
+        * @expectedException ApiUsageException
         */
        public function testNoToken() {
                $request = $this->getSampleRequest( [ 'token' => null ] );
@@ -163,13 +163,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        $request = $this->getSampleRequest();
 
                        $this->executeQuery( $request );
-               } catch ( UsageException $e ) {
-                       $this->assertEquals( 'notloggedin', $e->getCodeString() );
-                       $this->assertEquals( 'Anonymous users cannot change preferences', $e->getMessage() );
-
+               } catch ( ApiUsageException $e ) {
+                       $this->assertTrue( ApiTestCase::apiExceptionHasCode( $e, 'notloggedin' ) );
                        return;
                }
-               $this->fail( "UsageException was not thrown" );
+               $this->fail( "ApiUsageException was not thrown" );
        }
 
        public function testNoOptionname() {
@@ -177,13 +175,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        $request = $this->getSampleRequest( [ 'optionvalue' => '1' ] );
 
                        $this->executeQuery( $request );
-               } catch ( UsageException $e ) {
-                       $this->assertEquals( 'nooptionname', $e->getCodeString() );
-                       $this->assertEquals( 'The optionname parameter must be set', $e->getMessage() );
-
+               } catch ( ApiUsageException $e ) {
+                       $this->assertTrue( ApiTestCase::apiExceptionHasCode( $e, 'nooptionname' ) );
                        return;
                }
-               $this->fail( "UsageException was not thrown" );
+               $this->fail( "ApiUsageException was not thrown" );
        }
 
        public function testNoChanges() {
@@ -200,13 +196,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        $request = $this->getSampleRequest();
 
                        $this->executeQuery( $request );
-               } catch ( UsageException $e ) {
-                       $this->assertEquals( 'nochanges', $e->getCodeString() );
-                       $this->assertEquals( 'No changes were requested', $e->getMessage() );
-
+               } catch ( ApiUsageException $e ) {
+                       $this->assertTrue( ApiTestCase::apiExceptionHasCode( $e, 'nochanges' ) );
                        return;
                }
-               $this->fail( "UsageException was not thrown" );
+               $this->fail( "ApiUsageException was not thrown" );
        }
 
        public function testReset() {
@@ -400,7 +394,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'options' => 'success',
                        'warnings' => [
                                'options' => [
-                                       'warnings' => "Validation error for 'special': cannot be set by this module"
+                                       'warnings' => "Validation error for \"special\": cannot be set by this module."
                                ]
                        ]
                ], $response );
@@ -423,7 +417,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'options' => 'success',
                        'warnings' => [
                                'options' => [
-                                       'warnings' => "Validation error for 'unknownOption': not a valid preference"
+                                       'warnings' => "Validation error for \"unknownOption\": not a valid preference."
                                ]
                        ]
                ], $response );
index b72a4f8..f01a670 100644 (file)
@@ -23,12 +23,10 @@ class ApiParseTest extends ApiTestCase {
                                'page' => $somePage ] );
 
                        $this->fail( "API did not return an error when parsing a nonexistent page" );
-               } catch ( UsageException $ex ) {
-                       $this->assertEquals(
-                               'missingtitle',
-                               $ex->getCodeString(),
+               } catch ( ApiUsageException $ex ) {
+                       $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'missingtitle' ),
                                "Parse request for nonexistent page must give 'missingtitle' error: "
-                                       . var_export( $ex->getMessageArray(), true )
+                                       . var_export( self::getErrorFormatter()->arrayFromStatus( $ex->getStatusValue() ), true )
                        );
                }
        }
index eaeb3ae..0a2cd83 100644 (file)
@@ -1498,7 +1498,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
                $otherUser->setOption( 'watchlisttoken', '1234567890' );
                $otherUser->saveSettings();
 
-               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+               $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
 
                $this->doListWatchlistRequest( [
                        'wlowner' => $otherUser->getName(),
@@ -1507,7 +1507,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
        }
 
        public function testOwnerAndTokenParams_noWatchlistTokenSet() {
-               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+               $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
 
                $this->doListWatchlistRequest( [
                        'wlowner' => $this->getNonLoggedInTestUser()->getName(),
index d6f315d..0f01664 100644 (file)
@@ -503,7 +503,7 @@ class ApiQueryWatchlistRawIntegrationTest extends ApiTestCase {
                $otherUser->setOption( 'watchlisttoken', '1234567890' );
                $otherUser->saveSettings();
 
-               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+               $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
 
                $this->doListWatchlistRawRequest( [
                        'wrowner' => $otherUser->getName(),
@@ -512,7 +512,7 @@ class ApiQueryWatchlistRawIntegrationTest extends ApiTestCase {
        }
 
        public function testOwnerAndTokenParams_userHasNoWatchlistToken() {
-               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+               $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
 
                $this->doListWatchlistRawRequest( [
                        'wrowner' => $this->getNotLoggedInTestUser()->getName(),
index 7e1f9d8..6b299c9 100644 (file)
@@ -3,6 +3,8 @@
 abstract class ApiTestCase extends MediaWikiLangTestCase {
        protected static $apiUrl;
 
+       protected static $errorFormatter = null;
+
        /**
         * @var ApiTestContext
         */
@@ -196,6 +198,26 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
                return $data[0]['tokens'];
        }
 
+       protected static function getErrorFormatter() {
+               if ( self::$errorFormatter === null ) {
+                       self::$errorFormatter = new ApiErrorFormatter(
+                               new ApiResult( false ),
+                               Language::factory( 'en' ),
+                               'none'
+                       );
+               }
+               return self::$errorFormatter;
+       }
+
+       public static function apiExceptionHasCode( ApiUsageException $ex, $code ) {
+               return (bool)array_filter(
+                       self::getErrorFormatter()->arrayFromStatus( $ex->getStatusValue() ),
+                       function ( $e ) use ( $code ) {
+                               return is_array( $e ) && $e['code'] === $code;
+                       }
+               );
+       }
+
        public function testApiTestGroup() {
                $groups = PHPUnit_Util_Test::getGroups( get_class( $this ) );
                $constraint = PHPUnit_Framework_Assert::logicalOr(
index b63bf2e..971b63c 100644 (file)
@@ -14,7 +14,7 @@ class ApiUnblockTest extends ApiTestCase {
        }
 
        /**
-        * @expectedException UsageException
+        * @expectedException ApiUsageException
         */
        public function testWithNoToken() {
                $this->doApiRequest(
index de2b56b..9b79e6c 100644 (file)
@@ -67,9 +67,9 @@ class ApiUploadTest extends ApiTestCaseUpload {
                        $this->doApiRequest( [
                                'action' => 'upload'
                        ] );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
-                       $this->assertEquals( "The token parameter must be set", $e->getMessage() );
+                       $this->assertEquals( 'The "token" parameter must be set', $e->getMessage() );
                }
                $this->assertTrue( $exception, "Got exception" );
        }
@@ -83,7 +83,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                        $this->doApiRequestWithToken( [
                                'action' => 'upload',
                        ], $session, self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                        $this->assertEquals( "One of the parameters filekey, file, url is required",
                                $e->getMessage() );
@@ -129,7 +129,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
@@ -168,7 +168,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                $exception = false;
                try {
                        $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
                        $exception = true;
                }
@@ -218,7 +218,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
@@ -235,7 +235,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
@@ -289,7 +289,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
@@ -314,7 +314,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
@@ -371,7 +371,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertFalse( $exception );
@@ -400,12 +400,12 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertFalse( $exception, "No UsageException exception." );
+               $this->assertFalse( $exception, "No ApiUsageException exception." );
 
                // clean up
                $this->deleteFileByFileName( $fileName );
@@ -476,7 +476,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                                try {
                                        list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
                                                self::$users['uploader']->getUser() );
-                               } catch ( UsageException $e ) {
+                               } catch ( ApiUsageException $e ) {
                                        $this->markTestIncomplete( $e->getMessage() );
                                }
                                // Make sure we got a valid chunk continue:
@@ -504,7 +504,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                        try {
                                list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
                                        self::$users['uploader']->getUser() );
-                       } catch ( UsageException $e ) {
+                       } catch ( ApiUsageException $e ) {
                                $this->markTestIncomplete( $e->getMessage() );
                        }
                        // Make sure we got a valid chunk continue:
@@ -544,7 +544,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
                try {
                        list( $result ) = $this->doApiRequestWithToken( $params, $session,
                                self::$users['uploader']->getUser() );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                }
                $this->assertTrue( isset( $result['upload'] ) );
index 19afc14..7b91094 100644 (file)
@@ -16,8 +16,6 @@ class ApiWatchTest extends ApiTestCase {
                return $this->getTokenList( self::$users['sysop'] );
        }
 
-       /**
-        */
        public function testWatchEdit() {
                $tokens = $this->getTokens();
 
@@ -74,8 +72,6 @@ class ApiWatchTest extends ApiTestCase {
                return $data;
        }
 
-       /**
-        */
        public function testWatchProtect() {
                $tokens = $this->getTokens();
 
@@ -92,8 +88,6 @@ class ApiWatchTest extends ApiTestCase {
                $this->assertArrayHasKey( 'edit', $data[0]['protect']['protections'][0] );
        }
 
-       /**
-        */
        public function testGetRollbackToken() {
                $this->getTokens();
 
@@ -146,11 +140,11 @@ class ApiWatchTest extends ApiTestCase {
 
                        $this->assertArrayHasKey( 'rollback', $data[0] );
                        $this->assertArrayHasKey( 'title', $data[0]['rollback'] );
-               } catch ( UsageException $ue ) {
-                       if ( $ue->getCodeString() == 'onlyauthor' ) {
+               } catch ( ApiUsageException $ue ) {
+                       if ( self::apiExceptionHasCode( $ue, 'onlyauthor' ) ) {
                                $this->markTestIncomplete( "Only one author to 'Help:UTPage', cannot test rollback" );
                        } else {
-                               $this->fail( "Received error '" . $ue->getCodeString() . "'" );
+                               $this->fail( "Received error '" . $ue->getMessage() . "'" );
                        }
                }
        }
index d7db538..1407c10 100644 (file)
@@ -9,7 +9,11 @@ class MockApi extends ApiBase {
        public function __construct() {
        }
 
-       public function setWarning( $warning ) {
+       public function getModulePath() {
+               return $this->getModuleName();
+       }
+
+       public function addWarning( $warning, $code = null, $data = null ) {
                $this->warnings[] = $warning;
        }
 
index f5b50e5..9915a38 100644 (file)
@@ -12,4 +12,8 @@ class MockApiQueryBase extends ApiQueryBase {
        public function getModuleName() {
                return $this->name;
        }
+
+       public function getModulePath() {
+               return 'query+' . $this->getModuleName();
+       }
 }
index 0028bbb..3aa1db3 100644 (file)
@@ -133,12 +133,10 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
                        $printer->closePrinter();
                        ob_end_clean();
                        $this->fail( 'Expected exception not thrown' );
-               } catch ( UsageException $ex ) {
+               } catch ( ApiUsageException $ex ) {
                        ob_end_clean();
-                       $this->assertSame(
-                               'This response cannot be represented using format=php. ' .
-                                       'See https://phabricator.wikimedia.org/T68776',
-                               $ex->getMessage(),
+                       $this->assertTrue(
+                               $ex->getStatusValue()->hasMessage( 'apierror-formatphp' ),
                                'Expected exception'
                        );
                }
index 3fef0b0..0f8c8ee 100644 (file)
@@ -105,11 +105,11 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
                                [ 'includexmlnamespace' => 1 ] ],
 
                        // xslt param
-                       [ [], '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Invalid or non-existent stylesheet specified</xml></warnings></api>',
+                       [ [], '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Invalid or non-existent stylesheet specified.</xml></warnings></api>',
                                [ 'xslt' => 'DoesNotExist' ] ],
                        [ [], '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should be in the MediaWiki namespace.</xml></warnings></api>',
                                [ 'xslt' => 'ApiFormatXmlTest' ] ],
-                       [ [], '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should have .xsl extension.</xml></warnings></api>',
+                       [ [], '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should have &quot;.xsl&quot; extension.</xml></warnings></api>',
                                [ 'xslt' => 'MediaWiki:ApiFormatXmlTest' ] ],
                        [ [],
                                '<?xml version="1.0"?><?xml-stylesheet href="' .
index 8cb2327..9407edf 100644 (file)
@@ -99,11 +99,11 @@ class ApiQueryTest extends ApiTestCase {
                $exceptionCaught = false;
                try {
                        $this->assertEquals( $expected, $api->titlePartToKey( $titlePart, $namespace ) );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exceptionCaught = true;
                }
                $this->assertEquals( $expectException, $exceptionCaught,
-                       'UsageException thrown by titlePartToKey' );
+                       'ApiUsageException thrown by titlePartToKey' );
        }
 
        function provideTestTitlePartToKey() {
index cb34be2..72a03c3 100644 (file)
@@ -451,7 +451,7 @@ class LocalPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase
                $changeReq->password = $newpass;
                $provider->providerChangeAuthenticationData( $changeReq );
 
-               if ( $loginOnly ) {
+               if ( $loginOnly && $changed ) {
                        $old = 'fail';
                        $new = 'fail';
                        $expectExpiry = null;
index 697eb2d..ed82153 100644 (file)
@@ -61,14 +61,14 @@ class LocalisationCacheTest extends MediaWikiTestCase {
 
        public function testRecacheFallbacks() {
                $lc = $this->getMockLocalisationCache();
-               $lc->recache( 'uk' );
+               $lc->recache( 'ba' );
                $this->assertEquals(
                        [
-                               'present-uk' => 'uk',
+                               'present-ba' => 'ba',
                                'present-ru' => 'ru',
                                'present-en' => 'en',
                        ],
-                       $lc->getItem( 'uk', 'messages' ),
+                       $lc->getItem( 'ba', 'messages' ),
                        'Fallbacks are only used to fill missing data'
                );
        }
@@ -84,7 +84,7 @@ class LocalisationCacheTest extends MediaWikiTestCase {
                                        array &$cache
                                ) {
                                        if ( $code === 'ru' ) {
-                                               $cache['messages']['present-uk'] = 'ru-override';
+                                               $cache['messages']['present-ba'] = 'ru-override';
                                                $cache['messages']['present-ru'] = 'ru-override';
                                                $cache['messages']['present-en'] = 'ru-override';
                                        }
@@ -93,14 +93,14 @@ class LocalisationCacheTest extends MediaWikiTestCase {
                ] );
 
                $lc = $this->getMockLocalisationCache();
-               $lc->recache( 'uk' );
+               $lc->recache( 'ba' );
                $this->assertEquals(
                        [
-                               'present-uk' => 'uk',
+                               'present-ba' => 'ba',
                                'present-ru' => 'ru-override',
                                'present-en' => 'ru-override',
                        ],
-                       $lc->getItem( 'uk', 'messages' ),
+                       $lc->getItem( 'ba', 'messages' ),
                        'Updates provided by hooks follow the normal fallback order.'
                );
        }
diff --git a/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LogstashFormatterTest.php
new file mode 100644 (file)
index 0000000..8086b4b
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace MediaWiki\Logger\Monolog;
+
+class LogstashFormatterTest extends \PHPUnit_Framework_TestCase {
+       /**
+        * @dataProvider provideV1
+        * @param array $record The input record.
+        * @param array $expected Associative array of expected keys and their values.
+        * @param array $notExpected List of keys that should not exist.
+        */
+       public function testV1( array $record, array $expected, array $notExpected ) {
+               $formatter = new LogstashFormatter( 'app', 'system', null, null, LogstashFormatter::V1 );
+               $formatted = json_decode( $formatter->format( $record ), true );
+               foreach ( $expected as $key => $value ) {
+                       $this->assertArrayHasKey( $key, $formatted );
+                       $this->assertSame( $value, $formatted[$key] );
+               }
+               foreach ( $notExpected as $key ) {
+                       $this->assertArrayNotHasKey( $key, $formatted );
+               }
+       }
+
+       public function provideV1() {
+               return [
+                       [
+                               [ 'extra' => [ 'foo' => 1 ], 'context' => [ 'bar' => 2 ] ],
+                               [ 'foo' => 1, 'bar' => 2 ],
+                               [ 'logstash_formatter_key_conflict' ],
+                       ],
+                       [
+                               [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ],
+                               [ 'url' => 1, 'c_url' => 2, 'logstash_formatter_key_conflict' => [ 'url' ] ],
+                               [],
+                       ],
+                       [
+                               [ 'channel' => 'x', 'context' => [ 'channel' => 'y' ] ],
+                               [ 'channel' => 'x', 'c_channel' => 'y',
+                                       'logstash_formatter_key_conflict' => [ 'channel' ] ],
+                               [],
+                       ],
+               ];
+       }
+
+       public function testV1WithPrefix() {
+               $formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+               $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
+               $formatted = json_decode( $formatter->format( $record ), true );
+               $this->assertArrayHasKey( 'url', $formatted );
+               $this->assertSame( 1, $formatted['url'] );
+               $this->assertArrayHasKey( 'ctx_url', $formatted );
+               $this->assertSame( 2, $formatted['ctx_url'] );
+               $this->assertArrayNotHasKey( 'c_url', $formatted );
+       }
+}
diff --git a/tests/phpunit/includes/http/HttpTest.php b/tests/phpunit/includes/http/HttpTest.php
new file mode 100644 (file)
index 0000000..7e98d1c
--- /dev/null
@@ -0,0 +1,534 @@
+<?php
+
+/**
+ * @group Http
+ */
+class HttpTest extends MediaWikiTestCase {
+       /**
+        * @dataProvider cookieDomains
+        * @covers Cookie::validateCookieDomain
+        */
+       public function testValidateCookieDomain( $expected, $domain, $origin = null ) {
+               if ( $origin ) {
+                       $ok = Cookie::validateCookieDomain( $domain, $origin );
+                       $msg = "$domain against origin $origin";
+               } else {
+                       $ok = Cookie::validateCookieDomain( $domain );
+                       $msg = "$domain";
+               }
+               $this->assertEquals( $expected, $ok, $msg );
+       }
+
+       public static function cookieDomains() {
+               return [
+                       [ false, "org" ],
+                       [ false, ".org" ],
+                       [ true, "wikipedia.org" ],
+                       [ true, ".wikipedia.org" ],
+                       [ false, "co.uk" ],
+                       [ false, ".co.uk" ],
+                       [ false, "gov.uk" ],
+                       [ false, ".gov.uk" ],
+                       [ true, "supermarket.uk" ],
+                       [ false, "uk" ],
+                       [ false, ".uk" ],
+                       [ false, "127.0.0." ],
+                       [ false, "127." ],
+                       [ false, "127.0.0.1." ],
+                       [ true, "127.0.0.1" ],
+                       [ false, "333.0.0.1" ],
+                       [ true, "example.com" ],
+                       [ false, "example.com." ],
+                       [ true, ".example.com" ],
+
+                       [ true, ".example.com", "www.example.com" ],
+                       [ false, "example.com", "www.example.com" ],
+                       [ true, "127.0.0.1", "127.0.0.1" ],
+                       [ false, "127.0.0.1", "localhost" ],
+               ];
+       }
+
+       /**
+        * Test Http::isValidURI()
+        * @bug 27854 : Http::isValidURI is too lax
+        * @dataProvider provideURI
+        * @covers Http::isValidURI
+        */
+       public function testIsValidUri( $expect, $URI, $message = '' ) {
+               $this->assertEquals(
+                       $expect,
+                       (bool)Http::isValidURI( $URI ),
+                       $message
+               );
+       }
+
+       /**
+        * @covers Http::getProxy
+        */
+       public function testGetProxy() {
+               $this->setMwGlobals( 'wgHTTPProxy', 'proxy.domain.tld' );
+               $this->assertEquals(
+                       'proxy.domain.tld',
+                       Http::getProxy()
+               );
+       }
+
+       /**
+        * Feeds URI to test a long regular expression in Http::isValidURI
+        */
+       public static function provideURI() {
+               /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
+               return [
+                       [ false, '¿non sens before!! http://a', 'Allow anything before URI' ],
+
+                       # (http|https) - only two schemes allowed
+                       [ true, 'http://www.example.org/' ],
+                       [ true, 'https://www.example.org/' ],
+                       [ true, 'http://www.example.org', 'URI without directory' ],
+                       [ true, 'http://a', 'Short name' ],
+                       [ true, 'http://étoile', 'Allow UTF-8 in hostname' ], # 'étoile' is french for 'star'
+                       [ false, '\\host\directory', 'CIFS share' ],
+                       [ false, 'gopher://host/dir', 'Reject gopher scheme' ],
+                       [ false, 'telnet://host', 'Reject telnet scheme' ],
+
+                       # :\/\/ - double slashes
+                       [ false, 'http//example.org', 'Reject missing colon in protocol' ],
+                       [ false, 'http:/example.org', 'Reject missing slash in protocol' ],
+                       [ false, 'http:example.org', 'Must have two slashes' ],
+                       # Following fail since hostname can be made of anything
+                       [ false, 'http:///example.org', 'Must have exactly two slashes, not three' ],
+
+                       # (\w+:{0,1}\w*@)? - optional user:pass
+                       [ true, 'http://user@host', 'Username provided' ],
+                       [ true, 'http://user:@host', 'Username provided, no password' ],
+                       [ true, 'http://user:pass@host', 'Username and password provided' ],
+
+                       # (\S+) - host part is made of anything not whitespaces
+                       // commented these out in order to remove @group Broken
+                       // @todo are these valid tests? if so, fix Http::isValidURI so it can handle them
+                       // [ false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ],
+                       // [ false, 'http://exam:ple.org/', 'hostname can not use colons!' ],
+
+                       # (:[0-9]+)? - port number
+                       [ true, 'http://example.org:80/' ],
+                       [ true, 'https://example.org:80/' ],
+                       [ true, 'http://example.org:443/' ],
+                       [ true, 'https://example.org:443/' ],
+
+                       # Part after the hostname is / or / with something else
+                       [ true, 'http://example/#' ],
+                       [ true, 'http://example/!' ],
+                       [ true, 'http://example/:' ],
+                       [ true, 'http://example/.' ],
+                       [ true, 'http://example/?' ],
+                       [ true, 'http://example/+' ],
+                       [ true, 'http://example/=' ],
+                       [ true, 'http://example/&' ],
+                       [ true, 'http://example/%' ],
+                       [ true, 'http://example/@' ],
+                       [ true, 'http://example/-' ],
+                       [ true, 'http://example//' ],
+                       [ true, 'http://example/&' ],
+
+                       # Fragment
+                       [ true, 'http://exam#ple.org', ], # This one is valid, really!
+                       [ true, 'http://example.org:80#anchor' ],
+                       [ true, 'http://example.org/?id#anchor' ],
+                       [ true, 'http://example.org/?#anchor' ],
+
+                       [ false, 'http://a ¿non !!sens after', 'Allow anything after URI' ],
+               ];
+       }
+
+       /**
+        * Warning:
+        *
+        * These tests are for code that makes use of an artifact of how CURL
+        * handles header reporting on redirect pages, and will need to be
+        * rewritten when bug 29232 is taken care of (high-level handling of
+        * HTTP redirects).
+        */
+       public function testRelativeRedirections() {
+               $h = MWHttpRequestTester::factory( 'http://oldsite/file.ext', [], __METHOD__ );
+
+               # Forge a Location header
+               $h->setRespHeaders( 'location', [
+                               'http://newsite/file.ext',
+                               '/newfile.ext',
+                       ]
+               );
+               # Verify we correctly fix the Location
+               $this->assertEquals(
+                       'http://newsite/newfile.ext',
+                       $h->getFinalUrl(),
+                       "Relative file path Location: interpreted as full URL"
+               );
+
+               $h->setRespHeaders( 'location', [
+                               'https://oldsite/file.ext'
+                       ]
+               );
+               $this->assertEquals(
+                       'https://oldsite/file.ext',
+                       $h->getFinalUrl(),
+                       "Location to the HTTPS version of the site"
+               );
+
+               $h->setRespHeaders( 'location', [
+                               '/anotherfile.ext',
+                               'http://anotherfile/hoster.ext',
+                               'https://anotherfile/hoster.ext'
+                       ]
+               );
+               $this->assertEquals(
+                       'https://anotherfile/hoster.ext',
+                       $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!" )
+               );
+       }
+
+       /**
+        * Constant values are from PHP 5.3.28 using cURL 7.24.0
+        * @see https://secure.php.net/manual/en/curl.constants.php
+        *
+        * All constant values are present so that developers don’t need to remember
+        * to add them if added at a later date. The commented out constants were
+        * not found anywhere in the MediaWiki core code.
+        *
+        * Commented out constants that were not available in:
+        * HipHop VM 3.3.0 (rel)
+        * Compiler: heads/master-0-g08810d920dfff59e0774cf2d651f92f13a637175
+        * Repo schema: 3214fc2c684a4520485f715ee45f33f2182324b1
+        * Extension API: 20140829
+        *
+        * Commented out constants that were removed in PHP 5.6.0
+        *
+        * @covers CurlHttpRequest::execute
+        */
+       public function provideCurlConstants() {
+               return [
+                       [ 'CURLAUTH_ANY' ],
+                       [ 'CURLAUTH_ANYSAFE' ],
+                       [ 'CURLAUTH_BASIC' ],
+                       [ 'CURLAUTH_DIGEST' ],
+                       [ 'CURLAUTH_GSSNEGOTIATE' ],
+                       [ 'CURLAUTH_NTLM' ],
+                       // [ 'CURLCLOSEPOLICY_CALLBACK' ], // removed in PHP 5.6.0
+                       // [ 'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' ], // removed in PHP 5.6.0
+                       // [ 'CURLCLOSEPOLICY_LEAST_TRAFFIC' ], // removed in PHP 5.6.0
+                       // [ 'CURLCLOSEPOLICY_OLDEST' ], // removed in PHP 5.6.0
+                       // [ 'CURLCLOSEPOLICY_SLOWEST' ], // removed in PHP 5.6.0
+                       [ 'CURLE_ABORTED_BY_CALLBACK' ],
+                       [ 'CURLE_BAD_CALLING_ORDER' ],
+                       [ 'CURLE_BAD_CONTENT_ENCODING' ],
+                       [ 'CURLE_BAD_FUNCTION_ARGUMENT' ],
+                       [ 'CURLE_BAD_PASSWORD_ENTERED' ],
+                       [ 'CURLE_COULDNT_CONNECT' ],
+                       [ 'CURLE_COULDNT_RESOLVE_HOST' ],
+                       [ 'CURLE_COULDNT_RESOLVE_PROXY' ],
+                       [ 'CURLE_FAILED_INIT' ],
+                       [ 'CURLE_FILESIZE_EXCEEDED' ],
+                       [ 'CURLE_FILE_COULDNT_READ_FILE' ],
+                       [ 'CURLE_FTP_ACCESS_DENIED' ],
+                       [ 'CURLE_FTP_BAD_DOWNLOAD_RESUME' ],
+                       [ 'CURLE_FTP_CANT_GET_HOST' ],
+                       [ 'CURLE_FTP_CANT_RECONNECT' ],
+                       [ 'CURLE_FTP_COULDNT_GET_SIZE' ],
+                       [ 'CURLE_FTP_COULDNT_RETR_FILE' ],
+                       [ 'CURLE_FTP_COULDNT_SET_ASCII' ],
+                       [ 'CURLE_FTP_COULDNT_SET_BINARY' ],
+                       [ 'CURLE_FTP_COULDNT_STOR_FILE' ],
+                       [ 'CURLE_FTP_COULDNT_USE_REST' ],
+                       [ 'CURLE_FTP_PORT_FAILED' ],
+                       [ 'CURLE_FTP_QUOTE_ERROR' ],
+                       [ 'CURLE_FTP_SSL_FAILED' ],
+                       [ 'CURLE_FTP_USER_PASSWORD_INCORRECT' ],
+                       [ 'CURLE_FTP_WEIRD_227_FORMAT' ],
+                       [ 'CURLE_FTP_WEIRD_PASS_REPLY' ],
+                       [ 'CURLE_FTP_WEIRD_PASV_REPLY' ],
+                       [ 'CURLE_FTP_WEIRD_SERVER_REPLY' ],
+                       [ 'CURLE_FTP_WEIRD_USER_REPLY' ],
+                       [ 'CURLE_FTP_WRITE_ERROR' ],
+                       [ 'CURLE_FUNCTION_NOT_FOUND' ],
+                       [ 'CURLE_GOT_NOTHING' ],
+                       [ 'CURLE_HTTP_NOT_FOUND' ],
+                       [ 'CURLE_HTTP_PORT_FAILED' ],
+                       [ 'CURLE_HTTP_POST_ERROR' ],
+                       [ 'CURLE_HTTP_RANGE_ERROR' ],
+                       [ 'CURLE_LDAP_CANNOT_BIND' ],
+                       [ 'CURLE_LDAP_INVALID_URL' ],
+                       [ 'CURLE_LDAP_SEARCH_FAILED' ],
+                       [ 'CURLE_LIBRARY_NOT_FOUND' ],
+                       [ 'CURLE_MALFORMAT_USER' ],
+                       [ 'CURLE_OBSOLETE' ],
+                       [ 'CURLE_OK' ],
+                       [ 'CURLE_OPERATION_TIMEOUTED' ],
+                       [ 'CURLE_OUT_OF_MEMORY' ],
+                       [ 'CURLE_PARTIAL_FILE' ],
+                       [ 'CURLE_READ_ERROR' ],
+                       [ 'CURLE_RECV_ERROR' ],
+                       [ 'CURLE_SEND_ERROR' ],
+                       [ 'CURLE_SHARE_IN_USE' ],
+                       // [ 'CURLE_SSH' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLE_SSL_CACERT' ],
+                       [ 'CURLE_SSL_CERTPROBLEM' ],
+                       [ 'CURLE_SSL_CIPHER' ],
+                       [ 'CURLE_SSL_CONNECT_ERROR' ],
+                       [ 'CURLE_SSL_ENGINE_NOTFOUND' ],
+                       [ 'CURLE_SSL_ENGINE_SETFAILED' ],
+                       [ 'CURLE_SSL_PEER_CERTIFICATE' ],
+                       [ 'CURLE_TELNET_OPTION_SYNTAX' ],
+                       [ 'CURLE_TOO_MANY_REDIRECTS' ],
+                       [ 'CURLE_UNKNOWN_TELNET_OPTION' ],
+                       [ 'CURLE_UNSUPPORTED_PROTOCOL' ],
+                       [ 'CURLE_URL_MALFORMAT' ],
+                       [ 'CURLE_URL_MALFORMAT_USER' ],
+                       [ 'CURLE_WRITE_ERROR' ],
+                       [ 'CURLFTPAUTH_DEFAULT' ],
+                       [ 'CURLFTPAUTH_SSL' ],
+                       [ 'CURLFTPAUTH_TLS' ],
+                       // [ 'CURLFTPMETHOD_MULTICWD' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLFTPMETHOD_NOCWD' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLFTPMETHOD_SINGLECWD' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLFTPSSL_ALL' ],
+                       [ 'CURLFTPSSL_CONTROL' ],
+                       [ 'CURLFTPSSL_NONE' ],
+                       [ 'CURLFTPSSL_TRY' ],
+                       // [ 'CURLINFO_CERTINFO' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLINFO_CONNECT_TIME' ],
+                       [ 'CURLINFO_CONTENT_LENGTH_DOWNLOAD' ],
+                       [ 'CURLINFO_CONTENT_LENGTH_UPLOAD' ],
+                       [ 'CURLINFO_CONTENT_TYPE' ],
+                       [ 'CURLINFO_EFFECTIVE_URL' ],
+                       [ 'CURLINFO_FILETIME' ],
+                       [ 'CURLINFO_HEADER_OUT' ],
+                       [ 'CURLINFO_HEADER_SIZE' ],
+                       [ 'CURLINFO_HTTP_CODE' ],
+                       [ 'CURLINFO_NAMELOOKUP_TIME' ],
+                       [ 'CURLINFO_PRETRANSFER_TIME' ],
+                       [ 'CURLINFO_PRIVATE' ],
+                       [ 'CURLINFO_REDIRECT_COUNT' ],
+                       [ 'CURLINFO_REDIRECT_TIME' ],
+                       // [ 'CURLINFO_REDIRECT_URL' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLINFO_REQUEST_SIZE' ],
+                       [ 'CURLINFO_SIZE_DOWNLOAD' ],
+                       [ 'CURLINFO_SIZE_UPLOAD' ],
+                       [ 'CURLINFO_SPEED_DOWNLOAD' ],
+                       [ 'CURLINFO_SPEED_UPLOAD' ],
+                       [ 'CURLINFO_SSL_VERIFYRESULT' ],
+                       [ 'CURLINFO_STARTTRANSFER_TIME' ],
+                       [ 'CURLINFO_TOTAL_TIME' ],
+                       [ 'CURLMSG_DONE' ],
+                       [ 'CURLM_BAD_EASY_HANDLE' ],
+                       [ 'CURLM_BAD_HANDLE' ],
+                       [ 'CURLM_CALL_MULTI_PERFORM' ],
+                       [ 'CURLM_INTERNAL_ERROR' ],
+                       [ 'CURLM_OK' ],
+                       [ 'CURLM_OUT_OF_MEMORY' ],
+                       [ 'CURLOPT_AUTOREFERER' ],
+                       [ 'CURLOPT_BINARYTRANSFER' ],
+                       [ 'CURLOPT_BUFFERSIZE' ],
+                       [ 'CURLOPT_CAINFO' ],
+                       [ 'CURLOPT_CAPATH' ],
+                       // [ 'CURLOPT_CERTINFO' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLOPT_CLOSEPOLICY' ], // removed in PHP 5.6.0
+                       [ 'CURLOPT_CONNECTTIMEOUT' ],
+                       [ 'CURLOPT_CONNECTTIMEOUT_MS' ],
+                       [ 'CURLOPT_COOKIE' ],
+                       [ 'CURLOPT_COOKIEFILE' ],
+                       [ 'CURLOPT_COOKIEJAR' ],
+                       [ 'CURLOPT_COOKIESESSION' ],
+                       [ 'CURLOPT_CRLF' ],
+                       [ 'CURLOPT_CUSTOMREQUEST' ],
+                       [ 'CURLOPT_DNS_CACHE_TIMEOUT' ],
+                       [ 'CURLOPT_DNS_USE_GLOBAL_CACHE' ],
+                       [ 'CURLOPT_EGDSOCKET' ],
+                       [ 'CURLOPT_ENCODING' ],
+                       [ 'CURLOPT_FAILONERROR' ],
+                       [ 'CURLOPT_FILE' ],
+                       [ 'CURLOPT_FILETIME' ],
+                       [ 'CURLOPT_FOLLOWLOCATION' ],
+                       [ 'CURLOPT_FORBID_REUSE' ],
+                       [ 'CURLOPT_FRESH_CONNECT' ],
+                       [ 'CURLOPT_FTPAPPEND' ],
+                       [ 'CURLOPT_FTPLISTONLY' ],
+                       [ 'CURLOPT_FTPPORT' ],
+                       [ 'CURLOPT_FTPSSLAUTH' ],
+                       [ 'CURLOPT_FTP_CREATE_MISSING_DIRS' ],
+                       // [ 'CURLOPT_FTP_FILEMETHOD' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLOPT_FTP_SKIP_PASV_IP' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLOPT_FTP_SSL' ],
+                       [ 'CURLOPT_FTP_USE_EPRT' ],
+                       [ 'CURLOPT_FTP_USE_EPSV' ],
+                       [ 'CURLOPT_HEADER' ],
+                       [ 'CURLOPT_HEADERFUNCTION' ],
+                       [ 'CURLOPT_HTTP200ALIASES' ],
+                       [ 'CURLOPT_HTTPAUTH' ],
+                       [ 'CURLOPT_HTTPGET' ],
+                       [ 'CURLOPT_HTTPHEADER' ],
+                       [ 'CURLOPT_HTTPPROXYTUNNEL' ],
+                       [ 'CURLOPT_HTTP_VERSION' ],
+                       [ 'CURLOPT_INFILE' ],
+                       [ 'CURLOPT_INFILESIZE' ],
+                       [ 'CURLOPT_INTERFACE' ],
+                       [ 'CURLOPT_IPRESOLVE' ],
+                       // [ 'CURLOPT_KEYPASSWD' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLOPT_KRB4LEVEL' ],
+                       [ 'CURLOPT_LOW_SPEED_LIMIT' ],
+                       [ 'CURLOPT_LOW_SPEED_TIME' ],
+                       [ 'CURLOPT_MAXCONNECTS' ],
+                       [ 'CURLOPT_MAXREDIRS' ],
+                       // [ 'CURLOPT_MAX_RECV_SPEED_LARGE' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLOPT_MAX_SEND_SPEED_LARGE' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLOPT_NETRC' ],
+                       [ 'CURLOPT_NOBODY' ],
+                       [ 'CURLOPT_NOPROGRESS' ],
+                       [ 'CURLOPT_NOSIGNAL' ],
+                       [ 'CURLOPT_PORT' ],
+                       [ 'CURLOPT_POST' ],
+                       [ 'CURLOPT_POSTFIELDS' ],
+                       [ 'CURLOPT_POSTQUOTE' ],
+                       [ 'CURLOPT_POSTREDIR' ],
+                       [ 'CURLOPT_PRIVATE' ],
+                       [ 'CURLOPT_PROGRESSFUNCTION' ],
+                       // [ 'CURLOPT_PROTOCOLS' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLOPT_PROXY' ],
+                       [ 'CURLOPT_PROXYAUTH' ],
+                       [ 'CURLOPT_PROXYPORT' ],
+                       [ 'CURLOPT_PROXYTYPE' ],
+                       [ 'CURLOPT_PROXYUSERPWD' ],
+                       [ 'CURLOPT_PUT' ],
+                       [ 'CURLOPT_QUOTE' ],
+                       [ 'CURLOPT_RANDOM_FILE' ],
+                       [ 'CURLOPT_RANGE' ],
+                       [ 'CURLOPT_READDATA' ],
+                       [ 'CURLOPT_READFUNCTION' ],
+                       // [ 'CURLOPT_REDIR_PROTOCOLS' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLOPT_REFERER' ],
+                       [ 'CURLOPT_RESUME_FROM' ],
+                       [ 'CURLOPT_RETURNTRANSFER' ],
+                       // [ 'CURLOPT_SSH_AUTH_TYPES' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLOPT_SSH_PRIVATE_KEYFILE' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLOPT_SSH_PUBLIC_KEYFILE' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLOPT_SSLCERT' ],
+                       [ 'CURLOPT_SSLCERTPASSWD' ],
+                       [ 'CURLOPT_SSLCERTTYPE' ],
+                       [ 'CURLOPT_SSLENGINE' ],
+                       [ 'CURLOPT_SSLENGINE_DEFAULT' ],
+                       [ 'CURLOPT_SSLKEY' ],
+                       [ 'CURLOPT_SSLKEYPASSWD' ],
+                       [ 'CURLOPT_SSLKEYTYPE' ],
+                       [ 'CURLOPT_SSLVERSION' ],
+                       [ 'CURLOPT_SSL_CIPHER_LIST' ],
+                       [ 'CURLOPT_SSL_VERIFYHOST' ],
+                       [ 'CURLOPT_SSL_VERIFYPEER' ],
+                       [ 'CURLOPT_STDERR' ],
+                       [ 'CURLOPT_TCP_NODELAY' ],
+                       [ 'CURLOPT_TIMECONDITION' ],
+                       [ 'CURLOPT_TIMEOUT' ],
+                       [ 'CURLOPT_TIMEOUT_MS' ],
+                       [ 'CURLOPT_TIMEVALUE' ],
+                       [ 'CURLOPT_TRANSFERTEXT' ],
+                       [ 'CURLOPT_UNRESTRICTED_AUTH' ],
+                       [ 'CURLOPT_UPLOAD' ],
+                       [ 'CURLOPT_URL' ],
+                       [ 'CURLOPT_USERAGENT' ],
+                       [ 'CURLOPT_USERPWD' ],
+                       [ 'CURLOPT_VERBOSE' ],
+                       [ 'CURLOPT_WRITEFUNCTION' ],
+                       [ 'CURLOPT_WRITEHEADER' ],
+                       // [ 'CURLPROTO_ALL' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_DICT' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_FILE' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_FTP' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_FTPS' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_HTTP' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_HTTPS' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_LDAP' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_LDAPS' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_SCP' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_SFTP' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_TELNET' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLPROTO_TFTP' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLPROXY_HTTP' ],
+                       // [ 'CURLPROXY_SOCKS4' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLPROXY_SOCKS5' ],
+                       // [ 'CURLSSH_AUTH_DEFAULT' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLSSH_AUTH_HOST' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLSSH_AUTH_KEYBOARD' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLSSH_AUTH_NONE' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLSSH_AUTH_PASSWORD' ], // not present in HHVM 3.3.0-dev
+                       // [ 'CURLSSH_AUTH_PUBLICKEY' ], // not present in HHVM 3.3.0-dev
+                       [ 'CURLVERSION_NOW' ],
+                       [ 'CURL_HTTP_VERSION_1_0' ],
+                       [ 'CURL_HTTP_VERSION_1_1' ],
+                       [ 'CURL_HTTP_VERSION_NONE' ],
+                       [ 'CURL_IPRESOLVE_V4' ],
+                       [ 'CURL_IPRESOLVE_V6' ],
+                       [ 'CURL_IPRESOLVE_WHATEVER' ],
+                       [ 'CURL_NETRC_IGNORED' ],
+                       [ 'CURL_NETRC_OPTIONAL' ],
+                       [ 'CURL_NETRC_REQUIRED' ],
+                       [ 'CURL_TIMECOND_IFMODSINCE' ],
+                       [ 'CURL_TIMECOND_IFUNMODSINCE' ],
+                       [ 'CURL_TIMECOND_LASTMOD' ],
+                       [ 'CURL_VERSION_IPV6' ],
+                       [ 'CURL_VERSION_KERBEROS4' ],
+                       [ 'CURL_VERSION_LIBZ' ],
+                       [ 'CURL_VERSION_SSL' ],
+               ];
+       }
+
+       /**
+        * Added this test based on an issue experienced with HHVM 3.3.0-dev
+        * where it did not define a cURL constant.
+        *
+        * @bug 70570
+        * @dataProvider provideCurlConstants
+        */
+       public function testCurlConstants( $value ) {
+               $this->assertTrue( defined( $value ), $value . ' not defined' );
+       }
+}
+
+/**
+ * Class to let us overwrite MWHttpRequest respHeaders variable
+ */
+class MWHttpRequestTester extends MWHttpRequest {
+       // function derived from the MWHttpRequest factory function but
+       // returns appropriate tester class here
+       public static function factory( $url, $options = null, $caller = __METHOD__ ) {
+               if ( !Http::$httpEngine ) {
+                       Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
+               } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
+                       throw new DomainException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
+                               'Http::$httpEngine is set to "curl"' );
+               }
+
+               switch ( Http::$httpEngine ) {
+                       case 'curl':
+                               return new CurlHttpRequestTester( $url, $options, $caller );
+                       case 'php':
+                               if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
+                                       throw new DomainException( __METHOD__ .
+                                               ': allow_url_fopen needs to be enabled for pure PHP HTTP requests to work. '
+                                                       . 'If possible, curl should be used instead. See http://php.net/curl.' );
+                               }
+
+                               return new PhpHttpRequestTester( $url, $options, $caller );
+                       default:
+               }
+       }
+}
+
+class CurlHttpRequestTester extends CurlHttpRequest {
+       function setRespHeaders( $name, $value ) {
+               $this->respHeaders[$name] = $value;
+       }
+}
+
+class PhpHttpRequestTester extends PhpHttpRequest {
+       function setRespHeaders( $name, $value ) {
+               $this->respHeaders[$name] = $value;
+       }
+}
diff --git a/tests/phpunit/includes/installer/DatabaseUpdaterTest.php b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php
deleted file mode 100644 (file)
index 2a75cf4..0000000
+++ /dev/null
@@ -1,286 +0,0 @@
-<?php
-
-class DatabaseUpdaterTest extends MediaWikiTestCase {
-
-       public function testSetAppliedUpdates() {
-               $db = new FakeDatabase();
-               $dbu = new FakeDatabaseUpdater( $db );
-               $dbu->setAppliedUpdates( "test", [] );
-               $expected = "updatelist-test-" . time() . "0";
-               $actual = $db->lastInsertData['ul_key'];
-               $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) );
-               $dbu->setAppliedUpdates( "test", [] );
-               $expected = "updatelist-test-" . time() . "1";
-               $actual = $db->lastInsertData['ul_key'];
-               $this->assertEquals( $expected, $actual, var_export( $db->lastInsertData, true ) );
-       }
-}
-
-class FakeDatabase extends Database {
-       public $lastInsertTable;
-       public $lastInsertData;
-
-       function __construct() {
-               $this->cliMode = true;
-               $this->connLogger = new \Psr\Log\NullLogger();
-               $this->queryLogger = new \Psr\Log\NullLogger();
-               $this->errorLogger = function ( Exception $e ) {
-                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
-               };
-               $this->currentDomain = DatabaseDomain::newUnspecified();
-       }
-
-       function clearFlag( $arg, $remember = self::REMEMBER_NOTHING ) {
-       }
-
-       function setFlag( $arg, $remember = self::REMEMBER_NOTHING ) {
-       }
-
-       public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
-               $this->lastInsertTable = $table;
-               $this->lastInsertData = $a;
-       }
-
-       /**
-        * Get the type of the DBMS, as it appears in $wgDBtype.
-        *
-        * @return string
-        */
-       function getType() {
-               // TODO: Implement getType() method.
-       }
-
-       /**
-        * Open a connection to the database. Usually aborts on failure
-        *
-        * @param string $server Database server host
-        * @param string $user Database user name
-        * @param string $password Database user password
-        * @param string $dbName Database name
-        * @return bool
-        * @throws DBConnectionError
-        */
-       function open( $server, $user, $password, $dbName ) {
-               // TODO: Implement open() method.
-       }
-
-       /**
-        * Fetch the next row from the given result object, in object form.
-        * Fields can be retrieved with $row->fieldname, with fields acting like
-        * member variables.
-        * If no more rows are available, false is returned.
-        *
-        * @param ResultWrapper|stdClass $res Object as returned from Database::query(), etc.
-        * @return stdClass|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       function fetchObject( $res ) {
-               // TODO: Implement fetchObject() method.
-       }
-
-       /**
-        * Fetch the next row from the given result object, in associative array
-        * form. Fields are retrieved with $row['fieldname'].
-        * If no more rows are available, false is returned.
-        *
-        * @param ResultWrapper $res Result object as returned from Database::query(), etc.
-        * @return array|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       function fetchRow( $res ) {
-               // TODO: Implement fetchRow() method.
-       }
-
-       /**
-        * Get the number of rows in a result object
-        *
-        * @param mixed $res A SQL result
-        * @return int
-        */
-       function numRows( $res ) {
-               // TODO: Implement numRows() method.
-       }
-
-       /**
-        * Get the number of fields in a result object
-        * @see https://secure.php.net/mysql_num_fields
-        *
-        * @param mixed $res A SQL result
-        * @return int
-        */
-       function numFields( $res ) {
-               // TODO: Implement numFields() method.
-       }
-
-       /**
-        * Get a field name in a result object
-        * @see https://secure.php.net/mysql_field_name
-        *
-        * @param mixed $res A SQL result
-        * @param int $n
-        * @return string
-        */
-       function fieldName( $res, $n ) {
-               // TODO: Implement fieldName() method.
-       }
-
-       /**
-        * Get the inserted value of an auto-increment row
-        *
-        * The value inserted should be fetched from nextSequenceValue()
-        *
-        * Example:
-        * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
-        * $dbw->insert( 'page', [ 'page_id' => $id ] );
-        * $id = $dbw->insertId();
-        *
-        * @return int
-        */
-       function insertId() {
-               // TODO: Implement insertId() method.
-       }
-
-       /**
-        * Change the position of the cursor in a result object
-        * @see https://secure.php.net/mysql_data_seek
-        *
-        * @param mixed $res A SQL result
-        * @param int $row
-        */
-       function dataSeek( $res, $row ) {
-               // TODO: Implement dataSeek() method.
-       }
-
-       /**
-        * Get the last error number
-        * @see https://secure.php.net/mysql_errno
-        *
-        * @return int
-        */
-       function lastErrno() {
-               // TODO: Implement lastErrno() method.
-       }
-
-       /**
-        * Get a description of the last error
-        * @see https://secure.php.net/mysql_error
-        *
-        * @return string
-        */
-       function lastError() {
-               // TODO: Implement lastError() method.
-       }
-
-       /**
-        * mysql_fetch_field() wrapper
-        * Returns false if the field doesn't exist
-        *
-        * @param string $table Table name
-        * @param string $field Field name
-        *
-        * @return Field
-        */
-       function fieldInfo( $table, $field ) {
-               // TODO: Implement fieldInfo() method.
-       }
-
-       /**
-        * Get information about an index into an object
-        * @param string $table Table name
-        * @param string $index Index name
-        * @param string $fname Calling function name
-        * @return mixed Database-specific index description class or false if the index does not exist
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               // TODO: Implement indexInfo() method.
-       }
-
-       /**
-        * Get the number of rows affected by the last write query
-        * @see https://secure.php.net/mysql_affected_rows
-        *
-        * @return int
-        */
-       function affectedRows() {
-               // TODO: Implement affectedRows() method.
-       }
-
-       /**
-        * Wrapper for addslashes()
-        *
-        * @param string $s String to be slashed.
-        * @return string Slashed string.
-        */
-       function strencode( $s ) {
-               // TODO: Implement strencode() method.
-       }
-
-       /**
-        * Returns a wikitext link to the DB's website, e.g.,
-        *   return "[https://www.mysql.com/ MySQL]";
-        * Should at least contain plain text, if for some reason
-        * your database has no website.
-        *
-        * @return string Wikitext of a link to the server software's web site
-        */
-       function getSoftwareLink() {
-               // TODO: Implement getSoftwareLink() method.
-       }
-
-       /**
-        * A string describing the current software version, like from
-        * mysql_get_server_info().
-        *
-        * @return string Version information from the database server.
-        */
-       function getServerVersion() {
-               // TODO: Implement getServerVersion() method.
-       }
-
-       /**
-        * Closes underlying database connection
-        * @since 1.20
-        * @return bool Whether connection was closed successfully
-        */
-       protected function closeConnection() {
-               // TODO: Implement closeConnection() method.
-       }
-
-       /**
-        * The DBMS-dependent part of query()
-        *
-        * @param string $sql SQL query.
-        * @return ResultWrapper|bool Result object to feed to fetchObject,
-        *   fetchRow, ...; or false on failure
-        */
-       protected function doQuery( $sql ) {
-               // TODO: Implement doQuery() method.
-       }
-}
-
-class FakeDatabaseUpdater extends DatabaseUpdater {
-       function __construct( $db ) {
-               $this->db = $db;
-               self::$updateCounter = 0;
-       }
-
-       /**
-        * Get an array of updates to perform on the database. Should return a
-        * multi-dimensional array. The main key is the MediaWiki version (1.12,
-        * 1.13...) with the values being arrays of updates, identical to how
-        * updaters.inc did it (for now)
-        *
-        * @return array
-        */
-       protected function getCoreUpdateList() {
-               return [];
-       }
-
-       public function canUseNewUpdatelog() {
-               return true;
-       }
-
-       public function setAppliedUpdates( $version, $updates = [] ) {
-               parent::setAppliedUpdates( $version, $updates );
-       }
-}
diff --git a/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php b/tests/phpunit/includes/libs/rdbms/connectionmanager/ConnectionManagerTest.php
new file mode 100644 (file)
index 0000000..1677851
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+
+namespace Wikimedia\Tests\Rdbms;
+
+use IDatabase;
+use LoadBalancer;
+use PHPUnit_Framework_MockObject_MockObject;
+use Wikimedia\Rdbms\ConnectionManager;
+
+/**
+ * @covers Wikimedia\Rdbms\ConnectionManager
+ *
+ * @license GPL-2.0+
+ * @author Daniel Kinzler
+ */
+class ConnectionManagerTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @return IDatabase|PHPUnit_Framework_MockObject_MockObject
+        */
+       private function getIDatabaseMock() {
+               return $this->getMock( IDatabase::class );
+       }
+
+       /**
+        * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
+        */
+       private function getLoadBalancerMock() {
+               $lb = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               return $lb;
+       }
+
+       public function testGetReadConnection_nullGroups() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_REPLICA, [ 'group1' ], 'someDbName' )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+               $actual = $manager->getReadConnection();
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testGetReadConnection_withGroups() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_REPLICA, [ 'group2' ], 'someDbName' )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+               $actual = $manager->getReadConnection( [ 'group2' ] );
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testGetWriteConnection() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_MASTER, [ 'group1' ], 'someDbName' )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+               $actual = $manager->getWriteConnection();
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testReleaseConnection() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'reuseConnection' )
+                       ->with( $database )
+                       ->will( $this->returnValue( null ) );
+
+               $manager = new ConnectionManager( $lb );
+               $manager->releaseConnection( $database );
+       }
+
+       public function testGetReadConnectionRef_nullGroups() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnectionRef' )
+                       ->with( DB_REPLICA, [ 'group1' ], 'someDbName' )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+               $actual = $manager->getReadConnectionRef();
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testGetReadConnectionRef_withGroups() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnectionRef' )
+                       ->with( DB_REPLICA, [ 'group2' ], 'someDbName' )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+               $actual = $manager->getReadConnectionRef( [ 'group2' ] );
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testGetWriteConnectionRef() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnectionRef' )
+                       ->with( DB_MASTER, [ 'group1' ], 'someDbName' )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+               $actual = $manager->getWriteConnectionRef();
+
+               $this->assertSame( $database, $actual );
+       }
+
+}
diff --git a/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php b/tests/phpunit/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManagerTest.php
new file mode 100644 (file)
index 0000000..0d54659
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+namespace Wikimedia\Tests\Rdbms;
+
+use IDatabase;
+use LoadBalancer;
+use PHPUnit_Framework_MockObject_MockObject;
+use Wikimedia\Rdbms\SessionConsistentConnectionManager;
+
+/**
+ * @covers Wikimedia\Rdbms\SessionConsistentConnectionManager
+ *
+ * @license GPL-2.0+
+ * @author Daniel Kinzler
+ */
+class SessionConsistentConnectionManagerTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @return IDatabase|PHPUnit_Framework_MockObject_MockObject
+        */
+       private function getIDatabaseMock() {
+               return $this->getMock( IDatabase::class );
+       }
+
+       /**
+        * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
+        */
+       private function getLoadBalancerMock() {
+               $lb = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               return $lb;
+       }
+
+       public function testGetReadConnection() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_REPLICA )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new SessionConsistentConnectionManager( $lb );
+               $actual = $manager->getReadConnection();
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testGetReadConnectionReturnsWriteDbOnForceMatser() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_MASTER )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new SessionConsistentConnectionManager( $lb );
+               $manager->prepareForUpdates();
+               $actual = $manager->getReadConnection();
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testGetWriteConnection() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_MASTER )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new SessionConsistentConnectionManager( $lb );
+               $actual = $manager->getWriteConnection();
+
+               $this->assertSame( $database, $actual );
+       }
+
+       public function testForceMaster() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'getConnection' )
+                       ->with( DB_MASTER )
+                       ->will( $this->returnValue( $database ) );
+
+               $manager = new SessionConsistentConnectionManager( $lb );
+               $manager->prepareForUpdates();
+               $manager->getReadConnection();
+       }
+
+       public function testReleaseConnection() {
+               $database = $this->getIDatabaseMock();
+               $lb = $this->getLoadBalancerMock();
+
+               $lb->expects( $this->once() )
+                       ->method( 'reuseConnection' )
+                       ->with( $database )
+                       ->will( $this->returnValue( null ) );
+
+               $manager = new SessionConsistentConnectionManager( $lb );
+               $manager->releaseConnection( $database );
+       }
+}
index a96a296..7d0813d 100644 (file)
@@ -63,13 +63,9 @@ class ArticleTest extends MediaWikiTestCase {
         * @covers Article::onArticleCreate
         * @covers Article::onArticleDelete
         * @covers Article::onArticleEdit
-        * @covers Article::getAutosummary
         */
        public function testStaticFunctions() {
                $this->hideDeprecated( 'Article::selectFields' );
-               $this->hideDeprecated( 'Article::getAutosummary' );
-               $this->hideDeprecated( 'WikiPage::getAutosummary' );
-               $this->hideDeprecated( 'CategoryPage::getAutosummary' ); // Inherited from Article
 
                $this->assertEquals( WikiPage::selectFields(), Article::selectFields(),
                        "Article static functions" );
@@ -79,7 +75,5 @@ class ArticleTest extends MediaWikiTestCase {
                        "Article static functions" );
                $this->assertEquals( true, is_callable( "ImagePage::onArticleEdit" ),
                        "Article static functions" );
-               $this->assertTrue( is_string( CategoryPage::getAutosummary( '', '', 0 ) ),
-                       "Article static functions" );
        }
 }
index 4b7ebd3..6885ca3 100644 (file)
@@ -5,7 +5,7 @@
  * @group Database
  * ^--- important, causes temporary tables to be used instead of the real database
  * @group medium
- **/
+ */
 class WikiPageTest extends MediaWikiLangTestCase {
 
        protected $pages_to_delete;
@@ -161,7 +161,6 @@ class WikiPageTest extends MediaWikiLangTestCase {
         */
        public function testDoEdit() {
                $this->hideDeprecated( "WikiPage::doEdit" );
-               $this->hideDeprecated( "WikiPage::getText" );
                $this->hideDeprecated( "Revision::getText" );
 
                // NOTE: assume help namespace will default to wikitext
@@ -189,12 +188,6 @@ class WikiPageTest extends MediaWikiLangTestCase {
 
                $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
 
-               # ------------------------
-               $page = new WikiPage( $title );
-
-               $retrieved = $page->getText();
-               $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' );
-
                # ------------------------
                $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
                        . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.";
@@ -204,7 +197,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
                # ------------------------
                $page = new WikiPage( $title );
 
-               $retrieved = $page->getText();
+               $retrieved = $page->getContent()->getNativeData();
                $this->assertEquals( $text, $retrieved, 'retrieved text doesn\'t equal original' );
 
                # ------------------------
@@ -242,10 +235,6 @@ class WikiPageTest extends MediaWikiLangTestCase {
                        $page->getContent(),
                        "WikiPage::getContent should return null after page was deleted"
                );
-               $this->assertFalse(
-                       $page->getText(),
-                       "WikiPage::getText should return false after page was deleted"
-               );
 
                $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
                $this->assertFalse(
@@ -332,24 +321,6 @@ class WikiPageTest extends MediaWikiLangTestCase {
                $this->assertEquals( "some text", $content->getNativeData() );
        }
 
-       /**
-        * @covers WikiPage::getText
-        */
-       public function testGetText() {
-               $this->hideDeprecated( "WikiPage::getText" );
-
-               $page = $this->newPage( "WikiPageTest_testGetText" );
-
-               $text = $page->getText();
-               $this->assertFalse( $text );
-
-               # -----------------
-               $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
-
-               $text = $page->getText();
-               $this->assertEquals( "some text", $text );
-       }
-
        /**
         * @covers WikiPage::getContentModel
         */
@@ -1034,63 +1005,6 @@ more stuff
                $this->assertEquals( "one", $page->getContent()->getNativeData() );
        }
 
-       public static function provideGetAutosummary() {
-               return [
-                       [
-                               'Hello there, world!',
-                               '#REDIRECT [[Foo]]',
-                               0,
-                               '/^Redirected page .*Foo/'
-                       ],
-
-                       [
-                               null,
-                               'Hello world!',
-                               EDIT_NEW,
-                               '/^Created page .*Hello/'
-                       ],
-
-                       [
-                               'Hello there, world!',
-                               '',
-                               0,
-                               '/^Blanked/'
-                       ],
-
-                       [
-                               'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
-                               eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
-                               voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
-                               clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
-                               'Hello world!',
-                               0,
-                               '/^Replaced .*Hello/'
-                       ],
-
-                       [
-                               'foo',
-                               'bar',
-                               0,
-                               '/^$/'
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetAutoSummary
-        * @covers WikiPage::getAutosummary
-        */
-       public function testGetAutosummary( $old, $new, $flags, $expected ) {
-               $this->hideDeprecated( "WikiPage::getAutosummary" );
-
-               $page = $this->newPage( "WikiPageTest_testGetAutosummary" );
-
-               $summary = $page->getAutosummary( $old, $new, $flags );
-
-               $this->assertTrue( (bool)preg_match( $expected, $summary ),
-                       "Autosummary didn't match expected pattern $expected: $summary" );
-       }
-
        public static function provideGetAutoDeleteReason() {
                return [
                        [
index 1de4265..9b57e1c 100644 (file)
@@ -259,7 +259,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase {
                                                'JsonZeroConfig' => [
                                                        'namespace' => 480,
                                                        'nsName' => 'Zero',
-                                                       'isLocal' => false,
+                                                       'isLocal' => true,
                                                ],
                                        ],
                                ],
index 1093039..baf0b69 100644 (file)
@@ -104,4 +104,13 @@ class ResourceLoaderContextTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( 'Example', $ctx->getUser() );
                $this->assertEquals( 'Example', $ctx->getUserObj()->getName() );
        }
+
+       public function testMsg() {
+               $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [
+                       'lang' => 'en'
+               ] ) );
+               $msg = $ctx->msg( 'mainpage' );
+               $this->assertInstanceOf( Message::class, $msg );
+               $this->assertSame( 'Main Page', $msg->useDatabase( false )->plain() );
+       }
 }
index 8b29983..4a3b90a 100644 (file)
@@ -130,7 +130,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                $modules = self::getModules();
                $rl = new ResourceLoaderFileModule( $modules[$name] );
                $rl->setName( $name );
-               $ctx = $this->getResourceLoaderContext( 'en', 'ltr' );
+               $ctx = $this->getResourceLoaderContext();
                $this->assertEquals( $rl->getScript( $ctx ), $expected );
        }
 
@@ -210,8 +210,14 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                ] );
                $expectedModule->setName( 'testing' );
 
-               $contextLtr = $this->getResourceLoaderContext( 'en', 'ltr' );
-               $contextRtl = $this->getResourceLoaderContext( 'he', 'rtl' );
+               $contextLtr = $this->getResourceLoaderContext( [
+                       'lang' => 'en',
+                       'dir' => 'ltr',
+               ] );
+               $contextRtl = $this->getResourceLoaderContext( [
+                       'lang' => 'he',
+                       'dir' => 'rtl',
+               ] );
 
                // Since we want to compare the effect of @noflip+@embed against the effect of just @embed, and
                // the @noflip annotations are always preserved, we need to strip them first.
@@ -282,9 +288,9 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                        'File has leading BOM'
                );
 
-               $contextLtr = $this->getResourceLoaderContext( 'en', 'ltr' );
+               $context = $this->getResourceLoaderContext();
                $this->assertEquals(
-                       $testModule->getStyles( $contextLtr ),
+                       $testModule->getStyles( $context ),
                        [ 'all' => ".efbbbf_bom_char_at_start_of_file {}\n" ],
                        'Leading BOM removed when concatenating files'
                );
index 179a8ed..84b56d4 100644 (file)
@@ -61,7 +61,10 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
                static $contexts = [];
 
                $image = $this->getTestImage( $imageName );
-               $context = $this->getResourceLoaderContext( $languageCode, $dirMap[$languageCode] );
+               $context = $this->getResourceLoaderContext( [
+                       'lang' => $languageCode,
+                       'dir' => $dirMap[$languageCode],
+               ] );
 
                $this->assertEquals( $image->getPath( $context ), $this->imagesPath . '/' . $path );
        }
@@ -87,7 +90,7 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
         * @covers ResourceLoaderImage::massageSvgPathdata
         */
        public function testGetImageData() {
-               $context = $this->getResourceLoaderContext( 'en', 'ltr' );
+               $context = $this->getResourceLoaderContext();
 
                $image = $this->getTestImage( 'remove' );
                $data = file_get_contents( $this->imagesPath . '/remove.svg' );
index e0de588..a88264b 100644 (file)
@@ -126,11 +126,11 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
                                'results' => [
                                        'Special:ActiveUsers',
                                        'Special:AllMessages',
-                                       'Special:AllMyFiles',
+                                       'Special:AllMyUploads',
                                ],
                                // Third result when testing offset
                                'offsetresult' => [
-                                       'Special:AllMyUploads',
+                                       'Special:AllPages',
                                ],
                        ] ],
                        [ [
@@ -143,7 +143,7 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
                                ],
                                // Third result when testing offset
                                'offsetresult' => [
-                                       'Special:UncategorizedImages',
+                                       'Special:UncategorizedPages',
                                ],
                        ] ],
                        [ [
index c51217c..c11e6a3 100644 (file)
@@ -48,12 +48,24 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                );
 
                $this->assertEquals(
-                       $expected,
-                       $queryConditions,
+                       self::normalizeCondition( $expected ),
+                       self::normalizeCondition( $queryConditions ),
                        $message
                );
        }
 
+       private static function normalizeCondition( $conds ) {
+               $normalized = array_map(
+                       function ( $k, $v ) {
+                               return is_numeric( $k ) ? $v : "$k = $v";
+                       },
+                       array_keys( $conds ),
+                       $conds
+               );
+               sort( $normalized );
+               return $normalized;
+       }
+
        /** return false if condition begin with 'rc_timestamp ' */
        private static function filterOutRcTimestampCondition( $var ) {
                return ( false === strpos( $var, 'rc_timestamp ' ) );
@@ -63,8 +75,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_type != '6'",
-                               1 => "rc_namespace = '0'",
+                               "rc_type != '6'",
+                               "rc_namespace = '0'",
                        ],
                        [
                                'namespace' => NS_MAIN,
@@ -77,8 +89,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_type != '6'",
-                               1 => sprintf( "rc_namespace != '%s'", NS_MAIN ),
+                               "rc_type != '6'",
+                               "rc_namespace != '0'",
                        ],
                        [
                                'namespace' => NS_MAIN,
@@ -96,8 +108,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_type != '6'",
-                               1 => sprintf( "(rc_namespace = '%s' OR rc_namespace = '%s')", $ns1, $ns2 ),
+                               "rc_type != '6'",
+                               "(rc_namespace = '$ns1' OR rc_namespace = '$ns2')",
                        ],
                        [
                                'namespace' => $ns1,
@@ -115,8 +127,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_type != '6'",
-                               1 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ),
+                               "rc_type != '6'",
+                               "(rc_namespace != '$ns1' AND rc_namespace != '$ns2')",
                        ],
                        [
                                'namespace' => $ns1,
@@ -143,8 +155,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_user != '{$user->getId()}'",
-                               1 => "rc_type != '6'",
+                               "rc_user != '{$user->getId()}'",
+                               "rc_type != '6'",
                        ],
                        [
                                'hidemyself' => 1,
@@ -157,8 +169,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_user_text != '10.11.12.13'",
-                               1 => "rc_type != '6'",
+                               "rc_user_text != '10.11.12.13'",
+                               "rc_type != '6'",
                        ],
                        [
                                'hidemyself' => 1,
@@ -173,8 +185,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_user = '{$user->getId()}'",
-                               1 => "rc_type != '6'",
+                               "rc_user = '{$user->getId()}'",
+                               "rc_type != '6'",
                        ],
                        [
                                'hidebyothers' => 1,
@@ -187,8 +199,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_user_text = '10.11.12.13'",
-                               1 => "rc_type != '6'",
+                               "rc_user_text = '10.11.12.13'",
+                               "rc_type != '6'",
                        ],
                        [
                                'hidebyothers' => 1,
@@ -203,9 +215,9 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        [ # expected
                                'rc_bot' => 0,
-                               0 => "rc_user != '{$user->getId()}'",
-                               1 => "rc_user = '{$user->getId()}'",
-                               2 => "rc_type != '6'",
+                               "rc_user != '{$user->getId()}'",
+                               "rc_user = '{$user->getId()}'",
+                               "rc_type != '6'",
                        ],
                        [
                                'hidemyself' => 1,
@@ -215,4 +227,170 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                        $user
                );
        }
+
+       public function testRcHidepageedits() {
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_type != '6'",
+                               "rc_type != '0'",
+                       ],
+                       [
+                               'hidepageedits' => 1,
+                       ],
+                       "rc conditions: hidepageedits=1"
+               );
+       }
+
+       public function testRcHidenewpages() {
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_type != '6'",
+                               "rc_type != '1'",
+                       ],
+                       [
+                               'hidenewpages' => 1,
+                       ],
+                       "rc conditions: hidenewpages=1"
+               );
+       }
+
+       public function testRcHidelog() {
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_type != '6'",
+                               "rc_type != '3'",
+                       ],
+                       [
+                               'hidelog' => 1,
+                       ],
+                       "rc conditions: hidelog=1"
+               );
+       }
+
+       public function testRcHidehumans() {
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 1,
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hidebots' => 0,
+                               'hidehumans' => 1,
+                       ],
+                       "rc conditions: hidebots=0 hidehumans=1"
+               );
+       }
+
+       public function testRcHidepatrolledDisabledFilter() {
+               $user = $this->getTestUser()->getUser();
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hidepatrolled' => 1,
+                       ],
+                       "rc conditions: hidepatrolled=1 (user not allowed)",
+                       $user
+               );
+       }
+
+       public function testRcHideunpatrolledDisabledFilter() {
+               $user = $this->getTestUser()->getUser();
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hideunpatrolled' => 1,
+                       ],
+                       "rc conditions: hideunpatrolled=1 (user not allowed)",
+                       $user
+               );
+       }
+       public function testRcHidepatrolledFilter() {
+               $user = $this->getTestSysop()->getUser();
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_patrolled = 0",
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hidepatrolled' => 1,
+                       ],
+                       "rc conditions: hidepatrolled=1",
+                       $user
+               );
+       }
+
+       public function testRcHideunpatrolledFilter() {
+               $user = $this->getTestSysop()->getUser();
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_patrolled = 1",
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hideunpatrolled' => 1,
+                       ],
+                       "rc conditions: hideunpatrolled=1",
+                       $user
+               );
+       }
+
+       public function testRcHideminorFilter() {
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_minor = 0",
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hideminor' => 1,
+                       ],
+                       "rc conditions: hideminor=1"
+               );
+       }
+
+       public function testRcHidemajorFilter() {
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_minor = 1",
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hidemajor' => 1,
+                       ],
+                       "rc conditions: hidemajor=1"
+               );
+       }
+
+       // This is probably going to change when we do auto-fix of
+       // filters combinations that don't make sense but for now
+       // it's the behavior therefore it's the test.
+       public function testRcHidepatrolledHideunpatrolledFilter() {
+               $user = $this->getTestSysop()->getUser();
+               $this->assertConditions(
+                       [ # expected
+                               'rc_bot' => 0,
+                               "rc_patrolled = 0",
+                               "rc_patrolled = 1",
+                               "rc_type != '6'",
+                       ],
+                       [
+                               'hidepatrolled' => 1,
+                               'hideunpatrolled' => 1,
+                       ],
+                       "rc conditions: hidepatrolled=1 hideunpatrolled=1",
+                       $user
+               );
+       }
 }
index 6d17a68..62081aa 100644 (file)
@@ -58,7 +58,7 @@ class UploadFromUrlTest extends ApiTestCase {
                        $this->doApiRequest( [
                                'action' => 'upload',
                        ] );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                        $this->assertEquals( "The token parameter must be set", $e->getMessage() );
                }
@@ -70,7 +70,7 @@ class UploadFromUrlTest extends ApiTestCase {
                                'action' => 'upload',
                                'token' => $token,
                        ], $data );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                        $this->assertEquals( "One of the parameters sessionkey, file, url is required",
                                $e->getMessage() );
@@ -84,7 +84,7 @@ class UploadFromUrlTest extends ApiTestCase {
                                'url' => 'http://www.example.com/test.png',
                                'token' => $token,
                        ], $data );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                        $this->assertEquals( "The filename parameter must be set", $e->getMessage() );
                }
@@ -99,7 +99,7 @@ class UploadFromUrlTest extends ApiTestCase {
                                'filename' => 'UploadFromUrlTest.png',
                                'token' => $token,
                        ], $data );
-               } catch ( UsageException $e ) {
+               } catch ( ApiUsageException $e ) {
                        $exception = true;
                        $this->assertEquals( "Permission denied", $e->getMessage() );
                }
index cbf94d6..cb3d227 100644 (file)
@@ -24,7 +24,6 @@ use MediaWiki\MediaWikiServices;
  * Due to a hack in Maintenance.php using register_shutdown_function, be sure to
  * finally call simulateShutdown on MaintenanceFixup instance before a test
  * ends.
- *
  */
 class MaintenanceFixup extends Maintenance {
 
index 6ed4495..d712254 100644 (file)
@@ -34,7 +34,6 @@ class DummyContentHandlerForTesting extends ContentHandler {
 
        /**
         * Creates an empty Content object of the type supported by this ContentHandler.
-        *
         */
        public function makeEmptyContent() {
                return new DummyContentForTesting( '' );
index e11fd8a..8ba2aeb 100644 (file)
@@ -25,14 +25,16 @@ use JsonSchema\Validator;
  */
 class ExtensionJsonValidationTest extends PHPUnit_Framework_TestCase {
 
+       /**
+        * @var ExtensionJsonValidator
+        */
+       protected $validator;
+
        public function setUp() {
                parent::setUp();
-               if ( !class_exists( Validator::class ) ) {
-                       $this->markTestSkipped(
-                               'The JsonSchema library cannot be found,' .
-                               ' please install it through composer to run extension.json validation tests.'
-                       );
-               }
+
+               $this->validator = new ExtensionJsonValidator( [ $this, 'markTestSkipped' ] );
+               $this->validator->checkDependencies();
 
                if ( !ExtensionRegistry::getInstance()->getAllThings() ) {
                        $this->markTestSkipped(
@@ -55,56 +57,12 @@ class ExtensionJsonValidationTest extends PHPUnit_Framework_TestCase {
         * @param string $path Path to thing's json file
         */
        public function testPassesValidation( $path ) {
-               $data = json_decode( file_get_contents( $path ) );
-               $this->assertInstanceOf( 'stdClass', $data, "$path is not valid JSON" );
-
-               $this->assertObjectHasAttribute( 'manifest_version', $data,
-                       "$path does not have manifest_version set." );
-               $version = $data->manifest_version;
-               if ( $version !== ExtensionRegistry::MANIFEST_VERSION ) {
-                       $schemaPath = __DIR__ . "/../../../docs/extension.schema.v$version.json";
-               } else {
-                       $schemaPath = __DIR__ . '/../../../docs/extension.schema.json';
-               }
-
-               // Not too old
-               $this->assertTrue(
-                       $version >= ExtensionRegistry::OLDEST_MANIFEST_VERSION,
-                       "$path is using a non-supported schema version"
-               );
-               // Not too new
-               $this->assertTrue(
-                       $version <= ExtensionRegistry::MANIFEST_VERSION,
-                       "$path is using a non-supported schema version"
-               );
-
-               $licenseError = false;
-               if ( class_exists( SpdxLicenses::class ) && isset( $data->{'license-name'} )
-                       // Check if it's a string, if not, schema validation will display an error
-                       && is_string( $data->{'license-name'} )
-               ) {
-                       $licenses = new SpdxLicenses();
-                       $valid = $licenses->validate( $data->{'license-name'} );
-                       if ( !$valid ) {
-                               $licenseError = '[license-name] Invalid SPDX license identifier, '
-                                       . 'see <https://spdx.org/licenses/>';
-                       }
-               }
-
-               $validator = new Validator;
-               $validator->check( $data, (object)[ '$ref' => 'file://' . $schemaPath ] );
-               if ( $validator->isValid() && !$licenseError ) {
-                       // All good.
+               try {
+                       $this->validator->validate( $path );
+                       // All good
                        $this->assertTrue( true );
-               } else {
-                       $out = "$path did pass validation.\n";
-                       foreach ( $validator->getErrors() as $error ) {
-                               $out .= "[{$error['property']}] {$error['message']}\n";
-                       }
-                       if ( $licenseError ) {
-                               $out .= "$licenseError\n";
-                       }
-                       $this->assertTrue( false, $out );
+               } catch ( ExtensionJsonValidationError $e ) {
+                       $this->assertEquals( false, $e->getMessage() );
                }
        }
 }
index 2e6bf37..2fba76b 100644 (file)
@@ -11,7 +11,6 @@
  * @copyright © 2012, Niklas Laxström
  * @copyright © 2012, Santhosh Thottingal
  * @copyright © 2012, Timo Tijhof
- *
  */
 class ResourcesTest extends MediaWikiTestCase {
 
index 9d0fdf5..1676130 100644 (file)
@@ -3,16 +3,10 @@
                setup: function () {
                        this.server = this.sandbox.useFakeServer();
                        this.server.respondImmediately = true;
-                       this.clock = this.sandbox.useFakeTimers();
-               },
-               teardown: function () {
-                       // https://github.com/jquery/jquery/issues/2453
-                       this.clock.tick();
                }
        } ) );
 
-       QUnit.test( 'origin is included in GET requests', function ( assert ) {
-               QUnit.expect( 1 );
+       QUnit.test( 'origin is included in GET requests', 1, function ( assert ) {
                var api = new mw.ForeignApi( '//localhost:4242/w/api.php' );
 
                this.server.respond( function ( request ) {
                        request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
                } );
 
-               api.get( {} );
+               return api.get( {} );
        } );
 
-       QUnit.test( 'origin is included in POST requests', function ( assert ) {
-               QUnit.expect( 2 );
+       QUnit.test( 'origin is included in POST requests', 2, function ( assert ) {
                var api = new mw.ForeignApi( '//localhost:4242/w/api.php' );
 
                this.server.respond( function ( request ) {
@@ -33,7 +26,7 @@
                        request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
                } );
 
-               api.post( {} );
+               return api.post( {} );
        } );
 
 }( mediaWiki ) );
index a0c7daf..a79bff6 100644 (file)
@@ -2,29 +2,24 @@
        QUnit.module( 'mediawiki.api.category', QUnit.newMwEnvironment( {
                setup: function () {
                        this.server = this.sandbox.useFakeServer();
+                       this.server.respondImmediately = true;
                }
        } ) );
 
-       QUnit.test( '.getCategoriesByPrefix()', function ( assert ) {
-               QUnit.expect( 1 );
+       QUnit.test( '.getCategoriesByPrefix()', 1, function ( assert ) {
+               this.server.respondWith( [ 200, { 'Content-Type': 'application/json' },
+                       '{ "query": { "allpages": [ ' +
+                               '{ "title": "Category:Food" },' +
+                               '{ "title": "Category:Fool Supermarine S.6" },' +
+                               '{ "title": "Category:Fools" }' +
+                               '] } }'
+               ] );
 
-               var api = new mw.Api();
-
-               api.getCategoriesByPrefix( 'Foo' ).done( function ( matches ) {
+               return new mw.Api().getCategoriesByPrefix( 'Foo' ).then( function ( matches ) {
                        assert.deepEqual(
                                matches,
                                [ 'Food', 'Fool Supermarine S.6', 'Fools' ]
                        );
                } );
-
-               this.server.respond( function ( req ) {
-                       req.respond( 200, { 'Content-Type': 'application/json' },
-                               '{ "query": { "allpages": [ ' +
-                                       '{ "title": "Category:Food" },' +
-                                       '{ "title": "Category:Fool Supermarine S.6" },' +
-                                       '{ "title": "Category:Fools" }' +
-                                       '] } }'
-                       );
-               } );
        } );
 }( mediaWiki ) );
index 5880962..d8b5db8 100644 (file)
@@ -2,14 +2,21 @@
        QUnit.module( 'mediawiki.api.messages', QUnit.newMwEnvironment( {
                setup: function () {
                        this.server = this.sandbox.useFakeServer();
+                       this.server.respondImmediately = true;
                }
        } ) );
 
-       QUnit.test( '.getMessages()', function ( assert ) {
-               QUnit.expect( 1 );
+       QUnit.test( '.getMessages()', 1, function ( assert ) {
+               this.server.respondWith( /ammessages=foo%7Cbaz/, [
+                       200,
+                       { 'Content-Type': 'application/json' },
+                       '{ "query": { "allmessages": [' +
+                               '{ "name": "foo", "content": "Foo bar" },' +
+                               '{ "name": "baz", "content": "Baz Quux" }' +
+                               '] } }'
+               ] );
 
-               var api = new mw.Api();
-               api.getMessages( [ 'foo', 'baz' ] ).then( function ( messages ) {
+               return new mw.Api().getMessages( [ 'foo', 'baz' ] ).then( function ( messages ) {
                        assert.deepEqual(
                                messages,
                                {
                                }
                        );
                } );
-
-               this.server.respond( /ammessages=foo%7Cbaz/, [
-                       200,
-                       { 'Content-Type': 'application/json' },
-                       '{ "query": { "allmessages": [' +
-                               '{ "name": "foo", "content": "Foo bar" },' +
-                               '{ "name": "baz", "content": "Baz Quux" }' +
-                               '] } }'
-               ] );
        } );
 }( mediaWiki ) );
index 0797f32..7ed1875 100644 (file)
@@ -2,14 +2,12 @@
        QUnit.module( 'mediawiki.api.options', QUnit.newMwEnvironment( {
                setup: function () {
                        this.server = this.sandbox.useFakeServer();
+                       this.server.respondImmediately = true;
                }
        } ) );
 
-       QUnit.test( 'saveOption', function ( assert ) {
-               QUnit.expect( 2 );
-
-               var
-                       api = new mw.Api(),
+       QUnit.test( 'saveOption', 2, function ( assert ) {
+               var api = new mw.Api(),
                        stub = this.sandbox.stub( mw.Api.prototype, 'saveOptions' );
 
                api.saveOption( 'foo', 'bar' );
@@ -18,9 +16,7 @@
                assert.deepEqual( stub.getCall( 0 ).args, [ { foo: 'bar' } ], '#saveOptions called correctly' );
        } );
 
-       QUnit.test( 'saveOptions without Unit Separator', function ( assert ) {
-               QUnit.expect( 13 );
-
+       QUnit.test( 'saveOptions without Unit Separator', 13, function ( assert ) {
                var api = new mw.Api( { useUS: false } );
 
                // We need to respond to the request for token first, otherwise the other requests won't be sent
                                '{ "query": { "tokens": { "csrftoken": "+\\\\" } } }' ]
                );
 
-               api.saveOptions( {} ).done( function () {
-                       assert.ok( true, 'Request completed: empty case' );
-               } );
-               api.saveOptions( { foo: 'bar' } ).done( function () {
-                       assert.ok( true, 'Request completed: simple' );
-               } );
-               api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () {
-                       assert.ok( true, 'Request completed: two options' );
-               } );
-               api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () {
-                       assert.ok( true, 'Request completed: not bundleable' );
-               } );
-               api.saveOptions( { foo: null } ).done( function () {
-                       assert.ok( true, 'Request completed: reset an option' );
-               } );
-               api.saveOptions( { 'foo|bar=quux': null } ).done( function () {
-                       assert.ok( true, 'Request completed: reset an option, not bundleable' );
-               } );
-
                // Requests are POST, match requestBody instead of url
                this.server.respond( function ( request ) {
                        switch ( request.requestBody ) {
                                        assert.ok( false, 'Unexpected request: ' + request.requestBody );
                        }
                } );
-       } );
 
-       QUnit.test( 'saveOptions with Unit Separator', function ( assert ) {
-               QUnit.expect( 14 );
+               return QUnit.whenPromisesComplete(
+                       api.saveOptions( {} ).then( function () {
+                               assert.ok( true, 'Request completed: empty case' );
+                       } ),
+                       api.saveOptions( { foo: 'bar' } ).then( function () {
+                               assert.ok( true, 'Request completed: simple' );
+                       } ),
+                       api.saveOptions( { foo: 'bar', baz: 'quux' } ).then( function () {
+                               assert.ok( true, 'Request completed: two options' );
+                       } ),
+                       api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).then( function () {
+                               assert.ok( true, 'Request completed: not bundleable' );
+                       } ),
+                       api.saveOptions( { foo: null } ).then( function () {
+                               assert.ok( true, 'Request completed: reset an option' );
+                       } ),
+                       api.saveOptions( { 'foo|bar=quux': null } ).then( function () {
+                               assert.ok( true, 'Request completed: reset an option, not bundleable' );
+                       } )
+               );
+       } );
 
+       QUnit.test( 'saveOptions with Unit Separator', 14, function ( assert ) {
                var api = new mw.Api( { useUS: true } );
 
                // We need to respond to the request for token first, otherwise the other requests won't be sent
                                '{ "query": { "tokens": { "csrftoken": "+\\\\" } } }' ]
                );
 
-               api.saveOptions( {} ).done( function () {
-                       assert.ok( true, 'Request completed: empty case' );
-               } );
-               api.saveOptions( { foo: 'bar' } ).done( function () {
-                       assert.ok( true, 'Request completed: simple' );
-               } );
-               api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () {
-                       assert.ok( true, 'Request completed: two options' );
-               } );
-               api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () {
-                       assert.ok( true, 'Request completed: bundleable with unit separator' );
-               } );
-               api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', 'baz=baz': 'quux' } ).done( function () {
-                       assert.ok( true, 'Request completed: not bundleable with unit separator' );
-               } );
-               api.saveOptions( { foo: null } ).done( function () {
-                       assert.ok( true, 'Request completed: reset an option' );
-               } );
-               api.saveOptions( { 'foo|bar=quux': null } ).done( function () {
-                       assert.ok( true, 'Request completed: reset an option, not bundleable' );
-               } );
-
                // Requests are POST, match requestBody instead of url
                this.server.respond( function ( request ) {
                        switch ( request.requestBody ) {
                                        assert.ok( false, 'Unexpected request: ' + request.requestBody );
                        }
                } );
+
+               return QUnit.whenPromisesComplete(
+                       api.saveOptions( {} ).done( function () {
+                               assert.ok( true, 'Request completed: empty case' );
+                       } ),
+                       api.saveOptions( { foo: 'bar' } ).done( function () {
+                               assert.ok( true, 'Request completed: simple' );
+                       } ),
+                       api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () {
+                               assert.ok( true, 'Request completed: two options' );
+                       } ),
+                       api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () {
+                               assert.ok( true, 'Request completed: bundleable with unit separator' );
+                       } ),
+                       api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', 'baz=baz': 'quux' } ).done( function () {
+                               assert.ok( true, 'Request completed: not bundleable with unit separator' );
+                       } ),
+                       api.saveOptions( { foo: null } ).done( function () {
+                               assert.ok( true, 'Request completed: reset an option' );
+                       } ),
+                       api.saveOptions( { 'foo|bar=quux': null } ).done( function () {
+                               assert.ok( true, 'Request completed: reset an option, not bundleable' );
+                       } )
+               );
        } );
 }( mediaWiki ) );
index dc0cff4..7d27352 100644 (file)
@@ -2,42 +2,44 @@
        QUnit.module( 'mediawiki.api.parse', QUnit.newMwEnvironment( {
                setup: function () {
                        this.server = this.sandbox.useFakeServer();
+                       this.server.respondImmediately = true;
                }
        } ) );
 
-       QUnit.test( 'Hello world', function ( assert ) {
-               QUnit.expect( 3 );
+       QUnit.test( '.parse( string )', function ( assert ) {
+               this.server.respondWith( /action=parse.*&text='''Hello(\+|%20)world'''/, [ 200,
+                       { 'Content-Type': 'application/json' },
+                       '{ "parse": { "text": "<p><b>Hello world</b></p>" } }'
+               ] );
 
-               var api = new mw.Api();
-
-               api.parse( '\'\'\'Hello world\'\'\'' ).done( function ( html ) {
+               return new mw.Api().parse( '\'\'\'Hello world\'\'\'' ).done( function ( html ) {
                        assert.equal( html, '<p><b>Hello world</b></p>', 'Parse wikitext by string' );
                } );
+       } );
 
-               api.parse( {
+       QUnit.test( '.parse( Object.toString )', function ( assert ) {
+               this.server.respondWith( /action=parse.*&text='''Hello(\+|%20)world'''/, [ 200,
+                       { 'Content-Type': 'application/json' },
+                       '{ "parse": { "text": "<p><b>Hello world</b></p>" } }'
+               ] );
+
+               return new mw.Api().parse( {
                        toString: function () {
                                return '\'\'\'Hello world\'\'\'';
                        }
                } ).done( function ( html ) {
                        assert.equal( html, '<p><b>Hello world</b></p>', 'Parse wikitext by toString object' );
                } );
+       } );
 
-               this.server.respondWith( /action=parse.*&text='''Hello\+world'''/, function ( request ) {
-                       request.respond( 200, { 'Content-Type': 'application/json' },
-                               '{ "parse": { "text": "<p><b>Hello world</b></p>" } }'
-                       );
-               } );
+       QUnit.test( '.parse( mw.Title )', function ( assert ) {
+               this.server.respondWith( /action=parse.*&page=Earth/, [ 200,
+                       { 'Content-Type': 'application/json' },
+                       '{ "parse": { "text": "<p><b>Earth</b> is a planet.</p>" } }'
+               ] );
 
-               api.parse( new mw.Title( 'Earth' ) ).done( function ( html ) {
+               return new mw.Api().parse( new mw.Title( 'Earth' ) ).done( function ( html ) {
                        assert.equal( html, '<p><b>Earth</b> is a planet.</p>', 'Parse page by Title object'  );
                } );
-
-               this.server.respondWith( /action=parse.*&page=Earth/, function ( request ) {
-                       request.respond( 200, { 'Content-Type': 'application/json' },
-                               '{ "parse": { "text": "<p><b>Earth</b> is a planet.</p>" } }'
-                       );
-               } );
-
-               this.server.respond();
        } );
 }( mediaWiki ) );
index 10fcd5d..b1bd12b 100644 (file)
@@ -1,8 +1,7 @@
 ( function ( mw, $ ) {
        QUnit.module( 'mediawiki.api.upload', QUnit.newMwEnvironment( {} ) );
 
-       QUnit.test( 'Basic functionality', function ( assert ) {
-               QUnit.expect( 2 );
+       QUnit.test( 'Basic functionality', 2, function ( assert ) {
                var api = new mw.Api();
                assert.ok( api.upload );
                assert.throws( function () {
@@ -10,8 +9,7 @@
                } );
        } );
 
-       QUnit.test( 'Set up iframe upload', function ( assert ) {
-               QUnit.expect( 5 );
+       QUnit.test( 'Set up iframe upload', 5, function ( assert ) {
                var $iframe, $form, $input,
                        api = new mw.Api();
 
index 64a5184..8641469 100644 (file)
@@ -2,37 +2,45 @@
        QUnit.module( 'mediawiki.api.watch', QUnit.newMwEnvironment( {
                setup: function () {
                        this.server = this.sandbox.useFakeServer();
+                       this.server.respondImmediately = true;
                }
        } ) );
 
-       QUnit.test( '.watch()', function ( assert ) {
-               QUnit.expect( 4 );
-
-               var api = new mw.Api();
-
-               // Ensure we don't mistake a single item array for a single item and vice versa.
-               // The query parameter in request is the same either way (separated by pipe).
-               api.watch( 'Foo' ).done( function ( item ) {
-                       assert.equal( item.title, 'Foo' );
-               } );
-
-               api.watch( [ 'Foo' ] ).done( function ( items ) {
-                       assert.equal( items[ 0 ].title, 'Foo' );
+       QUnit.test( '.watch( string )', function ( assert ) {
+               this.server.respond( function ( req ) {
+                       // Match POST requestBody
+                       if ( /action=watch.*&titles=Foo(&|$)/.test( req.requestBody ) ) {
+                               req.respond( 200, { 'Content-Type': 'application/json' },
+                                       '{ "watch": [ { "title": "Foo", "watched": true, "message": "<b>Added</b>" } ] }'
+                               );
+                       }
                } );
 
-               api.watch( [ 'Foo', 'Bar' ] ).done( function ( items ) {
-                       assert.equal( items[ 0 ].title, 'Foo' );
-                       assert.equal( items[ 1 ].title, 'Bar' );
+               return new mw.Api().watch( 'Foo' ).done( function ( item ) {
+                       assert.equal( item.title, 'Foo' );
                } );
+       } );
 
-               // Requests are POST, match requestBody instead of url
+       // Ensure we don't mistake a single item array for a single item and vice versa.
+       // The query parameter in request is the same either way (separated by pipe).
+       QUnit.test( '.watch( Array ) - single', function ( assert ) {
                this.server.respond( function ( req ) {
+                       // Match POST requestBody
                        if ( /action=watch.*&titles=Foo(&|$)/.test( req.requestBody ) ) {
                                req.respond( 200, { 'Content-Type': 'application/json' },
                                        '{ "watch": [ { "title": "Foo", "watched": true, "message": "<b>Added</b>" } ] }'
                                );
                        }
+               } );
+
+               return new mw.Api().watch( [ 'Foo' ] ).done( function ( items ) {
+                       assert.equal( items[ 0 ].title, 'Foo' );
+               } );
+       } );
 
+       QUnit.test( '.watch( Array ) - multi', function ( assert ) {
+               this.server.respond( function ( req ) {
+                       // Match POST requestBody
                        if ( /action=watch.*&titles=Foo%7CBar/.test( req.requestBody ) ) {
                                req.respond( 200, { 'Content-Type': 'application/json' },
                                        '{ "watch": [ ' +
                                );
                        }
                } );
+
+               return new mw.Api().watch( [ 'Foo', 'Bar' ] ).done( function ( items ) {
+                       assert.equal( items[ 0 ].title, 'Foo' );
+                       assert.equal( items[ 1 ].title, 'Bar' );
+               } );
        } );
+
 }( mediaWiki ) );
index 1218137..f848f3e 100644 (file)
        QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
                mw.messages.set( mw.libs.phpParserData.messages );
                var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) {
+                       var done = assert.async();
                        return function ( next, abort ) {
-                               var done = assert.async();
                                getMwLanguage( test.lang )
                                        .then( function ( langClass ) {
                                                mw.config.set( 'wgUserLanguage', test.lang );
                },
                {
                        lang: 'hi',
-                       number: '१२३४५६,७८९',
+                       number: '१,२३,४५६',
                        result: '123456',
                        integer: true,
                        description: 'formatnum test for Hindi, Devanagari digits passed to get integer value'
                mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' );
                mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' );
                var queue = $.map( formatnumTests, function ( test ) {
+                       var done = assert.async();
                        return function ( next, abort ) {
-                               var done = assert.async();
                                getMwLanguage( test.lang )
                                        .then( function ( langClass ) {
                                                mw.config.set( 'wgUserLanguage', test.lang );
index e4c3851..b2fac3c 100644 (file)
                assert.equal( mw.language.commafy( 123456789.567, '###,###,#0.00' ), '1,234,567,89.56', 'Decimal part as group of 3 and last one 2' );
        } );
 
+       QUnit.test( 'mw.language.convertNumber', 2, function ( assert ) {
+               mw.language.setData( 'en', 'digitGroupingPattern', null );
+               mw.language.setData( 'en', 'digitTransformTable', null );
+               mw.language.setData( 'en', 'separatorTransformTable', { ',': '.', '.': ',' } );
+               mw.config.set( 'wgUserLanguage', 'en' );
+
+               assert.equal( mw.language.convertNumber( 1800 ), '1.800', 'formatting' );
+               assert.equal( mw.language.convertNumber( '1.800', true ), '1800', 'unformatting' );
+       } );
+
        function grammarTest( langCode, test ) {
                // The test works only if the content language is opt.language
                // because it requires [lang].js to be loaded.
index 92d1326..92ee7dd 100644 (file)
                .done( function () {
                        assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
                        delete mw.loader.testCallback;
-
                } )
                .fail( function () {
                        assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
        } );
 
        QUnit.test( '.using() Error: Circular dependency', function ( assert ) {
+               var done = assert.async();
+
                mw.loader.register( [
                        [ 'test.circle1', '0', [ 'test.circle2' ] ],
                        [ 'test.circle2', '0', [ 'test.circle3' ] ],
                        function fail( e ) {
                                assert.ok( /Circular/.test( String( e ) ), 'Detect circular dependency' );
                        }
-               );
+               )
+               .always( done );
        } );
 
        QUnit.test( '.load() - Error: Circular dependency', function ( assert ) {
        } );
 
        QUnit.test( '.using() - Error: Unregistered', function ( assert ) {
+               var done = assert.async();
+
                mw.loader.using( 'test.using.unreg' ).then(
                        function done() {
                                assert.ok( false, 'Unexpected resolution, expected error.' );
                        function fail( e ) {
                                assert.ok( /Unknown/.test( String( e ) ), 'Detect unknown dependency' );
                        }
-               );
+               ).always( done );
        } );
 
        QUnit.test( '.load() - Error: Unregistered (ignored)', 0, function ( assert ) {
index 6cef4a7..436cb2e 100644 (file)
@@ -1,36 +1,56 @@
 ( function ( mw ) {
        QUnit.module( 'mediawiki.storage' );
 
-       QUnit.test( 'set/get with localStorage', 3, function ( assert ) {
-               this.sandbox.stub( mw.storage, 'localStorage', {
+       QUnit.test( 'set/get with storage support', function ( assert ) {
+               var stub = {
                        setItem: this.sandbox.spy(),
                        getItem: this.sandbox.stub()
-               } );
+               };
+               stub.getItem.withArgs( 'foo' ).returns( 'test' );
+               stub.getItem.returns( null );
+               this.sandbox.stub( mw.storage, 'store', stub );
 
                mw.storage.set( 'foo', 'test' );
-               assert.ok( mw.storage.localStorage.setItem.calledOnce );
+               assert.ok( stub.setItem.calledOnce );
 
-               mw.storage.localStorage.getItem.withArgs( 'foo' ).returns( 'test' );
-               mw.storage.localStorage.getItem.returns( null );
                assert.strictEqual( mw.storage.get( 'foo' ), 'test', 'Check value gets stored.' );
                assert.strictEqual( mw.storage.get( 'bar' ), null, 'Unset values are null.' );
        } );
 
-       QUnit.test( 'set/get without localStorage', 3, function ( assert ) {
-               this.sandbox.stub( mw.storage, 'localStorage', {
+       QUnit.test( 'set/get with storage methods disabled', function ( assert ) {
+               // This covers browsers where storage is disabled
+               // (quota full, or security/privacy settings).
+               // On most browsers, these interface will be accessible with
+               // their methods throwing.
+               var stub = {
                        getItem: this.sandbox.stub(),
                        removeItem: this.sandbox.stub(),
                        setItem: this.sandbox.stub()
-               } );
+               };
+               stub.getItem.throws();
+               stub.setItem.throws();
+               stub.removeItem.throws();
+               this.sandbox.stub( mw.storage, 'store', stub );
 
-               mw.storage.localStorage.getItem.throws();
                assert.strictEqual( mw.storage.get( 'foo' ), false );
-
-               mw.storage.localStorage.setItem.throws();
                assert.strictEqual( mw.storage.set( 'foo', 'test' ), false );
+               assert.strictEqual( mw.storage.remove( 'foo', 'test' ), false );
+       } );
+
+       QUnit.test( 'set/get with storage object disabled', function ( assert ) {
+               // On other browsers, these entire object is disabled.
+               // `'localStorage' in window` would be true (and pass feature test)
+               // but trying to read the object as window.localStorage would throw
+               // an exception. Such case would instantiate SafeStorage with
+               // undefined after the internal try/catch.
+               var old = mw.storage.store;
+               mw.storage.store = undefined;
 
-               mw.storage.localStorage.removeItem.throws();
+               assert.strictEqual( mw.storage.get( 'foo' ), false );
+               assert.strictEqual( mw.storage.set( 'foo', 'test' ), false );
                assert.strictEqual( mw.storage.remove( 'foo', 'test' ), false );
+
+               mw.storage.store = old;
        } );
 
 }( mediaWiki ) );
index 045b633..e6e798b 100644 (file)
                        // MeeGo
                        'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
                        // UC Mini (speed mode on)
-                       'Mozilla/5.0 (X11; U; Linux i686; zh-CN; r:1.2.3.4) Gecko/'
+                       'Mozilla/5.0 (X11; U; Linux i686; zh-CN; r:1.2.3.4) Gecko/',
+                       // Google Web Light proxy
+                       'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19'
                ]
        };
 
index fca25c5..c38b89c 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -341,6 +341,7 @@ function wfStreamThumb( array $params ) {
        // Check for thumbnail generation errors...
        $msg = wfMessage( 'thumbnail_error' );
        $errorCode = 500;
+
        if ( !$thumb ) {
                $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
                if ( $errorMsg instanceof MessageSpecifier &&
@@ -350,6 +351,7 @@ function wfStreamThumb( array $params ) {
                }
        } elseif ( $thumb->isError() ) {
                $errorMsg = $thumb->getHtmlMsg();
+               $errorCode = $thumb->getHttpStatusCode();
        } elseif ( !$thumb->hasFile() ) {
                $errorMsg = $msg->rawParams( 'No path supplied in thumbnail object' )->escaped();
        } elseif ( $thumb->fileIsSource() ) {