Merge "Highlight new requirement"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 19 May 2016 18:51:26 +0000 (18:51 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 19 May 2016 18:51:26 +0000 (18:51 +0000)
721 files changed:
.stylelintrc [new file with mode: 0644]
Gruntfile.js
RELEASE-NOTES-1.27
RELEASE-NOTES-1.28 [new file with mode: 0644]
autoload.php
composer.json
docs/extension.schema.json
docs/hooks.txt
docs/injection.txt
extensions/README
includes/AuthPlugin.php
includes/CategoryViewer.php
includes/DefaultSettings.php
includes/DummyLinker.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/Html.php
includes/Linker.php
includes/MediaWiki.php
includes/MediaWikiServices.php
includes/Message.php
includes/MovePage.php
includes/OutputPage.php
includes/Preferences.php
includes/ProtectionForm.php
includes/Revision.php
includes/ServiceWiring.php
includes/Services/CannotReplaceActiveServiceException.php [new file with mode: 0644]
includes/Services/ContainerDisabledException.php [new file with mode: 0644]
includes/Services/DestructibleService.php [new file with mode: 0644]
includes/Services/NoSuchServiceException.php [new file with mode: 0644]
includes/Services/SalvageableService.php [new file with mode: 0644]
includes/Services/ServiceAlreadyDefinedException.php [new file with mode: 0644]
includes/Services/ServiceContainer.php
includes/Services/ServiceDisabledException.php [new file with mode: 0644]
includes/Setup.php
includes/SiteStats.php
includes/Status.php
includes/Title.php
includes/WatchedItem.php
includes/WatchedItemStore.php
includes/WebRequest.php
includes/actions/Action.php
includes/actions/InfoAction.php
includes/api/ApiAMCreateAccount.php [new file with mode: 0644]
includes/api/ApiAuthManagerHelper.php [new file with mode: 0644]
includes/api/ApiBase.php
includes/api/ApiChangeAuthenticationData.php [new file with mode: 0644]
includes/api/ApiClientLogin.php [new file with mode: 0644]
includes/api/ApiCreateAccount.php
includes/api/ApiFormatJson.php
includes/api/ApiHelp.php
includes/api/ApiLinkAccount.php [new file with mode: 0644]
includes/api/ApiLogin.php
includes/api/ApiMain.php
includes/api/ApiManageTags.php
includes/api/ApiOptions.php
includes/api/ApiPageSet.php
includes/api/ApiQuery.php
includes/api/ApiQueryAuthManagerInfo.php [new file with mode: 0644]
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiQueryUsers.php
includes/api/ApiRemoveAuthenticationData.php [new file with mode: 0644]
includes/api/ApiResetPassword.php [new file with mode: 0644]
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiStashEdit.php
includes/api/ApiUpload.php
includes/api/i18n/ba.json
includes/api/i18n/de.json
includes/api/i18n/en.json
includes/api/i18n/eo.json
includes/api/i18n/fr.json
includes/api/i18n/he.json
includes/api/i18n/id.json
includes/api/i18n/it.json
includes/api/i18n/jv.json [new file with mode: 0644]
includes/api/i18n/ko.json
includes/api/i18n/ku-latn.json
includes/api/i18n/mk.json
includes/api/i18n/pl.json
includes/api/i18n/qqq.json
includes/api/i18n/sv.json
includes/api/i18n/vi.json
includes/api/i18n/zh-hans.json
includes/auth/AbstractAuthenticationProvider.php [new file with mode: 0644]
includes/auth/AbstractPasswordPrimaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/AbstractPreAuthenticationProvider.php [new file with mode: 0644]
includes/auth/AbstractPrimaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/AbstractSecondaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/AuthManager.php [new file with mode: 0644]
includes/auth/AuthManagerAuthPlugin.php [new file with mode: 0644]
includes/auth/AuthPluginPrimaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/AuthenticationProvider.php [new file with mode: 0644]
includes/auth/AuthenticationRequest.php [new file with mode: 0644]
includes/auth/AuthenticationResponse.php [new file with mode: 0644]
includes/auth/ButtonAuthenticationRequest.php [new file with mode: 0644]
includes/auth/CheckBlocksSecondaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/ConfirmLinkAuthenticationRequest.php [new file with mode: 0644]
includes/auth/ConfirmLinkSecondaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/CreateFromLoginAuthenticationRequest.php [new file with mode: 0644]
includes/auth/CreatedAccountAuthenticationRequest.php [new file with mode: 0644]
includes/auth/CreationReasonAuthenticationRequest.php [new file with mode: 0644]
includes/auth/EmailNotificationSecondaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/LegacyHookPreAuthenticationProvider.php [new file with mode: 0644]
includes/auth/LocalPasswordPrimaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/PasswordAuthenticationRequest.php [new file with mode: 0644]
includes/auth/PasswordDomainAuthenticationRequest.php [new file with mode: 0644]
includes/auth/PreAuthenticationProvider.php [new file with mode: 0644]
includes/auth/PrimaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/RememberMeAuthenticationRequest.php [new file with mode: 0644]
includes/auth/ResetPasswordSecondaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/SecondaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/TemporaryPasswordAuthenticationRequest.php [new file with mode: 0644]
includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php [new file with mode: 0644]
includes/auth/ThrottlePreAuthenticationProvider.php [new file with mode: 0644]
includes/auth/Throttler.php [new file with mode: 0644]
includes/auth/UserDataAuthenticationRequest.php [new file with mode: 0644]
includes/auth/UsernameAuthenticationRequest.php [new file with mode: 0644]
includes/cache/CacheDependency.php
includes/cache/GenderCache.php
includes/cache/LinkBatch.php
includes/cache/LinkCache.php
includes/cache/localisation/LocalisationCache.php
includes/changes/EnhancedChangesList.php
includes/changetags/ChangeTags.php
includes/collation/IcuCollation.php
includes/compat/normal/UtfNormalUtil.php
includes/composer/ComposerPackageModifier.php
includes/config/ConfigFactory.php
includes/content/ContentHandler.php
includes/db/Database.php
includes/db/DatabaseMssql.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LoadBalancer.php
includes/debug/MWDebug.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksDeletionUpdate.php
includes/deferred/LinksUpdate.php
includes/diff/DairikiDiff.php
includes/diff/DiffEngine.php [new file with mode: 0644]
includes/diff/WikiDiff3.php [deleted file]
includes/diff/WordAccumulator.php [new file with mode: 0644]
includes/diff/WordLevelDiff.php [new file with mode: 0644]
includes/exception/UserNotLoggedIn.php
includes/filebackend/FileBackendMultiWrite.php
includes/filerepo/FileRepo.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/HTMLFormFieldCloner.php
includes/htmlform/OOUIHTMLForm.php
includes/installer/DatabaseInstaller.php
includes/installer/Installer.php
includes/installer/MssqlInstaller.php
includes/installer/MssqlUpdater.php
includes/installer/WebInstallerOutput.php
includes/installer/WebInstallerPage.php
includes/installer/i18n/jv.json
includes/installer/i18n/ko.json
includes/installer/i18n/vi.json
includes/installer/i18n/zh-hans.json
includes/installer/i18n/zh-hant.json
includes/interwiki/ClassicInterwikiLookup.php [new file with mode: 0644]
includes/interwiki/Interwiki.php
includes/interwiki/InterwikiLookup.php [new file with mode: 0644]
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobRunner.php
includes/jobqueue/jobs/AssembleUploadChunksJob.php
includes/jobqueue/jobs/PublishStashedFileJob.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/CSSMin.php
includes/libs/Xhprof.php
includes/libs/XhprofData.php [new file with mode: 0644]
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/IExpiringStore.php
includes/libs/objectcache/MemcachedClient.php
includes/libs/objectcache/WANObjectCache.php
includes/linker/LinkTarget.php
includes/mail/EmailNotification.php
includes/media/GIF.php
includes/media/PNG.php
includes/media/TransformationalImageHandler.php
includes/mime.types
includes/objectcache/ObjectCache.php
includes/objectcache/RedisBagOStuff.php
includes/page/Article.php
includes/page/WikiFilePage.php
includes/page/WikiPage.php
includes/parser/BlockLevelPass.php [new file with mode: 0644]
includes/parser/CoreParserFunctions.php
includes/parser/LinkHolderArray.php
includes/parser/Parser.php
includes/parser/ParserCache.php
includes/parser/ParserOutput.php
includes/parser/StripState.php
includes/password/PasswordPolicyChecks.php
includes/poolcounter/PoolCounter.php
includes/poolcounter/PoolCounterWork.php
includes/poolcounter/PoolCounterWorkViaCallback.php
includes/poolcounter/PoolWorkArticleView.php
includes/profiler/ProfilerXhprof.php
includes/registration/ExtensionProcessor.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderModule.php
includes/resourceloader/ResourceLoaderSkinModule.php
includes/resourceloader/ResourceLoaderUploadDialogModule.php [new file with mode: 0644]
includes/resourceloader/ResourceLoaderUserGroupsModule.php
includes/resourceloader/ResourceLoaderUserModule.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/search/SearchEngine.php
includes/session/CookieSessionProvider.php
includes/session/ImmutableSessionProviderWithCookie.php
includes/session/Session.php
includes/session/SessionInfo.php
includes/session/SessionManager.php
includes/session/SessionManagerInterface.php
includes/session/SessionProvider.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specialpage/AuthManagerSpecialPage.php [new file with mode: 0644]
includes/specialpage/LoginSignupSpecialPage.php [new file with mode: 0644]
includes/specialpage/QueryPage.php
includes/specialpage/SpecialPage.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialActiveusers.php
includes/specials/SpecialBlock.php
includes/specials/SpecialBooksources.php
includes/specials/SpecialChangeContentModel.php
includes/specials/SpecialChangeCredentials.php [new file with mode: 0644]
includes/specials/SpecialChangeEmail.php
includes/specials/SpecialChangePassword.php
includes/specials/SpecialCreateAccount.php
includes/specials/SpecialEditWatchlist.php
includes/specials/SpecialLinkAccounts.php [new file with mode: 0644]
includes/specials/SpecialLockdb.php
includes/specials/SpecialMIMEsearch.php
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialMergeHistory.php
includes/specials/SpecialPasswordReset.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialRemoveCredentials.php [new file with mode: 0644]
includes/specials/SpecialSearch.php
includes/specials/SpecialTags.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialUnlinkAccounts.php [new file with mode: 0644]
includes/specials/SpecialUnlockdb.php
includes/specials/SpecialUpload.php
includes/specials/SpecialUserLogin.php [new file with mode: 0644]
includes/specials/SpecialUserLogout.php [new file with mode: 0644]
includes/specials/SpecialUserlogin.php [deleted file]
includes/specials/SpecialUserlogout.php [deleted file]
includes/specials/SpecialUserrights.php
includes/specials/SpecialWatchlist.php
includes/specials/SpecialWhatlinkshere.php
includes/specials/helpers/LoginHelper.php [new file with mode: 0644]
includes/specials/pagers/ActiveUsersPager.php
includes/specials/pre-authmanager/README [new file with mode: 0644]
includes/specials/pre-authmanager/SpecialChangeEmail.php [new file with mode: 0644]
includes/specials/pre-authmanager/SpecialChangePassword.php [new file with mode: 0644]
includes/specials/pre-authmanager/SpecialCreateAccount.php [new file with mode: 0644]
includes/specials/pre-authmanager/SpecialPasswordReset.php [new file with mode: 0644]
includes/specials/pre-authmanager/SpecialUserlogin.php [new file with mode: 0644]
includes/specials/pre-authmanager/SpecialUserlogout.php [new file with mode: 0644]
includes/templates/Usercreate.php
includes/templates/Userlogin.php
includes/title/MediaWikiTitleCodec.php
includes/title/TitleFormatter.php
includes/title/TitleValue.php
includes/user/PasswordReset.php [new file with mode: 0644]
includes/user/User.php
includes/widget/SearchInputWidget.php [changed mode: 0644->0755]
languages/Language.php
languages/classes/LanguageAz.php
languages/classes/LanguageEo.php
languages/classes/LanguageKaa.php
languages/classes/LanguageKk.php
languages/classes/LanguageQqx.php
languages/classes/LanguageTr.php
languages/classes/LanguageWa.php
languages/data/ZhConversion.php
languages/i18n/ace.json
languages/i18n/af.json
languages/i18n/aln.json
languages/i18n/am.json
languages/i18n/an.json
languages/i18n/ang.json
languages/i18n/ar.json
languages/i18n/arq.json
languages/i18n/ary.json
languages/i18n/arz.json
languages/i18n/as.json
languages/i18n/ast.json
languages/i18n/avk.json
languages/i18n/awa.json
languages/i18n/az.json
languages/i18n/azb.json
languages/i18n/ba.json
languages/i18n/bcc.json
languages/i18n/bcl.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bgn.json
languages/i18n/bho.json
languages/i18n/bjn.json
languages/i18n/bn.json
languages/i18n/bpy.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/cdo.json
languages/i18n/ce.json
languages/i18n/ceb.json
languages/i18n/ckb.json
languages/i18n/crh-cyrl.json
languages/i18n/crh-latn.json
languages/i18n/cs.json
languages/i18n/csb.json
languages/i18n/cy.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/dsb.json
languages/i18n/dtp.json
languages/i18n/dty.json
languages/i18n/egl.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/ext.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fo.json
languages/i18n/fr.json
languages/i18n/frp.json
languages/i18n/frr.json
languages/i18n/fur.json
languages/i18n/fy.json
languages/i18n/ga.json
languages/i18n/gd.json
languages/i18n/gl.json
languages/i18n/gom-deva.json
languages/i18n/gom-latn.json
languages/i18n/grc.json
languages/i18n/gsw.json
languages/i18n/gu.json
languages/i18n/gv.json
languages/i18n/hak.json
languages/i18n/haw.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hif-latn.json
languages/i18n/hil.json
languages/i18n/hr.json
languages/i18n/hrx.json
languages/i18n/hsb.json
languages/i18n/ht.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/ilo.json
languages/i18n/inh.json
languages/i18n/io.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jam.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/kaa.json
languages/i18n/kab.json
languages/i18n/kbd-cyrl.json
languages/i18n/khw.json
languages/i18n/kiu.json
languages/i18n/kk-cyrl.json
languages/i18n/km.json
languages/i18n/kn.json
languages/i18n/ko.json
languages/i18n/krc.json
languages/i18n/ksh.json
languages/i18n/ku-latn.json
languages/i18n/ky.json
languages/i18n/la.json
languages/i18n/lb.json
languages/i18n/lg.json
languages/i18n/li.json
languages/i18n/lij.json
languages/i18n/lki.json
languages/i18n/lmo.json
languages/i18n/lrc.json
languages/i18n/lt.json
languages/i18n/lus.json
languages/i18n/lv.json
languages/i18n/lzh.json
languages/i18n/mai.json
languages/i18n/map-bms.json
languages/i18n/mdf.json
languages/i18n/mg.json
languages/i18n/mhr.json
languages/i18n/min.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/mn.json
languages/i18n/mr.json
languages/i18n/ms.json
languages/i18n/mt.json
languages/i18n/mwl.json
languages/i18n/myv.json
languages/i18n/nah.json
languages/i18n/nan.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/nds-nl.json
languages/i18n/nds.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/nso.json
languages/i18n/oc.json
languages/i18n/olo.json
languages/i18n/or.json
languages/i18n/os.json
languages/i18n/pa.json
languages/i18n/pl.json
languages/i18n/pms.json
languages/i18n/pnb.json
languages/i18n/pnt.json
languages/i18n/prg.json
languages/i18n/ps.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/qu.json
languages/i18n/rm.json
languages/i18n/ro.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/rue.json
languages/i18n/sa.json
languages/i18n/sah.json
languages/i18n/sc.json
languages/i18n/scn.json
languages/i18n/sco.json
languages/i18n/sd.json
languages/i18n/ses.json
languages/i18n/sgs.json
languages/i18n/sh.json
languages/i18n/shi.json
languages/i18n/si.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sli.json
languages/i18n/so.json
languages/i18n/sq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/stq.json
languages/i18n/su.json
languages/i18n/sv.json
languages/i18n/sw.json
languages/i18n/szl.json
languages/i18n/ta.json
languages/i18n/tcy.json
languages/i18n/te.json
languages/i18n/tg-cyrl.json
languages/i18n/tg-latn.json
languages/i18n/th.json
languages/i18n/tk.json
languages/i18n/tl.json
languages/i18n/to.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/tt-latn.json
languages/i18n/ug-arab.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/uz.json
languages/i18n/vec.json
languages/i18n/vep.json
languages/i18n/vi.json
languages/i18n/vo.json
languages/i18n/vro.json
languages/i18n/wa.json
languages/i18n/war.json
languages/i18n/wuu.json
languages/i18n/xal.json
languages/i18n/xmf.json
languages/i18n/yi.json
languages/i18n/yo.json
languages/i18n/yue.json
languages/i18n/zea.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesCs.php
languages/messages/MessagesEn.php
load.php
maintenance/convertExtensionToRegistration.php
maintenance/language/zhtable/toCN.manual
maintenance/language/zhtable/toHK.manual
maintenance/language/zhtable/toTW.manual
maintenance/language/zhtable/toTrad.manual
maintenance/language/zhtable/tradphrases.manual
maintenance/language/zhtable/tradphrases_exclude.manual
maintenance/mssql/archives/named_constraints.sql [deleted file]
maintenance/mssql/archives/patch-add-cl_collation_ext_index.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-archive-drop-fks.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-bot_passwords.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-categorylinks-constraints.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-drop-page_counter.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-drop-rc_cur_time.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-drop-ss_total_views.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-drop-user_options.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-filearchive-constraints.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-filearchive-schema.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-il_from_namespace.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-image-constraints.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-image-schema.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-kill-cl_collation_index.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-logging-drop-fks.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-oldimage-constraints.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-oldimage-schema.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-pl_from_namespace.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-pp_sortkey.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-recentchanges-drop-fks.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-tl_from_namespace.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-uploadstash-constraints.sql [new file with mode: 0644]
maintenance/mssql/tables.sql
maintenance/resources/update-oojs-ui.sh
maintenance/resources/update-oojs.sh
maintenance/storage/recompressTracked.php
maintenance/updateCollation.php
mw-config/config-cc.css
mw-config/config.css
opensearch_desc.php
package.json
profileinfo.php
resources/Resources.php
resources/ResourcesOOUI.php
resources/lib/oojs-router/AUTHORS.txt [new file with mode: 0644]
resources/lib/oojs-router/LICENSE-MIT [new file with mode: 0644]
resources/lib/oojs-router/oojs-router.js [new file with mode: 0644]
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/src/jquery.tipsy/jquery.tipsy.css
resources/src/jquery/jquery.arrowSteps.css
resources/src/jquery/jquery.badge.css
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.suggestions.css
resources/src/mediawiki.action/mediawiki.action.history.css
resources/src/mediawiki.action/mediawiki.action.history.diff.css
resources/src/mediawiki.action/mediawiki.action.view.filepage.css
resources/src/mediawiki.action/mediawiki.action.view.metadata.js
resources/src/mediawiki.action/mediawiki.action.view.postEdit.css
resources/src/mediawiki.legacy/commonPrint.css
resources/src/mediawiki.legacy/oldshared.css
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.router/index.js [new file with mode: 0644]
resources/src/mediawiki.skinning/content.css
resources/src/mediawiki.skinning/content.parsoid.less
resources/src/mediawiki.skinning/elements.css
resources/src/mediawiki.skinning/interface.css
resources/src/mediawiki.special/mediawiki.special.css
resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
resources/src/mediawiki.special/mediawiki.special.search.css [changed mode: 0755->0644]
resources/src/mediawiki.special/mediawiki.special.upload.css [new file with mode: 0644]
resources/src/mediawiki.special/mediawiki.special.upload.js
resources/src/mediawiki.special/mediawiki.special.userlogin.signup.css
resources/src/mediawiki.widgets.datetime/CalendarWidget.less
resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.less
resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.less
resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js [changed mode: 0644->0755]
resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less
resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
resources/src/mediawiki/api.js
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js
resources/src/mediawiki/mediawiki.ForeignUpload.js
resources/src/mediawiki/mediawiki.Upload.BookletLayout.js
resources/src/mediawiki/mediawiki.apihelp.css
resources/src/mediawiki/mediawiki.content.json.css
resources/src/mediawiki/mediawiki.debug.less
resources/src/mediawiki/mediawiki.filewarning.less
resources/src/mediawiki/mediawiki.htmlform.css
resources/src/mediawiki/mediawiki.htmlform.ooui.css
resources/src/mediawiki/mediawiki.jqueryMsg.js
resources/src/mediawiki/mediawiki.notification.css
resources/src/mediawiki/mediawiki.searchSuggest.css
resources/src/mediawiki/mediawiki.util.js
resources/src/mediawiki/page/gallery.css
resources/src/mediawiki/page/patrol.ajax.js
resources/src/moment-dmy.js [new file with mode: 0644]
resources/src/moment-local-dmy.js [deleted file]
resources/src/moment-locale-overrides.js [new file with mode: 0644]
skins/README
tests/TestsAutoLoader.php
tests/browser/features/edit_page.feature
tests/browser/features/step_definitions/create_account_steps.rb
tests/browser/features/step_definitions/file_steps.rb
tests/browser/features/step_definitions/login_steps.rb
tests/browser/features/step_definitions/preferences_appearance_steps.rb
tests/browser/features/step_definitions/preferences_editing_steps.rb
tests/browser/features/step_definitions/preferences_user_profile_steps.rb
tests/browser/features/support/pages/create_account_page.rb
tests/browser/features/support/pages/file_does_not_exist_page.rb
tests/browser/features/support/pages/preferences_appearance_page.rb
tests/browser/features/support/pages/preferences_editing_page.rb
tests/browser/features/support/pages/preferences_page.rb
tests/browser/features/support/pages/preferences_user_profile_page.rb
tests/browser/features/view_history.feature
tests/parser/parserTest.inc
tests/parser/parserTests.txt
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/GlobalFunctions/wfAppendQueryTest.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/LinkerTest.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/MergeHistoryTest.php
tests/phpunit/includes/MessageTest.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/PrefixSearchTest.php
tests/phpunit/includes/Services/ServiceContainerTest.php
tests/phpunit/includes/TestUser.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/WatchedItemIntegrationTest.php
tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
tests/phpunit/includes/WatchedItemStoreUnitTest.php
tests/phpunit/includes/WatchedItemUnitTest.php
tests/phpunit/includes/XmlSelectTest.php
tests/phpunit/includes/XmlTest.php
tests/phpunit/includes/api/ApiCreateAccountTest.php [deleted file]
tests/phpunit/includes/api/ApiLoginTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiTestCase.php
tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AbstractPreAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AbstractPrimaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AbstractSecondaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AuthManagerTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AuthenticationRequestTestCase.php [new file with mode: 0644]
tests/phpunit/includes/auth/AuthenticationResponseTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/ButtonAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/ConfirmLinkAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/ConfirmLinkSecondaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/CreateFromLoginAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/CreatedAccountAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/CreationReasonAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/RememberMeAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/ResetPasswordSecondaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/ThrottlerTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/UsernameAuthenticationRequestTest.php [new file with mode: 0644]
tests/phpunit/includes/changes/RecentChangeTest.php
tests/phpunit/includes/config/ConfigFactoryTest.php
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/content/JsonContentTest.php
tests/phpunit/includes/debug/MWDebugTest.php
tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php [new file with mode: 0644]
tests/phpunit/includes/interwiki/InterwikiTest.php
tests/phpunit/includes/libs/XhprofDataTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/XhprofTest.php
tests/phpunit/includes/media/GIFTest.php
tests/phpunit/includes/media/MediaHandlerTest.php
tests/phpunit/includes/media/PNGTest.php
tests/phpunit/includes/objectcache/RedisBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/includes/page/WikiCategoryPageTest.php
tests/phpunit/includes/parser/NewParserTest.php
tests/phpunit/includes/poolcounter/PoolCounterTest.php
tests/phpunit/includes/registration/ExtensionProcessorTest.php
tests/phpunit/includes/search/SearchEnginePrefixTest.php
tests/phpunit/includes/session/BotPasswordSessionProviderTest.php
tests/phpunit/includes/session/CookieSessionProviderTest.php
tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php
tests/phpunit/includes/session/SessionInfoTest.php
tests/phpunit/includes/session/SessionManagerTest.php
tests/phpunit/includes/session/SessionProviderTest.php
tests/phpunit/includes/session/SessionTest.php
tests/phpunit/includes/title/MediaWikiTitleCodecTest.php
tests/phpunit/includes/title/TitleValueTest.php
tests/phpunit/includes/user/BotPasswordTest.php
tests/phpunit/includes/user/PasswordResetTest.php [new file with mode: 0644]
tests/phpunit/phpunit.php
tests/phpunit/tests/MediaWikiTestCaseTest.php
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js

diff --git a/.stylelintrc b/.stylelintrc
new file mode 100644 (file)
index 0000000..e8e1567
--- /dev/null
@@ -0,0 +1,16 @@
+{
+       "rules": {
+               "color-hex-case": [ "lower" ],
+               "color-hex-length": [ "short" ],
+               "color-named": [ "never" ],
+               "color-no-invalid-hex": true,
+
+               "declaration-bang-space-after": [ "never" ],
+               "declaration-bang-space-before": [ "always" ],
+               "declaration-colon-space-after": [ "always" ],
+               "declaration-colon-space-before": [ "never" ],
+
+               "font-family-name-quotes": [ "single-unless-keyword" ],
+               "font-weight-notation": [ "named-where-possible" ]
+       }
+}
index 354f048..a08db5c 100644 (file)
@@ -2,6 +2,7 @@
 module.exports = function ( grunt ) {
        grunt.loadNpmTasks( 'grunt-contrib-copy' );
        grunt.loadNpmTasks( 'grunt-contrib-jshint' );
+       grunt.loadNpmTasks( 'grunt-stylelint' );
        grunt.loadNpmTasks( 'grunt-contrib-watch' );
        grunt.loadNpmTasks( 'grunt-banana-checker' );
        grunt.loadNpmTasks( 'grunt-jscs' );
@@ -39,9 +40,15 @@ module.exports = function ( grunt ) {
                        api: 'includes/api/i18n/',
                        installer: 'includes/installer/i18n/'
                },
+               stylelint: {
+                       options: {
+                               syntax: 'less'
+                       },
+                       src: '{resources/src/*,mw-config/**}/*.{css,less}'
+               },
                watch: {
                        files: [
-                               '.js*',
+                               '.{stylelintrc,jscsrc,jshintignore,jshintrc}',
                                '**/*',
                                '!{docs,extensions,node_modules,skins,vendor}/**'
                        ],
@@ -96,7 +103,7 @@ module.exports = function ( grunt ) {
                return !!( process.env.MW_SERVER && process.env.MW_SCRIPT_PATH );
        } );
 
-       grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'jsonlint', 'banana' ] );
+       grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'jsonlint', 'banana', 'stylelint' ] );
        grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:main' ] );
 
        grunt.registerTask( 'test', [ 'lint' ] );
index 039aa05..e644ae4 100644 (file)
@@ -13,6 +13,8 @@ HHVM 3.1. Additionally, the following PHP extensions are required:
 * json
 * mbstring (new requirement in 1.27)
 * xml
+The following PHP extensions are strongly recommended:
+* openssl
 
 === Configuration changes in 1.27 ===
 * $wgAllowMicrodataAttributes and $wgAllowRdfaAttributes were removed,
@@ -114,6 +116,31 @@ HHVM 3.1. Additionally, the following PHP extensions are required:
   module should express a dependency on it.
 * Removed configuration option $wgCopyrightIcon (deprecated since 1.18). Use
   $wgFooterIcons['copyright']['copyright'] instead.
+* If the openssl and mcrypt PHP extensions are both unavailable, secure
+  session storage (used for login) will raise an exception. This exception may
+  be bypassed by setting $wgSessionInsecureSecrets = true.
+* Massive overhaul to authentication:
+** AuthPlugin and AuthPluginUser are deprecated.
+** LoginForm and associated templates are deprecated. Extensions which called
+   static LoginForm methods should be converted into authentication providers.
+** The following hooks are deprecated:
+*** AbortAutoAccount (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** AbortLogin (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** AbortNewAccount (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** AddNewAccount (use LocalUserCreated instead)
+*** AuthPluginSetup (create a MediaWiki\Auth\PrimaryAuthenticationProvider instead)
+*** ChangePasswordForm (use AuthChangeFormFields instead, or security levels)
+*** LoginUserMigrated (create a MediaWiki\Auth\PreAuthenticationProvider instead)
+*** UserCreateForm (create a MediaWiki\Auth\AuthenticationProvider of some type instead)
+*** UserLoginForm (create a MediaWiki\Auth\AuthenticationProvider of some type instead)
+** The following hooks are removed:
+*** AbortChangePassword
+*** LoginPasswordResetMessage
+*** PrefsPasswordAudit
+** The UserLoginComplete hook will no longer be called for all logins, only for
+   those via the web UI. Use UserLoggedIn if you need to do something on all
+   logins.
+** $wgRequirePasswordforEmailChange is removed.
 
 === New features in 1.27 ===
 * $wgDataCenterUpdateStickTTL was also added. This decides how long a user
@@ -193,6 +220,27 @@ HHVM 3.1. Additionally, the following PHP extensions are required:
 * $wgJpegPixelFormat was added to override chroma subsampling for JPEG image
   thumbnails created via ImageMagick. Defaults to 'yuv420', providing bandwidth
   savings versus the previous behavior on many files.
+* MediaWiki\Auth infrastructure (called "AuthManager") allows for more flexible
+  configuration of multiple authentication pieces that was possible with
+  AuthPlugin. For example, it's now easy to plug in second-factor
+  authentication, or add additional checks to the login process, or to support
+  multiple login methods at once, or to support non-password-based login methods.
+** Providers are configured via the global setting $wgAuthManagerConfig.
+** A global, $wgDisableAuthManager, is temporarily available to disable
+   AuthManager until extensions are ready to support it.
+** New hook, AuthChangeFormFields, to adjust the form fields on
+   AuthManager-related special pages.
+** New hook, AuthManagerLoginAuthenticateAudit, for additional logging of
+   AuthManager-related authentication requests.
+** New hook, ChangeAuthenticationDataAudit, for additional logging of
+   AuthManager-related authentication data changes.
+** New hook, SecuritySensitiveOperationStatus, to work with the new mechanism
+   for requiring a recent login before taking security-sensitive operations
+   like changing a password.
+** Two new globals, $wgChangeCredentialsBlacklist and $wgRemoveCredentialsBlacklist
+   can be used to prevent the web UI and the API changing certain authentication data.
+* The file upload dialog (available if you install WikiEditor or VisualEditor)
+  can now be configured using $wgUploadDialog.
 
 === External library changes in 1.27 ===
 
@@ -234,6 +282,18 @@ HHVM 3.1. Additionally, the following PHP extensions are required:
   merely need to change the username and password used after setting up a bot
   password.
 * action=upload no longer understands statuskey, asyncdownload or leavemessage.
+* Several changes when $wgDisableAuthManager is false:
+** action=login is deprecated for uses other than bot passwords.
+** list=users can now indicate if a missing username is creatable.
+** action=createaccount is changed in a non-backwards-compatible manner.
+** Added action=query&meta=authmanagerinfo.
+** Added action=clientlogin to be used to log into the main account instead of
+   action=login.
+** Added action=linkaccount.
+** Added action=unlinkaccount.
+** Added action=changeauthenticationdata.
+** Added action=removeauthenticationdata.
+** Added action=resetpassword.
 
 === Action API internal changes in 1.27 ===
 * ApiQueryORM removed.
@@ -266,6 +326,7 @@ HHVM 3.1. Additionally, the following PHP extensions are required:
 * ApiMain::addFormat() was removed (deprecated in 1.21).
 * ApiMain::getFormats() was removed (deprecated in 1.21).
 * ApiPageSet::finishPageSetGeneration() was removed (deprecated in 1.21).
+* ApiCreateAccount is deprecated, and will be removed soon.
 
 === Languages updated in 1.27 ===
 
@@ -470,6 +531,11 @@ changes to languages because of Phabricator reports.
   performance on complex changes. $wgExternalDiffEngine = 'wikidiff3' therefore
   makes no difference now. Users are still recommended to use wikidiff2 if possible,
   though.
+* User::addNewUserLogEntry() was deprecated.
+* User::addNewUserLogEntryAutoCreate() was deprecated.
+* User::isPasswordReminderThrottled() was deprecated.
+* Bot-oriented parameters to Special:UserLogin (wpCookieCheck, wpSkipCookieCheck)
+  were removed.
 
 == Compatibility ==
 
diff --git a/RELEASE-NOTES-1.28 b/RELEASE-NOTES-1.28
new file mode 100644 (file)
index 0000000..e365486
--- /dev/null
@@ -0,0 +1,111 @@
+== MediaWiki 1.28 ==
+
+THIS IS NOT A RELEASE YET
+
+MediaWiki 1.28 is an alpha-quality branch and is not recommended for use in
+production.
+
+=== Configuration changes in 1.28 ===
+* The load.php entry point now enforces the existing policy of not allowing
+  access to session data, which includes the session user and the session
+  user's language. If such access is attempted, an exception will be thrown.
+
+=== New features in 1.28 ===
+* User::isBot() method for checking if an account is a bot role account.
+* Added a new hook, 'UserIsBot', to aid in determining if a user is a bot.
+
+
+=== External library changes in 1.28 ===
+
+==== Upgraded external libraries ====
+
+
+==== New external libraries ====
+
+
+==== Removed and replaced external libraries ====
+
+
+=== Bug fixes in 1.28 ===
+
+
+=== Action API changes in 1.28 ===
+
+
+=== Action API internal changes in 1.28 ===
+
+
+=== Languages updated in 1.28 ===
+
+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.
+
+=== Other changes in 1.27 ===
+
+
+== Compatibility ==
+
+MediaWiki 1.28 requires PHP 5.5.9 or later. There is experimental support for
+HHVM 3.6.5 or later.
+
+MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
+support for them is somewhat less mature. There is experimental support for
+Oracle and Microsoft SQL Server.
+
+The supported versions are:
+
+* MySQL 5.0.3 or later
+* PostgreSQL 8.3 or later
+* SQLite 3.3.7 or later
+* Oracle 9.0.1 or later
+* Microsoft SQL Server 2005 (9.00.1399)
+
+== Upgrading ==
+
+1.28 has several database changes since 1.27, and will not work without schema
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
+
+If upgrading from before 1.11, and you are using a wiki as a commons
+repository, make sure that it is updated as well. Otherwise, errors may arise
+due to database schema changes.
+
+If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
+new database fields are filled with data.
+
+If you are upgrading from MediaWiki 1.4.x or earlier, you should upgrade to
+1.5 first. The upgrade script maintenance/upgrade1_5.php has been removed
+with MediaWiki 1.21.
+
+Don't forget to always back up your database before upgrading!
+
+See the file UPGRADE for more detailed upgrade instructions.
+
+For notes on 1.27.x and older releases, see HISTORY.
+
+== Online documentation ==
+
+Documentation for both end-users and site administrators is available on
+MediaWiki.org, and is covered under the GNU Free Documentation License (except
+for pages that explicitly state that their contents are in the public domain):
+
+       https://www.mediawiki.org/wiki/Documentation
+
+== Mailing list ==
+
+A mailing list is available for MediaWiki user support and discussion:
+
+       https://lists.wikimedia.org/mailman/listinfo/mediawiki-l
+
+A low-traffic announcements-only list is also available:
+
+       https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce
+
+It's highly recommended that you sign up for one of these lists if you're
+going to run a public MediaWiki, so you can be notified of security fixes.
+
+== IRC help ==
+
+There's usually someone online in #mediawiki on irc.freenode.net.
index f802ddd..4e8259b 100644 (file)
@@ -17,10 +17,14 @@ $wgAutoloadLocalClasses = [
        'AlterSharedConstraints' => __DIR__ . '/maintenance/oracle/alterSharedConstraints.php',
        'AncientPagesPage' => __DIR__ . '/includes/specials/SpecialAncientpages.php',
        'AnsiTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
+       'ApiAMCreateAccount' => __DIR__ . '/includes/api/ApiAMCreateAccount.php',
+       'ApiAuthManagerHelper' => __DIR__ . '/includes/api/ApiAuthManagerHelper.php',
        'ApiBase' => __DIR__ . '/includes/api/ApiBase.php',
        'ApiBlock' => __DIR__ . '/includes/api/ApiBlock.php',
+       'ApiChangeAuthenticationData' => __DIR__ . '/includes/api/ApiChangeAuthenticationData.php',
        'ApiCheckToken' => __DIR__ . '/includes/api/ApiCheckToken.php',
        'ApiClearHasMsg' => __DIR__ . '/includes/api/ApiClearHasMsg.php',
+       'ApiClientLogin' => __DIR__ . '/includes/api/ApiClientLogin.php',
        'ApiComparePages' => __DIR__ . '/includes/api/ApiComparePages.php',
        'ApiContinuationManager' => __DIR__ . '/includes/api/ApiContinuationManager.php',
        'ApiCreateAccount' => __DIR__ . '/includes/api/ApiCreateAccount.php',
@@ -48,6 +52,7 @@ $wgAutoloadLocalClasses = [
        'ApiImageRotate' => __DIR__ . '/includes/api/ApiImageRotate.php',
        'ApiImport' => __DIR__ . '/includes/api/ApiImport.php',
        'ApiImportReporter' => __DIR__ . '/includes/api/ApiImport.php',
+       'ApiLinkAccount' => __DIR__ . '/includes/api/ApiLinkAccount.php',
        'ApiLogin' => __DIR__ . '/includes/api/ApiLogin.php',
        'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
        'ApiMain' => __DIR__ . '/includes/api/ApiMain.php',
@@ -75,6 +80,7 @@ $wgAutoloadLocalClasses = [
        'ApiQueryAllPages' => __DIR__ . '/includes/api/ApiQueryAllPages.php',
        'ApiQueryAllRevisions' => __DIR__ . '/includes/api/ApiQueryAllRevisions.php',
        'ApiQueryAllUsers' => __DIR__ . '/includes/api/ApiQueryAllUsers.php',
+       'ApiQueryAuthManagerInfo' => __DIR__ . '/includes/api/ApiQueryAuthManagerInfo.php',
        'ApiQueryBacklinks' => __DIR__ . '/includes/api/ApiQueryBacklinks.php',
        'ApiQueryBacklinksprop' => __DIR__ . '/includes/api/ApiQueryBacklinksprop.php',
        'ApiQueryBase' => __DIR__ . '/includes/api/ApiQueryBase.php',
@@ -123,6 +129,8 @@ $wgAutoloadLocalClasses = [
        'ApiQueryWatchlist' => __DIR__ . '/includes/api/ApiQueryWatchlist.php',
        'ApiQueryWatchlistRaw' => __DIR__ . '/includes/api/ApiQueryWatchlistRaw.php',
        'ApiRawMessage' => __DIR__ . '/includes/api/ApiMessage.php',
+       'ApiRemoveAuthenticationData' => __DIR__ . '/includes/api/ApiRemoveAuthenticationData.php',
+       'ApiResetPassword' => __DIR__ . '/includes/api/ApiResetPassword.php',
        'ApiResult' => __DIR__ . '/includes/api/ApiResult.php',
        'ApiRevisionDelete' => __DIR__ . '/includes/api/ApiRevisionDelete.php',
        'ApiRollback' => __DIR__ . '/includes/api/ApiRollback.php',
@@ -145,6 +153,7 @@ $wgAutoloadLocalClasses = [
        'AtomFeed' => __DIR__ . '/includes/Feed.php',
        'AtomicSectionUpdate' => __DIR__ . '/includes/deferred/AtomicSectionUpdate.php',
        'AttachLatest' => __DIR__ . '/maintenance/attachLatest.php',
+       'AuthManagerSpecialPage' => __DIR__ . '/includes/specialpage/AuthManagerSpecialPage.php',
        'AuthPlugin' => __DIR__ . '/includes/AuthPlugin.php',
        'AuthPluginUser' => __DIR__ . '/includes/AuthPlugin.php',
        'AutoLoader' => __DIR__ . '/includes/AutoLoader.php',
@@ -180,6 +189,7 @@ $wgAutoloadLocalClasses = [
        'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
        'Blob' => __DIR__ . '/includes/db/DatabaseUtility.php',
        'Block' => __DIR__ . '/includes/Block.php',
+       'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
        'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
        'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
        'BmpHandler' => __DIR__ . '/includes/media/BMP.php',
@@ -342,7 +352,7 @@ $wgAutoloadLocalClasses = [
        'DerivativeResourceLoaderContext' => __DIR__ . '/includes/resourceloader/DerivativeResourceLoaderContext.php',
        'DescribeFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'Diff' => __DIR__ . '/includes/diff/DairikiDiff.php',
-       'DiffEngine' => __DIR__ . '/includes/diff/DairikiDiff.php',
+       'DiffEngine' => __DIR__ . '/includes/diff/DiffEngine.php',
        'DiffFormatter' => __DIR__ . '/includes/diff/DiffFormatter.php',
        'DiffHistoryBlob' => __DIR__ . '/includes/HistoryBlob.php',
        'DiffOp' => __DIR__ . '/includes/diff/DairikiDiff.php',
@@ -420,6 +430,7 @@ $wgAutoloadLocalClasses = [
        'FSFileOpHandle' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSLockManager' => __DIR__ . '/includes/filebackend/lockmanager/FSLockManager.php',
        'FSRepo' => __DIR__ . '/includes/filerepo/FSRepo.php',
+       'FakeAuthTemplate' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
        'FakeConverter' => __DIR__ . '/languages/FakeConverter.php',
        'FakeMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
        'FakeResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
@@ -711,6 +722,7 @@ $wgAutoloadLocalClasses = [
        'LoadMonitorNull' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php',
        'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
        'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
+       'LocalFileLockError' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
        'LocalFileMoveBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
        'LocalFileRestoreBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
        'LocalIdLookup' => __DIR__ . '/includes/user/LocalIdLookup.php',
@@ -728,7 +740,11 @@ $wgAutoloadLocalClasses = [
        'LogPager' => __DIR__ . '/includes/logging/LogPager.php',
        'LoggedOutEditToken' => __DIR__ . '/includes/user/LoggedOutEditToken.php',
        'LoggedUpdateMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
-       'LoginForm' => __DIR__ . '/includes/specials/SpecialUserlogin.php',
+       'LoginForm' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
+       'LoginFormAuthManager' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
+       'LoginFormPreAuthManager' => __DIR__ . '/includes/specials/pre-authmanager/SpecialUserlogin.php',
+       'LoginHelper' => __DIR__ . '/includes/specials/helpers/LoginHelper.php',
+       'LoginSignupSpecialPage' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
        'LonelyPagesPage' => __DIR__ . '/includes/specials/SpecialLonelypages.php',
        'LongPagesPage' => __DIR__ . '/includes/specials/SpecialLongpages.php',
        'MIMEsearchPage' => __DIR__ . '/includes/specials/SpecialMIMEsearch.php',
@@ -756,13 +772,13 @@ $wgAutoloadLocalClasses = [
        'MagicWord' => __DIR__ . '/includes/MagicWord.php',
        'MagicWordArray' => __DIR__ . '/includes/MagicWordArray.php',
        'MailAddress' => __DIR__ . '/includes/mail/MailAddress.php',
+       'MainConfigDependency' => __DIR__ . '/includes/cache/CacheDependency.php',
        'Maintenance' => __DIR__ . '/maintenance/Maintenance.php',
        'MaintenanceFormatInstallDoc' => __DIR__ . '/maintenance/formatInstallDoc.php',
        'MakeTestEdits' => __DIR__ . '/maintenance/makeTestEdits.php',
        'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php',
        'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
        'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php',
-       'MappedDiff' => __DIR__ . '/includes/diff/DairikiDiff.php',
        'MappedIterator' => __DIR__ . '/includes/libs/MappedIterator.php',
        'MarkpatrolledAction' => __DIR__ . '/includes/actions/MarkpatrolledAction.php',
        'McTest' => __DIR__ . '/maintenance/mctest.php',
@@ -777,6 +793,44 @@ $wgAutoloadLocalClasses = [
        'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php',
        'MediaWikiTitleCodec' => __DIR__ . '/includes/title/MediaWikiTitleCodec.php',
        'MediaWikiVersionFetcher' => __DIR__ . '/includes/MediaWikiVersionFetcher.php',
+       'MediaWiki\\Interwiki\\ClassicInterwikiLookup' => __DIR__ . '/includes/interwiki/ClassicInterwikiLookup.php',
+       'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ . '/includes/interwiki/InterwikiLookup.php',
+       'MediaWiki\\Auth\\AbstractAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractAuthenticationProvider.php',
+       'MediaWiki\\Auth\\AbstractPasswordPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractPasswordPrimaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\AbstractPreAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractPreAuthenticationProvider.php',
+       'MediaWiki\\Auth\\AbstractPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractPrimaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\AbstractSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractSecondaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\AuthManager' => __DIR__ . '/includes/auth/AuthManager.php',
+       'MediaWiki\\Auth\\AuthManagerAuthPlugin' => __DIR__ . '/includes/auth/AuthManagerAuthPlugin.php',
+       'MediaWiki\\Auth\\AuthManagerAuthPluginUser' => __DIR__ . '/includes/auth/AuthManagerAuthPlugin.php',
+       'MediaWiki\\Auth\\AuthPluginPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/AuthPluginPrimaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\AuthenticationProvider' => __DIR__ . '/includes/auth/AuthenticationProvider.php',
+       'MediaWiki\\Auth\\AuthenticationRequest' => __DIR__ . '/includes/auth/AuthenticationRequest.php',
+       'MediaWiki\\Auth\\AuthenticationResponse' => __DIR__ . '/includes/auth/AuthenticationResponse.php',
+       'MediaWiki\\Auth\\ButtonAuthenticationRequest' => __DIR__ . '/includes/auth/ButtonAuthenticationRequest.php',
+       'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\ConfirmLinkAuthenticationRequest' => __DIR__ . '/includes/auth/ConfirmLinkAuthenticationRequest.php',
+       'MediaWiki\\Auth\\ConfirmLinkSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/ConfirmLinkSecondaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\CreateFromLoginAuthenticationRequest' => __DIR__ . '/includes/auth/CreateFromLoginAuthenticationRequest.php',
+       'MediaWiki\\Auth\\CreatedAccountAuthenticationRequest' => __DIR__ . '/includes/auth/CreatedAccountAuthenticationRequest.php',
+       'MediaWiki\\Auth\\CreationReasonAuthenticationRequest' => __DIR__ . '/includes/auth/CreationReasonAuthenticationRequest.php',
+       'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/EmailNotificationSecondaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\LegacyHookPreAuthenticationProvider' => __DIR__ . '/includes/auth/LegacyHookPreAuthenticationProvider.php',
+       'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\PasswordAuthenticationRequest' => __DIR__ . '/includes/auth/PasswordAuthenticationRequest.php',
+       'MediaWiki\\Auth\\PasswordDomainAuthenticationRequest' => __DIR__ . '/includes/auth/PasswordDomainAuthenticationRequest.php',
+       'MediaWiki\\Auth\\PreAuthenticationProvider' => __DIR__ . '/includes/auth/PreAuthenticationProvider.php',
+       'MediaWiki\\Auth\\PrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/PrimaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\RememberMeAuthenticationRequest' => __DIR__ . '/includes/auth/RememberMeAuthenticationRequest.php',
+       'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/ResetPasswordSecondaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\SecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/SecondaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest' => __DIR__ . '/includes/auth/TemporaryPasswordAuthenticationRequest.php',
+       'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php',
+       'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => __DIR__ . '/includes/auth/ThrottlePreAuthenticationProvider.php',
+       'MediaWiki\\Auth\\Throttler' => __DIR__ . '/includes/auth/Throttler.php',
+       'MediaWiki\\Auth\\UserDataAuthenticationRequest' => __DIR__ . '/includes/auth/UserDataAuthenticationRequest.php',
+       'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ . '/includes/auth/UsernameAuthenticationRequest.php',
+       'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
        'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
        'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php',
        'MediaWiki\\Linker\\LinkTarget' => __DIR__ . '/includes/linker/LinkTarget.php',
@@ -795,7 +849,14 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
        'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
        'MediaWiki\\MediaWikiServices' => __DIR__ . '/includes/MediaWikiServices.php',
+       'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/Services/CannotReplaceActiveServiceException.php',
+       'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/Services/ContainerDisabledException.php',
+       'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/Services/DestructibleService.php',
+       'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/Services/SalvageableService.php',
+       'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/Services/NoSuchServiceException.php',
+       'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/Services/ServiceAlreadyDefinedException.php',
        'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/Services/ServiceContainer.php',
+       'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/Services/ServiceDisabledException.php',
        'MediaWiki\\Session\\BotPasswordSessionProvider' => __DIR__ . '/includes/session/BotPasswordSessionProvider.php',
        'MediaWiki\\Session\\CookieSessionProvider' => __DIR__ . '/includes/session/CookieSessionProvider.php',
        'MediaWiki\\Session\\ImmutableSessionProviderWithCookie' => __DIR__ . '/includes/session/ImmutableSessionProviderWithCookie.php',
@@ -954,6 +1015,7 @@ $wgAutoloadLocalClasses = [
        'PasswordError' => __DIR__ . '/includes/password/PasswordError.php',
        'PasswordFactory' => __DIR__ . '/includes/password/PasswordFactory.php',
        'PasswordPolicyChecks' => __DIR__ . '/includes/password/PasswordPolicyChecks.php',
+       'PasswordReset' => __DIR__ . '/includes/user/PasswordReset.php',
        'PatchSql' => __DIR__ . '/maintenance/patchSql.php',
        'PathRouter' => __DIR__ . '/includes/PathRouter.php',
        'PathRouterPatternReplacer' => __DIR__ . '/includes/PathRouter.php',
@@ -1028,7 +1090,7 @@ $wgAutoloadLocalClasses = [
        'RCFeedFormatter' => __DIR__ . '/includes/rcfeed/RCFeedFormatter.php',
        'RSSFeed' => __DIR__ . '/includes/Feed.php',
        'RandomPage' => __DIR__ . '/includes/specials/SpecialRandompage.php',
-       'RangeDifference' => __DIR__ . '/includes/diff/WikiDiff3.php',
+       'RangeDifference' => __DIR__ . '/includes/diff/DiffEngine.php',
        'RawAction' => __DIR__ . '/includes/actions/RawAction.php',
        'RawMessage' => __DIR__ . '/includes/Message.php',
        'ReadOnlyError' => __DIR__ . '/includes/exception/ReadOnlyError.php',
@@ -1085,6 +1147,7 @@ $wgAutoloadLocalClasses = [
        'ResourceLoaderSkinModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSkinModule.php',
        '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',
        'ResourceLoaderUserGroupsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserGroupsModule.php',
@@ -1193,11 +1256,15 @@ $wgAutoloadLocalClasses = [
        'SpecialCachedPage' => __DIR__ . '/includes/specials/SpecialCachedPage.php',
        'SpecialCategories' => __DIR__ . '/includes/specials/SpecialCategories.php',
        'SpecialChangeContentModel' => __DIR__ . '/includes/specials/SpecialChangeContentModel.php',
+       'SpecialChangeCredentials' => __DIR__ . '/includes/specials/SpecialChangeCredentials.php',
        'SpecialChangeEmail' => __DIR__ . '/includes/specials/SpecialChangeEmail.php',
+       'SpecialChangeEmailPreAuthManager' => __DIR__ . '/includes/specials/pre-authmanager/SpecialChangeEmail.php',
        'SpecialChangePassword' => __DIR__ . '/includes/specials/SpecialChangePassword.php',
+       'SpecialChangePasswordPreAuthManager' => __DIR__ . '/includes/specials/pre-authmanager/SpecialChangePassword.php',
        'SpecialComparePages' => __DIR__ . '/includes/specials/SpecialComparePages.php',
        'SpecialContributions' => __DIR__ . '/includes/specials/SpecialContributions.php',
        'SpecialCreateAccount' => __DIR__ . '/includes/specials/SpecialCreateAccount.php',
+       'SpecialCreateAccountPreAuthManager' => __DIR__ . '/includes/specials/pre-authmanager/SpecialCreateAccount.php',
        'SpecialDiff' => __DIR__ . '/includes/specials/SpecialDiff.php',
        'SpecialEditTags' => __DIR__ . '/includes/specials/SpecialEditTags.php',
        'SpecialEditWatchlist' => __DIR__ . '/includes/specials/SpecialEditWatchlist.php',
@@ -1207,6 +1274,7 @@ $wgAutoloadLocalClasses = [
        'SpecialFilepath' => __DIR__ . '/includes/specials/SpecialFilepath.php',
        'SpecialImport' => __DIR__ . '/includes/specials/SpecialImport.php',
        'SpecialJavaScriptTest' => __DIR__ . '/includes/specials/SpecialJavaScriptTest.php',
+       'SpecialLinkAccounts' => __DIR__ . '/includes/specials/SpecialLinkAccounts.php',
        'SpecialListAdmins' => __DIR__ . '/includes/specials/SpecialListusers.php',
        'SpecialListBots' => __DIR__ . '/includes/specials/SpecialListusers.php',
        'SpecialListFiles' => __DIR__ . '/includes/specials/SpecialListfiles.php',
@@ -1229,6 +1297,7 @@ $wgAutoloadLocalClasses = [
        'SpecialPageLanguage' => __DIR__ . '/includes/specials/SpecialPageLanguage.php',
        'SpecialPagesWithProp' => __DIR__ . '/includes/specials/SpecialPagesWithProp.php',
        'SpecialPasswordReset' => __DIR__ . '/includes/specials/SpecialPasswordReset.php',
+       'SpecialPasswordResetPreAuthManager' => __DIR__ . '/includes/specials/pre-authmanager/SpecialPasswordReset.php',
        'SpecialPermanentLink' => __DIR__ . '/includes/specials/SpecialPermanentLink.php',
        'SpecialPreferences' => __DIR__ . '/includes/specials/SpecialPreferences.php',
        'SpecialPrefixindex' => __DIR__ . '/includes/specials/SpecialPrefixindex.php',
@@ -1241,6 +1310,7 @@ $wgAutoloadLocalClasses = [
        'SpecialRecentChangesLinked' => __DIR__ . '/includes/specials/SpecialRecentchangeslinked.php',
        'SpecialRedirect' => __DIR__ . '/includes/specials/SpecialRedirect.php',
        'SpecialRedirectToSpecial' => __DIR__ . '/includes/specialpage/RedirectSpecialPage.php',
+       'SpecialRemoveCredentials' => __DIR__ . '/includes/specials/SpecialRemoveCredentials.php',
        'SpecialResetTokens' => __DIR__ . '/includes/specials/SpecialResetTokens.php',
        'SpecialRevisionDelete' => __DIR__ . '/includes/specials/SpecialRevisiondelete.php',
        'SpecialRunJobs' => __DIR__ . '/includes/specials/SpecialRunJobs.php',
@@ -1251,11 +1321,14 @@ $wgAutoloadLocalClasses = [
        'SpecialTrackingCategories' => __DIR__ . '/includes/specials/SpecialTrackingCategories.php',
        'SpecialUnblock' => __DIR__ . '/includes/specials/SpecialUnblock.php',
        'SpecialUndelete' => __DIR__ . '/includes/specials/SpecialUndelete.php',
+       'SpecialUnlinkAccounts' => __DIR__ . '/includes/specials/SpecialUnlinkAccounts.php',
        'SpecialUnlockdb' => __DIR__ . '/includes/specials/SpecialUnlockdb.php',
        'SpecialUpload' => __DIR__ . '/includes/specials/SpecialUpload.php',
        'SpecialUploadStash' => __DIR__ . '/includes/specials/SpecialUploadStash.php',
        'SpecialUploadStashTooLargeException' => __DIR__ . '/includes/specials/SpecialUploadStash.php',
-       'SpecialUserlogout' => __DIR__ . '/includes/specials/SpecialUserlogout.php',
+       'SpecialUserLogin' => __DIR__ . '/includes/specials/SpecialUserLogin.php',
+       'SpecialUserLogout' => __DIR__ . '/includes/specials/SpecialUserLogout.php',
+       'SpecialUserlogoutPreAuthManager' => __DIR__ . '/includes/specials/pre-authmanager/SpecialUserlogout.php',
        'SpecialVersion' => __DIR__ . '/includes/specials/SpecialVersion.php',
        'SpecialWatchlist' => __DIR__ . '/includes/specials/SpecialWatchlist.php',
        'SpecialWhatLinksHere' => __DIR__ . '/includes/specials/SpecialWhatlinkshere.php',
@@ -1435,7 +1508,6 @@ $wgAutoloadLocalClasses = [
        'WebRequestUpload' => __DIR__ . '/includes/WebRequestUpload.php',
        'WebResponse' => __DIR__ . '/includes/WebResponse.php',
        'WikiCategoryPage' => __DIR__ . '/includes/page/WikiCategoryPage.php',
-       'WikiDiff3' => __DIR__ . '/includes/diff/WikiDiff3.php',
        'WikiExporter' => __DIR__ . '/includes/export/WikiExporter.php',
        'WikiFilePage' => __DIR__ . '/includes/page/WikiFilePage.php',
        'WikiImporter' => __DIR__ . '/includes/import/WikiImporter.php',
@@ -1448,7 +1520,7 @@ $wgAutoloadLocalClasses = [
        'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
        'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
        'WithoutInterwikiPage' => __DIR__ . '/includes/specials/SpecialWithoutinterwiki.php',
-       'WordLevelDiff' => __DIR__ . '/includes/diff/DairikiDiff.php',
+       'WordLevelDiff' => __DIR__ . '/includes/diff/WordLevelDiff.php',
        'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
        'XCFHandler' => __DIR__ . '/includes/media/XCF.php',
        'XCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/XCacheBagOStuff.php',
@@ -1457,6 +1529,7 @@ $wgAutoloadLocalClasses = [
        'XMPReader' => __DIR__ . '/includes/media/XMP.php',
        'XMPValidate' => __DIR__ . '/includes/media/XMPValidate.php',
        'Xhprof' => __DIR__ . '/includes/libs/Xhprof.php',
+       'XhprofData' => __DIR__ . '/includes/libs/XhprofData.php',
        'Xml' => __DIR__ . '/includes/Xml.php',
        'XmlDumpWriter' => __DIR__ . '/includes/export/XmlDumpWriter.php',
        'XmlJsCode' => __DIR__ . '/includes/Xml.php',
index 4fb107e..ef85ec4 100644 (file)
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.17.0",
+               "oojs/oojs-ui": "0.17.2",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
                "wikimedia/assert": "0.2.2",
                "wikimedia/base-convert": "1.0.1",
-               "wikimedia/cdb": "1.3.0",
+               "wikimedia/cdb": "1.4.0",
                "wikimedia/cldr-plural-rule-parser": "1.0.0",
                "wikimedia/composer-merge-plugin": "1.3.1",
                "wikimedia/html-formatter": "1.0.1",
-               "wikimedia/ip-set": "1.0.1",
+               "wikimedia/ip-set": "1.1.0",
                "wikimedia/php-session-serializer": "1.0.3",
                "wikimedia/relpath": "1.0.3",
                "wikimedia/running-stat": "1.1.0",
@@ -46,7 +46,7 @@
        "require-dev": {
                "jakub-onderka/php-parallel-lint": "0.9.2",
                "justinrainbow/json-schema": "~1.3",
-               "mediawiki/mediawiki-codesniffer": "0.6.0",
+               "mediawiki/mediawiki-codesniffer": "0.7.1",
                "monolog/monolog": "~1.18.2",
                "nikic/php-parser": "1.4.1",
                "nmred/kafka-php": "0.1.5",
index 3c2c057..1d2b2f0 100644 (file)
                        "type": "object",
                        "description": "Registry of factory functions to create Config objects"
                },
+               "SessionProviders": {
+                       "type": "object",
+                       "description": "Session providers"
+               },
+               "AuthManagerAutoConfig": {
+                       "type": "object",
+                       "description": "AuthManager auto-configuration",
+                       "additionalProperties": false,
+                       "properties": {
+                               "preauth": {
+                                       "type": "object",
+                                       "description": "Pre-authentication providers"
+                               },
+                               "primaryauth": {
+                                       "type": "object",
+                                       "description": "Primary authentication providers"
+                               },
+                               "secondaryauth": {
+                                       "type": "object",
+                                       "description": "Secondary authentication providers"
+                               }
+                       }
+               },
                "CentralIdLookupProviders": {
                        "type": "object",
                        "description": "Central ID lookup providers"
                        "type": "object"
                },
                "Hooks": {
-                       "type": "object",
+                       "type": [ "string", "object" ],
                        "description": "Hooks this extension uses (mapping of hook name to callback)"
                },
                "JobClasses": {
index 31e9d92..f652786 100644 (file)
@@ -238,9 +238,10 @@ MediaWiki 1.4rc1.
 This is a list of known events and parameters; please add to it if you're going
 to add events to the MediaWiki code.
 
-'AbortAutoAccount': Return false to cancel automated local account creation,
-where normally authentication against an external auth plugin would be creating
-a local account.
+'AbortAutoAccount': DEPRECATED! Create a PreAuthenticationProvider instead.
+Return false to cancel automated local account creation, where normally
+authentication against an external auth plugin would be creating a local
+account.
 $user: the User object about to be created (read-only, incomplete)
 &$abortMsg: out parameter: name of error message to be displayed to user
 
@@ -248,12 +249,6 @@ $user: the User object about to be created (read-only, incomplete)
 $autoblockip: The IP going to be autoblocked.
 &$block: The block from which the autoblock is coming.
 
-'AbortChangePassword': Return false to cancel password change.
-$user: the User object to which the password change is occuring
-$mOldpass: the old password provided by the user
-$newpass: the new password provided by the user
-&$abortMsg: the message identifier for abort reason
-
 'AbortDiffCache': Can be used to cancel the caching of a diff.
 &$diffEngine: DifferenceEngine object
 
@@ -262,7 +257,8 @@ $editor: The User who made the change.
 $title: The Title of the page that was edited.
 $rc: The current RecentChange object.
 
-'AbortLogin': Return false to cancel account login.
+'AbortLogin': DEPRECATED! Create a PreAuthenticationProvider instead.
+Return false to cancel account login.
 $user: the User object being authenticated against
 $password: the password being submitted, not yet checked for validity
 &$retval: a LoginForm class constant to return from authenticateUserData();
@@ -271,7 +267,8 @@ $password: the password being submitted, not yet checked for validity
 &$msg: the message identifier for abort reason (new in 1.18, not available
   before 1.18)
 
-'AbortNewAccount': Return false to cancel explicit account creation.
+'AbortNewAccount': DEPRECATED! Create a PreAuthenticationProvider instead.
+Return false to cancel explicit account creation.
 $user: the User object about to be created (read-only, incomplete)
 &$msg: out parameter: HTML to display on abort
 &$status: out parameter: Status object to return, replaces the older $msg param
@@ -295,7 +292,8 @@ $name: name of the action
 &$fields: HTMLForm descriptor array
 $article: Article object
 
-'AddNewAccount': After a user account is created.
+'AddNewAccount': DEPRECATED! Use LocalUserCreated.
+After a user account is created.
 $user: the User object that was created. (Parameter added in 1.7)
 $byEmail: true when account was created "by email" (added in 1.12)
 
@@ -744,13 +742,32 @@ viewing.
 redirect was followed.
 &$article: target article (object)
 
+'AuthChangeFormFields': After converting a field information array obtained
+from a set of AuthenticationRequest classes into a form descriptor; hooks
+can tweak the array to change how login etc. forms should look.
+$requests: array of AuthenticationRequests the fields are created from
+$fieldInfo: field information array (union of all AuthenticationRequest::getFieldInfo() responses).
+&$formDescriptor: HTMLForm descriptor. The special key 'weight' can be set
+  to change the order of the fields.
+$action: one of the AuthManager::ACTION_* constants.
+
+'AuthManagerLoginAuthenticateAudit': A login attempt either succeeded or failed
+for a reason other than misconfiguration or session loss. No return data is
+accepted; this hook is for auditing only.
+$response: The MediaWiki\Auth\AuthenticationResponse in either a PASS or FAIL state.
+$user: The User object being authenticated against, or null if authentication
+  failed before getting that far.
+$username: A guess at the user name being authenticated, or null if we can't
+  even determine that.
+
 'AuthPluginAutoCreate': DEPRECATED! Use the 'LocalUserCreated' hook instead.
 Called when creating a local account for an user logged in from an external
 authentication method.
 $user: User object created locally
 
-'AuthPluginSetup': Update or replace authentication plugin object ($wgAuth).
-Gives a chance for an extension to set it programmatically to a variable class.
+'AuthPluginSetup': DEPRECATED! Extensions should be updated to use AuthManager.
+Update or replace authentication plugin object ($wgAuth). Gives a chance for an
+extension to set it programmatically to a variable class.
 &$auth: the $wgAuth object, probably a stub
 
 'AutopromoteCondition': Check autopromote condition for user.
@@ -916,8 +933,14 @@ $html: Requested html content of anchor
 &$link: Returned value. When set to a non-null value by a hook subscriber
   this value will be used as the anchor instead of Linker::link
 
-'ChangePasswordForm': For extensions that need to add a field to the
-ChangePassword form via the Preferences form.
+'ChangeAuthenticationDataAudit': Called when user changes his password.
+No return data is accepted; this hook is for auditing only.
+$req: AuthenticationRequest object describing the change (and target user)
+$status: StatusValue with the result of the action
+
+'ChangePasswordForm': DEPRECATED! Use AuthChangeFormFields or security levels.
+For extensions that need to add a field to the ChangePassword form via the
+Preferences form.
 &$extraFields: An array of arrays that hold fields like would be passed to the
   pretty function.
 
@@ -1924,16 +1947,11 @@ in LoginForm::$validErrorMessages).
 &$messages: Already added messages (inclusive messages from
   LoginForm::$validErrorMessages)
 
-'LoginPasswordResetMessage': User is being requested to reset their password on
-login. Use this hook to change the Message that will be output on
-Special:ChangePassword.
-&$msg: Message object that will be shown to the user
-$username: Username of the user who's password was expired.
-
-'LoginUserMigrated': Called during login to allow extensions the opportunity to
-inform a user that their username doesn't exist for a specific reason, instead
-of letting the login form give the generic error message that the account does
-not exist. For example, when the account has been renamed or deleted.
+'LoginUserMigrated': DEPRECATED! Create a PreAuthenticationProvider instead.
+Called during login to allow extensions the opportunity to inform a user that
+their username doesn't exist for a specific reason, instead of letting the
+login form give the generic error message that the account does not exist. For
+example, when the account has been renamed or deleted.
 $user: the User object being authenticated against.
 &$msg: the message identifier for abort reason, or an array to pass a message
   key and parameters.
@@ -1997,9 +2015,10 @@ $user: $wgUser
 $request: $wgRequest
 $mediaWiki: The $mediawiki object
 
-'MediaWikiServices': Override services in the default MediaWikiServices instance.
-Extensions may use this to define, replace, or wrap existing services.
-However, the preferred way to define a new service is the $wgServiceWiringFiles array.
+'MediaWikiServices': Called when a global MediaWikiServices instance is
+initialized. Extensions may use this to define, replace, or wrap services.
+However, the preferred way to define a new service is
+the $wgServiceWiringFiles array.
 $services: MediaWikiServices
 
 'MessageCache::get': When fetching a message. Can be used to override the key
@@ -2127,6 +2146,7 @@ $sk: The Skin that called OutputPage::headElement
 since the last visit.
 &$modifiedTimes: array of timestamps.
   The following keys are set: page, user, epoch
+$out: OutputPage object (since 1.28)
 
 'OutputPageMakeCategoryLinks': Links are about to be generated for the page's
 categories. Implementations should return false if they generate the category
@@ -2425,11 +2445,6 @@ $user: User (object) changing his email address
 $oldaddr: old email address (string)
 $newaddr: new email address (string)
 
-'PrefsPasswordAudit': Called when user changes his password.
-$user: User (object) changing his password
-$newPass: new password
-$error: error (string) 'badretype', 'wrongpassword', 'error' or 'success'
-
 'ProtectionForm::buildForm': Called after all protection type fieldsets are made
 in the form.
 $article: the title being (un)protected
@@ -2569,6 +2584,17 @@ $parserOutput: ParserOutput representing the rendered version of the page
 &$updates: a list of DataUpdate objects, to be modified or replaced by
   the hook handler.
 
+'SecuritySensitiveOperationStatus': Affect the return value from
+MediaWiki\Auth\AuthManager::securitySensitiveOperationStatus().
+&$status: (string) The status to be returned. One of the AuthManager::SEC_*
+  constants. SEC_REAUTH will be automatically changed to SEC_FAIL if
+  authentication isn't possible for the current session type.
+$operation: (string) The operation being checked.
+$session: (MediaWiki\Session\Session) The current session. The
+  currently-authenticated user may be retrieved as $session->getUser().
+$timeSinceAuth: (int) The time since last authentication. PHP_INT_MAX if
+  the time of last auth is unknown, or -1 if authentication is not possible.
+
 'SelfLinkBegin': Called before a link to the current article is displayed to
 allow the display of the link to be customized.
 $nt: the Title object
@@ -3215,6 +3241,10 @@ $mime: (string) The uploaded file's MIME type, as detected by MediaWiki.
   representing the problem with the file, where the first element is the message
   key and the remaining elements are used as parameters to the message.
 
+'UserIsBot': when determining whether a user is a bot account
+$user: the user
+&$isBot: whether this is user a bot or not (boolean)
+
 'User::mailPasswordInternal': before creation and mailing of a user's new
 temporary password
 &$user: the user who sent the message out
@@ -3248,7 +3278,8 @@ messages!" message, return false to not delete it.
 &$user: User (object) that will clear the message
 $oldid: ID of the talk page revision being viewed (0 means the most recent one)
 
-'UserCreateForm': change to manipulate the login form
+'UserCreateForm': DEPRECATED! Create an AuthenticationProvider instead.
+Manipulate the login form.
 &$template: SimpleTemplate instance for the form
 
 'UserEffectiveGroups': Called in User::getEffectiveGroups().
@@ -3351,12 +3382,14 @@ $user: User object
 'UserLoggedIn': Called after a user is logged in
 $user: User object for the logged-in user
 
-'UserLoginComplete': After a user has logged in.
+'UserLoginComplete': Show custom content after a user has logged in via the web interface.
+For functionality that needs to run after any login (API or web) use UserLoggedIn.
 &$user: the user object that was created on login
 &$inject_html: Any HTML to inject after the "logged in" message.
 
-'UserLoginForm': change to manipulate the login form
-&$template: SimpleTemplate instance for the form
+'UserLoginForm': DEPRECATED! Create an AuthenticationProvider instead.
+Manipulate the login form.
+&$template: QuickTemplate instance for the form
 
 'UserLogout': Before a user logs out.
 &$user: the user object that is about to be logged out
index e0466c4..2badea9 100644 (file)
@@ -60,6 +60,27 @@ MediaWikiServices::getInstance() should ideally be accessed only in "static
 entry points" such as hook handler functions. See "Migration" below.
 
 
+== Service Reset ==
+
+Services get their configuration injected, and changes to global
+configuration variables will not have any effect on services that were already
+instantiated. This would typically be the case for low level services like
+the ConfigFactory or the ObjectCacheManager, which are used during extension
+registration. To address this issue, Setup.php resets the global service
+locator instance by calling MediaWikiServices::resetGlobalInstance() once
+configuration and extension registration is complete.
+
+Note that "unmanaged" legacy services services that manage their own singleton
+must not keep references to services managed by MediaWikiServices, to allow a
+clean reset. After the global MediaWikiServices instance got reset, any such
+references would be stale, and using a stale service will result in an error.
+
+Services should either have all dependencies injected and be themselves managed
+by MediaWikiServices, or they should use the Service Locator pattern, accessing
+service instances via the global MediaWikiServices instance state when needed.
+This ensures that no stale service references remain after a reset.
+
+
 == Configuration ==
 
 When the default MediaWikiServices instance is created, a Config object is
index bad230e..923a19b 100644 (file)
@@ -11,8 +11,7 @@ directory and make a symbolic link:
  mediawiki/extensions$ ln -s ../../extensions-trunk/FooBar
 
 Most extensions are available through Git:
-    https://gerrit.wikimedia.org/r/#/admin/projects/?filter=mediawiki%252Fextensions%252F
-    https://git.wikimedia.org/project/mediawiki
+    https://phabricator.wikimedia.org/diffusion/MEXT/
 
 
 Please note that under POSIX systems (Linux...), parent of a symbolic path
index 6449d37..0b65593 100644 (file)
@@ -32,6 +32,8 @@
  * accounts authenticate externally, or use it only as a fallback; also
  * you can transparently create internal wiki accounts the first time
  * someone logs in who can be authenticated externally.
+ *
+ * @deprecated since 1.27
  */
 class AuthPlugin {
        /**
@@ -322,6 +324,9 @@ class AuthPlugin {
        }
 }
 
+/**
+ * @deprecated since 1.27
+ */
 class AuthPluginUser {
        function __construct( $user ) {
                # Override this!
@@ -352,6 +357,9 @@ class AuthPluginUser {
                return false;
        }
 
+       /**
+        * @deprecated since 1.28, use SessionManager::invalidateSessionForUser() instead.
+        */
        public function resetAuthToken() {
                # Override this!
                return true;
index f749003..389b077 100644 (file)
@@ -138,9 +138,13 @@ class CategoryViewer extends ContextSource {
                }
 
                $lang = $this->getLanguage();
-               $langAttribs = [ 'lang' => $lang->getHtmlCode(), 'dir' => $lang->getDir() ];
+               $attribs = [
+                       'class' => 'mw-category-generated',
+                       'lang' => $lang->getHtmlCode(),
+                       'dir' => $lang->getDir()
+               ];
                # put a div around the headings which are in the user language
-               $r = Html::openElement( 'div', $langAttribs ) . $r . '</div>';
+               $r = Html::openElement( 'div', $attribs ) . $r . '</div>';
 
                return $r;
        }
index 5b3684b..d6db388 100644 (file)
@@ -75,7 +75,7 @@ $wgConfigRegistry = [
  * MediaWiki version number
  * @since 1.2
  */
-$wgVersion = '1.27.0-alpha';
+$wgVersion = '1.28.0-alpha';
 
 /**
  * Name of the site. It must be changed in LocalSettings.php
@@ -535,6 +535,64 @@ $wgUseInstantCommons = false;
  */
 $wgForeignUploadTargets = [];
 
+/**
+ * Configuration for file uploads using the embeddable upload dialog
+ * (https://www.mediawiki.org/wiki/Upload_dialog).
+ *
+ * This applies also to foreign uploads to this wiki (the configuration is loaded by remote wikis
+ * using the action=query&meta=siteinfo API).
+ *
+ * See below for documentation of each property. None of the properties may be omitted.
+ */
+$wgUploadDialog = [
+       // Fields to make available in the dialog. `true` means that this field is visible, `false` means
+       // that it is hidden. The "Name" field can't be hidden. Note that you also have to add the
+       // matching replacement to the 'filepage' format key below to make use of these.
+       'fields' => [
+               'description' => true,
+               'date' => false,
+               'categories' => false,
+       ],
+       // Suffix of localisation messages used to describe the license under which the uploaded file will
+       // be released. The same value may be set for both 'local' and 'foreign' uploads.
+       'licensemessages' => [
+               // The 'local' messages are used for local uploads on this wiki:
+               // * upload-form-label-own-work-message-generic-local
+               // * upload-form-label-not-own-work-message-generic-local
+               // * upload-form-label-not-own-work-local-generic-local
+               'local' => 'generic-local',
+               // The 'foreign' messages are used for cross-wiki uploads from other wikis to this wiki:
+               // * upload-form-label-own-work-message-generic-foreign
+               // * upload-form-label-not-own-work-message-generic-foreign
+               // * upload-form-label-not-own-work-local-generic-foreign
+               'foreign' => 'generic-foreign',
+       ],
+       // Upload comment to use. Available replacements:
+       // * $HOST - domain name from which a cross-wiki upload originates
+       // * $PAGENAME - wiki page name from which an upload originates
+       'comment' => '',
+       // Format of the file page wikitext to be generated from the fields input by the user.
+       'format' => [
+               // Wrapper for the whole page. Available replacements:
+               // * $DESCRIPTION - file description, as input by the user (only if the 'description' field is
+               //   enabled), wrapped as defined below in the 'description' key
+               // * $DATE - file creation date, as input by the user (only if the 'date' field is enabled)
+               // * $SOURCE - as defined below in the 'ownwork' key, may be extended in the future
+               // * $AUTHOR - linked user name, may be extended in the future
+               // * $LICENSE - as defined below in the 'license' key, may be extended in the future
+               // * $CATEGORIES - file categories wikitext, as input by the user (only if the 'categories'
+               //   field is enabled), or if no input, as defined below in the 'uncategorized' key
+               'filepage' => '$DESCRIPTION',
+               // Wrapped for file description. Available replacements:
+               // * $LANGUAGE - source wiki's content language
+               // * $TEXT - input by the user
+               'description' => '$TEXT',
+               'ownwork' => '',
+               'license' => '',
+               'uncategorized' => '',
+       ],
+];
+
 /**
  * File backend structure configuration.
  *
@@ -3130,24 +3188,6 @@ $wgHTMLFormAllowTableFormat = true;
  */
 $wgUseMediaWikiUIEverywhere = false;
 
-/**
- * Should we try to make our HTML output well-formed XML?  If set to false,
- * output will be a few bytes shorter, and the HTML will arguably be more
- * readable.  If set to true, life will be much easier for the authors of
- * screen-scraping bots, and the HTML will arguably be more readable.
- *
- * Setting this to false may omit quotation marks on some attributes, omit
- * slashes from some self-closing tags, omit some ending tags, etc., where
- * permitted by HTML5.  Setting it to true will not guarantee that all pages
- * will be well-formed, although non-well-formed pages should be rare and it's
- * a bug if you find one.  Conversely, setting it to false doesn't mean that
- * all XML-y constructs will be omitted, just that they might be.
- *
- * Because of compatibility with screen-scraping bots, and because it's
- * controversial, this is currently left to true by default.
- */
-$wgWellFormedXml = true;
-
 /**
  * Permit other namespaces in addition to the w3.org default.
  *
@@ -4406,6 +4446,144 @@ $wgPasswordPolicy = [
        ],
 ];
 
+/**
+ * Disable AuthManager
+ * @since 1.27
+ * @deprecated since 1.27, for use during development only
+ */
+$wgDisableAuthManager = true;
+
+/**
+ * Configure AuthManager
+ *
+ * All providers are constructed using ObjectFactory, see that for the general
+ * structure. The array may also contain a key "sort" used to order providers:
+ * providers are stably sorted by this value, which should be an integer
+ * (default is 0).
+ *
+ * Elements are:
+ * - preauth: Array (keys ignored) of specifications for PreAuthenticationProviders
+ * - primaryauth: Array (keys ignored) of specifications for PrimaryAuthenticationProviders
+ * - secondaryauth: Array (keys ignored) of specifications for SecondaryAuthenticationProviders
+ *
+ * @since 1.27
+ * @note If this is null or empty, the value from $wgAuthManagerAutoConfig is
+ *  used instead. Local customization should generally set this variable from
+ *  scratch to the desired configuration. Extensions that want to
+ *  auto-configure themselves should use $wgAuthManagerAutoConfig instead.
+ */
+$wgAuthManagerConfig = null;
+
+/**
+ * @see $wgAuthManagerConfig
+ * @since 1.27
+ */
+$wgAuthManagerAutoConfig = [
+       'preauth' => [
+               MediaWiki\Auth\LegacyHookPreAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\LegacyHookPreAuthenticationProvider::class,
+                       'sort' => 0,
+               ],
+               MediaWiki\Auth\ThrottlePreAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\ThrottlePreAuthenticationProvider::class,
+                       'sort' => 0,
+               ],
+       ],
+       'primaryauth' => [
+               // TemporaryPasswordPrimaryAuthenticationProvider should come before
+               // any other PasswordAuthenticationRequest-based
+               // PrimaryAuthenticationProvider (or at least any that might return
+               // FAIL rather than ABSTAIN for a wrong password), or password reset
+               // won't work right. Do not remove this (or change the key) or
+               // auto-configuration of other such providers in extensions will
+               // probably auto-insert themselves in the wrong place.
+               MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
+                       'args' => [ [
+                               // Fall through to LocalPasswordPrimaryAuthenticationProvider
+                               'authoritative' => false,
+                       ] ],
+                       'sort' => 0,
+               ],
+               MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
+                       'args' => [ [
+                               // Last one should be authoritative, or else the user will get
+                               // a less-than-helpful error message (something like "supplied
+                               // authentication info not supported" rather than "wrong
+                               // password") if it too fails.
+                               'authoritative' => true,
+                       ] ],
+                       'sort' => 100,
+               ],
+       ],
+       'secondaryauth' => [
+               MediaWiki\Auth\CheckBlocksSecondaryAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\CheckBlocksSecondaryAuthenticationProvider::class,
+                       'sort' => 0,
+               ],
+               MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider::class,
+                       'sort' => 100,
+               ],
+               // Linking during login is experimental, enable at your own risk - T134952
+               // MediaWiki\Auth\ConfirmLinkSecondaryAuthenticationProvider::class => [
+               //      'class' => MediaWiki\Auth\ConfirmLinkSecondaryAuthenticationProvider::class,
+               //      'sort' => 100,
+               // ],
+               MediaWiki\Auth\EmailNotificationSecondaryAuthenticationProvider::class => [
+                       'class' => MediaWiki\Auth\EmailNotificationSecondaryAuthenticationProvider::class,
+                       'sort' => 200,
+               ],
+       ],
+];
+
+/**
+ * If it has been this long since the last authentication, recommend
+ * re-authentication before security-sensitive operations (e.g. password or
+ * email changes). Set negative to disable.
+ * @since 1.27
+ * @var int[] operation => time in seconds. A 'default' key must always be provided.
+ */
+$wgReauthenticateTime = [
+       'default' => 300,
+];
+
+/**
+ * Whether to allow security-sensitive operations when authentication is not possible.
+ * @since 1.27
+ * @var bool[] operation => boolean. A 'default' key must always be provided.
+ */
+$wgAllowSecuritySensitiveOperationIfCannotReauthenticate = [
+       'default' => true,
+];
+
+/**
+ * List of AuthenticationRequest class names which are not changeable through
+ * Special:ChangeCredentials and the changeauthenticationdata API.
+ * This is only enforced on the client level; AuthManager itself (e.g.
+ * AuthManager::allowsAuthenticationDataChange calls) is not affected.
+ * Class names are checked for exact match (not for subclasses).
+ * @since 1.27
+ * @var string[]
+ */
+$wgChangeCredentialsBlacklist = [
+       \MediaWiki\Auth\TemporaryPasswordAuthenticationRequest::class
+];
+
+/**
+ * List of AuthenticationRequest class names which are not removable through
+ * Special:RemoveCredentials and the removeauthenticationdata API.
+ * This is only enforced on the client level; AuthManager itself (e.g.
+ * AuthManager::allowsAuthenticationDataChange calls) is not affected.
+ * Class names are checked for exact match (not for subclasses).
+ * @since 1.27
+ * @var string[]
+ */
+$wgRemoveCredentialsBlacklist = [
+       \MediaWiki\Auth\PasswordAuthenticationRequest::class,
+];
+
 /**
  * For compatibility with old installations set to false
  * @deprecated since 1.24 will be removed in future
@@ -4492,9 +4670,9 @@ $wgPasswordConfig = [
        ],
        'pbkdf2' => [
                'class' => 'Pbkdf2Password',
-               'algo' => 'sha256',
-               'cost' => '10000',
-               'length' => '128',
+               'algo' => 'sha512',
+               'cost' => '30000',
+               'length' => '64',
        ],
 ];
 
@@ -4666,7 +4844,7 @@ $wgSessionProviders = [
        MediaWiki\Session\BotPasswordSessionProvider::class => [
                'class' => MediaWiki\Session\BotPasswordSessionProvider::class,
                'args' => [ [
-                       'priority' => 40,
+                       'priority' => 75,
                ] ],
        ],
 ];
@@ -4903,6 +5081,7 @@ $wgGroupPermissions['sysop']['suppressredirect'] = true;
 # $wgGroupPermissions['sysop']['upload_by_url'] = true;
 $wgGroupPermissions['sysop']['mergehistory'] = true;
 $wgGroupPermissions['sysop']['managechangetags'] = true;
+$wgGroupPermissions['sysop']['deletechangetags'] = true;
 
 // Permission to change users' group assignments
 $wgGroupPermissions['bureaucrat']['userrights'] = true;
@@ -7291,7 +7470,7 @@ $wgActionFilteredLogs = [
        ],
        'newusers' => [
                'create' => [ 'create', 'newusers' ],
-               'create2' => ['create2' ],
+               'create2' => [ 'create2' ],
                'autocreate' => [ 'autocreate' ],
                'byemail' => [ 'byemail' ],
        ],
@@ -7303,7 +7482,7 @@ $wgActionFilteredLogs = [
                'protect' => [ 'protect' ],
                'modify' => [ 'modify' ],
                'unprotect' => [ 'unprotect' ],
-               'move_prot' => ['move_prot'],
+               'move_prot' => [ 'move_prot' ],
        ],
        'rights' => [
                'rights' => [ 'rights' ],
@@ -7990,6 +8169,23 @@ $wgPagePropsHaveSortkey = true;
  */
 $wgHttpsPort = 443;
 
+/**
+ * Secret for session storage.
+ * This should be set in LocalSettings.php, otherwise wgSecretKey will
+ * be used.
+ * @since 1.27
+ */
+$wgSessionSecret = false;
+
+/**
+ * If for some reason you can't install the PHP OpenSSL or mcrypt extensions,
+ * you can set this to true to make MediaWiki work again at the cost of storing
+ * sensitive session data insecurely. But it would be much more secure to just
+ * install the OpenSSL extension.
+ * @since 1.27
+ */
+$wgSessionInsecureSecrets = false;
+
 /**
  * Secret for hmac-based key derivation function (fast,
  * cryptographically secure random numbers).
index 45535ce..6545c4a 100644 (file)
@@ -54,9 +54,9 @@ class DummyLinker {
        public function link(
                $target,
                $html = null,
-               $customAttribs = [ ],
-               $query = [ ],
-               $options = [ ]
+               $customAttribs = [],
+               $query = [],
+               $options = []
        ) {
                return Linker::link(
                        $target,
@@ -70,8 +70,8 @@ class DummyLinker {
        public function linkKnown(
                $target,
                $html = null,
-               $customAttribs = [ ],
-               $query = [ ],
+               $customAttribs = [],
+               $query = [],
                $options = [ 'known', 'noclasses' ]
        ) {
                return Linker::linkKnown(
@@ -123,8 +123,8 @@ class DummyLinker {
                Parser $parser,
                Title $title,
                $file,
-               $frameParams = [ ],
-               $handlerParams = [ ],
+               $frameParams = [],
+               $handlerParams = [],
                $time = false,
                $query = "",
                $widthOption = null
@@ -147,7 +147,7 @@ class DummyLinker {
                $label = '',
                $alt,
                $align = 'right',
-               $params = [ ],
+               $params = [],
                $framed = false,
                $manualthumb = ""
        ) {
@@ -166,8 +166,8 @@ class DummyLinker {
        public function makeThumbLink2(
                Title $title,
                $file,
-               $frameParams = [ ],
-               $handlerParams = [ ],
+               $frameParams = [],
+               $handlerParams = [],
                $time = false,
                $query = ""
        ) {
@@ -232,7 +232,7 @@ class DummyLinker {
                $text,
                $escape = true,
                $linktype = '',
-               $attribs = [ ],
+               $attribs = [],
                $title = null
        ) {
                return Linker::makeExternalLink(
@@ -329,7 +329,7 @@ class DummyLinker {
                Title $title,
                $text,
                $wikiId = null,
-               $options = [ ]
+               $options = []
        ) {
                return Linker::makeCommentLink(
                        $title,
@@ -471,7 +471,7 @@ class DummyLinker {
                return Linker::formatSize( $size );
        }
 
-       public function titleAttrib( $name, $options = null, array $msgParams = [ ] ) {
+       public function titleAttrib( $name, $options = null, array $msgParams = [] ) {
                return Linker::titleAttrib(
                        $name,
                        $options,
@@ -491,7 +491,7 @@ class DummyLinker {
                );
        }
 
-       public function revDeleteLink( $query = [ ], $restricted = false, $delete = true ) {
+       public function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
                return Linker::revDeleteLink(
                        $query,
                        $restricted,
@@ -503,7 +503,7 @@ class DummyLinker {
                return Linker::revDeleteLinkDisabled( $delete );
        }
 
-       public function tooltipAndAccesskeyAttribs( $name, array $msgParams = [ ] ) {
+       public function tooltipAndAccesskeyAttribs( $name, array $msgParams = [] ) {
                return Linker::tooltipAndAccesskeyAttribs(
                        $name,
                        $msgParams
index 3522531..0f52983 100644 (file)
@@ -2835,10 +2835,10 @@ class EditPage {
                                                // Log-in link
                                                '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}',
                                                // Sign-up link
-                                               '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' ]
+                                               '{{fullurl:Special:CreateAccount|returnto={{FULLPAGENAMEE}}}}' ]
                                );
                        } else {
-                               $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>",
+                               $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
                                        'anonpreviewwarning'
                                );
                        }
@@ -3596,7 +3596,7 @@ HTML
         */
        function getPreviewText() {
                global $wgOut, $wgUser, $wgRawHtml, $wgLang;
-               global $wgAllowUserCss, $wgAllowUserJs;
+               global $wgAllowUserCss, $wgAllowUserJs, $wgAjaxEditStash;
 
                $stats = $wgOut->getContext()->getStats();
 
@@ -3708,10 +3708,12 @@ HTML
 
                        # Try to stash the edit for the final submission step
                        # @todo: different date format preferences cause cache misses
-                       ApiStashEdit::stashEditFromPreview(
-                               $this->getArticle(), $content, $pstContent,
-                               $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
-                       );
+                       if ( $wgAjaxEditStash ) {
+                               ApiStashEdit::stashEditFromPreview(
+                                       $this->getArticle(), $content, $pstContent,
+                                       $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
+                               );
+                       }
 
                        $parserOutput->setEditSectionTokens( false ); // no section edit links
                        $previewHTML = $parserOutput->getText();
@@ -3777,7 +3779,7 @@ HTML
         * Shows a bulletin board style toolbar for common editing functions.
         * It can be disabled in the user preferences.
         *
-        * @param $title Title object for the page being edited (optional)
+        * @param Title $title Title object for the page being edited (optional)
         * @return string
         */
        static function getEditToolbar( $title = null ) {
@@ -3858,7 +3860,7 @@ HTML
                        ],
                        $showSignature ? [
                                'id'     => 'mw-editbutton-signature',
-                               'open'   => '--~~~~',
+                               'open'   => wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(),
                                'close'  => '',
                                'sample' => '',
                                'tip'    => wfMessage( 'sig_tip' )->text(),
index 5c42bc2..618fa4c 100644 (file)
@@ -501,12 +501,26 @@ function wfAppendQuery( $url, $query ) {
                $query = wfArrayToCgi( $query );
        }
        if ( $query != '' ) {
+               // Remove the fragment, if there is one
+               $fragment = false;
+               $hashPos = strpos( $url, '#' );
+               if ( $hashPos !== false ) {
+                       $fragment = substr( $url, $hashPos );
+                       $url = substr( $url, 0, $hashPos );
+               }
+
+               // Add parameter
                if ( false === strpos( $url, '?' ) ) {
                        $url .= '?';
                } else {
                        $url .= '&';
                }
                $url .= $query;
+
+               // Put the fragment back
+               if ( $fragment !== false ) {
+                       $url .= $fragment;
+               }
        }
        return $url;
 }
@@ -2120,6 +2134,24 @@ function wfTempDir() {
                        return $tmp;
                }
        }
+
+       /**
+        * PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to it
+        * so create a directory within that called 'mwtmp' with a suffix of the user running the
+        * current process.
+        * The user is included as if various scripts are run by different users they will likely
+        * not be able to access each others temporary files.
+        */
+       if ( wfIsWindows() ) {
+               $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp' . '-' . get_current_user();
+               if ( !file_exists( $tmp ) ) {
+                       mkdir( $tmp );
+               }
+               if ( file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
+                       return $tmp;
+               }
+       }
+
        throw new MWException( 'No writable temporary directory could be found. ' .
                'Please set $wgTmpDirectory to a writable directory.' );
 }
@@ -3109,6 +3141,9 @@ function wfSplitWikiID( $wiki ) {
  * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
  * updater to ensure that a proper database is being updated.
  *
+ * @todo Replace calls to wfGetDB with calls to LoadBalancer::getConnection()
+ *       on an injected instance of LoadBalancer.
+ *
  * @return DatabaseBase
  */
 function wfGetDB( $db, $groups = [], $wiki = false ) {
@@ -3118,20 +3153,30 @@ function wfGetDB( $db, $groups = [], $wiki = false ) {
 /**
  * Get a load balancer object.
  *
+ * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancer()
+ *              or MediaWikiServices::getDBLoadBalancerFactory() instead.
+ *
  * @param string|bool $wiki Wiki ID, or false for the current wiki
  * @return LoadBalancer
  */
 function wfGetLB( $wiki = false ) {
-       return wfGetLBFactory()->getMainLB( $wiki );
+       if ( $wiki === false ) {
+               return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancer();
+       } else {
+               $factory = \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               return $factory->getMainLB( $wiki );
+       }
 }
 
 /**
  * Get the load balancer factory object
  *
+ * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
+ *
  * @return LBFactory
  */
 function wfGetLBFactory() {
-       return LBFactory::singleton();
+       return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
 }
 
 /**
index 890beb0..e5128d1 100644 (file)
@@ -38,8 +38,6 @@
  *
  * $wgMimeType: If this is set to an xml MIME type then output should be
  *     valid XHTML5.
- * $wgWellFormedXml: If this is set to true, then all output should be
- *     well-formed XML (quotes on attributes, self-closing tags, etc.).
  *
  * This class is meant to be confined to utility functions that are called from
  * trusted code paths.  It does not do enforcement of policy like not allowing
@@ -199,8 +197,7 @@ class Html {
         * This is quite similar to Xml::tags(), but it implements some useful
         * HTML-specific logic.  For instance, there is no $allowShortTag
         * parameter: the closing tag is magically omitted if $element has an empty
-        * content model.  If $wgWellFormedXml is false, then a few bytes will be
-        * shaved off the HTML output as well.
+        * content model.
         *
         * @param string $element The element's name, e.g., 'a'
         * @param array $attribs Associative array of attributes, e.g., array(
@@ -211,14 +208,10 @@ class Html {
         * @return string Raw HTML
         */
        public static function rawElement( $element, $attribs = [], $contents = '' ) {
-               global $wgWellFormedXml;
                $start = self::openElement( $element, $attribs );
                if ( in_array( $element, self::$voidElements ) ) {
-                       if ( $wgWellFormedXml ) {
-                               // Silly XML.
-                               return substr( $start, 0, -1 ) . '/>';
-                       }
-                       return $start;
+                       // Silly XML.
+                       return substr( $start, 0, -1 ) . '/>';
                } else {
                        return "$start$contents" . self::closeElement( $element );
                }
@@ -443,8 +436,6 @@ class Html {
         * 'http://www.mediawiki.org/' ) becomes something like
         * ' href="http://www.mediawiki.org"'.  Again, this is like
         * Xml::expandAttributes(), but it implements some HTML-specific logic.
-        * For instance, it will omit quotation marks if $wgWellFormedXml is false,
-        * and will treat boolean attributes specially.
         *
         * Attributes that can contain space-separated lists ('class', 'accesskey' and 'rel') array
         * values are allowed as well, which will automagically be normalized
@@ -479,8 +470,6 @@ class Html {
         *   (starting with a space if at least one attribute is output)
         */
        public static function expandAttributes( array $attribs ) {
-               global $wgWellFormedXml;
-
                $ret = '';
                foreach ( $attribs as $key => $value ) {
                        // Support intuitive array( 'checked' => true/false ) form
@@ -564,31 +553,10 @@ class Html {
                                throw new MWException( "HTML attribute $key can not contain a list of values" );
                        }
 
-                       // See the "Attributes" section in the HTML syntax part of HTML5,
-                       // 9.1.2.3 as of 2009-08-10.  Most attributes can have quotation
-                       // marks omitted, but not all.  (Although a literal " is not
-                       // permitted, we don't check for that, since it will be escaped
-                       // anyway.)
-
-                       // See also research done on further characters that need to be
-                       // escaped: http://code.google.com/p/html5lib/issues/detail?id=93
-                       $badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}"
-                               . "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}"
-                               . "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}";
-                       if ( $wgWellFormedXml || $value === '' || preg_match( "![$badChars]!u", $value ) ) {
-                               $quote = '"';
-                       } else {
-                               $quote = '';
-                       }
+                       $quote = '"';
 
                        if ( in_array( $key, self::$boolAttribs ) ) {
-                               // In HTML5, we can leave the value empty. If we don't need
-                               // well-formed XML, we can omit the = entirely.
-                               if ( !$wgWellFormedXml ) {
-                                       $ret .= " $key";
-                               } else {
-                                       $ret .= " $key=\"\"";
-                               }
+                               $ret .= " $key=\"\"";
                        } else {
                                // Apparently we need to entity-encode \n, \r, \t, although the
                                // spec doesn't mention that.  Since we're doing strtr() anyway,
@@ -599,22 +567,18 @@ class Html {
                                // don't because we're stubborn and like our marginal savings on
                                // byte size from not having to encode unnecessary quotes.
                                // The only difference between this transform and the one by
-                               // Sanitizer::encodeAttribute() is '<' is only encoded here if
-                               // $wgWellFormedXml is set, and ' is not encoded.
+                               // Sanitizer::encodeAttribute() is ' is not encoded.
                                $map = [
                                        '&' => '&amp;',
                                        '"' => '&quot;',
                                        '>' => '&gt;',
+                                       // '<' allegedly allowed per spec
+                                       // but breaks some tools if not escaped.
+                                       "<" => '&lt;',
                                        "\n" => '&#10;',
                                        "\r" => '&#13;',
                                        "\t" => '&#9;'
                                ];
-                               if ( $wgWellFormedXml ) {
-                                       // This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
-                                       // But reportedly it breaks some XML tools?
-                                       // @todo FIXME: Is this really true?
-                                       $map['<'] = '&lt;';
-                               }
                                $ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
                        }
                }
@@ -631,11 +595,9 @@ class Html {
         * @return string Raw HTML
         */
        public static function inlineScript( $contents ) {
-               global $wgWellFormedXml;
-
                $attrs = [];
 
-               if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
+               if ( preg_match( '/[<&]/', $contents ) ) {
                        $contents = "/*<![CDATA[*/$contents/*]]>*/";
                }
 
@@ -665,9 +627,7 @@ class Html {
         * @return string Raw HTML
         */
        public static function inlineStyle( $contents, $media = 'all' ) {
-               global $wgWellFormedXml;
-
-               if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
+               if ( preg_match( '/[<&]/', $contents ) ) {
                        $contents = "/*<![CDATA[*/$contents/*]]>*/";
                }
 
index 071f95e..6a869dd 100644 (file)
@@ -40,6 +40,7 @@ class Linker {
        /**
         * Get the appropriate HTML attributes to add to the "a" element of an interwiki link.
         *
+        * @since 1.16.3
         * @deprecated since 1.25
         *
         * @param string $title The title text for the link, URL-encoded (???) but
@@ -66,6 +67,7 @@ class Linker {
        /**
         * Get the appropriate HTML attributes to add to the "a" element of an internal link.
         *
+        * @since 1.16.3
         * @deprecated since 1.25
         *
         * @param string $title The title text for the link, URL-encoded (???) but
@@ -86,6 +88,7 @@ class Linker {
         * Get the appropriate HTML attributes to add to the "a" element of an internal
         * link, given the Title object for the page we want to link to.
         *
+        * @since 1.16.3
         * @deprecated since 1.25
         *
         * @param Title $nt
@@ -107,6 +110,7 @@ class Linker {
        /**
         * Common code for getLinkAttributesX functions
         *
+        * @since 1.16.3
         * @deprecated since 1.25
         *
         * @param string $title
@@ -132,6 +136,7 @@ class Linker {
        /**
         * Return the CSS colour of a known link
         *
+        * @since 1.16.3
         * @param Title $t
         * @param int $threshold User defined threshold
         * @return string CSS class
@@ -258,6 +263,7 @@ class Linker {
 
        /**
         * Identical to link(), except $options defaults to 'known'.
+        * @since 1.16.3
         * @see Linker::link
         * @return string
         */
@@ -398,6 +404,7 @@ class Linker {
         * same as the other make*LinkObj static functions, despite $query not
         * being used.
         *
+        * @since 1.16.3
         * @param Title $nt
         * @param string $html [optional]
         * @param string $query [optional]
@@ -446,10 +453,11 @@ class Linker {
        }
 
        /**
+        * @since 1.16.3
         * @param LinkTarget $target
         * @return LinkTarget|Title You will get back the same type you passed in, or a Title object
         */
-       static function normaliseSpecialPage( LinkTarget $target ) {
+       public static function normaliseSpecialPage( LinkTarget $target ) {
                if ( $target->getNamespace() == NS_SPECIAL ) {
                        list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $target->getDBkey() );
                        if ( !$name ) {
@@ -484,6 +492,7 @@ class Linker {
         * Return the code for images which were added via external links,
         * via Parser::maybeMakeExternalImage().
         *
+        * @since 1.16.3
         * @param string $url
         * @param string $alt
         *
@@ -901,6 +910,7 @@ class Linker {
        /**
         * Make a "broken" link to an image
         *
+        * @since 1.16.3
         * @param Title $title
         * @param string $label Link label (plain text)
         * @param string $query Query string
@@ -946,6 +956,7 @@ class Linker {
        /**
         * Get the URL to upload a certain file
         *
+        * @since 1.16.3
         * @param Title $destFile Title object of the file to upload
         * @param string $query Urlencoded query string to prepend
         * @return string Urlencoded URL
@@ -970,6 +981,7 @@ class Linker {
        /**
         * Create a direct link to a given uploaded file.
         *
+        * @since 1.16.3
         * @param Title $title
         * @param string $html Pre-sanitized HTML
         * @param string $time MW timestamp of file creation time
@@ -984,6 +996,7 @@ class Linker {
         * Create a direct link to a given uploaded file.
         * This will make a broken link if $file is false.
         *
+        * @since 1.16.3
         * @param Title $title
         * @param File|bool $file File object or false
         * @param string $html Pre-sanitized HTML
@@ -1027,6 +1040,7 @@ class Linker {
         * a message key from the link text.
         * Usage example: Linker::specialLink( 'Recentchanges' )
         *
+        * @since 1.16.3
         * @param string $name
         * @param string $key
         * @return string
@@ -1041,6 +1055,7 @@ class Linker {
 
        /**
         * Make an external link
+        * @since 1.16.3. $title added in 1.21
         * @param string $url URL to link to
         * @param string $text Text of link
         * @param bool $escape Do we escape the link text?
@@ -1088,7 +1103,7 @@ class Linker {
         * @param string $userName User name in database.
         * @param string $altUserName Text to display instead of the user name (optional)
         * @return string HTML fragment
-        * @since 1.19 Method exists for a long time. $altUserName was added in 1.19.
+        * @since 1.16.3. $altUserName was added in 1.19.
         */
        public static function userLink( $userId, $userName, $altUserName = false ) {
                $classes = 'mw-userlink';
@@ -1112,6 +1127,7 @@ class Linker {
        /**
         * Generate standard user tool links (talk, contributions, block link, etc.)
         *
+        * @since 1.16.3
         * @param int $userId User identifier
         * @param string $userText User name or IP address
         * @param bool $redContribsWhenNoEdits Should the contributions link be
@@ -1171,6 +1187,7 @@ class Linker {
 
        /**
         * Alias for userToolLinks( $userId, $userText, true );
+        * @since 1.16.3
         * @param int $userId User identifier
         * @param string $userText User name or IP address
         * @param int $edits User edit count (optional, for performance)
@@ -1181,6 +1198,7 @@ class Linker {
        }
 
        /**
+        * @since 1.16.3
         * @param int $userId User id in database.
         * @param string $userText User name in database.
         * @return string HTML fragment with user talk link
@@ -1192,6 +1210,7 @@ class Linker {
        }
 
        /**
+        * @since 1.16.3
         * @param int $userId Userid
         * @param string $userText User name in database.
         * @return string HTML fragment with block link
@@ -1215,6 +1234,7 @@ class Linker {
 
        /**
         * Generate a user link if the current user is allowed to view it
+        * @since 1.16.3
         * @param Revision $rev
         * @param bool $isPublic Show only if all users can see it
         * @return string HTML fragment
@@ -1236,6 +1256,7 @@ class Linker {
 
        /**
         * Generate a user tool link cluster if the current user is allowed to view it
+        * @since 1.16.3
         * @param Revision $rev
         * @param bool $isPublic Show only if all users can see it
         * @return string HTML
@@ -1264,6 +1285,7 @@ class Linker {
         * auto-generated comments (from section editing) and formats [[wikilinks]].
         *
         * @author Erik Moeller <moeller@scireview.de>
+        * @since 1.16.3. $wikiId added in 1.26
         *
         * Note: there's not always a title to pass to this function.
         * Since you can't set a default parameter for a reference, I've turned it
@@ -1389,7 +1411,9 @@ class Linker {
         * Formats wiki links and media links in text; all other wiki formatting
         * is ignored
         *
+        * @since 1.16.3. $wikiId added in 1.26
         * @todo FIXME: Doesn't handle sub-links as in image thumb texts like the main parser
+        *
         * @param string $comment Text to format links in. WARNING! Since the output of this
         *      function is html, $comment must be sanitized for use as html. You probably want
         *      to pass $comment through Sanitizer::escapeHtmlAllowEntities() before calling
@@ -1612,6 +1636,7 @@ class Linker {
         * Wrap a comment in standard punctuation and formatting if
         * it's non-empty, otherwise return empty string.
         *
+        * @since 1.16.3. $wikiId added in 1.26
         * @param string $comment
         * @param Title|null $title Title object (to generate link to section in autocomment) or null
         * @param bool $local Whether section links should refer to local page
@@ -1639,6 +1664,7 @@ class Linker {
         * Wrap and format the given revision's comment block, if the current
         * user is allowed to view it.
         *
+        * @since 1.16.3
         * @param Revision $rev
         * @param bool $local Whether section links should refer to local page
         * @param bool $isPublic Show only if all users can see it
@@ -1663,6 +1689,7 @@ class Linker {
        }
 
        /**
+        * @since 1.16.3
         * @param int $size
         * @return string
         */
@@ -1679,6 +1706,7 @@ class Linker {
        /**
         * Add another level to the Table of Contents
         *
+        * @since 1.16.3
         * @return string
         */
        public static function tocIndent() {
@@ -1688,6 +1716,7 @@ class Linker {
        /**
         * Finish one or more sublevels on the Table of Contents
         *
+        * @since 1.16.3
         * @param int $level
         * @return string
         */
@@ -1698,6 +1727,7 @@ class Linker {
        /**
         * parameter level defines if we are on an indentation level
         *
+        * @since 1.16.3
         * @param string $anchor
         * @param string $tocline
         * @param string $tocnumber
@@ -1720,6 +1750,7 @@ class Linker {
         * End a Table Of Contents line.
         * tocUnindent() will be used instead if we're ending a line below
         * the new level.
+        * @since 1.16.3
         * @return string
         */
        public static function tocLineEnd() {
@@ -1729,6 +1760,7 @@ class Linker {
        /**
         * Wraps the TOC in a table and provides the hide/collapse javascript.
         *
+        * @since 1.16.3
         * @param string $toc Html of the Table Of Contents
         * @param string|Language|bool $lang Language for the toc title, defaults to user language
         * @return string Full html of the TOC
@@ -1746,6 +1778,7 @@ class Linker {
        /**
         * Generate a table of contents from a section tree.
         *
+        * @since 1.16.3. $lang added in 1.17
         * @param array $tree Return value of ParserOutput::getSections()
         * @param string|Language|bool $lang Language for the toc title, defaults to user language
         * @return string HTML fragment
@@ -1775,6 +1808,7 @@ class Linker {
        /**
         * Create a headline for content
         *
+        * @since 1.16.3
         * @param int $level The level of the headline (1-6)
         * @param string $attribs Any attributes for the headline, starting with
         *   a space and ending with '>'
@@ -1840,6 +1874,8 @@ class Linker {
         *
         * If the option noBrackets is set the rollback link wont be enclosed in []
         *
+        * @since 1.16.3. $context added in 1.20. $options added in 1.21
+        *
         * @param Revision $rev
         * @param IContextSource $context Context to use or null for the main context.
         * @param array $options
@@ -1938,6 +1974,7 @@ class Linker {
        /**
         * Build a raw rollback link, useful for collections of "tool" links
         *
+        * @since 1.16.3. $context added in 1.20. $editCount added in 1.21
         * @param Revision $rev
         * @param IContextSource|null $context Context to use or null for the main context.
         * @param int $editCount Number of edits that would be reverted
@@ -2021,6 +2058,7 @@ class Linker {
         * directly paste it in as the link (escaping needs to be done manually).
         * Finally, if $more is a Message, call toString().
         *
+        * @since 1.16.3. $more added in 1.21
         * @param Title[] $templates Array of templates
         * @param bool $preview Whether this is for a preview
         * @param bool $section Whether this is for a section edit
@@ -2116,6 +2154,7 @@ class Linker {
        /**
         * Returns HTML for the "hidden categories on this page" list.
         *
+        * @since 1.16.3
         * @param array $hiddencats Array of hidden categories from Article::getHiddenCategories
         *   or similar
         * @return string HTML output
@@ -2144,6 +2183,7 @@ class Linker {
         * Format a size in bytes for output, using an appropriate
         * unit (B, KB, MB or GB) according to the magnitude in question
         *
+        * @since 1.16.3
         * @param int $size Size to format
         * @return string
         */
@@ -2158,6 +2198,7 @@ class Linker {
         * isn't always, because sometimes the accesskey needs to go on a different
         * element than the id, for reverse-compatibility, etc.)
         *
+        * @since 1.16.3 $msgParams added in 1.27
         * @param string $name Id of the element, minus prefixes.
         * @param string|null $options Null or the string 'withaccess' to add an access-
         *   key hint
@@ -2204,6 +2245,7 @@ class Linker {
         * the id but isn't always, because sometimes the accesskey needs to go on
         * a different element than the id, for reverse-compatibility, etc.)
         *
+        * @since 1.16.3
         * @param string $name Id of the element, minus prefixes.
         * @return string Contents of the accesskey attribute (which you must HTML-
         *   escape), or false for no accesskey attribute
@@ -2301,6 +2343,7 @@ class Linker {
        /**
         * Creates a dead (show/hide) link for deleting revisions/log entries
         *
+        * @since 1.16.3
         * @param bool $delete Set to true to use (show/hide) rather than (show)
         *
         * @return string HTML text wrapped in a span to allow for customization
@@ -2318,6 +2361,7 @@ class Linker {
        /**
         * Returns the attributes for the tooltip and access key.
         *
+        * @since 1.16.3. $msgParams introduced in 1.27
         * @param string $name
         * @param array $msgParams Params for constructing the message
         *
@@ -2342,6 +2386,7 @@ class Linker {
 
        /**
         * Returns raw bits of HTML, use titleAttrib()
+        * @since 1.16.3
         * @param string $name
         * @param array|null $options
         * @return null|string
index 3dd7420..ff469e4 100644 (file)
@@ -487,6 +487,7 @@ class MediaWiki {
                        $trxProfiler = Profiler::instance()->getTransactionProfiler();
                        if ( $request->wasPosted() && !$action->doesWrites() ) {
                                $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
+                               $request->markAsSafeRequest();
                        }
 
                        # Let CDN cache things if we can purge them.
index 4b6ddd4..4028aa2 100644 (file)
@@ -1,21 +1,31 @@
 <?php
 namespace MediaWiki;
 
+use Config;
 use ConfigFactory;
 use EventRelayerGroup;
+use GenderCache;
 use GlobalVarConfig;
-use Config;
 use Hooks;
 use LBFactory;
+use LinkCache;
 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
 use LoadBalancer;
+use MediaWiki\Services\SalvageableService;
 use MediaWiki\Services\ServiceContainer;
+use MWException;
+use ObjectCache;
+use ResourceLoader;
 use SearchEngine;
 use SearchEngineConfig;
 use SearchEngineFactory;
 use SiteLookup;
 use SiteStore;
+use WatchedItemStore;
 use SkinFactory;
+use TitleFormatter;
+use TitleParser;
+use MediaWiki\Interwiki\InterwikiLookup;
 
 /**
  * Service locator for MediaWiki core services.
@@ -55,9 +65,16 @@ use SkinFactory;
  */
 class MediaWikiServices extends ServiceContainer {
 
+       /**
+        * @var MediaWikiServices|null
+        */
+       private static $instance = null;
+
        /**
         * Returns the global default instance of the top level service locator.
         *
+        * @since 1.27
+        *
         * The default instance is initialized using the service instantiator functions
         * defined in ServiceWiring.php.
         *
@@ -68,27 +85,273 @@ class MediaWikiServices extends ServiceContainer {
         * @return MediaWikiServices
         */
        public static function getInstance() {
-               static $instance = null;
-
-               if ( $instance === null ) {
+               if ( self::$instance === null ) {
                        // NOTE: constructing GlobalVarConfig here is not particularly pretty,
                        // but some information from the global scope has to be injected here,
                        // even if it's just a file name or database credentials to load
                        // configuration from.
-                       $config = new GlobalVarConfig();
-                       $instance = new self( $config );
+                       $bootstrapConfig = new GlobalVarConfig();
+                       self::$instance = self::newInstance( $bootstrapConfig, 'load' );
+               }
 
-                       // Load the default wiring from the specified files.
-                       $wiringFiles = $config->get( 'ServiceWiringFiles' );
-                       $instance->loadWiringFiles( $wiringFiles );
+               return self::$instance;
+       }
+
+       /**
+        * Replaces the global MediaWikiServices instance.
+        *
+        * @since 1.28
+        *
+        * @note This is for use in PHPUnit tests only!
+        *
+        * @throws MWException if called outside of PHPUnit tests.
+        *
+        * @param MediaWikiServices $services The new MediaWikiServices object.
+        *
+        * @return MediaWikiServices The old MediaWikiServices object, so it can be restored later.
+        */
+       public static function forceGlobalInstance( MediaWikiServices $services ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException( __METHOD__ . ' must not be used outside unit tests.' );
+               }
+
+               $old = self::getInstance();
+               self::$instance = $services;
+
+               return $old;
+       }
+
+       /**
+        * Creates a new instance of MediaWikiServices and sets it as the global default
+        * instance. getInstance() will return a different MediaWikiServices object
+        * after every call to resetGlobalInstance().
+        *
+        * @since 1.28
+        *
+        * @warning This should not be used during normal operation. It is intended for use
+        * when the configuration has changed significantly since bootstrap time, e.g.
+        * during the installation process or during testing.
+        *
+        * @warning Calling resetGlobalInstance() may leave the application in an inconsistent
+        * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to
+        * any of the services managed by MediaWikiServices exist. If any service objects
+        * managed by the old MediaWikiServices instance remain in use, they may INTERFERE
+        * with the operation of the services managed by the new MediaWikiServices.
+        * Operating with a mix of services created by the old and the new
+        * MediaWikiServices instance may lead to INCONSISTENCIES and even DATA LOSS!
+        * Any class implementing LAZY LOADING is especially prone to this problem,
+        * since instances would typically retain a reference to a storage layer service.
+        *
+        * @see forceGlobalInstance()
+        * @see resetGlobalInstance()
+        * @see resetBetweenTest()
+        *
+        * @param Config|null $bootstrapConfig The Config object to be registered as the
+        *        'BootstrapConfig' service. This has to contain at least the information
+        *        needed to set up the 'ConfigFactory' service. If not given, the bootstrap
+        *        config of the old instance of MediaWikiServices will be re-used. If there
+        *        was no previous instance, a new GlobalVarConfig object will be used to
+        *        bootstrap the services.
+        *
+        * @param string $quick Set this to "quick" to allow expensive resources to be re-used.
+        * See SalvageableService for details.
+        *
+        * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in
+        *         Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN
+        *          is defined).
+        */
+       public static function resetGlobalInstance( Config $bootstrapConfig = null, $quick = '' ) {
+               if ( self::$instance === null ) {
+                       // no global instance yet, nothing to reset
+                       return;
+               }
+
+               self::failIfResetNotAllowed( __METHOD__ );
 
-                       // Provide a traditional hook point to allow extensions to configure services.
-                       Hooks::run( 'MediaWikiServices', [ $instance ] );
+               if ( $bootstrapConfig === null ) {
+                       $bootstrapConfig = self::$instance->getBootstrapConfig();
                }
 
+               $oldInstance = self::$instance;
+
+               self::$instance = self::newInstance( $bootstrapConfig );
+               self::$instance->importWiring( $oldInstance, [ 'BootstrapConfig' ] );
+
+               if ( $quick === 'quick' ) {
+                       self::$instance->salvage( $oldInstance );
+               } else {
+                       $oldInstance->destroy();
+               }
+
+       }
+
+       /**
+        * Salvages the state of any salvageable service instances in $other.
+        *
+        * @note $other will have been destroyed when salvage() returns.
+        *
+        * @param MediaWikiServices $other
+        */
+       private function salvage( self $other ) {
+               foreach ( $this->getServiceNames() as $name ) {
+                       $oldService = $other->peekService( $name );
+
+                       if ( $oldService instanceof SalvageableService ) {
+                               /** @var SalvageableService $newService */
+                               $newService = $this->getService( $name );
+                               $newService->salvage( $oldService );
+                       }
+               }
+
+               $other->destroy();
+       }
+
+       /**
+        * Creates a new MediaWikiServices instance and initializes it according to the
+        * given $bootstrapConfig. In particular, all wiring files defined in the
+        * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called.
+        *
+        * @param Config|null $bootstrapConfig The Config object to be registered as the
+        *        'BootstrapConfig' service.
+        *
+        * @param string $loadWiring set this to 'load' to load the wiring files specified
+        *        in the 'ServiceWiringFiles' setting in $bootstrapConfig.
+        *
+        * @return MediaWikiServices
+        * @throws MWException
+        * @throws \FatalError
+        */
+       private static function newInstance( Config $bootstrapConfig, $loadWiring = '' ) {
+               $instance = new self( $bootstrapConfig );
+
+               // Load the default wiring from the specified files.
+               if ( $loadWiring === 'load' ) {
+                       $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
+                       $instance->loadWiringFiles( $wiringFiles );
+               }
+
+               // Provide a traditional hook point to allow extensions to configure services.
+               Hooks::run( 'MediaWikiServices', [ $instance ] );
+
                return $instance;
        }
 
+       /**
+        * Disables all storage layer services. After calling this, any attempt to access the
+        * storage layer will result in an error. Use resetGlobalInstance() to restore normal
+        * operation.
+        *
+        * @since 1.28
+        *
+        * @warning This is intended for extreme situations only and should never be used
+        * while serving normal web requests. Legitimate use cases for this method include
+        * the installation process. Test fixtures may also use this, if the fixture relies
+        * on globalState.
+        *
+        * @see resetGlobalInstance()
+        * @see resetChildProcessServices()
+        */
+       public static function disableStorageBackend() {
+               // TODO: also disable some Caches, JobQueues, etc
+               $destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ];
+               $services = self::getInstance();
+
+               foreach ( $destroy as $name ) {
+                       $services->disableService( $name );
+               }
+
+               ObjectCache::clear();
+       }
+
+       /**
+        * Resets any services that may have become stale after a child process
+        * returns from after pcntl_fork(). It's also safe, but generally unnecessary,
+        * to call this method from the parent process.
+        *
+        * @since 1.28
+        *
+        * @note This is intended for use in the context of process forking only!
+        *
+        * @see resetGlobalInstance()
+        * @see disableStorageBackend()
+        */
+       public static function resetChildProcessServices() {
+               // NOTE: for now, just reset everything. Since we don't know the interdependencies
+               // between services, we can't do this more selectively at this time.
+               self::resetGlobalInstance();
+
+               // Child, reseed because there is no bug in PHP:
+               // http://bugs.php.net/bug.php?id=42465
+               mt_srand( getmypid() );
+       }
+
+       /**
+        * Resets the given service for testing purposes.
+        *
+        * @since 1.28
+        *
+        * @warning This is generally unsafe! Other services may still retain references
+        * to the stale service instance, leading to failures and inconsistencies. Subclasses
+        * may use this method to reset specific services under specific instances, but
+        * it should not be exposed to application logic.
+        *
+        * @note With proper dependency injection used throughout the codebase, this method
+        * should not be needed. It is provided to allow tests that pollute global service
+        * instances to clean up.
+        *
+        * @param string $name
+        * @param bool $destroy Whether the service instance should be destroyed if it exists.
+        *        When set to false, any existing service instance will effectively be detached
+        *        from the container.
+        *
+        * @throws MWException if called outside of PHPUnit tests.
+        */
+       public function resetServiceForTesting( $name, $destroy = true ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
+                       throw new MWException( 'resetServiceForTesting() must not be used outside unit tests.' );
+               }
+
+               $this->resetService( $name, $destroy );
+       }
+
+       /**
+        * Convenience method that throws an exception unless it is called during a phase in which
+        * resetting of global services is allowed. In general, services should not be reset
+        * individually, since that may introduce inconsistencies.
+        *
+        * @since 1.28
+        *
+        * This method will throw an exception if:
+        *
+        * - self::$resetInProgress is false (to allow all services to be reset together
+        *   via resetGlobalInstance)
+        * - and MEDIAWIKI_INSTALL is not defined (to allow services to be reset during installation)
+        * - and MW_PHPUNIT_TEST is not defined (to allow services to be reset during testing)
+        *
+        * This method is intended to be used to safeguard against accidentally resetting
+        * global service instances that are not yet managed by MediaWikiServices. It is
+        * defined here in the MediaWikiServices services class to have a central place
+        * for managing service bootstrapping and resetting.
+        *
+        * @param string $method the name of the caller method, as given by __METHOD__.
+        *
+        * @throws MWException if called outside bootstrap mode.
+        *
+        * @see resetGlobalInstance()
+        * @see forceGlobalInstance()
+        * @see disableStorageBackend()
+        */
+       public static function failIfResetNotAllowed( $method ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' )
+                       && !defined( 'MW_PARSER_TEST' )
+                       && !defined( 'MEDIAWIKI_INSTALL' )
+                       && !defined( 'RUN_MAINTENANCE_IF_MAIN' )
+                       && defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' )
+               ) {
+                       throw new MWException( $method . ' may only be called during bootstrapping and unit tests!' );
+               }
+       }
+
        /**
         * @param Config $config The Config object to be registered as the 'BootstrapConfig' service.
         *        This has to contain at least the information needed to set up the 'ConfigFactory'
@@ -97,12 +360,14 @@ class MediaWikiServices extends ServiceContainer {
        public function __construct( Config $config ) {
                parent::__construct();
 
-               // register the given Config object as the bootstrap config service.
+               // Register the given Config object as the bootstrap config service.
                $this->defineService( 'BootstrapConfig', function() use ( $config ) {
                        return $config;
                } );
        }
 
+       // CONVENIENCE GETTERS ////////////////////////////////////////////////////
+
        /**
         * Returns the Config object containing the bootstrap configuration.
         * Bootstrap configuration would typically include database credentials
@@ -113,6 +378,7 @@ class MediaWikiServices extends ServiceContainer {
         * when creating the MainConfig service. Application logic should
         * use getMainConfig() to get a Config instances.
         *
+        * @since 1.27
         * @return Config
         */
        public function getBootstrapConfig() {
@@ -120,6 +386,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return ConfigFactory
         */
        public function getConfigFactory() {
@@ -130,6 +397,7 @@ class MediaWikiServices extends ServiceContainer {
         * Returns the Config object that provides configuration for MediaWiki core.
         * This may or may not be the same object that is returned by getBootstrapConfig().
         *
+        * @since 1.27
         * @return Config
         */
        public function getMainConfig() {
@@ -137,6 +405,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SiteLookup
         */
        public function getSiteLookup() {
@@ -144,6 +413,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SiteStore
         */
        public function getSiteStore() {
@@ -151,6 +421,15 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.28
+        * @return InterwikiLookup
+        */
+       public function getInterwikiLookup() {
+               return $this->getService( 'InterwikiLookup' );
+       }
+
+       /**
+        * @since 1.27
         * @return StatsdDataFactory
         */
        public function getStatsdDataFactory() {
@@ -158,6 +437,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return EventRelayerGroup
         */
        public function getEventRelayerGroup() {
@@ -165,6 +445,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SearchEngine
         */
        public function newSearchEngine() {
@@ -173,6 +454,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SearchEngineFactory
         */
        public function getSearchEngineFactory() {
@@ -180,6 +462,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SearchEngineConfig
         */
        public function getSearchEngineConfig() {
@@ -187,12 +470,69 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SkinFactory
         */
        public function getSkinFactory() {
                return $this->getService( 'SkinFactory' );
        }
 
+       /**
+        * @since 1.28
+        * @return LBFactory
+        */
+       public function getDBLoadBalancerFactory() {
+               return $this->getService( 'DBLoadBalancerFactory' );
+       }
+
+       /**
+        * @since 1.28
+        * @return LoadBalancer The main DB load balancer for the local wiki.
+        */
+       public function getDBLoadBalancer() {
+               return $this->getService( 'DBLoadBalancer' );
+       }
+
+       /**
+        * @since 1.28
+        * @return WatchedItemStore
+        */
+       public function getWatchedItemStore() {
+               return $this->getService( 'WatchedItemStore' );
+       }
+
+       /**
+        * @since 1.28
+        * @return GenderCache
+        */
+       public function getGenderCache() {
+               return $this->getService( 'GenderCache' );
+       }
+
+       /**
+        * @since 1.28
+        * @return LinkCache
+        */
+       public function getLinkCache() {
+               return $this->getService( 'LinkCache' );
+       }
+
+       /**
+        * @since 1.28
+        * @return TitleFormatter
+        */
+       public function getTitleFormatter() {
+               return $this->getService( 'TitleFormatter' );
+       }
+
+       /**
+        * @since 1.28
+        * @return TitleParser
+        */
+       public function getTitleParser() {
+               return $this->getService( 'TitleParser' );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service getter here, don't forget to add a test
        // case for it in MediaWikiServicesTest::provideGetters() and in
index fd016fc..c7752aa 100644 (file)
@@ -382,6 +382,32 @@ class Message implements MessageSpecifier, Serializable {
                return new self( $key, $params );
        }
 
+       /**
+        * Transform a MessageSpecifier or a primitive value used interchangeably with
+        * specifiers (a message key string, or a key + params array) into a proper Message
+        * @param string|array|MessageSpecifier $value
+        * @return Message
+        * @throws InvalidArgumentException
+        * @since 1.27
+        */
+       public static function newFromSpecifier( $value ) {
+               if ( $value instanceof RawMessage ) {
+                       $message = new RawMessage( $value->getKey(), $value->getParams() );
+               } elseif ( $value instanceof MessageSpecifier ) {
+                       $message = new Message( $value );
+               } elseif ( is_array( $value ) ) {
+                       $key = array_shift( $value );
+                       $message = new Message( $key, $value );
+               } elseif ( is_string( $value ) ) {
+                       $message = new Message( $value );
+               } else {
+                       throw new InvalidArgumentException( __METHOD__ . ': invalid argument type '
+                               . gettype( $value ) );
+               }
+
+               return $message;
+       }
+
        /**
         * Factory function accepting multiple message keys and returning a message instance
         * for the first message which is non-empty. If all messages are empty then an
index b9af755..708dea1 100644 (file)
@@ -19,6 +19,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Handles the backend logic of moving a page from one title
  * to another.
@@ -369,7 +371,7 @@ class MovePage {
                $oldsnamespace = MWNamespace::getSubject( $this->oldTitle->getNamespace() );
                $newsnamespace = MWNamespace::getSubject( $this->newTitle->getNamespace() );
                if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) {
-                       $store = WatchedItemStore::getDefaultInstance();
+                       $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                        $store->duplicateAllAssociatedEntries( $this->oldTitle, $this->newTitle );
                }
 
index c724207..67e9a4f 100644 (file)
@@ -780,7 +780,7 @@ class OutputPage extends ContextSource {
                        // bug 44570: the core page itself may not change, but resources might
                        $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
                }
-               Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes ] );
+               Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
 
                $maxModified = max( $modifiedTimes );
                $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
index 3f56240..9a55ae3 100644 (file)
@@ -19,6 +19,8 @@
  *
  * @file
  */
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\PasswordAuthenticationRequest;
 
 /**
  * We're now using the HTMLForm object with some customisation to generate the
@@ -205,7 +207,7 @@ class Preferences {
         * @return void
         */
        static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) {
-               global $wgAuth, $wgContLang, $wgParser;
+               global $wgAuth, $wgContLang, $wgParser, $wgDisableAuthManager;
 
                $config = $context->getConfig();
                // retrieving user name for GENDER and misc.
@@ -281,16 +283,21 @@ class Preferences {
                $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
 
                // Actually changeable stuff
+               $realnameChangeAllowed = $wgDisableAuthManager ? $wgAuth->allowPropChange( 'realname' )
+                       : AuthManager::singleton()->allowsPropertyChange( 'realname' );
                $defaultPreferences['realname'] = [
                        // (not really "private", but still shouldn't be edited without permission)
-                       'type' => $canEditPrivateInfo && $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
+                       'type' => $canEditPrivateInfo && $realnameChangeAllowed ? 'text' : 'info',
                        'default' => $user->getRealName(),
                        'section' => 'personal/info',
                        'label-message' => 'yourrealname',
                        'help-message' => 'prefs-help-realname',
                ];
 
-               if ( $canEditPrivateInfo && $wgAuth->allowPasswordChange() ) {
+               $allowPasswordChange = $wgDisableAuthManager ? $wgAuth->allowPasswordChange()
+                       : AuthManager::singleton()->allowsAuthenticationDataChange(
+                               new PasswordAuthenticationRequest(), false );
+               if ( $canEditPrivateInfo && $allowPasswordChange ) {
                        $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
                                $context->msg( 'prefs-resetpass' )->escaped(), [],
                                [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
@@ -411,8 +418,10 @@ class Preferences {
                        'default' => $oldsigHTML,
                        'section' => 'personal/signature',
                ];
+               $nicknameChangeAllowed = $wgDisableAuthManager ? $wgAuth->allowPropChange( 'nickname' )
+                       : AuthManager::singleton()->allowsPropertyChange( 'nickname' );
                $defaultPreferences['nickname'] = [
-                       'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
+                       'type' => $nicknameChangeAllowed ? 'text' : 'info',
                        'maxlength' => $config->get( 'MaxSigChars' ),
                        'label-message' => 'yournick',
                        'validation-callback' => [ 'Preferences', 'validateSignature' ],
@@ -441,7 +450,9 @@ class Preferences {
                                }
 
                                $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
-                               if ( $canEditPrivateInfo && $wgAuth->allowPropChange( 'emailaddress' ) ) {
+                               $emailChangeAllowed = $wgDisableAuthManager ? $wgAuth->allowPropChange( 'emailaddress' )
+                                       : AuthManager::singleton()->allowsPropertyChange( 'emailaddress' );
+                               if ( $canEditPrivateInfo && $emailChangeAllowed ) {
                                        $link = Linker::link(
                                                SpecialPage::getTitleFor( 'ChangeEmail' ),
                                                $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
@@ -1410,8 +1421,6 @@ class Preferences {
         * @return bool|Status|string
         */
        static function tryFormSubmit( $formData, $form ) {
-               global $wgAuth;
-
                $user = $form->getModifiedUser();
                $hiddenPrefs = $form->getConfig()->get( 'HiddenPrefs' );
                $result = true;
@@ -1430,6 +1439,7 @@ class Preferences {
 
                // Fortunately, the realname field is MUCH simpler
                // (not really "private", but still shouldn't be edited without permission)
+
                if ( !in_array( 'realname', $hiddenPrefs )
                        && $user->isAllowed( 'editmyprivateinfo' )
                        && array_key_exists( 'realname', $formData )
@@ -1462,7 +1472,7 @@ class Preferences {
                        Hooks::run( 'PreferencesFormPreSave', [ $formData, $form, $user, &$result ] );
                }
 
-               $wgAuth->updateExternalDB( $user );
+               MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] );
                $user->saveSettings();
 
                return $result;
index 70192b9..451635e 100644 (file)
@@ -70,7 +70,9 @@ class ProtectionForm {
                // Check if the form should be disabled.
                // If it is, the form will be available in read-only to show levels.
                $this->mPermErrors = $this->mTitle->getUserPermissionsErrors(
-                       'protect', $this->mContext->getUser()
+                       'protect',
+                       $this->mContext->getUser(),
+                       $this->mContext->getRequest()->wasPosted() ? 'secure' : 'full' // T92357
                );
                if ( wfReadOnly() ) {
                        $this->mPermErrors[] = [ 'readonlytext', wfReadOnlyReason() ];
index 40daf3d..0e45b25 100644 (file)
@@ -1553,7 +1553,6 @@ class Revision implements IDBAccessObject {
                        }
                        $text = $cache->get( $key );
                        if ( is_string( $text ) ) {
-                               wfDebug( __METHOD__ . ": got id $textId from cache\n" );
                                $processCache->set( $key, $text );
                                return $text;
                        }
index c02b06e..6bdacf0 100644 (file)
  *      MediaWiki code base.
  */
 
+use MediaWiki\Interwiki\ClassicInterwikiLookup;
 use MediaWiki\MediaWikiServices;
 
 return [
+       'DBLoadBalancerFactory' => function( MediaWikiServices $services ) {
+               $config = $services->getMainConfig()->get( 'LBFactoryConf' );
+
+               $class = LBFactory::getLBFactoryClass( $config );
+               if ( !isset( $config['readOnlyReason'] ) ) {
+                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
+                       $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
+               }
+
+               return new $class( $config );
+       },
+
+       'DBLoadBalancer' => function( MediaWikiServices $services ) {
+               // just return the default LB from the DBLoadBalancerFactory service
+               return $services->getDBLoadBalancerFactory()->getMainLB();
+       },
+
        'SiteStore' => function( MediaWikiServices $services ) {
-               $loadBalancer = wfGetLB(); // TODO: use LB from MediaWikiServices
-               $rawSiteStore = new DBSiteStore( $loadBalancer );
+               $rawSiteStore = new DBSiteStore( $services->getDBLoadBalancer() );
 
                // TODO: replace wfGetCache with a CacheFactory service.
                // TODO: replace wfIsHHVM with a capabilities service.
@@ -72,6 +89,19 @@ return [
                return $services->getConfigFactory()->makeConfig( 'main' );
        },
 
+       'InterwikiLookup' => function( MediaWikiServices $services ) {
+               global $wgContLang; // TODO: manage $wgContLang as a service
+               $config = $services->getMainConfig();
+               return new ClassicInterwikiLookup(
+                       $wgContLang,
+                       ObjectCache::getMainWANInstance(),
+                       $config->get( 'InterwikiExpiry' ),
+                       $config->get( 'InterwikiCache' ),
+                       $config->get( 'InterwikiScopes' ),
+                       $config->get( 'InterwikiFallbackSite' )
+               );
+       },
+
        'StatsdDataFactory' => function( MediaWikiServices $services ) {
                return new BufferingStatsdDataFactory(
                        rtrim( $services->getMainConfig()->get( 'StatsdMetricPrefix' ), '.' )
@@ -83,19 +113,72 @@ return [
        },
 
        'SearchEngineFactory' => function( MediaWikiServices $services ) {
-               // Create search engine
-               return new SearchEngineFactory( $services->getService( 'SearchEngineConfig' ) );
+               return new SearchEngineFactory( $services->getSearchEngineConfig() );
        },
 
        'SearchEngineConfig' => function( MediaWikiServices $services ) {
                global $wgContLang;
-               // Create a search engine config from main config.
-               $config = $services->getService( 'MainConfig' );
-               return new SearchEngineConfig( $config, $wgContLang );
+               return new SearchEngineConfig( $services->getMainConfig(), $wgContLang );
        },
 
        'SkinFactory' => function( MediaWikiServices $services ) {
-               return new SkinFactory();
+               $factory = new SkinFactory();
+
+               $names = $services->getMainConfig()->get( 'ValidSkinNames' );
+
+               foreach ( $names as $name => $skin ) {
+                       $factory->register( $name, $skin, function () use ( $name, $skin ) {
+                               $class = "Skin$skin";
+                               return new $class( $name );
+                       } );
+               }
+               // Register a hidden "fallback" skin
+               $factory->register( 'fallback', 'Fallback', function () {
+                       return new SkinFallback;
+               } );
+               // Register a hidden skin for api output
+               $factory->register( 'apioutput', 'ApiOutput', function () {
+                       return new SkinApi;
+               } );
+
+               return $factory;
+       },
+
+       'WatchedItemStore' => function( MediaWikiServices $services ) {
+               $store = new WatchedItemStore(
+                       $services->getDBLoadBalancer(),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+               $store->setStatsdDataFactory( $services->getStatsdDataFactory() );
+               return $store;
+       },
+
+       'LinkCache' => function( MediaWikiServices $services ) {
+               return new LinkCache(
+                       $services->getTitleFormatter()
+               );
+       },
+
+       'GenderCache' => function( MediaWikiServices $services ) {
+               return new GenderCache();
+       },
+
+       '_MediaWikiTitleCodec' => function( MediaWikiServices $services ) {
+               global $wgContLang;
+
+               return new MediaWikiTitleCodec(
+                       $wgContLang,
+                       $services->getGenderCache(),
+                       $services->getMainConfig()->get( 'LocalInterwikis' )
+               );
+       },
+
+       'TitleFormatter' => function( MediaWikiServices $services ) {
+               return $services->getService( '_MediaWikiTitleCodec' );
+       },
+
+       'TitleParser' => function( MediaWikiServices $services ) {
+               return $services->getService( '_MediaWikiTitleCodec' );
        },
 
        ///////////////////////////////////////////////////////////////////////////
diff --git a/includes/Services/CannotReplaceActiveServiceException.php b/includes/Services/CannotReplaceActiveServiceException.php
new file mode 100644 (file)
index 0000000..4993073
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ *
+ * 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
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ */
+class CannotReplaceActiveServiceException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
+       }
+
+}
diff --git a/includes/Services/ContainerDisabledException.php b/includes/Services/ContainerDisabledException.php
new file mode 100644 (file)
index 0000000..ede076d
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ *
+ * 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
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ */
+class ContainerDisabledException extends RuntimeException {
+
+       /**
+        * @param Exception|null $previous
+        */
+       public function __construct( Exception $previous = null ) {
+               parent::__construct( 'Container disabled!', 0, $previous );
+       }
+
+}
diff --git a/includes/Services/DestructibleService.php b/includes/Services/DestructibleService.php
new file mode 100644 (file)
index 0000000..6ce9af2
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for destructible services.
+ *
+ * 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
+ *
+ * @since 1.27
+ */
+
+/**
+ * DestructibleService defines a standard interface for shutting down a service instance.
+ * The intended use is for a service container to be able to shut down services that should
+ * no longer be used, and allow such services to release any system resources.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface DestructibleService {
+
+       /**
+        * Notifies the service object that it should expect to no longer be used, and should release
+        * any system resources it may own. The behavior of all service methods becomes undefined after
+        * destroy() has been called. It is recommended that implementing classes should throw an
+        * exception when service methods are accessed after destroy() has been called.
+        */
+       public function destroy();
+
+}
diff --git a/includes/Services/NoSuchServiceException.php b/includes/Services/NoSuchServiceException.php
new file mode 100644 (file)
index 0000000..36e50d2
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when the requested service is not known.
+ *
+ * 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
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when the requested service is not known.
+ */
+class NoSuchServiceException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "No such service: $serviceName", 0, $previous );
+       }
+
+}
diff --git a/includes/Services/SalvageableService.php b/includes/Services/SalvageableService.php
new file mode 100644 (file)
index 0000000..a613050
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for salvageable services.
+ *
+ * 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
+ *
+ * @since 1.28
+ */
+
+/**
+ * SalvageableService defines an interface for services that are able to salvage state from a
+ * previous instance of the same class. The intent is to allow new service instances to re-use
+ * resources that would be expensive to re-create, such as cached data or network connections.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface SalvageableService {
+
+       /**
+        * Re-uses state from $other. $other must not be used after being passed to salvage(),
+        * and should be considered to be destroyed.
+        *
+        * @note Implementations are responsible for determining what parts of $other can be re-used
+        * safely. In particular, implementations should check that the relevant configuration of
+        * $other is the same as in $this before re-using resources from $other.
+        *
+        * @note Implementations must take care to detach any re-used resources from the original
+        * service instance. If $other is destroyed later, resources that are now used by the
+        * new service instance must not be affected.
+        *
+        * @note If $other is a DestructibleService, implementations should make sure that $other
+        * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
+        * after carefully detaching all relevant resources.
+        *
+        * @param SalvageableService $other The object to salvage state from. $other must have the
+        * exact same type as $this.
+        */
+       public function salvage( SalvageableService $other );
+
+}
diff --git a/includes/Services/ServiceAlreadyDefinedException.php b/includes/Services/ServiceAlreadyDefinedException.php
new file mode 100644 (file)
index 0000000..c6344d3
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ *
+ * 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
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ */
+class ServiceAlreadyDefinedException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Service already defined: $serviceName", 0, $previous );
+       }
+
+}
index e3cda2e..b336795 100644 (file)
@@ -43,7 +43,7 @@ use Wikimedia\Assert\Assert;
  * @see docs/injection.txt for an overview of using dependency injection in the
  *      MediaWiki code base.
  */
-class ServiceContainer {
+class ServiceContainer implements DestructibleService {
 
        /**
         * @var object[]
@@ -55,11 +55,21 @@ class ServiceContainer {
         */
        private $serviceInstantiators = [];
 
+       /**
+        * @var boolean[] disabled status, per service name
+        */
+       private $disabled = [];
+
        /**
         * @var array
         */
        private $extraInstantiationParams;
 
+       /**
+        * @var boolean
+        */
+       private $destroyed = false;
+
        /**
         * @param array $extraInstantiationParams Any additional parameters to be passed to the
         * instantiator function when creating a service. This is typically used to provide
@@ -69,6 +79,25 @@ class ServiceContainer {
                $this->extraInstantiationParams = $extraInstantiationParams;
        }
 
+       /**
+        * Destroys all contained service instances that implement the DestructibleService
+        * interface. This will render all services obtained from this MediaWikiServices
+        * instance unusable. In particular, this will disable access to the storage backend
+        * via any of these services. Any future call to getService() will throw an exception.
+        *
+        * @see resetGlobalInstance()
+        */
+       public function destroy() {
+               foreach ( $this->getServiceNames() as $name ) {
+                       $service = $this->peekService( $name );
+                       if ( $service !== null && $service instanceof DestructibleService ) {
+                               $service->destroy();
+                       }
+               }
+
+               $this->destroyed = true;
+       }
+
        /**
         * @param array $wiringFiles A list of PHP files to load wiring information from.
         * Each file is loaded using PHP's include mechanism. Each file is expected to
@@ -102,6 +131,28 @@ class ServiceContainer {
                }
        }
 
+       /**
+        * Imports all wiring defined in $container. Wiring defined in $container
+        * will override any wiring already defined locally. However, already
+        * existing service instances will be preserved.
+        *
+        * @since 1.28
+        *
+        * @param ServiceContainer $container
+        * @param string[] $skip A list of service names to skip during import
+        */
+       public function importWiring( ServiceContainer $container, $skip = [] ) {
+               $newInstantiators = array_diff_key(
+                       $container->serviceInstantiators,
+                       array_flip( $skip )
+               );
+
+               $this->serviceInstantiators = array_merge(
+                       $this->serviceInstantiators,
+                       $newInstantiators
+               );
+       }
+
        /**
         * Returns true if a service is defined for $name, that is, if a call to getService( $name )
         * would return a service instance.
@@ -114,6 +165,29 @@ class ServiceContainer {
                return isset( $this->serviceInstantiators[$name] );
        }
 
+       /**
+        * Returns the service instance for $name only if that service has already been instantiated.
+        * This is intended for situations where services get destroyed/cleaned up, so we can
+        * avoid creating a service just to destroy it again.
+        *
+        * @note This is intended for internal use and for test fixtures.
+        * Application logic should use getService() instead.
+        *
+        * @see getService().
+        *
+        * @param string $name
+        *
+        * @return object|null The service instance, or null if the service has not yet been instantiated.
+        * @throws RuntimeException if $name does not refer to a known service.
+        */
+       public function peekService( $name ) {
+               if ( !$this->hasService( $name ) ) {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               return isset( $this->services[$name] ) ? $this->services[$name] : null;
+       }
+
        /**
         * @return string[]
         */
@@ -139,7 +213,7 @@ class ServiceContainer {
                Assert::parameterType( 'string', $name, '$name' );
 
                if ( $this->hasService( $name ) ) {
-                       throw new RuntimeException( 'Service already defined: ' . $name );
+                       throw new ServiceAlreadyDefinedException( $name );
                }
 
                $this->serviceInstantiators[$name] = $instantiator;
@@ -165,14 +239,76 @@ class ServiceContainer {
                Assert::parameterType( 'string', $name, '$name' );
 
                if ( !$this->hasService( $name ) ) {
-                       throw new RuntimeException( 'Service not defined: ' . $name );
+                       throw new NoSuchServiceException( $name );
                }
 
                if ( isset( $this->services[$name] ) ) {
-                       throw new RuntimeException( 'Cannot redefine a service that is already in use: ' . $name );
+                       throw new CannotReplaceActiveServiceException( $name );
                }
 
                $this->serviceInstantiators[$name] = $instantiator;
+               unset( $this->disabled[$name] );
+       }
+
+       /**
+        * Disables a service.
+        *
+        * @note Attempts to call getService() for a disabled service will result
+        * in a DisabledServiceException. Calling peekService for a disabled service will
+        * return null. Disabled services are listed by getServiceNames(). A disabled service
+        * can be enabled again using redefineService().
+        *
+        * @note If the service was already active (that is, instantiated) when getting disabled,
+        * and the service instance implements DestructibleService, destroy() is called on the
+        * service instance.
+        *
+        * @see redefineService()
+        * @see resetService()
+        *
+        * @param string $name The name of the service to disable.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       public function disableService( $name ) {
+               $this->resetService( $name );
+
+               $this->disabled[$name] = true;
+       }
+
+       /**
+        * Resets a service by dropping the service instance.
+        * If the service instances implements DestructibleService, destroy()
+        * is called on the service instance.
+        *
+        * @warning This is generally unsafe! Other services may still retain references
+        * to the stale service instance, leading to failures and inconsistencies. Subclasses
+        * may use this method to reset specific services under specific instances, but
+        * it should not be exposed to application logic.
+        *
+        * @note This is declared final so subclasses can not interfere with the expectations
+        * disableService() has when calling resetService().
+        *
+        * @see redefineService()
+        * @see disableService().
+        *
+        * @param string $name The name of the service to reset.
+        * @param bool $destroy Whether the service instance should be destroyed if it exists.
+        *        When set to false, any existing service instance will effectively be detached
+        *        from the container.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       final protected function resetService( $name, $destroy = true ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               $instance = $this->peekService( $name );
+
+               if ( $destroy && $instance instanceof DestructibleService )  {
+                       $instance->destroy();
+               }
+
+               unset( $this->services[$name] );
+               unset( $this->disabled[$name] );
        }
 
        /**
@@ -189,10 +325,21 @@ class ServiceContainer {
         *
         * @param string $name The service name
         *
-        * @throws InvalidArgumentException if $name is not a known service.
+        * @throws NoSuchServiceException if $name is not a known service.
+        * @throws ContainerDisabledException if this container has already been destroyed.
+        * @throws ServiceDisabledException if the requested service has been disabled.
+        *
         * @return object The service instance
         */
        public function getService( $name ) {
+               if ( $this->destroyed ) {
+                       throw new ContainerDisabledException();
+               }
+
+               if ( isset( $this->disabled[$name] ) ) {
+                       throw new ServiceDisabledException( $name );
+               }
+
                if ( !isset( $this->services[$name] ) ) {
                        $this->services[$name] = $this->createService( $name );
                }
@@ -212,8 +359,9 @@ class ServiceContainer {
                                $this->serviceInstantiators[$name],
                                array_merge( [ $this ], $this->extraInstantiationParams )
                        );
+                       // NOTE: when adding more wiring logic here, make sure copyWiring() is kept in sync!
                } else {
-                       throw new InvalidArgumentException( 'Unknown service: ' . $name );
+                       throw new NoSuchServiceException( $name );
                }
 
                return $service;
diff --git a/includes/Services/ServiceDisabledException.php b/includes/Services/ServiceDisabledException.php
new file mode 100644 (file)
index 0000000..ae15b7c
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ *
+ * 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
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ */
+class ServiceDisabledException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Service disabled: $serviceName", 0, $previous );
+       }
+
+}
index 9898b84..5b19b5f 100644 (file)
@@ -23,6 +23,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * This file is not a valid entry point, perform no further processing unless
@@ -290,25 +291,6 @@ if ( $wgSkipSkin ) {
        $wgSkipSkins[] = $wgSkipSkin;
 }
 
-// Register skins
-// Use a closure to avoid leaking into global state
-call_user_func( function () use ( $wgValidSkinNames ) {
-       $factory = SkinFactory::getDefaultInstance();
-       foreach ( $wgValidSkinNames as $name => $skin ) {
-               $factory->register( $name, $skin, function () use ( $name, $skin ) {
-                       $class = "Skin$skin";
-                       return new $class( $name );
-               } );
-       }
-       // Register a hidden "fallback" skin
-       $factory->register( 'fallback', 'Fallback', function () {
-               return new SkinFallback;
-       } );
-       // Register a hidden skin for api output
-       $factory->register( 'apioutput', 'ApiOutput', function () {
-               return new SkinApi;
-       } );
-} );
 $wgSkipSkins[] = 'fallback';
 $wgSkipSkins[] = 'apioutput';
 
@@ -468,6 +450,22 @@ if ( $wgProfileOnly ) {
        $wgDebugLogFile = '';
 }
 
+// Disable AuthManager API modules if $wgDisableAuthManager
+if ( $wgDisableAuthManager ) {
+       $wgAPIModules += [
+               'clientlogin' => 'ApiDisabled',
+               'createaccount' => 'ApiCreateAccount', // Use the non-AuthManager version
+               'linkaccount' => 'ApiDisabled',
+               'unlinkaccount' => 'ApiDisabled',
+               'changeauthenticationdata' => 'ApiDisabled',
+               'removeauthenticationdata' => 'ApiDisabled',
+               'resetpassword' => 'ApiDisabled',
+       ];
+       $wgAPIMetaModules += [
+               'authmanagerinfo' => 'ApiQueryDisabled',
+       ];
+}
+
 // Backwards compatibility with old password limits
 if ( $wgMinimalPasswordLength !== false ) {
        $wgPasswordPolicy['policies']['default']['MinimalPasswordLength'] = $wgMinimalPasswordLength;
@@ -517,6 +515,14 @@ if ( !class_exists( 'AutoLoader' ) ) {
        require_once "$IP/includes/AutoLoader.php";
 }
 
+// Reset the global service locator, so any services that have already been created will be
+// re-created while taking into account any custom settings and extensions.
+MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' );
+
+// Define a constant that indicates that the bootstrapping of the service locator
+// is complete.
+define( 'MW_SERVICE_BOOTSTRAP_COMPLETE', 1 );
+
 // Install a header callback to prevent caching of responses with cookies (T127993)
 if ( !$wgCommandLineMode ) {
        header_register_callback( function () {
@@ -702,9 +708,22 @@ $wgContLang->initContLang();
 $wgRequest->interpolateTitle();
 
 if ( !is_object( $wgAuth ) ) {
-       $wgAuth = new AuthPlugin;
+       $wgAuth = $wgDisableAuthManager ? new AuthPlugin : new MediaWiki\Auth\AuthManagerAuthPlugin;
        Hooks::run( 'AuthPluginSetup', [ &$wgAuth ] );
 }
+if ( !$wgDisableAuthManager &&
+       $wgAuth && !$wgAuth instanceof MediaWiki\Auth\AuthManagerAuthPlugin
+) {
+       MediaWiki\Auth\AuthManager::singleton()->forcePrimaryAuthenticationProviders( [
+               new MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider( [
+                       'authoritative' => false,
+               ] ),
+               new MediaWiki\Auth\AuthPluginPrimaryAuthenticationProvider( $wgAuth ),
+               new MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider( [
+                       'authoritative' => true,
+               ] ),
+       ], '$wgAuth is ' . get_class( $wgAuth ) );
+}
 
 // Set up the session
 $ps_session = Profiler::instance()->scopedProfileIn( $fname . '-session' );
@@ -830,7 +849,15 @@ if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
        $sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser();
        if ( $sessionUser->getId() === 0 && User::isValidUserName( $sessionUser->getName() ) ) {
                $ps_autocreate = Profiler::instance()->scopedProfileIn( $fname . '-autocreate' );
-               MediaWiki\Session\SessionManager::autoCreateUser( $sessionUser );
+               if ( $wgDisableAuthManager ) {
+                       MediaWiki\Session\SessionManager::autoCreateUser( $sessionUser );
+               } else {
+                       MediaWiki\Auth\AuthManager::singleton()->autoCreateUser(
+                               $sessionUser,
+                               MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSSION,
+                               true
+                       );
+               }
                Profiler::instance()->scopedProfileOut( $ps_autocreate );
        }
        unset( $sessionUser );
index 215378b..6c536dd 100644 (file)
@@ -193,7 +193,7 @@ class SiteStats {
                                        __METHOD__
                                );
                        },
-                       [ 'pcTTL' => 10 ]
+                       [ 'pcTTL' => $cache::TTL_PROC_LONG ]
                );
        }
 
index 3d2c887..f8370e4 100644 (file)
@@ -115,6 +115,14 @@ class Status {
                $this->sv->setResult( $ok, $value );
        }
 
+       /**
+        * Returns the wrapped StatusValue object
+        * @return StatusValue
+        */
+       public function getStatusValue() {
+               return $this->sv;
+       }
+
        /**
         * Returns whether the operation completed and didn't have any error or
         * warnings
index 7887890..72c21fc 100644 (file)
@@ -22,6 +22,7 @@
  * @file
  */
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Represents a title within MediaWiki.
@@ -157,42 +158,6 @@ class Title implements LinkTarget {
        private $mIsBigDeletion = null;
        // @}
 
-       /**
-        * B/C kludge: provide a TitleParser for use by Title.
-        * Ideally, Title would have no methods that need this.
-        * Avoid usage of this singleton by using TitleValue
-        * and the associated services when possible.
-        *
-        * @return MediaWikiTitleCodec
-        */
-       private static function getMediaWikiTitleCodec() {
-               global $wgContLang, $wgLocalInterwikis;
-
-               static $titleCodec = null;
-               static $titleCodecFingerprint = null;
-
-               // $wgContLang and $wgLocalInterwikis may change (especially while testing),
-               // make sure we are using the right one. To detect changes over the course
-               // of a request, we remember a fingerprint of the config used to create the
-               // codec singleton, and re-create it if the fingerprint doesn't match.
-               $fingerprint = spl_object_hash( $wgContLang ) . '|' . implode( '+', $wgLocalInterwikis );
-
-               if ( $fingerprint !== $titleCodecFingerprint ) {
-                       $titleCodec = null;
-               }
-
-               if ( !$titleCodec ) {
-                       $titleCodec = new MediaWikiTitleCodec(
-                               $wgContLang,
-                               GenderCache::singleton(),
-                               $wgLocalInterwikis
-                       );
-                       $titleCodecFingerprint = $fingerprint;
-               }
-
-               return $titleCodec;
-       }
-
        /**
         * B/C kludge: provide a TitleParser for use by Title.
         * Ideally, Title would have no methods that need this.
@@ -202,11 +167,12 @@ class Title implements LinkTarget {
         * @return TitleFormatter
         */
        private static function getTitleFormatter() {
-               // NOTE: we know that getMediaWikiTitleCodec() returns a MediaWikiTitleCodec,
-               //      which implements TitleFormatter.
-               return self::getMediaWikiTitleCodec();
+               return MediaWikiServices::getInstance()->getTitleFormatter();
        }
 
+       /**
+        * @access protected
+        */
        function __construct() {
        }
 
@@ -942,7 +908,9 @@ class Title implements LinkTarget {
         * @return string Content model id
         */
        public function getContentModel( $flags = 0 ) {
-               if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
+               if ( ( !$this->mContentModel || $flags === Title::GAID_FOR_UPDATE ) &&
+                       $this->getArticleID( $flags )
+               ) {
                        $linkCache = LinkCache::singleton();
                        $linkCache->addLinkObj( $this ); # in case we already had an article ID
                        $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
@@ -1747,9 +1715,9 @@ class Title implements LinkTarget {
 
                                if ( $url === false
                                        && $wgVariantArticlePath
+                                       && preg_match( '/^variant=([^&]*)$/', $query, $matches )
                                        && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
                                        && $this->getPageLanguage()->hasVariants()
-                                       && preg_match( '/^variant=([^&]*)$/', $query, $matches )
                                ) {
                                        $variant = urldecode( $matches[1] );
                                        if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
@@ -3332,9 +3300,11 @@ class Title implements LinkTarget {
                // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
                //        the parsing code with Title, while avoiding massive refactoring.
                // @todo: get rid of secureAndSplit, refactor parsing code.
-               $titleParser = self::getMediaWikiTitleCodec();
+               // @note: getTitleParser() returns a TitleParser implementation which does not have a
+               //        splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
+               $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
                // MalformedTitleException can be thrown here
-               $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
+               $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
 
                # Fill fields
                $this->setFragment( '#' . $parts['fragment'] );
@@ -4438,7 +4408,8 @@ class Title implements LinkTarget {
                        $this->mNotificationTimestamp = [];
                }
 
-               $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+               $watchedItem = $store->getWatchedItem( $user, $this );
                if ( $watchedItem ) {
                        $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
                } else {
index db6ce87..b070e1e 100644 (file)
@@ -18,6 +18,7 @@
  * @file
  * @ingroup Watchlist
  */
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Linker\LinkTarget;
 
 /**
@@ -118,7 +119,7 @@ class WatchedItem {
                        if ( $this->checkRights && !$this->user->isAllowed( 'viewmywatchlist' ) ) {
                                return false;
                        }
-                       $item = WatchedItemStore::getDefaultInstance()
+                       $item = MediaWikiServices::getInstance()->getWatchedItemStore()
                                ->loadWatchedItem( $this->user, $this->linkTarget );
                        if ( $item ) {
                                $this->notificationTimestamp = $item->getNotificationTimestamp();
@@ -151,7 +152,7 @@ class WatchedItem {
         *             or WatchedItemStore::loadWatchedItem()
         */
        public static function fromUserTitle( $user, $title, $checkRights = User::CHECK_USER_RIGHTS ) {
-               // wfDeprecated( __METHOD__, '1.27' );
+               wfDeprecated( __METHOD__, '1.27' );
                return new self( $user, $title, self::DEPRECATED_USAGE_TIMESTAMP, (bool)$checkRights );
        }
 
@@ -159,11 +160,11 @@ class WatchedItem {
         * @deprecated since 1.27 Use WatchedItemStore::resetNotificationTimestamp()
         */
        public function resetNotificationTimestamp( $force = '', $oldid = 0 ) {
-               // wfDeprecated( __METHOD__, '1.27' );
+               wfDeprecated( __METHOD__, '1.27' );
                if ( $this->checkRights && !$this->user->isAllowed( 'editmywatchlist' ) ) {
                        return;
                }
-               WatchedItemStore::getDefaultInstance()->resetNotificationTimestamp(
+               MediaWikiServices::getInstance()->getWatchedItemStore()->resetNotificationTimestamp(
                        $this->user,
                        $this->getTitle(),
                        $force,
@@ -175,7 +176,7 @@ class WatchedItem {
         * @deprecated since 1.27 Use WatchedItemStore::addWatchBatch()
         */
        public static function batchAddWatch( array $items ) {
-               // wfDeprecated( __METHOD__, '1.27' );
+               wfDeprecated( __METHOD__, '1.27' );
                if ( !$items ) {
                        return false;
                }
@@ -194,7 +195,7 @@ class WatchedItem {
                        $targets[$userId][] = $watchedItem->getTitle()->getTalkPage();
                }
 
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                $success = true;
                foreach ( $users as $userId => $user ) {
                        $success &= $store->addWatchBatchForUser( $user, $targets[$userId] );
@@ -208,7 +209,7 @@ class WatchedItem {
         * @return bool
         */
        public function addWatch() {
-               // wfDeprecated( __METHOD__, '1.27' );
+               wfDeprecated( __METHOD__, '1.27' );
                $this->user->addWatch( $this->getTitle(), $this->checkRights );
                return true;
        }
@@ -218,7 +219,7 @@ class WatchedItem {
         * @return bool
         */
        public function removeWatch() {
-               // wfDeprecated( __METHOD__, '1.27' );
+               wfDeprecated( __METHOD__, '1.27' );
                if ( $this->checkRights && !$this->user->isAllowed( 'editmywatchlist' ) ) {
                        return false;
                }
@@ -231,7 +232,7 @@ class WatchedItem {
         * @return bool
         */
        public function isWatched() {
-               // wfDeprecated( __METHOD__, '1.27' );
+               wfDeprecated( __METHOD__, '1.27' );
                return $this->user->isWatched( $this->getTitle(), $this->checkRights );
        }
 
@@ -239,8 +240,8 @@ class WatchedItem {
         * @deprecated since 1.27 Use WatchedItemStore::duplicateAllAssociatedEntries()
         */
        public static function duplicateEntries( Title $oldTitle, Title $newTitle ) {
-               // wfDeprecated( __METHOD__, '1.27' );
-               $store = WatchedItemStore::getDefaultInstance();
+               wfDeprecated( __METHOD__, '1.27' );
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                $store->duplicateAllAssociatedEntries( $oldTitle, $newTitle );
        }
 
index fcf6d3b..f0619d6 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Linker\LinkTarget;
 use Wikimedia\Assert\Assert;
 
@@ -50,11 +51,6 @@ class WatchedItemStore implements StatsdAwareInterface {
         */
        private $stats;
 
-       /**
-        * @var self|null
-        */
-       private static $instance;
-
        /**
         * @param LoadBalancer $loadBalancer
         * @param HashBagOStuff $cache
@@ -125,46 +121,6 @@ class WatchedItemStore implements StatsdAwareInterface {
                } );
        }
 
-       /**
-        * Overrides the default instance of this class
-        * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
-        *
-        * If this method is used it MUST also be called with null after a test to ensure a new
-        * default instance is created next time getDefaultInstance is called.
-        *
-        * @param WatchedItemStore|null $store
-        *
-        * @return ScopedCallback to reset the overridden value
-        * @throws MWException
-        */
-       public static function overrideDefaultInstance( WatchedItemStore $store = null ) {
-               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
-                       throw new MWException(
-                               'Cannot override ' . __CLASS__ . 'default instance in operation.'
-                       );
-               }
-
-               $previousValue = self::$instance;
-               self::$instance = $store;
-               return new ScopedCallback( function() use ( $previousValue ) {
-                       self::$instance = $previousValue;
-               } );
-       }
-
-       /**
-        * @return self
-        */
-       public static function getDefaultInstance() {
-               if ( !self::$instance ) {
-                       self::$instance = new self(
-                               wfGetLB(),
-                               new HashBagOStuff( [ 'maxKeys' => 100 ] )
-                       );
-                       self::$instance->setStatsdDataFactory( RequestContext::getMain()->getStats() );
-               }
-               return self::$instance;
-       }
-
        private function getCacheKey( User $user, LinkTarget $target ) {
                return $this->cache->makeKey(
                        (string)$target->getNamespace(),
@@ -189,16 +145,28 @@ class WatchedItemStore implements StatsdAwareInterface {
        }
 
        private function uncacheLinkTarget( LinkTarget $target ) {
+               $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
                if ( !isset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] ) ) {
                        return;
                }
-               $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
                foreach ( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] as $key ) {
                        $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget.items' );
                        $this->cache->delete( $key );
                }
        }
 
+       private function uncacheUser( User $user ) {
+               $this->stats->increment( 'WatchedItemStore.uncacheUser' );
+               foreach ( $this->cacheIndex as $ns => $dbKeyArray ) {
+                       foreach ( $dbKeyArray as $dbKey => $userArray ) {
+                               if ( isset( $userArray[$user->getId()] ) ) {
+                                       $this->stats->increment( 'WatchedItemStore.uncacheUser.items' );
+                                       $this->cache->delete( $userArray[$user->getId()] );
+                               }
+                       }
+               }
+       }
+
        /**
         * @param User $user
         * @param LinkTarget $target
@@ -711,6 +679,41 @@ class WatchedItemStore implements StatsdAwareInterface {
                return $success;
        }
 
+       /**
+        * @param User $user The user to set the timestamp for
+        * @param string $timestamp Set the update timestamp to this value
+        * @param LinkTarget[] $targets List of targets to update. Default to all targets
+        *
+        * @return bool success
+        */
+       public function setNotificationTimestampsForUser( User $user, $timestamp, array $targets = [] ) {
+               // Only loggedin user can have a watchlist
+               if ( $user->isAnon() ) {
+                       return false;
+               }
+
+               $dbw = $this->getConnection( DB_MASTER );
+
+               $conds = [ 'wl_user' => $user->getId() ];
+               if ( $targets ) {
+                       $batch = new LinkBatch( $targets );
+                       $conds[] = $batch->constructSet( 'wl', $dbw );
+               }
+
+               $success = $dbw->update(
+                       'watchlist',
+                       [ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) ],
+                       $conds,
+                       __METHOD__
+               );
+
+               $this->reuseConnection( $dbw );
+
+               $this->uncacheUser( $user );
+
+               return $success;
+       }
+
        /**
         * @param User $editor The editor that triggered the update. Their notification
         *  timestamp will not be updated(they have already seen it)
index b159f79..2333c78 100644 (file)
@@ -80,6 +80,9 @@ class WebRequest {
         */
        protected $sessionId = null;
 
+       /** @var bool Whether this HTTP request is "safe" (even if it is an HTTP post) */
+       protected $markedAsSafe = false;
+
        public function __construct() {
                $this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
                        ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
@@ -318,7 +321,7 @@ class WebRequest {
         *
         * @param string $path The URL path given from the client
         * @param array $bases One or more URLs, optionally with $1 at the end
-        * @param string $key If provided, the matching key in $bases will be
+        * @param string|bool $key If provided, the matching key in $bases will be
         *    passed on as the value of this URL parameter
         * @return array Array of URL variables to interpolate; empty if no match
         */
@@ -1022,7 +1025,7 @@ class WebRequest {
         * @param mixed $data
         */
        public function setSessionData( $key, $data ) {
-               return $this->getSession()->set( $key, $data );
+               $this->getSession()->set( $key, $data );
        }
 
        /**
@@ -1245,4 +1248,50 @@ HTML;
        public function setIP( $ip ) {
                $this->ip = $ip;
        }
+
+       /**
+        * Whether this request should be identified as being "safe"
+        *
+        * This means that the client is not requesting any state changes and that database writes
+        * are not inherently required. Ideally, no visible updates would happen at all. If they
+        * must, then they should not be publically attributed to the end user.
+        *
+        * In more detail:
+        *   - Cache populations and refreshes MAY occur.
+        *   - Private user session updates and private server logging MAY occur.
+        *   - Updates to private viewing activity data MAY occur via DeferredUpdates.
+        *   - Other updates SHOULD NOT occur (e.g. modifying content assets).
+        *
+        * @return bool
+        * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+        * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+        * @since 1.28
+        */
+       public function isSafeRequest() {
+               if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
+                       return false; // CLI mode
+               }
+
+               if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
+                       return $this->markedAsSafe;
+               } elseif ( in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS' ] ) ) {
+                       return true; // HTTP "safe methods"
+               }
+
+               return false; // PUT/DELETE
+       }
+
+       /**
+        * Mark this request is identified as being nullipotent even if it is a POST request
+        *
+        * POST requests are often used due to the need for a client payload, even if the request
+        * is otherwise equivalent to a "safe method" request.
+        *
+        * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+        * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+        * @since 1.28
+        */
+       public function markAsSafeRequest() {
+               $this->markedAsSafe = true;
+       }
 }
index 839d7b2..84bf16e 100644 (file)
@@ -62,7 +62,7 @@ abstract class Action {
         * the action is disabled, or null if it's not recognised
         * @param string $action
         * @param array $overrides
-        * @return bool|null|string|callable
+        * @return bool|null|string|callable|Action
         */
        final private static function getClass( $action, array $overrides ) {
                global $wgActions;
index f7c30b7..b5f7ff2 100644 (file)
@@ -22,6 +22,8 @@
  * @ingroup Actions
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Displays information about a page.
  *
@@ -677,7 +679,7 @@ class InfoAction extends FormlessAction {
 
                                $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
 
-                               $watchedItemStore = WatchedItemStore::getDefaultInstance();
+                               $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
 
                                $result = [];
                                $result['watchers'] = $watchedItemStore->countWatchers( $title );
diff --git a/includes/api/ApiAMCreateAccount.php b/includes/api/ApiAMCreateAccount.php
new file mode 100644 (file)
index 0000000..806b8d2
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+
+/**
+ * Create an account with AuthManager
+ *
+ * @ingroup API
+ */
+class ApiAMCreateAccount extends ApiBase {
+
+       public function __construct( ApiMain $main, $action ) {
+               parent::__construct( $main, $action, 'create' );
+       }
+
+       public function getFinalDescription() {
+               // A bit of a hack to append 'api-help-authmanager-general-usage'
+               $msgs = parent::getFinalDescription();
+               $msgs[] = ApiBase::makeMessage( 'api-help-authmanager-general-usage', $this->getContext(), [
+                       $this->getModulePrefix(),
+                       $this->getModuleName(),
+                       $this->getModulePath(),
+                       AuthManager::ACTION_CREATE,
+                       self::needsToken(),
+               ] );
+               return $msgs;
+       }
+
+       public function execute() {
+               $params = $this->extractRequestParams();
+
+               $this->requireAtLeastOneParameter( $params, 'continue', 'returnurl' );
+
+               if ( $params['returnurl'] !== null ) {
+                       $bits = wfParseUrl( $params['returnurl'] );
+                       if ( !$bits || $bits['scheme'] === '' ) {
+                               $encParamName = $this->encodeParamName( 'returnurl' );
+                               $this->dieUsage(
+                                       "Invalid value '{$params['returnurl']}' for url parameter $encParamName",
+                                       "badurl_{$encParamName}"
+                               );
+                       }
+               }
+
+               $helper = new ApiAuthManagerHelper( $this );
+               $manager = AuthManager::singleton();
+
+               // Make sure it's possible to log in
+               if ( !$manager->canCreateAccounts() ) {
+                       $this->getResult()->addValue( null, 'createaccount', $helper->formatAuthenticationResponse(
+                               AuthenticationResponse::newFail(
+                                       $this->msg( 'userlogin-cannot-' . AuthManager::ACTION_CREATE )
+                               )
+                       ) );
+                       return;
+               }
+
+               // Perform the create step
+               if ( $params['continue'] ) {
+                       $reqs = $helper->loadAuthenticationRequests( AuthManager::ACTION_CREATE_CONTINUE );
+                       $res = $manager->continueAccountCreation( $reqs );
+               } else {
+                       $reqs = $helper->loadAuthenticationRequests( AuthManager::ACTION_CREATE );
+                       if ( $params['preservestate'] ) {
+                               $req = $helper->getPreservedRequest();
+                               if ( $req ) {
+                                       $reqs[] = $req;
+                               }
+                       }
+                       $res = $manager->beginAccountCreation( $this->getUser(), $reqs, $params['returnurl'] );
+               }
+
+               $this->getResult()->addValue( null, 'createaccount',
+                       $helper->formatAuthenticationResponse( $res ) );
+       }
+
+       public function isReadMode() {
+               return false;
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function needsToken() {
+               return 'createaccount';
+       }
+
+       public function getAllowedParams() {
+               return ApiAuthManagerHelper::getStandardParams( AuthManager::ACTION_CREATE,
+                       'requests', 'messageformat', 'mergerequestfields', 'preservestate', 'returnurl', 'continue'
+               );
+       }
+
+       public function dynamicParameterDocumentation() {
+               return [ 'api-help-authmanagerhelper-additional-params', AuthManager::ACTION_CREATE ];
+       }
+
+       protected function getExamplesMessages() {
+               return [
+                       'action=createaccount&username=Example&password=ExamplePassword&retype=ExamplePassword'
+                               . '&createreturnurl=http://example.org/&createtoken=123ABC'
+                               => 'apihelp-createaccount-example-create',
+               ];
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Account_creation';
+       }
+}
diff --git a/includes/api/ApiAuthManagerHelper.php b/includes/api/ApiAuthManagerHelper.php
new file mode 100644 (file)
index 0000000..2997405
--- /dev/null
@@ -0,0 +1,365 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.27
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\CreateFromLoginAuthenticationRequest;
+
+/**
+ * Helper class for AuthManager-using API modules. Intended for use via
+ * composition.
+ *
+ * @ingroup API
+ */
+class ApiAuthManagerHelper {
+
+       /** @var ApiBase API module, for context and parameters */
+       private $module;
+
+       /** @var string Message output format */
+       private $messageFormat;
+
+       /**
+        * @param ApiBase $module API module, for context and parameters
+        */
+       public function __construct( ApiBase $module ) {
+               $this->module = $module;
+
+               $params = $module->extractRequestParams();
+               $this->messageFormat = isset( $params['messageformat'] ) ? $params['messageformat'] : 'wikitext';
+       }
+
+       /**
+        * Static version of the constructor, for chaining
+        * @param ApiBase $module API module, for context and parameters
+        * @return ApiAuthManagerHelper
+        */
+       public static function newForModule( ApiBase $module ) {
+               return new self( $module );
+       }
+
+       /**
+        * Format a message for output
+        * @param array &$res Result array
+        * @param string $key Result key
+        * @param Message $message
+        */
+       private function formatMessage( array &$res, $key, Message $message ) {
+               switch ( $this->messageFormat ) {
+                       case 'none':
+                               break;
+
+                       case 'wikitext':
+                               $res[$key] = $message->setContext( $this->module )->text();
+                               break;
+
+                       case 'html':
+                               $res[$key] = $message->setContext( $this->module )->parseAsBlock();
+                               $res[$key] = Parser::stripOuterParagraph( $res[$key] );
+                               break;
+
+                       case 'raw':
+                               $res[$key] = [
+                                       'key' => $message->getKey(),
+                                       'params' => $message->getParams(),
+                               ];
+                               break;
+               }
+       }
+
+       /**
+        * Call $manager->securitySensitiveOperationStatus()
+        * @param string $operation Operation being checked.
+        * @throws UsageException
+        */
+       public function securitySensitiveOperation( $operation ) {
+               $status = AuthManager::singleton()->securitySensitiveOperationStatus( $operation );
+               switch ( $status ) {
+                       case AuthManager::SEC_OK:
+                               return;
+
+                       case AuthManager::SEC_REAUTH:
+                               $this->module->dieUsage(
+                                       'You have not authenticated recently in this session, please reauthenticate.', 'reauthenticate'
+                               );
+
+                       case AuthManager::SEC_FAIL:
+                               $this->module->dieUsage(
+                                       'This action is not available as your identify cannot be verified.', 'cannotreauthenticate'
+                               );
+
+                       default:
+                               throw new UnexpectedValueException( "Unknown status \"$status\"" );
+               }
+       }
+
+       /**
+        * Filter out authentication requests by class name
+        * @param AuthenticationRequest[] $reqs Requests to filter
+        * @param string[] $blacklist Class names to remove
+        * @return AuthenticationRequest[]
+        */
+       public static function blacklistAuthenticationRequests( array $reqs, array $blacklist ) {
+               if ( $blacklist ) {
+                       $blacklist = array_flip( $blacklist );
+                       $reqs = array_filter( $reqs, function ( $req ) use ( $blacklist ) {
+                               return !isset( $blacklist[get_class( $req )] );
+                       } );
+               }
+               return $reqs;
+       }
+
+       /**
+        * Fetch and load the AuthenticationRequests for an action
+        * @param string $action One of the AuthManager::ACTION_* constants
+        * @return AuthenticationRequest[]
+        */
+       public function loadAuthenticationRequests( $action ) {
+               $params = $this->module->extractRequestParams();
+
+               $manager = AuthManager::singleton();
+               $reqs = $manager->getAuthenticationRequests( $action, $this->module->getUser() );
+
+               // Filter requests, if requested to do so
+               $wantedRequests = null;
+               if ( isset( $params['requests'] ) ) {
+                       $wantedRequests = array_flip( $params['requests'] );
+               } elseif ( isset( $params['request'] ) ) {
+                       $wantedRequests = [ $params['request'] => true ];
+               }
+               if ( $wantedRequests !== null ) {
+                       $reqs = array_filter( $reqs, function ( $req ) use ( $wantedRequests ) {
+                               return isset( $wantedRequests[$req->getUniqueId()] );
+                       } );
+               }
+
+               // Collect the fields for all the requests
+               $fields = [];
+               foreach ( $reqs as $req ) {
+                       $fields += (array)$req->getFieldInfo();
+               }
+
+               // Extract the request data for the fields and mark those request
+               // parameters as used
+               $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields );
+               $this->module->getMain()->markParamsUsed( array_keys( $data ) );
+
+               return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
+       }
+
+       /**
+        * Format an AuthenticationResponse for return
+        * @param AuthenticationResponse $res
+        * @return array
+        */
+       public function formatAuthenticationResponse( AuthenticationResponse $res ) {
+               $params = $this->module->extractRequestParams();
+
+               $ret = [
+                       'status' => $res->status,
+               ];
+
+               if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) {
+                       $ret['username'] = $res->username;
+               }
+
+               if ( $res->status === AuthenticationResponse::REDIRECT ) {
+                       $ret['redirecttarget'] = $res->redirectTarget;
+                       if ( $res->redirectApiData !== null ) {
+                               $ret['redirectdata'] = $res->redirectApiData;
+                       }
+               }
+
+               if ( $res->status === AuthenticationResponse::REDIRECT ||
+                       $res->status === AuthenticationResponse::UI ||
+                       $res->status === AuthenticationResponse::RESTART
+               ) {
+                       $ret += $this->formatRequests( $res->neededRequests );
+               }
+
+               if ( $res->status === AuthenticationResponse::FAIL ||
+                       $res->status === AuthenticationResponse::UI ||
+                       $res->status === AuthenticationResponse::RESTART
+               ) {
+                       $this->formatMessage( $ret, 'message', $res->message );
+               }
+
+               if ( $res->status === AuthenticationResponse::FAIL ||
+                       $res->status === AuthenticationResponse::RESTART
+               ) {
+                       $this->module->getRequest()->getSession()->set(
+                               'ApiAuthManagerHelper::createRequest',
+                               $res->createRequest
+                       );
+                       $ret['canpreservestate'] = $res->createRequest !== null;
+               } else {
+                       $this->module->getRequest()->getSession()->remove( 'ApiAuthManagerHelper::createRequest' );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Fetch the preserved CreateFromLoginAuthenticationRequest, if any
+        * @return CreateFromLoginAuthenticationRequest|null
+        */
+       public function getPreservedRequest() {
+               $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' );
+               return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null;
+       }
+
+       /**
+        * Format an array of AuthenticationRequests for return
+        * @param AuthenticationRequest[] $reqs
+        * @return array Will have a 'requests' key, and also 'fields' if $module's
+        *  params include 'mergerequestfields'.
+        */
+       public function formatRequests( array $reqs ) {
+               $params = $this->module->extractRequestParams();
+               $mergeFields = !empty( $params['mergerequestfields'] );
+
+               $ret = [ 'requests' => [] ];
+               foreach ( $reqs as $req ) {
+                       $describe = $req->describeCredentials();
+                       $reqInfo = [
+                               'id' => $req->getUniqueId(),
+                               'metadata' => $req->getMetadata(),
+                       ];
+                       switch ( $req->required ) {
+                               case AuthenticationRequest::OPTIONAL:
+                                       $reqInfo['required'] = 'optional';
+                                       break;
+                               case AuthenticationRequest::REQUIRED:
+                                       $reqInfo['required'] = 'required';
+                                       break;
+                               case AuthenticationRequest::PRIMARY_REQUIRED:
+                                       $reqInfo['required'] = 'primary-required';
+                                       break;
+                       }
+                       $this->formatMessage( $reqInfo, 'provider', $describe['provider'] );
+                       $this->formatMessage( $reqInfo, 'account', $describe['account'] );
+                       if ( !$mergeFields ) {
+                               $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() );
+                       }
+                       $ret['requests'][] = $reqInfo;
+               }
+
+               if ( $mergeFields ) {
+                       $fields = AuthenticationRequest::mergeFieldInfo( $reqs );
+                       $ret['fields'] = $this->formatFields( $fields );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Clean up a field array for output
+        * @param ApiBase $module For context and parameters 'mergerequestfields'
+        *  and 'messageformat'
+        * @param array $fields
+        * @return array
+        */
+       private function formatFields( array $fields ) {
+               static $copy = [
+                       'type' => true,
+                       'image' => true,
+                       'value' => true,
+               ];
+
+               $module = $this->module;
+               $retFields = [];
+
+               foreach ( $fields as $name => $field ) {
+                       $ret = array_intersect_key( $field, $copy );
+
+                       if ( isset( $field['options'] ) ) {
+                               $ret['options'] = array_map( function ( $msg ) use ( $module ) {
+                                       return $msg->setContext( $module )->plain();
+                               }, $field['options'] );
+                               ApiResult::setArrayType( $ret['options'], 'assoc' );
+                       }
+                       $this->formatMessage( $ret, 'label', $field['label'] );
+                       $this->formatMessage( $ret, 'help', $field['help'] );
+                       $ret['optional'] = !empty( $field['optional'] );
+
+                       $retFields[$name] = $ret;
+               }
+
+               ApiResult::setArrayType( $retFields, 'assoc' );
+
+               return $retFields;
+       }
+
+       /**
+        * Fetch the standard parameters this helper recognizes
+        * @param string $action AuthManager action
+        * @param string $param... Parameters to use
+        * @return array
+        */
+       public static function getStandardParams( $action, $param /* ... */ ) {
+               $params = [
+                       'requests' => [
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_ISMULTI => true,
+                               ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ],
+                       ],
+                       'request' => [
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => true,
+                               ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ],
+                       ],
+                       'messageformat' => [
+                               ApiBase::PARAM_DFLT => 'wikitext',
+                               ApiBase::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
+                               ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat',
+                       ],
+                       'mergerequestfields' => [
+                               ApiBase::PARAM_DFLT => false,
+                               ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields',
+                       ],
+                       'preservestate' => [
+                               ApiBase::PARAM_DFLT => false,
+                               ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate',
+                       ],
+                       'returnurl' => [
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl',
+                       ],
+                       'continue' => [
+                               ApiBase::PARAM_DFLT => false,
+                               ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue',
+                       ],
+               ];
+
+               $ret = [];
+               $wantedParams = func_get_args();
+               array_shift( $wantedParams );
+               foreach ( $wantedParams as $name ) {
+                       if ( isset( $params[$name] ) ) {
+                               $ret[$name] = $params[$name];
+                       }
+               }
+               return $ret;
+       }
+}
index da64c03..7d0ae32 100644 (file)
@@ -1522,20 +1522,20 @@ abstract class ApiBase extends ContextSource {
                        throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
                }
 
-               $errors = $status->getErrorsArray();
+               $errors = $status->getErrorsByType( 'error' );
                if ( !$errors ) {
                        // No errors? Assume the warnings should be treated as errors
-                       $errors = $status->getWarningsArray();
+                       $errors = $status->getErrorsByType( 'warning' );
                }
                if ( !$errors ) {
                        // Still no errors? Punt
-                       $errors = [ [ 'unknownerror-nocode' ] ];
+                       $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
                }
 
                // Cannot use dieUsageMsg() because extensions might return custom
                // error messages.
-               if ( $errors[0] instanceof Message ) {
-                       $msg = $errors[0];
+               if ( $errors[0]['message'] instanceof Message ) {
+                       $msg = $errors[0]['message'];
                        if ( $msg instanceof IApiMessage ) {
                                $extraData = $msg->getApiData();
                                $code = $msg->getApiCode();
@@ -1543,8 +1543,8 @@ abstract class ApiBase extends ContextSource {
                                $code = $msg->getKey();
                        }
                } else {
-                       $code = array_shift( $errors[0] );
-                       $msg = wfMessage( $code, $errors[0] );
+                       $code = $errors[0]['message'];
+                       $msg = wfMessage( $code, $errors[0]['params'] );
                }
                if ( isset( ApiBase::$messageMap[$code] ) ) {
                        // Translate message to code, for backwards compatibility
diff --git a/includes/api/ApiChangeAuthenticationData.php b/includes/api/ApiChangeAuthenticationData.php
new file mode 100644 (file)
index 0000000..54547ef
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * Change authentication data with AuthManager
+ *
+ * @ingroup API
+ */
+class ApiChangeAuthenticationData extends ApiBase {
+
+       public function __construct( ApiMain $main, $action ) {
+               parent::__construct( $main, $action, 'changeauth' );
+       }
+
+       public function execute() {
+               if ( !$this->getUser()->isLoggedIn() ) {
+                       $this->dieUsage( 'Must be logged in to change authentication data', 'notloggedin' );
+               }
+
+               $helper = new ApiAuthManagerHelper( $this );
+               $manager = AuthManager::singleton();
+
+               // Check security-sensitive operation status
+               $helper->securitySensitiveOperation( 'ChangeCredentials' );
+
+               // Fetch the request
+               $reqs = ApiAuthManagerHelper::blacklistAuthenticationRequests(
+                       $helper->loadAuthenticationRequests( AuthManager::ACTION_CHANGE ),
+                       $this->getConfig()->get( 'ChangeCredentialsBlacklist' )
+               );
+               if ( count( $reqs ) !== 1 ) {
+                       $this->dieUsage( 'Failed to create change request', 'badrequest' );
+               }
+               $req = reset( $reqs );
+
+               // Make the change
+               $status = $manager->allowsAuthenticationDataChange( $req, true );
+               if ( !$status->isGood() ) {
+                       $this->dieStatus( $status );
+               }
+               $manager->changeAuthenticationData( $req );
+
+               $this->getResult()->addValue( null, 'changeauthenticationdata', [ 'status' => 'success' ] );
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       public function getAllowedParams() {
+               return ApiAuthManagerHelper::getStandardParams( AuthManager::ACTION_CHANGE,
+                       'request'
+               );
+       }
+
+       public function dynamicParameterDocumentation() {
+               return [ 'api-help-authmanagerhelper-additional-params', AuthManager::ACTION_CHANGE ];
+       }
+
+       protected function getExamplesMessages() {
+               return [
+                       'action=changeauthenticationdata' .
+                               '&changeauthrequest=MediaWiki%5CAuth%5CPasswordAuthenticationRequest' .
+                               '&password=ExamplePassword&retype=ExamplePassword&changeauthtoken=123ABC'
+                               => 'apihelp-changeauthenticationdata-example-password',
+               ];
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Manage_authentication_data';
+       }
+}
diff --git a/includes/api/ApiClientLogin.php b/includes/api/ApiClientLogin.php
new file mode 100644 (file)
index 0000000..711234a
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+
+/**
+ * Log in to the wiki with AuthManager
+ *
+ * @ingroup API
+ */
+class ApiClientLogin extends ApiBase {
+
+       public function __construct( ApiMain $main, $action ) {
+               parent::__construct( $main, $action, 'login' );
+       }
+
+       public function getFinalDescription() {
+               // A bit of a hack to append 'api-help-authmanager-general-usage'
+               $msgs = parent::getFinalDescription();
+               $msgs[] = ApiBase::makeMessage( 'api-help-authmanager-general-usage', $this->getContext(), [
+                       $this->getModulePrefix(),
+                       $this->getModuleName(),
+                       $this->getModulePath(),
+                       AuthManager::ACTION_LOGIN,
+                       self::needsToken(),
+               ] );
+               return $msgs;
+       }
+
+       public function execute() {
+               $params = $this->extractRequestParams();
+
+               $this->requireAtLeastOneParameter( $params, 'continue', 'returnurl' );
+
+               if ( $params['returnurl'] !== null ) {
+                       $bits = wfParseUrl( $params['returnurl'] );
+                       if ( !$bits || $bits['scheme'] === '' ) {
+                               $encParamName = $this->encodeParamName( 'returnurl' );
+                               $this->dieUsage(
+                                       "Invalid value '{$params['returnurl']}' for url parameter $encParamName",
+                                       "badurl_{$encParamName}"
+                               );
+                       }
+               }
+
+               $helper = new ApiAuthManagerHelper( $this );
+               $manager = AuthManager::singleton();
+
+               // Make sure it's possible to log in
+               if ( !$manager->canAuthenticateNow() ) {
+                       $this->getResult()->addValue( null, 'clientlogin', $helper->formatAuthenticationResponse(
+                               AuthenticationResponse::newFail( $this->msg( 'userlogin-cannot-' . AuthManager::ACTION_LOGIN ) )
+                       ) );
+                       return;
+               }
+
+               // Perform the login step
+               if ( $params['continue'] ) {
+                       $reqs = $helper->loadAuthenticationRequests( AuthManager::ACTION_LOGIN_CONTINUE );
+                       $res = $manager->continueAuthentication( $reqs );
+               } else {
+                       $reqs = $helper->loadAuthenticationRequests( AuthManager::ACTION_LOGIN );
+                       if ( $params['preservestate'] ) {
+                               $req = $helper->getPreservedRequest();
+                               if ( $req ) {
+                                       $reqs[] = $req;
+                               }
+                       }
+                       $res = $manager->beginAuthentication( $reqs, $params['returnurl'] );
+               }
+
+               $this->getResult()->addValue( null, 'clientlogin',
+                       $helper->formatAuthenticationResponse( $res ) );
+       }
+
+       public function isReadMode() {
+               return false;
+       }
+
+       public function needsToken() {
+               return 'login';
+       }
+
+       public function getAllowedParams() {
+               return ApiAuthManagerHelper::getStandardParams( AuthManager::ACTION_LOGIN,
+                       'requests', 'messageformat', 'mergerequestfields', 'preservestate', 'returnurl', 'continue'
+               );
+       }
+
+       public function dynamicParameterDocumentation() {
+               return [ 'api-help-authmanagerhelper-additional-params', AuthManager::ACTION_LOGIN ];
+       }
+
+       protected function getExamplesMessages() {
+               return [
+                       'action=clientlogin&username=Example&password=ExamplePassword&'
+                               . 'loginreturnurl=http://example.org/&logintoken=123ABC'
+                               => 'apihelp-clientlogin-example-login',
+                       'action=clientlogin&logincontinue=1&OATHToken=987654&logintoken=123ABC'
+                               => 'apihelp-clientlogin-example-login2',
+               ];
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Login';
+       }
+}
index 5552a85..6a48610 100644 (file)
@@ -27,6 +27,7 @@ use MediaWiki\Logger\LoggerFactory;
  * Unit to authenticate account registration attempts to the current wiki.
  *
  * @ingroup API
+ * @deprecated since 1.27, only used when $wgDisableAuthManager is true
  */
 class ApiCreateAccount extends ApiBase {
        public function execute() {
index 36cbbd9..41de925 100644 (file)
@@ -42,7 +42,7 @@ class ApiFormatJson extends ApiFormatBase {
                        # outside the control of the end user.
                        # (and do it here because ApiMain::reportUnusedParams() gets called
                        # before our ::execute())
-                       $this->getMain()->getCheck( '_' );
+                       $this->getMain()->markParamsUsed( '_' );
                }
        }
 
index f7539ce..0f0fbdc 100644 (file)
@@ -255,28 +255,43 @@ class ApiHelp extends ApiBase {
                                }
 
                                if ( $module->isMain() ) {
-                                       $header = $context->msg( 'api-help-main-header' )->parse();
+                                       $headerContent = $context->msg( 'api-help-main-header' )->parse();
+                                       $headerAttr = [
+                                               'class' => 'apihelp-header',
+                                       ];
                                } else {
                                        $name = $module->getModuleName();
-                                       $header = $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
+                                       $headerContent = $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
                                                "=$name";
                                        if ( $module->getModulePrefix() !== '' ) {
-                                               $header .= ' ' .
+                                               $headerContent .= ' ' .
                                                        $context->msg( 'parentheses', $module->getModulePrefix() )->parse();
                                        }
+                                       // Module names are always in English and not localized,
+                                       // so English language and direction must be set explicitly,
+                                       // otherwise parentheses will get broken in RTL wikis
+                                       $headerAttr = [
+                                               'class' => 'apihelp-header apihelp-module-name',
+                                               'dir' => 'ltr',
+                                               'lang' => 'en',
+                                       ];
                                }
+
+                               $headerAttr['id'] = $anchor;
+
                                $haveModules[$anchor] = [
                                        'toclevel' => count( $tocnumber ),
                                        'level' => $level,
                                        'anchor' => $anchor,
-                                       'line' => $header,
+                                       'line' => $headerContent,
                                        'number' => implode( '.', $tocnumber ),
                                        'index' => false,
                                ];
                                if ( empty( $options['noheader'] ) ) {
-                                       $help['header'] .= Html::element( 'h' . min( 6, $level ),
-                                               [ 'id' => $anchor, 'class' => 'apihelp-header' ],
-                                               $header
+                                       $help['header'] .= Html::element(
+                                               'h' . min( 6, $level ),
+                                               $headerAttr,
+                                               $headerContent
                                        );
                                }
                        } else {
diff --git a/includes/api/ApiLinkAccount.php b/includes/api/ApiLinkAccount.php
new file mode 100644 (file)
index 0000000..14347d8
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+
+/**
+ * Link an account with AuthManager
+ *
+ * @ingroup API
+ */
+class ApiLinkAccount extends ApiBase {
+
+       public function __construct( ApiMain $main, $action ) {
+               parent::__construct( $main, $action, 'link' );
+       }
+
+       public function getFinalDescription() {
+               // A bit of a hack to append 'api-help-authmanager-general-usage'
+               $msgs = parent::getFinalDescription();
+               $msgs[] = ApiBase::makeMessage( 'api-help-authmanager-general-usage', $this->getContext(), [
+                       $this->getModulePrefix(),
+                       $this->getModuleName(),
+                       $this->getModulePath(),
+                       AuthManager::ACTION_LINK,
+                       self::needsToken(),
+               ] );
+               return $msgs;
+       }
+
+       public function execute() {
+               if ( !$this->getUser()->isLoggedIn() ) {
+                       $this->dieUsage( 'Must be logged in to link accounts', 'notloggedin' );
+               }
+
+               $params = $this->extractRequestParams();
+
+               $this->requireAtLeastOneParameter( $params, 'continue', 'returnurl' );
+
+               if ( $params['returnurl'] !== null ) {
+                       $bits = wfParseUrl( $params['returnurl'] );
+                       if ( !$bits || $bits['scheme'] === '' ) {
+                               $encParamName = $this->encodeParamName( 'returnurl' );
+                               $this->dieUsage(
+                                       "Invalid value '{$params['returnurl']}' for url parameter $encParamName",
+                                       "badurl_{$encParamName}"
+                               );
+                       }
+               }
+
+               $helper = new ApiAuthManagerHelper( $this );
+               $manager = AuthManager::singleton();
+
+               // Check security-sensitive operation status
+               $helper->securitySensitiveOperation( 'LinkAccounts' );
+
+               // Make sure it's possible to link accounts
+               if ( !$manager->canLinkAccounts() ) {
+                       $this->getResult()->addValue( null, 'linkaccount', $helper->formatAuthenticationResponse(
+                               AuthenticationResponse::newFail( $this->msg( 'userlogin-cannot-' . AuthManager::ACTION_LINK ) )
+                       ) );
+                       return;
+               }
+
+               // Perform the link step
+               if ( $params['continue'] ) {
+                       $reqs = $helper->loadAuthenticationRequests( AuthManager::ACTION_LINK_CONTINUE );
+                       $res = $manager->continueAccountLink( $reqs );
+               } else {
+                       $reqs = $helper->loadAuthenticationRequests( AuthManager::ACTION_LINK );
+                       $res = $manager->beginAccountLink( $this->getUser(), $reqs, $params['returnurl'] );
+               }
+
+               $this->getResult()->addValue( null, 'linkaccount',
+                       $helper->formatAuthenticationResponse( $res ) );
+       }
+
+       public function isReadMode() {
+               return false;
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       public function getAllowedParams() {
+               return ApiAuthManagerHelper::getStandardParams( AuthManager::ACTION_LINK,
+                       'requests', 'messageformat', 'mergerequestfields', 'returnurl', 'continue'
+               );
+       }
+
+       public function dynamicParameterDocumentation() {
+               return [ 'api-help-authmanagerhelper-additional-params', AuthManager::ACTION_LINK ];
+       }
+
+       protected function getExamplesMessages() {
+               return [
+                       'action=linkaccount&provider=Example&linkreturnurl=http://example.org/&linktoken=123ABC'
+                               => 'apihelp-linkaccount-example-link',
+               ];
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Linkaccount';
+       }
+}
index 3891415..3572229 100644 (file)
@@ -25,6 +25,9 @@
  * @file
  */
 
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
 use MediaWiki\Logger\LoggerFactory;
 
 /**
@@ -38,6 +41,16 @@ class ApiLogin extends ApiBase {
                parent::__construct( $main, $action, 'lg' );
        }
 
+       protected function getDescriptionMessage() {
+               if ( $this->getConfig()->get( 'DisableAuthManager' ) ) {
+                       return 'apihelp-login-description-nonauthmanager';
+               } elseif ( $this->getConfig()->get( 'EnableBotPasswords' ) ) {
+                       return 'apihelp-login-description';
+               } else {
+                       return 'apihelp-login-description-nobotpasswords';
+               }
+       }
+
        /**
         * Executes the log-in attempt using the parameters passed. If
         * the log-in succeeds, it attaches a cookie to the session
@@ -83,11 +96,11 @@ class ApiLogin extends ApiBase {
                $loginType = 'N/A';
 
                // Check login token
-               $token = LoginForm::getLoginToken();
+               $token = $session->getToken( '', 'login' );
                if ( $token->wasNew() || !$params['token'] ) {
-                       $authRes = LoginForm::NEED_TOKEN;
+                       $authRes = 'NeedToken';
                } elseif ( !$token->match( $params['token'] ) ) {
-                       $authRes = LoginForm::WRONG_TOKEN;
+                       $authRes = 'WrongToken';
                }
 
                // Try bot passwords
@@ -99,48 +112,104 @@ class ApiLogin extends ApiBase {
                        );
                        if ( $status->isOK() ) {
                                $session = $status->getValue();
-                               $authRes = LoginForm::SUCCESS;
+                               $authRes = 'Success';
                                $loginType = 'BotPassword';
                        } else {
+                               $authRes = 'Failed';
+                               $message = $status->getMessage();
                                LoggerFactory::getInstance( 'authmanager' )->info(
                                        'BotPassword login failed: ' . $status->getWikiText( false, false, 'en' )
                                );
                        }
                }
 
-               // Normal login
                if ( $authRes === false ) {
-                       $context->setRequest( new DerivativeRequest(
-                               $this->getContext()->getRequest(),
-                               [
-                                       'wpName' => $params['name'],
-                                       'wpPassword' => $params['password'],
-                                       'wpDomain' => $params['domain'],
-                                       'wpLoginToken' => $params['token'],
-                                       'wpRemember' => ''
-                               ]
-                       ) );
-                       $loginForm = new LoginForm();
-                       $loginForm->setContext( $context );
-                       $authRes = $loginForm->authenticateUserData();
-                       $loginType = 'LoginForm';
+                       if ( $this->getConfig()->get( 'DisableAuthManager' ) ) {
+                               // Non-AuthManager login
+                               $context->setRequest( new DerivativeRequest(
+                                       $this->getContext()->getRequest(),
+                                       [
+                                               'wpName' => $params['name'],
+                                               'wpPassword' => $params['password'],
+                                               'wpDomain' => $params['domain'],
+                                               'wpLoginToken' => $params['token'],
+                                               'wpRemember' => ''
+                                       ]
+                               ) );
+                               $loginForm = new LoginForm();
+                               $loginForm->setContext( $context );
+                               $authRes = $loginForm->authenticateUserData();
+                               $loginType = 'LoginForm';
+
+                               switch ( $authRes ) {
+                                       case LoginForm::SUCCESS:
+                                               $authRes = 'Success';
+                                               break;
+                                       case LoginForm::NEED_TOKEN:
+                                               $authRes = 'NeedToken';
+                                               break;
+                               }
+                       } else {
+                               // Simplified AuthManager login, for backwards compatibility
+                               $manager = AuthManager::singleton();
+                               $reqs = AuthenticationRequest::loadRequestsFromSubmission(
+                                       $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN, $this->getUser() ),
+                                       [
+                                               'username' => $params['name'],
+                                               'password' => $params['password'],
+                                               'domain' => $params['domain'],
+                                               'rememberMe' => true,
+                                       ]
+                               );
+                               $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
+                               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.';
+                                               } else {
+                                                       $warn = 'Login via action=login is deprecated and may stop working without warning.';
+                                                       $warn .= ' To safely log in, see action=clientlogin.';
+                                               }
+                                               $this->setWarning( $warn );
+                                               $authRes = 'Success';
+                                               $loginType = 'AuthManager';
+                                               break;
+
+                                       case AuthenticationResponse::FAIL:
+                                               // Hope it's not a PreAuthenticationProvider that failed...
+                                               $authRes = 'Failed';
+                                               $message = $res->message;
+                                               \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
+                                                       ->info( __METHOD__ . ': Authentication failed: ' . $message->plain() );
+                                               break;
+
+                                       default:
+                                               $authRes = 'Aborted';
+                                               break;
+                               }
+                       }
                }
 
+               $result['result'] = $authRes;
                switch ( $authRes ) {
-                       case LoginForm::SUCCESS:
-                               $user = $context->getUser();
-                               $this->getContext()->setUser( $user );
-                               $user->setCookies( $this->getRequest(), null, true );
+                       case 'Success':
+                               if ( $this->getConfig()->get( 'DisableAuthManager' ) ) {
+                                       $user = $context->getUser();
+                                       $this->getContext()->setUser( $user );
+                                       $user->setCookies( $this->getRequest(), null, true );
+                               } else {
+                                       $user = $session->getUser();
+                               }
 
                                ApiQueryInfo::resetTokenCache();
 
-                               // Run hooks.
-                               // @todo FIXME: Split back and frontend from this hook.
-                               // @todo FIXME: This hook should be placed in the backend
+                               // Deprecated hook
                                $injected_html = '';
                                Hooks::run( 'UserLoginComplete', [ &$user, &$injected_html ] );
 
-                               $result['result'] = 'Success';
                                $result['lguserid'] = intval( $user->getId() );
                                $result['lgusername'] = $user->getName();
 
@@ -148,15 +217,14 @@ class ApiLogin extends ApiBase {
                                // point (1.28 at the earliest, and see T121527). They were ok
                                // when the core cookie-based login was the only thing, but
                                // CentralAuth broke that a while back and
-                               // SessionManager/AuthManager are *really* going to break it.
+                               // SessionManager/AuthManager *really* break it.
                                $result['lgtoken'] = $user->getToken();
                                $result['cookieprefix'] = $this->getConfig()->get( 'CookiePrefix' );
                                $result['sessionid'] = $session->getId();
                                break;
 
-                       case LoginForm::NEED_TOKEN:
-                               $result['result'] = 'NeedToken';
-                               $result['token'] = LoginForm::getLoginToken()->toString();
+                       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' );
@@ -166,6 +234,25 @@ class ApiLogin extends ApiBase {
                                $result['sessionid'] = $session->getId();
                                break;
 
+                       case 'WrongToken':
+                               break;
+
+                       case 'Failed':
+                               $result['reason'] = $message->useDatabase( 'false' )->inLanguage( 'en' )->text();
+                               break;
+
+                       case 'Aborted':
+                               $result['reason'] = 'Authentication requires user interaction, ' .
+                                  'which is not supported by action=login.';
+                               if ( $this->getConfig()->get( 'EnableBotPasswords' ) ) {
+                                       $result['reason'] .= ' To be able to login with action=login, see [[Special:BotPasswords]].';
+                                       $result['reason'] .= ' To continue using main-account login, see action=clientlogin.';
+                               } else {
+                                       $result['reason'] .= ' To log in, see action=clientlogin.';
+                               }
+                               break;
+
+                       // Results from LoginForm for when $wgDisableAuthManager is true
                        case LoginForm::WRONG_TOKEN:
                                $result['result'] = 'WrongToken';
                                break;
@@ -230,14 +317,22 @@ class ApiLogin extends ApiBase {
 
                $this->getResult()->addValue( null, 'login', $result );
 
+               if ( $loginType === 'LoginForm' && isset( LoginForm::$statusCodes[$authRes] ) ) {
+                       $authRes = LoginForm::$statusCodes[$authRes];
+               }
                LoggerFactory::getInstance( 'authmanager' )->info( 'Login attempt', [
                        'event' => 'login',
-                       'successful' => $authRes === LoginForm::SUCCESS,
+                       'successful' => $authRes === 'Success',
                        'loginType' => $loginType,
-                       'status' => LoginForm::$statusCodes[$authRes],
+                       'status' => $authRes,
                ] );
        }
 
+       public function isDeprecated() {
+               return !$this->getConfig()->get( 'DisableAuthManager' ) &&
+                       !$this->getConfig()->get( 'EnableBotPasswords' );
+       }
+
        public function mustBePosted() {
                return true;
        }
index 07642c4..9c54eac 100644 (file)
@@ -49,8 +49,14 @@ class ApiMain extends ApiBase {
         */
        private static $Modules = [
                'login' => 'ApiLogin',
+               'clientlogin' => 'ApiClientLogin',
                'logout' => 'ApiLogout',
-               'createaccount' => 'ApiCreateAccount',
+               'createaccount' => 'ApiAMCreateAccount',
+               'linkaccount' => 'ApiLinkAccount',
+               'unlinkaccount' => 'ApiRemoveAuthenticationData',
+               'changeauthenticationdata' => 'ApiChangeAuthenticationData',
+               'removeauthenticationdata' => 'ApiRemoveAuthenticationData',
+               'resetpassword' => 'ApiResetPassword',
                'query' => 'ApiQuery',
                'expandtemplates' => 'ApiExpandTemplates',
                'parse' => 'ApiParse',
@@ -134,7 +140,9 @@ class ApiMain extends ApiBase {
        private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager;
        private $mAction;
        private $mEnableWrite;
-       private $mInternalMode, $mSquidMaxage, $mModule;
+       private $mInternalMode, $mSquidMaxage;
+       /** @var ApiBase */
+       private $mModule;
 
        private $mCacheMode = 'private';
        private $mCacheControl = [];
@@ -397,13 +405,7 @@ class ApiMain extends ApiBase {
                if ( $this->mInternalMode ) {
                        $this->executeAction();
                } else {
-                       $start = microtime( true );
                        $this->executeActionWithErrorHandling();
-                       if ( $this->isWriteMode() && $this->getRequest()->wasPosted() ) {
-                               $timeMs = 1000 * max( 0, microtime( true ) - $start );
-                               $this->getStats()->timing(
-                                       'api.' . $this->getModuleName() . '.executeTiming', $timeMs );
-                       }
                }
        }
 
@@ -433,8 +435,12 @@ class ApiMain extends ApiBase {
                $isError = false;
                try {
                        $this->executeAction();
-                       $this->logRequest( microtime( true ) - $t );
-
+                       $runTime = microtime( true ) - $t;
+                       $this->logRequest( $runTime );
+                       if ( $this->mModule->isWriteMode() && $this->getRequest()->wasPosted() ) {
+                               $this->getStats()->timing(
+                                       'api.' . $this->getModuleName() . '.executeTiming', 1000 * $runTime );
+                       }
                } catch ( Exception $e ) {
                        $this->handleException( $e );
                        $this->logRequest( microtime( true ) - $t, $e );
@@ -1135,7 +1141,7 @@ class ApiMain extends ApiBase {
                                                                        TS_MW, time() - $this->getConfig()->get( 'SquidMaxage' )
                                                                );
                                                        }
-                                                       Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes ] );
+                                                       Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this->getOutput() ] );
                                                        $lastMod = max( $modifiedTimes );
                                                        $return304 = wfTimestamp( TS_MW, $lastMod ) <= $ts->getTimestamp( TS_MW );
                                                }
@@ -1355,6 +1361,7 @@ class ApiMain extends ApiBase {
                                $trxProfiler->setExpectations( $limits['POST'], __METHOD__ );
                        } else {
                                $trxProfiler->setExpectations( $limits['POST-nonwrite'], __METHOD__ );
+                               $this->getRequest()->markAsSafeRequest();
                        }
                } else {
                        $trxProfiler->setExpectations( $limits['GET'], __METHOD__ );
@@ -1437,6 +1444,14 @@ class ApiMain extends ApiBase {
                return array_keys( $this->mParamsUsed );
        }
 
+       /**
+        * Mark parameters as used
+        * @param string|string[] $params
+        */
+       public function markParamsUsed( $params ) {
+               $this->mParamsUsed += array_fill_keys( (array)$params, true );
+       }
+
        /**
         * Get a request value, and register the fact that it was used, for logging.
         * @param string $name
@@ -1630,9 +1645,14 @@ class ApiMain extends ApiBase {
                        $tocnumber = &$options['tocnumber'];
 
                        $header = $this->msg( 'api-help-datatypes-header' )->parse();
+
+                       // Add an additional span with sanitized ID
+                       if ( !$this->getConfig()->get( 'ExperimentalHtmlIds' ) ) {
+                               $header = Html::element( 'span', [ 'id' => Sanitizer::escapeId( 'main/datatypes' ) ] ) .
+                                       $header;
+                       }
                        $help['datatypes'] .= Html::rawElement( 'h' . min( 6, $level ),
                                [ 'id' => 'main/datatypes', 'class' => 'apihelp-header' ],
-                               Html::element( 'span', [ 'id' => Sanitizer::escapeId( 'main/datatypes' ) ] ) .
                                $header
                        );
                        $help['datatypes'] .= $this->msg( 'api-help-datatypes' )->parseAsBlock();
@@ -1648,10 +1668,14 @@ class ApiMain extends ApiBase {
                                ];
                        }
 
+                       // Add an additional span with sanitized ID
+                       if ( !$this->getConfig()->get( 'ExperimentalHtmlIds' ) ) {
+                               $header = Html::element( 'span', [ 'id' => Sanitizer::escapeId( 'main/credits' ) ] ) .
+                                       $header;
+                       }
                        $header = $this->msg( 'api-credits-header' )->parse();
                        $help['credits'] .= Html::rawElement( 'h' . min( 6, $level ),
                                [ 'id' => 'main/credits', 'class' => 'apihelp-header' ],
-                               Html::element( 'span', [ 'id' => Sanitizer::escapeId( 'main/credits' ) ] ) .
                                $header
                        );
                        $help['credits'] .= $this->msg( 'api-credits' )->useDatabase( false )->parseAsBlock();
index 60fb4dc..617db22 100644 (file)
@@ -29,8 +29,14 @@ class ApiManageTags extends ApiBase {
                $params = $this->extractRequestParams();
 
                // make sure the user is allowed
-               if ( !$this->getUser()->isAllowed( 'managechangetags' ) ) {
-                       $this->dieUsage( "You don't have permission to manage change tags", 'permissiondenied' );
+               if ( $params['operation'] !== 'delete'
+                       && !$this->getUser()->isAllowed( 'managechangetags' )
+               ) {
+                       $this->dieUsage( "You don't have permission to manage change tags",
+                               'permissiondenied' );
+               } elseif ( !$this->getUser()->isAllowed( 'deletechangetags' ) ) {
+                       $this->dieUsage( "You don't have permission to delete change tags",
+                               'permissiondenied' );
                }
 
                $result = $this->getResult();
index e51d46d..8bfe447 100644 (file)
@@ -86,8 +86,7 @@ class ApiOptions extends ApiBase {
                                                // We need a dummy HTMLForm for the validate callback...
                                                $htmlForm = new HTMLForm( [], $this );
                                        }
-                                       $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key] );
-                                       $field->mParent = $htmlForm;
+                                       $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
                                        $validation = $field->validate( $value, $user->getOptions() );
                                        break;
                                case 'registered-multiselect':
index f278989..af4e536 100644 (file)
@@ -178,7 +178,7 @@ class ApiPageSet extends ApiBase {
                                // Prevent warnings from being reported on these parameters
                                $main = $this->getMain();
                                foreach ( $generator->extractRequestParams() as $paramName => $param ) {
-                                       $main->getVal( $generator->encodeParamName( $paramName ) );
+                                       $main->markParamsUsed( $generator->encodeParamName( $paramName ) );
                                }
                        }
 
index 733ea2c..3ca4c08 100644 (file)
@@ -112,6 +112,7 @@ class ApiQuery extends ApiBase {
         */
        private static $QueryMetaModules = [
                'allmessages' => 'ApiQueryAllMessages',
+               'authmanagerinfo' => 'ApiQueryAuthManagerInfo',
                'siteinfo' => 'ApiQuerySiteinfo',
                'userinfo' => 'ApiQueryUserInfo',
                'filerepoinfo' => 'ApiQueryFileRepoInfo',
diff --git a/includes/api/ApiQueryAuthManagerInfo.php b/includes/api/ApiQueryAuthManagerInfo.php
new file mode 100644 (file)
index 0000000..b591f9c
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.27
+ */
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * A query action to return meta information about AuthManager state.
+ *
+ * @ingroup API
+ */
+class ApiQueryAuthManagerInfo extends ApiQueryBase {
+
+       public function __construct( ApiQuery $query, $moduleName ) {
+               parent::__construct( $query, $moduleName, 'ami' );
+       }
+
+       public function execute() {
+               $params = $this->extractRequestParams();
+               $helper = new ApiAuthManagerHelper( $this );
+
+               $manager = AuthManager::singleton();
+               $ret = [
+                       'canauthenticatenow' => $manager->canAuthenticateNow(),
+                       'cancreateaccounts' => $manager->canCreateAccounts(),
+                       'canlinkaccounts' => $manager->canLinkAccounts(),
+                       'haspreservedstate' => $helper->getPreservedRequest() !== null,
+               ];
+
+               if ( $params['securitysensitiveoperation'] !== null ) {
+                       $ret['securitysensitiveoperationstatus'] = $manager->securitySensitiveOperationStatus(
+                               $params['securitysensitiveoperation']
+                       );
+               }
+
+               if ( $params['requestsfor'] ) {
+                       $reqs = $manager->getAuthenticationRequests( $params['requestsfor'], $this->getUser() );
+
+                       // Filter out blacklisted requests, depending on the action
+                       switch ( $params['requestsfor'] ) {
+                               case AuthManager::ACTION_CHANGE:
+                                       $reqs = ApiAuthManagerHelper::blacklistAuthenticationRequests(
+                                               $reqs, $this->getConfig()->get( 'ChangeCredentialsBlacklist' )
+                                       );
+                                       break;
+                               case AuthManager::ACTION_REMOVE:
+                                       $reqs = ApiAuthManagerHelper::blacklistAuthenticationRequests(
+                                               $reqs, $this->getConfig()->get( 'RemoveCredentialsBlacklist' )
+                                       );
+                                       break;
+                       }
+
+                       $ret += $helper->formatRequests( $reqs );
+               }
+
+               $this->getResult()->addValue( [ 'query' ], $this->getModuleName(), $ret );
+       }
+
+       public function getCacheMode( $params ) {
+               return 'public';
+       }
+
+       public function getAllowedParams() {
+               return [
+                       'securitysensitiveoperation' => null,
+                       'requestsfor' => [
+                               ApiBase::PARAM_TYPE => [
+                                       AuthManager::ACTION_LOGIN,
+                                       AuthManager::ACTION_LOGIN_CONTINUE,
+                                       AuthManager::ACTION_CREATE,
+                                       AuthManager::ACTION_CREATE_CONTINUE,
+                                       AuthManager::ACTION_LINK,
+                                       AuthManager::ACTION_LINK_CONTINUE,
+                                       AuthManager::ACTION_CHANGE,
+                                       AuthManager::ACTION_REMOVE,
+                                       AuthManager::ACTION_UNLINK,
+                               ],
+                       ],
+               ] + ApiAuthManagerHelper::getStandardParams( '', 'mergerequestfields' );
+       }
+
+       protected function getExamplesMessages() {
+               return [
+                       'action=query&meta=authmanagerinfo&amirequestsfor=' . urlencode( AuthManager::ACTION_LOGIN )
+                               => 'apihelp-query+filerepoinfo-example-login',
+                       'action=query&meta=authmanagerinfo&amirequestsfor=' . urlencode( AuthManager::ACTION_LOGIN ) .
+                               '&amimergerequestfields=1'
+                               => 'apihelp-query+filerepoinfo-example-login-merged',
+                       'action=query&meta=authmanagerinfo&amisecuritysensitiveoperation=foo'
+                               => 'apihelp-query+filerepoinfo-example-securitysensitiveoperation',
+               ];
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Authmanagerinfo';
+       }
+}
index 13e6340..d1fcfa3 100644 (file)
@@ -325,8 +325,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
         * allows us to catch certain error conditions early (such as missing
         * required parameter).
         *
-        * @param $image File
-        * @param $finalParams array List of parameters to transform image with
+        * @param File $image
+        * @param array $finalParams List of parameters to transform image with
         */
        protected function checkParameterNormalise( $image, $finalParams ) {
                $h = $image->getHandler();
index b94f567..f5c49ad 100644 (file)
@@ -23,6 +23,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Linker\LinkTarget;
 
 /**
@@ -760,7 +761,7 @@ class ApiQueryInfo extends ApiQueryBase {
                $this->watched = [];
                $this->notificationtimestamps = [];
 
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                $timestamps = $store->getNotificationTimestampsBatch( $user, $this->everything );
 
                if ( $this->fld_watched ) {
@@ -800,7 +801,7 @@ class ApiQueryInfo extends ApiQueryBase {
                        $countOptions['minimumWatchers'] = $unwatchedPageThreshold;
                }
 
-               $this->watchers = WatchedItemStore::getDefaultInstance()->countWatchersMultiple(
+               $this->watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchersMultiple(
                        $this->everything,
                        $countOptions
                );
@@ -867,8 +868,8 @@ class ApiQueryInfo extends ApiQueryBase {
                                )
                        );
                }
-
-               $this->visitingwatchers = WatchedItemStore::getDefaultInstance()->countVisitingWatchersMultiple(
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+               $this->visitingwatchers = $store->countVisitingWatchersMultiple(
                        $titlesWithThresholds,
                        !$canUnwatchedpages ? $unwatchedPageThreshold : null
                );
index 74c2214..f0fd2f4 100644 (file)
@@ -206,7 +206,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        ) {
                                if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
                                        $this->dieUsage(
-                                               'You need the patrol right to request the patrolled flag',
+                                               'You need patrol or patrolmarks permission to request the patrolled flag',
                                                'permissiondenied'
                                        );
                                }
@@ -277,7 +277,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
 
                        if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
                                $this->dieUsage(
-                                       'You need the patrol right to request the patrolled flag',
+                                       'You need patrol or patrolmarks permission to request the patrolled flag',
                                        'permissiondenied'
                                );
                        }
index f05556e..a08740a 100644 (file)
@@ -108,6 +108,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                case 'defaultoptions':
                                        $fit = $this->appendDefaultOptions( $p );
                                        break;
+                               case 'uploaddialog':
+                                       $fit = $this->appendUploadDialog( $p );
+                                       break;
                                default:
                                        ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
                        }
@@ -771,6 +774,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                return $this->getResult()->addValue( 'query', $property, $options );
        }
 
+       public function appendUploadDialog( $property ) {
+               $config = $this->getConfig()->get( 'UploadDialog' );
+               return $this->getResult()->addValue( 'query', $property, $config );
+       }
+
        private function formatParserTags( $item ) {
                return "<{$item}>";
        }
@@ -838,6 +846,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                        'variables',
                                        'protocols',
                                        'defaultoptions',
+                                       'uploaddialog',
                                ],
                                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
                        ],
index 0fc443a..d3cd0c4 100644 (file)
@@ -24,6 +24,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Query module to get information about the currently logged-in user
  *
@@ -225,7 +227,8 @@ class ApiQueryUserInfo extends ApiQueryBase {
                }
 
                if ( isset( $this->prop['unreadcount'] ) ) {
-                       $unreadNotifications = WatchedItemStore::getDefaultInstance()->countUnreadNotifications(
+                       $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+                       $unreadNotifications = $store->countUnreadNotifications(
                                $user,
                                self::WL_UNREAD_LIMIT
                        );
index ee6ec76..68ec38d 100644 (file)
@@ -49,6 +49,7 @@ class ApiQueryUsers extends ApiQueryBase {
                'emailable',
                'gender',
                'centralids',
+               'cancreate',
        ];
 
        public function __construct( ApiQuery $query, $moduleName ) {
@@ -260,6 +261,10 @@ class ApiQueryUsers extends ApiQueryBase {
                                        }
                                } else {
                                        $data[$u]['missing'] = true;
+                                       if ( isset( $this->prop['cancreate'] ) && !$this->getConfig()->get( 'DisableAuthManager' ) ) {
+                                               $data[$u]['cancreate'] = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount( $u )
+                                                       ->isGood();
+                                       }
                                }
                        } else {
                                if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
@@ -299,7 +304,7 @@ class ApiQueryUsers extends ApiQueryBase {
        }
 
        public function getAllowedParams() {
-               return [
+               $ret = [
                        'prop' => [
                                ApiBase::PARAM_ISMULTI => true,
                                ApiBase::PARAM_TYPE => [
@@ -312,6 +317,8 @@ class ApiQueryUsers extends ApiQueryBase {
                                        'emailable',
                                        'gender',
                                        'centralids',
+                                       // When adding a prop, consider whether it should be added
+                                       // to self::$publicProps
                                ],
                                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
                        ],
@@ -326,6 +333,10 @@ class ApiQueryUsers extends ApiQueryBase {
                                ApiBase::PARAM_ISMULTI => true
                        ],
                ];
+               if ( !$this->getConfig()->get( 'DisableAuthManager' ) ) {
+                       $ret['prop'][ApiBase::PARAM_TYPE][] = 'cancreate';
+               }
+               return $ret;
        }
 
        protected function getExamplesMessages() {
diff --git a/includes/api/ApiRemoveAuthenticationData.php b/includes/api/ApiRemoveAuthenticationData.php
new file mode 100644 (file)
index 0000000..30e40fb
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * Remove authentication data from AuthManager
+ *
+ * @ingroup API
+ */
+class ApiRemoveAuthenticationData extends ApiBase {
+
+       private $authAction;
+       private $operation;
+
+       public function __construct( ApiMain $main, $action ) {
+               parent::__construct( $main, $action );
+
+               $this->authAction = $action === 'unlinkaccount'
+                       ? AuthManager::ACTION_UNLINK
+                       : AuthManager::ACTION_REMOVE;
+               $this->operation = $action === 'unlinkaccount'
+                       ? 'UnlinkAccount'
+                       : 'RemoveCredentials';
+       }
+
+       public function execute() {
+               if ( !$this->getUser()->isLoggedIn() ) {
+                       $this->dieUsage( 'Must be logged in to remove authentication data', 'notloggedin' );
+               }
+
+               $params = $this->extractRequestParams();
+               $manager = AuthManager::singleton();
+
+               // Check security-sensitive operation status
+               ApiAuthManagerHelper::newForModule( $this )->securitySensitiveOperation( $this->operation );
+
+               // Fetch the request. No need to load from the request, so don't use
+               // ApiAuthManagerHelper's method.
+               $blacklist = $this->authAction === AuthManager::ACTION_REMOVE
+                       ? array_flip( $this->getConfig()->get( 'RemoveCredentialsBlacklist' ) )
+                       : [];
+               $reqs = array_filter(
+                       $manager->getAuthenticationRequests( $this->authAction, $this->getUser() ),
+                       function ( $req ) use ( $params, $blacklist ) {
+                               return $req->getUniqueId() === $params['request'] &&
+                                       !isset( $blacklist[get_class( $req )] );
+                       }
+               );
+               if ( count( $reqs ) !== 1 ) {
+                       $this->dieUsage( 'Failed to create change request', 'badrequest' );
+               }
+               $req = reset( $reqs );
+
+               // Perform the removal
+               $status = $manager->allowsAuthenticationDataChange( $req, true );
+               if ( !$status->isGood() ) {
+                       $this->dieStatus( $status );
+               }
+               $manager->changeAuthenticationData( $req );
+
+               $this->getResult()->addValue( null, $this->getModuleName(), [ 'status' => 'success' ] );
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       public function getAllowedParams() {
+               return ApiAuthManagerHelper::getStandardParams( $this->authAction,
+                       'request'
+               );
+       }
+
+       protected function getExamplesMessages() {
+               $path = $this->getModulePath();
+               $action = $this->getModuleName();
+               return [
+                       "action={$action}&request=FooAuthenticationRequest&token=123ABC"
+                               => "apihelp-{$path}-example-simple",
+               ];
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Manage_authentication_data';
+       }
+}
diff --git a/includes/api/ApiResetPassword.php b/includes/api/ApiResetPassword.php
new file mode 100644 (file)
index 0000000..042ad69
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * Reset password, with AuthManager
+ *
+ * @ingroup API
+ */
+class ApiResetPassword extends ApiBase {
+
+       private $hasAnyRoutes = null;
+
+       /**
+        * Determine whether any reset routes are available.
+        * @return bool
+        */
+       private function hasAnyRoutes() {
+               if ( $this->hasAnyRoutes === null ) {
+                       $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
+                       $this->hasAnyRoutes = !empty( $resetRoutes['username'] ) || !empty( $resetRoutes['email'] );
+               }
+               return $this->hasAnyRoutes;
+       }
+
+       protected function getDescriptionMessage() {
+               if ( !$this->hasAnyRoutes() ) {
+                       return 'apihelp-resetpassword-description-noroutes';
+               }
+               return parent::getDescriptionMessage();
+       }
+
+       public function execute() {
+               if ( !$this->hasAnyRoutes() ) {
+                       $this->dieUsage( 'No password reset routes are available.', 'moduledisabled' );
+               }
+
+               $params = $this->extractRequestParams() + [
+                       // Make sure the keys exist even if getAllowedParams didn't define them
+                       'user' => null,
+                       'email' => null,
+               ];
+
+               $this->requireOnlyOneParameter( $params, 'user', 'email' );
+
+               $passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
+
+               $status = $passwordReset->isAllowed( $this->getUser(), $params['capture'] );
+               if ( !$status->isOK() ) {
+                       $this->dieStatus( Status::wrap( $status ) );
+               }
+
+               $status = $passwordReset->execute(
+                       $this->getUser(), $params['user'], $params['email'], $params['capture']
+               );
+               if ( !$status->isOK() ) {
+                       $status->value = null;
+                       $this->dieStatus( Status::wrap( $status ) );
+               }
+
+               $result = $this->getResult();
+               $result->addValue( [ 'resetpassword' ], 'status', 'success' );
+               if ( $params['capture'] ) {
+                       $passwords = $status->getValue() ?: [];
+                       ApiResult::setArrayType( $passwords, 'kvp', 'user' );
+                       ApiResult::setIndexedTagName( $passwords, 'p' );
+                       $result->addValue( [ 'resetpassword' ], 'passwords', $passwords );
+               }
+       }
+
+       public function isWriteMode() {
+               return $this->hasAnyRoutes();
+       }
+
+       public function needsToken() {
+               if ( !$this->hasAnyRoutes() ) {
+                       return false;
+               }
+               return 'csrf';
+       }
+
+       public function getAllowedParams() {
+               if ( !$this->hasAnyRoutes() ) {
+                       return [];
+               }
+
+               $ret = [
+                       'user' => [
+                               ApiBase::PARAM_TYPE => 'user',
+                       ],
+                       'email' => [
+                               ApiBase::PARAM_TYPE => 'string',
+                       ],
+                       'capture' => false,
+               ];
+
+               $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
+               if ( empty( $resetRoutes['username'] ) ) {
+                       unset( $ret['user'] );
+               }
+               if ( empty( $resetRoutes['email'] ) ) {
+                       unset( $ret['email'] );
+               }
+
+               return $ret;
+       }
+
+       protected function getExamplesMessages() {
+               $ret = [];
+               $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
+
+               if ( !empty( $resetRoutes['username'] ) ) {
+                       $ret['action=resetpassword&user=Example&token=123ABC'] = 'apihelp-resetpassword-example-user';
+               }
+               if ( !empty( $resetRoutes['email'] ) ) {
+                       $ret['action=resetpassword&user=user@example.com&token=123ABC'] =
+                               'apihelp-resetpassword-example-email';
+               }
+
+               return $ret;
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Manage_authentication_data';
+       }
+}
index ea52e14..f335682 100644 (file)
@@ -24,6 +24,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * API interface for setting the wl_notificationtimestamp field
@@ -98,13 +99,14 @@ class ApiSetNotificationTimestamp extends ApiBase {
                        }
                }
 
+               $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
                $apiResult = $this->getResult();
                $result = [];
                if ( $params['entirewatchlist'] ) {
                        // Entire watchlist mode: Just update the thing and return a success indicator
-                       $dbw->update( 'watchlist', [ 'wl_notificationtimestamp' => $timestamp ],
-                               [ 'wl_user' => $user->getId() ],
-                               __METHOD__
+                       $watchedItemStore->setNotificationTimestampsForUser(
+                               $user,
+                               $timestamp
                        );
 
                        $result['notificationtimestamp'] = is_null( $timestamp )
@@ -133,23 +135,17 @@ class ApiSetNotificationTimestamp extends ApiBase {
 
                        if ( $pageSet->getTitles() ) {
                                // Now process the valid titles
-                               $lb = new LinkBatch( $pageSet->getTitles() );
-                               $dbw->update( 'watchlist', [ 'wl_notificationtimestamp' => $timestamp ],
-                                       [ 'wl_user' => $user->getId(), $lb->constructSet( 'wl', $dbw ) ],
-                                       __METHOD__
+                               $watchedItemStore->setNotificationTimestampsForUser(
+                                       $user,
+                                       $timestamp,
+                                       $pageSet->getTitles()
                                );
 
                                // Query the results of our update
-                               $timestamps = [];
-                               $res = $dbw->select(
-                                       'watchlist',
-                                       [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
-                                       [ 'wl_user' => $user->getId(), $lb->constructSet( 'wl', $dbw ) ],
-                                       __METHOD__
+                               $timestamps = $watchedItemStore->getNotificationTimestampsBatch(
+                                       $user,
+                                       $pageSet->getTitles()
                                );
-                               foreach ( $res as $row ) {
-                                       $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
-                               }
 
                                // Now, put the valid titles into the result
                                /** @var $title Title */
index cc8e390..93003cc 100644 (file)
@@ -40,6 +40,8 @@ class ApiStashEdit extends ApiBase {
        const ERROR_CACHE = 'error_cache';
        const ERROR_UNCACHEABLE = 'uncacheable';
 
+       const PRESUME_FRESH_TTL_SEC = 30;
+
        public function execute() {
                $user = $this->getUser();
                $params = $this->extractRequestParams();
@@ -145,13 +147,15 @@ class ApiStashEdit extends ApiBase {
                        Hooks::run( 'ParserOutputStashForEdit', [ $page, $content, $editInfo->output ] );
 
                        list( $stashInfo, $ttl ) = self::buildStashValue(
-                               $editInfo->pstContent, $editInfo->output, $editInfo->timestamp
+                               $editInfo->pstContent,
+                               $editInfo->output,
+                               $editInfo->timestamp,
+                               $user
                        );
 
                        if ( $stashInfo ) {
                                $ok = $cache->set( $key, $stashInfo, $ttl );
                                if ( $ok ) {
-
                                        $logger->debug( "Cached parser output for key '$key'." );
                                        return self::ERROR_NONE;
                                } else {
@@ -217,8 +221,11 @@ class ApiStashEdit extends ApiBase {
                        return false;
                }
 
+               // Set the time the output was generated
+               $pOut->setCacheTime( wfTimestampNow() );
+
                // Build a value to cache with a proper TTL
-               list( $stashInfo, $ttl ) = self::buildStashValue( $pstContent, $pOut, $timestamp );
+               list( $stashInfo, $ttl ) = self::buildStashValue( $pstContent, $pOut, $timestamp, $user );
                if ( !$stashInfo ) {
                        $logger->info( "Uncacheable parser output for key '$key' (rev/TTL)." );
                        return false;
@@ -281,11 +288,23 @@ class ApiStashEdit extends ApiBase {
                        return false;
                }
 
-               $time = wfTimestamp( TS_UNIX, $editInfo->output->getTimestamp() );
-               if ( ( time() - $time ) <= 3 ) {
+               $age = time() - wfTimestamp( TS_UNIX, $editInfo->output->getCacheTime() );
+               if ( $age <= self::PRESUME_FRESH_TTL_SEC ) {
                        $stats->increment( 'editstash.cache_hits.presumed_fresh' );
-                       $logger->debug( "Timestamp-based cache hit for key '$key'." );
+                       $logger->debug( "Timestamp-based cache hit for key '$key' (age: $age sec)." );
                        return $editInfo; // assume nothing changed
+               } elseif ( isset( $editInfo->edits ) && $editInfo->edits === $user->getEditCount() ) {
+                       // Logged-in user made no local upload/template edits in the meantime
+                       $stats->increment( 'editstash.cache_hits.presumed_fresh' );
+                       $logger->debug( "Edit count based cache hit for key '$key' (age: $age sec)." );
+                       return $editInfo;
+               } elseif ( $user->isAnon()
+                       && self::lastEditTime( $user ) < $editInfo->output->getCacheTime()
+               ) {
+                       // Logged-out user made no local upload/template edits in the meantime
+                       $stats->increment( 'editstash.cache_hits.presumed_fresh' );
+                       $logger->debug( "Edit check based cache hit for key '$key' (age: $age sec)." );
+                       return $editInfo;
                }
 
                $dbr = wfGetDB( DB_SLAVE );
@@ -313,7 +332,7 @@ class ApiStashEdit extends ApiBase {
 
                        if ( $changed || $res->numRows() != $templateUses ) {
                                $stats->increment( 'editstash.cache_misses.proven_stale' );
-                               $logger->info( "Stale cache for key '$key'; template changed." );
+                               $logger->info( "Stale cache for key '$key'; template changed. (age: $age sec)" );
                                return false;
                        }
                }
@@ -337,17 +356,32 @@ class ApiStashEdit extends ApiBase {
 
                        if ( $changed || $res->numRows() != count( $files ) ) {
                                $stats->increment( 'editstash.cache_misses.proven_stale' );
-                               $logger->info( "Stale cache for key '$key'; file changed." );
+                               $logger->info( "Stale cache for key '$key'; file changed. (age: $age sec)" );
                                return false;
                        }
                }
 
                $stats->increment( 'editstash.cache_hits.proven_fresh' );
-               $logger->debug( "Cache hit for key '$key'." );
+               $logger->debug( "Verified cache hit for key '$key' (age: $age sec)." );
 
                return $editInfo;
        }
 
+       /**
+        * @param User $user
+        * @return string|null TS_MW timestamp or null
+        */
+       private static function lastEditTime( User $user ) {
+               $time = wfGetDB( DB_SLAVE )->selectField(
+                       'recentchanges',
+                       'MAX(rc_timestamp)',
+                       [ 'rc_user_text' => $user->getName() ],
+                       __METHOD__
+               );
+
+               return wfTimestampOrNull( TS_MW, $time );
+       }
+
        /**
         * Get the temporary prepared edit stash key for a user
         *
@@ -360,7 +394,7 @@ class ApiStashEdit extends ApiBase {
         * @param User $user User to get parser options from
         * @return string
         */
-       protected static function getStashKey( Title $title, Content $content, User $user ) {
+       private static function getStashKey( Title $title, Content $content, User $user ) {
                $hash = sha1( implode( ':', [
                        $content->getModel(),
                        $content->getDefaultFormat(),
@@ -380,12 +414,14 @@ class ApiStashEdit extends ApiBase {
         * @param Content $pstContent
         * @param ParserOutput $parserOutput
         * @param string $timestamp TS_MW
+        * @param User $user
         * @return array (stash info array, TTL in seconds) or (null, 0)
         */
-       protected static function buildStashValue(
-               Content $pstContent, ParserOutput $parserOutput, $timestamp
+       private static function buildStashValue(
+               Content $pstContent, ParserOutput $parserOutput, $timestamp, User $user
        ) {
-               // If an item is renewed, mind the cache TTL determined by config and parser functions
+               // If an item is renewed, mind the cache TTL determined by config and parser functions.
+               // Put an upper limit on the TTL for sanity to avoid extreme template/file staleness.
                $since = time() - wfTimestamp( TS_UNIX, $parserOutput->getTimestamp() );
                $ttl = min( $parserOutput->getCacheExpiry() - $since, 5 * 60 );
 
@@ -394,7 +430,8 @@ class ApiStashEdit extends ApiBase {
                        $stashInfo = (object)[
                                'pstContent' => $pstContent,
                                'output'     => $parserOutput,
-                               'timestamp'  => $timestamp
+                               'timestamp'  => $timestamp,
+                               'edits'      => $user->getEditCount()
                        ];
                        return [ $stashInfo, $ttl ];
                }
index 1571b27..0a79aa4 100644 (file)
@@ -488,6 +488,16 @@ class ApiUpload extends ApiBase {
 
                        $this->dieUsageMsg( 'badaccess-groups' );
                }
+
+               // Check blocks
+               if ( $user->isBlocked() ) {
+                       $this->dieBlocked( $user->getBlock() );
+               }
+
+               // Global blocks
+               if ( $user->isBlockedGlobally() ) {
+                       $this->dieBlocked( $user->getGlobalBlock() );
+               }
        }
 
        /**
index 0e8b437..512ab83 100644 (file)
@@ -12,7 +12,8 @@
                        "Гульчатай",
                        "Ilmira",
                        "Гизатуллина",
-                       "Танзиля Кутлугильдина"
+                       "Танзиля Кутлугильдина",
+                       "Ләйсән"
                ]
        },
        "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:Хаталар һәм иҫкәртеүҙәр]].\n\n<strong>Тестлау:</strong>   API-һоратыуҙарҙы тестлау уңайлы булһын өсөн ҡара. [[Special:ApiSandbox]]",
        "apihelp-edit-param-appendtext": "Был тексты биттең аҙағынаса өҫтәгеҙ.$1text алмаштыра.\n$1section -ды файҙаланығыҙ = яңы, яңы бүлек өҫтәү өсөн, ә был параметрға түгел.",
        "apihelp-edit-param-undo": "Был версияны кире алырға. $1text,$1prependtext,$1appendtext алмаштыра.",
        "apihelp-edit-param-undoafter": "$1undo- нан алып барлыҡ үҙгәртеүҙәрҙе кире алырға. Әгәр ул ҡуйылмаған булһа, бер тикшереүҙе кире алыу ҙа етә.",
-       "apihelp-edit-param-redirect": "Автоматик йүнәлтәүҙе рөхсәт итергә.",
+       "apihelp-edit-param-redirect": "Автоматик йүнәлтеүҙе рөхсәт итергә.",
        "apihelp-edit-param-contentformat": "Текстҡа ҡуйыу өсөн йөкмәткенең сериализация форматы.",
        "apihelp-edit-param-contentmodel": "Яңы йөкмәткенең контент моделе.",
        "apihelp-edit-param-token": "Маркер һуңғы параметр сифатында ебәрелергә тейеш, йәки, һәрхәлдә $1text параметрынан һуң.",
index 5402ab4..938a61a 100644 (file)
        "apihelp-import-param-namespace": "In diesen Namensraum importieren. Kann nicht zusammen mit <var>$1rootpage</var> verwendet werden.",
        "apihelp-import-param-rootpage": "Als Unterseite dieser Seite importieren. Kann nicht zusammen mit <var>$1namespace</var> verwendet werden.",
        "apihelp-import-example-import": "Importiere [[meta:Help:ParserFunctions]] mit der kompletten Versionsgeschichte in den Namensraum 100.",
-       "apihelp-login-description": "Anmelden und Authentifizierungs-Cookies beziehen.\n\nFalls das Anmelden erfolgreich war, werden die benötigten Cookies im Header der HTTP-Antwort des Servers übermittelt. Bei fehlgeschlagenen Anmeldeversuchen können weitere Versuche gedrosselt werden, um automatische Passwortermittlungsattacken zu verhinden.",
+       "apihelp-login-description": "Anmelden und Authentifizierungs-Cookies beziehen.\n\nDiese Aktion sollte nur in Kombination mit [[Special:BotPasswords]] verwendet werden. Die Verwendung für die Anmeldung beim Hauptkonto ist veraltet und kann ohne Warnung fehlschlagen. Um sich sicher beim Hauptkonto anzumelden, verwende <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
        "apihelp-login-param-name": "Benutzername.",
        "apihelp-login-param-password": "Passwort.",
        "apihelp-login-param-domain": "Domain (optional).",
        "apihelp-query+usercontribs-paramvalue-prop-ids": "Fügt die Seiten- und Versionskennung hinzu.",
        "apihelp-query+usercontribs-paramvalue-prop-timestamp": "Ergänzt den Zeitstempel der Bearbeitung.",
        "apihelp-query+usercontribs-paramvalue-prop-comment": "Fügt den Kommentar der Bearbeitung hinzu.",
+       "apihelp-query+usercontribs-paramvalue-prop-patrolled": "Markiert kontrollierte Bearbeitungen.",
+       "apihelp-query+usercontribs-paramvalue-prop-tags": "Listet die Markierungen für die Bearbeitung auf.",
        "apihelp-query+userinfo-paramvalue-prop-blockinfo": "Markiert, ob der aktuelle Benutzer gesperrt ist, von wem und aus welchem Grund.",
        "apihelp-query+userinfo-paramvalue-prop-editcount": "Ergänzt den Bearbeitungszähler des aktuellen Benutzers.",
        "apihelp-query+userinfo-paramvalue-prop-realname": "Fügt den bürgerlichen Namen des Benutzers hinzu.",
        "apihelp-query+users-paramvalue-prop-implicitgroups": "Listet alle Gruppen auf, bei denen der Benutzer automatisch Mitglied ist.",
        "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-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-query+watchlist-param-prop": "Zusätzlich zurückzugebende Eigenschaften:",
+       "apihelp-query+watchlist-paramvalue-prop-ids": "Ergänzt die Versions- und Seitenkennungen.",
+       "apihelp-query+watchlist-paramvalue-prop-title": "Ergänzt den Titel der Seite.",
+       "apihelp-query+watchlist-paramvalue-prop-flags": "Ergänzt die Markierungen für die Bearbeitungen.",
        "apihelp-query+watchlist-paramvalue-prop-user": "Ergänzt den Benutzer, der die Bearbeitung ausgeführt hat.",
        "apihelp-query+watchlist-paramvalue-prop-userid": "Ergänzt die Kennung des Benutzers, der die Bearbeitung ausgeführt hat.",
        "apihelp-query+watchlist-paramvalue-prop-comment": "Ergänzt den Kommentar der Bearbeitung.",
index 2b23da0..a802cc7 100644 (file)
@@ -34,6 +34,9 @@
        "apihelp-block-example-ip-simple": "Block IP address <kbd>192.0.2.5</kbd> for three days with reason <kbd>First strike</kbd>.",
        "apihelp-block-example-user-complex": "Block user <kbd>Vandal</kbd> indefinitely with reason <kbd>Vandalism</kbd>, and prevent new account creation and email sending.",
 
+       "apihelp-changeauthenticationdata-description": "Change authentication data for the current user.",
+       "apihelp-changeauthenticationdata-example-password": "Attempt to change the current user's password to <kbd>ExamplePassword</kbd>.",
+
        "apihelp-checktoken-description": "Check the validity of a token from <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-checktoken-param-type": "Type of token being tested.",
        "apihelp-checktoken-param-token": "Token to test.",
        "apihelp-clearhasmsg-description": "Clears the <code>hasmsg</code> flag for the current user.",
        "apihelp-clearhasmsg-example-1": "Clear the <code>hasmsg</code> flag for the current user.",
 
+       "apihelp-clientlogin-description": "Log in to the wiki using the interactive flow.",
+       "apihelp-clientlogin-example-login": "Start the process of logging in to the wiki as user <kbd>Example</kbd> with password <kbd>ExamplePassword</kbd>.",
+       "apihelp-clientlogin-example-login2": "Continue logging in after a UI response for two-factor auth, supplying an <var>OATHToken</var> of <kbd>987654</kbd>.",
+
        "apihelp-compare-description": "Get the difference between 2 pages.\n\nA revision number, a page title, or a page ID for both \"from\" and \"to\" must be passed.",
        "apihelp-compare-param-fromtitle": "First title to compare.",
        "apihelp-compare-param-fromid": "First page ID to compare.",
@@ -53,6 +60,7 @@
        "apihelp-compare-example-1": "Create a diff between revision 1 and 2.",
 
        "apihelp-createaccount-description": "Create a new user account.",
+       "apihelp-createaccount-example-create": "Start the process of creating user <kbd>Example</kbd> with password <kbd>ExamplePassword</kbd>.",
        "apihelp-createaccount-param-name": "Username.",
        "apihelp-createaccount-param-password": "Password (ignored if <var>$1mailpassword</var> is set).",
        "apihelp-createaccount-param-domain": "Domain for external authentication (optional).",
        "apihelp-import-param-rootpage": "Import as subpage of this page. Cannot be used together with <var>$1namespace</var>.",
        "apihelp-import-example-import": "Import [[meta:Help:ParserFunctions]] to namespace 100 with full history.",
 
-       "apihelp-login-description": "Log in and get authentication cookies.\n\nIn the event of a successful log-in, the needed cookies will be included in the HTTP response headers. In the event of a failed log-in, further attempts may be throttled to limit automated password guessing attacks.",
+       "apihelp-linkaccount-description": "Link an account from a third-party provider to the current user.",
+       "apihelp-linkaccount-example-link": "Start the process of linking to an account from <kbd>Example</kbd>.",
+
+       "apihelp-login-description": "Log in and get authentication cookies.\n\nThis action should only be used in combination with [[Special:BotPasswords]]; use for main-account login is deprecated and may fail without warning. To safely log in to the main account, use <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nobotpasswords": "Log in and get authentication cookies.\n\nThis action is deprecated and may fail without warning. To safely log in, use <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nonauthmanager": "Log in and get authentication cookies.\n\nIn the event of a successful log-in, the needed cookies will be included in the HTTP response headers. In the event of a failed log-in, further attempts may be throttled to limit automated password guessing attacks.",
        "apihelp-login-param-name": "User name.",
        "apihelp-login-param-password": "Password.",
        "apihelp-login-param-domain": "Domain (optional).",
        "apihelp-query+allusers-param-attachedwiki": "With <kbd>$1prop=centralids</kbd>, also indicate whether the user is attached with the wiki identified by this ID.",
        "apihelp-query+allusers-example-Y": "List users starting at <kbd>Y</kbd>.",
 
+       "apihelp-query+authmanagerinfo-description": "Retrieve information about the current authentication status.",
+       "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Test whether the user's current authentication status is sufficient for the specified security-sensitive operation.",
+       "apihelp-query+authmanagerinfo-param-requestsfor": "Fetch information about the authentication requests needed for the specified authentication action.",
+       "apihelp-query+filerepoinfo-example-login": "Fetch the requests that may be used when beginning a login.",
+       "apihelp-query+filerepoinfo-example-login-merged": "Fetch the requests that may be used when beginning a login, with form fields merged.",
+       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Test whether authentication is sufficient for action <kbd>foo</kbd>.",
+
        "apihelp-query+backlinks-description": "Find all pages that link to the given page.",
        "apihelp-query+backlinks-param-title": "Title to search. Cannot be used together with <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "Page ID to search. Cannot be used together with <var>$1title</var>.",
        "apihelp-query+siteinfo-paramvalue-prop-variables": "Returns a list of variable IDs.",
        "apihelp-query+siteinfo-paramvalue-prop-protocols": "Returns a list of protocols that are allowed in external links.",
        "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "Returns the default values for user preferences.",
+       "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "Returns the upload dialog configuration.",
        "apihelp-query+siteinfo-param-filteriw": "Return only local or only nonlocal entries of the interwiki map.",
        "apihelp-query+siteinfo-param-showalldb": "List all database servers, not just the one lagging the most.",
        "apihelp-query+siteinfo-param-numberingroup": "Lists the number of users in user groups.",
        "apihelp-query+users-paramvalue-prop-emailable": "Tags if the user can and wants to receive email through [[Special:Emailuser]].",
        "apihelp-query+users-paramvalue-prop-gender": "Tags the gender of the user. Returns \"male\", \"female\", or \"unknown\".",
        "apihelp-query+users-paramvalue-prop-centralids": "Adds the central IDs and attachment status for the user.",
+       "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-token": "Use <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> instead.",
        "apihelp-query+watchlistraw-example-simple": "List pages on the current user's watchlist.",
        "apihelp-query+watchlistraw-example-generator": "Fetch page info for pages on the current user's watchlist.",
 
+       "apihelp-removeauthenticationdata-description": "Remove authentication data for the current user.",
+       "apihelp-removeauthenticationdata-example-simple": "Attempt to remove the current user's data for <kbd>FooAuthenticationRequest</kbd>.",
+
+       "apihelp-resetpassword-description": "Send a password reset email to a user.",
+       "apihelp-resetpassword-description-noroutes": "No password reset routes are available.\n\nEnable routes in <var>[[mw:Manual:$wgPasswordResetRoutes|$wgPasswordResetRoutes]]</var> to use this module.",
+       "apihelp-resetpassword-param-user": "User being reset.",
+       "apihelp-resetpassword-param-email": "Email address of the user being reset.",
+       "apihelp-resetpassword-param-capture": "Return the temporary passwords that were sent. Requires the <code>passwordreset</code> user right.",
+       "apihelp-resetpassword-example-user": "Send a password reset email to user <kbd>Example</kbd>.",
+       "apihelp-resetpassword-example-email": "Send a password reset email for all users with email address <kbd>user@example.com</kbd>.",
+
        "apihelp-revisiondelete-description": "Delete and undelete revisions.",
        "apihelp-revisiondelete-param-type": "Type of revision deletion being performed.",
        "apihelp-revisiondelete-param-target": "Page title for the revision deletion, if required for the type.",
        "apihelp-undelete-example-page": "Undelete page <kbd>Main Page</kbd>.",
        "apihelp-undelete-example-revisions": "Undelete two revisions of page <kbd>Main Page</kbd>.",
 
+       "apihelp-unlinkaccount-description": "Remove a linked third-party account from the current user.",
+       "apihelp-unlinkaccount-example-simple": "Attempt to remove the current user's link for the provider associated with <kbd>FooAuthenticationRequest</kbd>.",
+
        "apihelp-upload-description": "Upload a file, or get the status of pending uploads.\n\nSeveral methods are available:\n* Upload file contents directly, using the <var>$1file</var> parameter.\n* Upload the file in pieces, using the <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var> parameters.\n* Have the MediaWiki server fetch a file from a URL, using the <var>$1url</var> parameter.\n* Complete an earlier upload that failed due to warnings, using the <var>$1filekey</var> parameter.\nNote that the HTTP POST must be done as a file upload (i.e. using <code>multipart/form-data</code>) when sending the <var>$1file</var>.",
        "apihelp-upload-param-filename": "Target filename.",
        "apihelp-upload-param-comment": "Upload comment. Also used as the initial page text for new files if <var>$1text</var> is not specified.",
        "api-help-right-apihighlimits": "Use higher limits in API queries (slow queries: $1; fast queries: $2). The limits for slow queries also apply to multivalue parameters.",
        "api-help-open-in-apisandbox": "<small>[open in sandbox]</small>",
 
+       "api-help-authmanager-general-usage": "The general procedure to use this module is:\n# Fetch the fields available from <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> with <kbd>amirequestsfor=$4</kbd>, and a <kbd>$5</kbd> token from <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]].\n# Present the fields to the user, and obtain their submission.\n# Post to this module, supplying <var>$1returnurl</var> and any relevant fields.\n# Check the <samp>status</samp> in the response.\n#* If you received <samp>PASS</samp> or <samp>FAIL</samp>, you're done. The operation either succeeded or it didn't.\n#* If you received <samp>UI</samp>, present the new fields to the user and obtain their submission. Then post to this module with <var>$1continue</var> and the relevant fields set, and repeat step 4.\n#* If you received <samp>REDIRECT</samp>, direct the user to the <samp>redirecttarget</samp> and wait for the return to <var>$1returnurl</var>. Then post to this module with <var>$1continue</var> and any fields passed to the return URL, and repeat step 4.\n#* If you received <samp>RESTART</samp>, that means the authentication worked but we don't have an linked user account. You might treat this as UI or as FAIL.",
+       "api-help-authmanagerhelper-requests": "Only use these authentication requests, by the <samp>id</samp> returned from <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> with <kbd>amirequestsfor=$1</kbd> or from a previous response from this module.",
+       "api-help-authmanagerhelper-request": "Use this authentication request, by the <samp>id</samp> returned from <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> with <kbd>amirequestsfor=$1</kbd>.",
+       "api-help-authmanagerhelper-messageformat": "Format to use for returning messages.",
+       "api-help-authmanagerhelper-mergerequestfields": "Merge field information for all authentication requests into one array.",
+       "api-help-authmanagerhelper-preservestate": "Preserve state from a previous failed login attempt, if possible.",
+       "api-help-authmanagerhelper-returnurl": "Return URL for third-party authentication flows, must be absolute. Either this or <var>$1continue</var> is required.\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": "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.",
+
        "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 45060c2..ff97cc8 100644 (file)
@@ -1,12 +1,28 @@
 {
        "@metadata": {
                "authors": [
-                       "Robin van der Vliet"
+                       "Robin van der Vliet",
+                       "Renardo"
                ]
        },
+       "apihelp-main-param-format": "La formo de la eligaĵo.",
+       "apihelp-block-description": "Forbari uzulon.",
+       "apihelp-block-param-user": "Salutnomo, IP-adreso aŭ IP-adresa intervalo forbarota.",
+       "apihelp-block-param-expiry": "Eksvalidiĝa tempo. Ĝi povas esti relativa (ekz. <kbd>5 months</kbd> aŭ <kbd>2 weeks</kbd> aŭ absoluta (ekz. <kbd>2014-09-18T12:34:56Z</kbd>). Se vi indikas <kbd>infinite</kbd> (senfine), <kbd>indefinite</kbd> (nedifinite) aŭ <kbd>never</kbd> (neniam), la forbaro neniam eksvalidiĝos.",
        "apihelp-createaccount-param-name": "Uzantnomo.",
        "apihelp-delete-description": "Forigi paĝon.",
+       "apihelp-edit-param-minor": "Redakteto.",
        "apihelp-edit-example-edit": "Redakti paĝon.",
+       "apihelp-feedrecentchanges-param-hideminor": "Kaŝi redaktetojn.",
+       "apihelp-feedrecentchanges-param-hidebots": "Kaŝi robotajn ŝanĝojn.",
+       "apihelp-feedrecentchanges-param-hideanons": "Kaŝi redaktojn de anonimuloj.",
+       "apihelp-feedrecentchanges-param-hideliu": "Kaŝi redaktojn de ensalutintaj uzantoj.",
+       "apihelp-feedrecentchanges-param-hidepatrolled": "Kaŝi reviziitajn ŝanĝojn.",
+       "apihelp-feedrecentchanges-param-hidemyself": "Kaŝi redaktojn de la nun ensalutinta uzulo (= vi).",
+       "apihelp-feedrecentchanges-param-hidecategorization": "Kaŝi ŝanĝojn de kategoria aneco.",
+       "apihelp-feedrecentchanges-example-simple": "Montri ĵusajn ŝanĝojn.",
+       "apihelp-filerevert-description": "Restarigi malnovan version de dosiero.",
+       "apihelp-filerevert-param-comment": "Alŝuta komento.",
        "apihelp-login-param-name": "Uzantnomo.",
        "apihelp-login-param-password": "Pasvorto.",
        "apihelp-login-example-login": "Ensaluti."
index 4c0d177..f25039a 100644 (file)
@@ -23,7 +23,8 @@
                        "Umherirrender",
                        "Elfix",
                        "Lbayle",
-                       "Verdy p"
+                       "Verdy p",
+                       "Yasten"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> Toutes les fonctionnalités affichées sur cette page devraient fonctionner, mais l’API est encore en cours de développement et peut changer à tout moment. Inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un en-tête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet en-tête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Test :</strong> Pour faciliter le test des requêtes de l’API, voyez [[Special:ApiSandbox]].",
@@ -52,6 +53,8 @@
        "apihelp-block-param-watchuser": "Surveiller les pages utilisateur et de discussion de l’utilisateur ou de l’adresse IP.",
        "apihelp-block-example-ip-simple": "Bloquer l’adresse IP <kbd>192.0.2.5</kbd> pour trois jours avec le motif <kbd>Premier avertissement</kbd>.",
        "apihelp-block-example-user-complex": "Bloquer indéfiniment l’utilisateur <kbd>Vandal</kbd> avec le motif <kbd>Vandalism</kbd>, et empêcher la création de nouveau compte et l'envoi de courriel.",
+       "apihelp-changeauthenticationdata-description": "Modifier les données d’authentification pour l’utilisateur actuel.",
+       "apihelp-changeauthenticationdata-example-password": "Tentative de modification du mot de passe de l’utilisateur actuel en <kbd>ExempleMotDePasse</kbd>.",
        "apihelp-checktoken-description": "Vérifier la validité d'un jeton de <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-checktoken-param-type": "Type de jeton testé",
        "apihelp-checktoken-param-token": "Jeton à tester.",
@@ -59,6 +62,9 @@
        "apihelp-checktoken-example-simple": "Tester la validité d'un jeton de <kbd>csrf</kbd>.",
        "apihelp-clearhasmsg-description": "Efface le drapeau <code>hasmsg</code> pour l’utilisateur courant.",
        "apihelp-clearhasmsg-example-1": "Effacer le drapeau <code>hasmsg</code> pour l’utilisateur courant",
+       "apihelp-clientlogin-description": "Se connecter au wiki en utilisant le flux interactif.",
+       "apihelp-clientlogin-example-login": "Commencer le processus de connexion au wiki en tant qu’utilisateur <kbd>Exemple</kbd> avec le mot de passe <kbd>ExempleMotDePasse</kbd>.",
+       "apihelp-clientlogin-example-login2": "Continuer la connexion après une réponse de l’IHM pour l’authentification à deux facteurs, en fournissant un <var>OATHToken</var> valant <kbd>987654</kbd>.",
        "apihelp-compare-description": "Obtenir la différence entre 2 pages.\n\nVous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
        "apihelp-compare-param-fromtitle": "Premier titre à comparer.",
        "apihelp-compare-param-fromid": "ID de la première page à comparer.",
@@ -68,6 +74,7 @@
        "apihelp-compare-param-torev": "Seconde révision à comparer.",
        "apihelp-compare-example-1": "Créer une différence entre les révisions 1 et 2",
        "apihelp-createaccount-description": "Créer un nouveau compte utilisateur.",
+       "apihelp-createaccount-example-create": "Commencer le processus de création d’un utilisateur <kbd>Exemple</kbd> avec le mot de passe <kbd>ExempleMotDePasse</kbd>.",
        "apihelp-createaccount-param-name": "Nom d’utilisateur.",
        "apihelp-createaccount-param-password": "Mot de passe (ignoré si <var>$1mailpassword</var> est défini).",
        "apihelp-createaccount-param-domain": "Domaine pour l’authentification externe (facultatif).",
        "apihelp-import-param-namespace": "Importer vers cet espace de noms. Impossible à utiliser avec <var>$1rootpage</var>.",
        "apihelp-import-param-rootpage": "Importer comme une sous-page de cette page. Impossible à utiliser avec <var>$1namespace</var>.",
        "apihelp-import-example-import": "Importer [[meta:Help:ParserFunctions]] vers l’espace de noms 100 avec tout l’historique.",
-       "apihelp-login-description": "Se connecter et obtenir les cookies d’authentification.\n\nDans le cas d’une connexion réussie, les cookies nécessaires seront inclus dans les entêtes de la réponse HTTP. Dans le cas d’une connexion en échec, les essais ultérieurs pourront être réduits afin de limiter les attaques automatisées de découverte du mot de passe.",
+       "apihelp-linkaccount-description": "Lier un compte d’un fournisseur tiers à l’utilisateur actuel.",
+       "apihelp-linkaccount-example-link": "Commencer le processus de liaison d’un compte depuis <kbd>Exemple</kbd>.",
+       "apihelp-login-description": "Se connecter et obtenir les cookies d’authentification.\n\nCette action ne devrait être utilisée qu’en lien avec [[Special:BotPasswords]] ; l’utiliser pour la connexion du compte principal est obsolète et peut échouer sans avertissement. Pour se connecter sans problème au compte principal, utiliser <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nobotpasswords": "Se connecter et obtenir les cookies d’authentification.\n\nCette action est obsolète et peut échouer sans prévenir. Pour se connecter sans problème, utiliser <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nonauthmanager": "Se connecter et obtenir les cookies d’authentification.\n\nDans le cas d’une connexion réussie, les cookies nécessaires seront inclus dans les entêtes HTTP de la réponse. Dans le cas d’une connexion en échec, des tentatives ultérieures pourront être limitées pour éviter les attaques automatiques pour deviner les mots de passe.",
        "apihelp-login-param-name": "Nom d’utilisateur.",
        "apihelp-login-param-password": "Mot de passe.",
        "apihelp-login-param-domain": "Domaine (facultatif).",
        "apihelp-query+allusers-param-activeusers": "Lister uniquement les utilisateurs actifs durant {{PLURAL:$1|le dernier jour|les $1 derniers jours}}.",
        "apihelp-query+allusers-param-attachedwiki": "Avec <kbd>$1prop=centralids</kbd>, indiquer aussi si l’utilisateur est attaché avec le wiki identifié par cet ID.",
        "apihelp-query+allusers-example-Y": "Lister les utilisateurs en commençant à <kbd>Y</kbd>.",
+       "apihelp-query+authmanagerinfo-description": "Récupérer les informations concernant l’état d’authentification actuel.",
+       "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Tester si l’état d’authentification actuel de l’utilisateur est suffisant pour l’opération spécifiée comme sensible du point de vue sécurité.",
+       "apihelp-query+authmanagerinfo-param-requestsfor": "Récupérer les informations sur les requêtes d’authentification nécessaires pour l’action d’authentification spécifiée.",
+       "apihelp-query+filerepoinfo-example-login": "Récupérer les requêtes qui peuvent être utilisées en commençant une connexion.",
+       "apihelp-query+filerepoinfo-example-login-merged": "Récupérer les requêtes qui peuvent être utilisées au début de la connexion, avec les champs de formulaire intégrés.",
+       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Tester si l’authentification est suffisante pour l’action <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "Trouver toutes les pages qui ont un lien vers la page donnée.",
        "apihelp-query+backlinks-param-title": "Titre à rechercher. Impossible à utiliser avec <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "ID de la page à chercher. Impossible à utiliser avec <var>$1title</var>.",
        "apihelp-query+siteinfo-paramvalue-prop-variables": "Renvoie une liste des IDs de variable.",
        "apihelp-query+siteinfo-paramvalue-prop-protocols": "Renvoie une liste des protocoles qui sont autorisés dans les liens externes.",
        "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "Renvoie les valeurs par défaut pour les préférences utilisateur.",
+       "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "Renvoie la configuration du dialogue de téléversement.",
        "apihelp-query+siteinfo-param-filteriw": "Renvoyer uniquement les entrées locales ou uniquement les non locales de la correspondance interwiki.",
        "apihelp-query+siteinfo-param-showalldb": "Lister tous les serveurs de base de données, pas seulement celui avec la plus grande latence.",
        "apihelp-query+siteinfo-param-numberingroup": "Liste le nombre d’utilisateurs dans les groupes.",
        "apihelp-query+users-paramvalue-prop-emailable": "Marque si l’utilisateur peut et veut recevoir des courriels via [[Special:Emailuser]].",
        "apihelp-query+users-paramvalue-prop-gender": "Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
        "apihelp-query+users-paramvalue-prop-centralids": "Ajoute les IDs centraux et l’état d’attachement de l’utilisateur.",
+       "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-token": "Utiliser plutôt <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-query+watchlistraw-param-totitle": "Terminer l'énumération avec ce Titre (inclure le préfixe d'espace de noms) :",
        "apihelp-query+watchlistraw-example-simple": "Lister les pages dans la liste de suivi de l’utilisateur actuel",
        "apihelp-query+watchlistraw-example-generator": "Chercher l’information sur les pages de la liste de suivi de l’utilisateur actuel",
+       "apihelp-removeauthenticationdata-description": "Supprimer les données d’authentification pour l’utilisateur actuel.",
+       "apihelp-removeauthenticationdata-example-simple": "Tentative de suppression des données de l’utilisateur pour <kbd>FooAuthenticationRequest</kbd>.",
+       "apihelp-resetpassword-description": "Envoyer un courriel de réinitialisation du mot de passe à un utilisateur.",
+       "apihelp-resetpassword-description-noroutes": "Aucun chemin pour réinitialiser le mot de passe n’est disponible.\n\nActiver les chemins dans <var>[[mw:Manual:$wgPasswordResetRoutes|$wgPasswordResetRoutes]]</var> pour utiliser ce module.",
+       "apihelp-resetpassword-param-user": "Utilisateur ayant été réinitialisé.",
+       "apihelp-resetpassword-param-email": "Adresse courriel de l’utilisateur ayant été réinitialisé.",
+       "apihelp-resetpassword-example-email": "Envoyer un courriel pour la réinitialisation de mot de passe à tous les utilisateurs avec une adresse email <kbd>user@example.com</kbd>.",
        "apihelp-revisiondelete-description": "Supprimer et annuler la suppression des révisions.",
        "apihelp-revisiondelete-param-type": "Type de suppression de révision en cours de traitement.",
        "apihelp-revisiondelete-param-target": "Titre de page pour la suppression de révision, s’il est nécessaire pour le type.",
index 7a842f4..92d14bd 100644 (file)
@@ -40,6 +40,8 @@
        "apihelp-block-param-watchuser": "לעקוב אחרי דף המשתמש ודף השיחה של המשתמש או של כתובת ה־IP.",
        "apihelp-block-example-ip-simple": "חסימת כתובת ה־IP‏ <kbd>192.0.2.5</kbd> לשלושה ימים עם הסיבה <kbd>First strike</kbd>.",
        "apihelp-block-example-user-complex": "חסימת המשתמש <kbd>Vandal</kbd> ללא הגבלת זמן עם הסיבה <kbd>Vandalism</kbd>, ומניעת יצירת חשבונות חדשים ושליחת דוא\"ל.",
+       "apihelp-changeauthenticationdata-description": "שינוי נתוני אימות עבור המשתמש הנוכחי.",
+       "apihelp-changeauthenticationdata-example-password": "ניסיון לשנות את הססמה של המשתמש הנוכחי ל־<kbd>ExamplePassword</kbd>.",
        "apihelp-checktoken-description": "בדיקת התקינות של האסימון מ־<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-checktoken-param-type": "סוג האסימון שבבדיקה.",
        "apihelp-checktoken-param-token": "איזה אסימון לבדוק.",
@@ -47,6 +49,9 @@
        "apihelp-checktoken-example-simple": "בדיקת התקינות של אסימון <kbd>csrf</kbd>.",
        "apihelp-clearhasmsg-description": "מנקה את דגל <code>hasmsg</code> עבור המשתמש הנוכחי.",
        "apihelp-clearhasmsg-example-1": "לנקות את דגל <code>hasmsg</code> עבור המשתמש הנוכחי.",
+       "apihelp-clientlogin-description": "כניסה לוויקי באמצעות זרימה הידודית.",
+       "apihelp-clientlogin-example-login": "תחילת תהליך כניסה לוויקי בתור משתמש <kbd>Example</kbd> עם הססמה <kbd>ExamplePassword</kbd>.",
+       "apihelp-clientlogin-example-login2": "המשך כניסה אחרי תשובת UI לאימות דו־גורמי, עם <var>OATHToken</var> של <kbd>987654</kbd>.",
        "apihelp-compare-description": "קבלת ההבדל בין 2 דפים.\n\nיש להעביר מספר גרסה, כותרת דף או מזהה דף גם ל־\"from\" וגם ל־\"to\".",
        "apihelp-compare-param-fromtitle": "כותרת ראשונה להשוואה.",
        "apihelp-compare-param-fromid": "מס׳ זיהוי של העמוד הראשון להשוואה.",
@@ -56,6 +61,7 @@
        "apihelp-compare-param-torev": "גרסה שנייה להשוואה.",
        "apihelp-compare-example-1": "יצירת תיעוד שינוי בין גרסה 1 ל־2.",
        "apihelp-createaccount-description": "יצירת חשבון משתמש חדש.",
+       "apihelp-createaccount-example-create": "תחילת תהליך יצירת המשתמש <kbd>Example</kbd> עם הססמה <kbd>ExamplePassword</kbd>.",
        "apihelp-createaccount-param-name": "שם משתמש.",
        "apihelp-createaccount-param-password": "ססמה (לא ישפיע אם הוגדר <var>$1mailpassword</var>).",
        "apihelp-createaccount-param-domain": "שם מתחם לאימות חיצוני (רשות).",
        "apihelp-import-param-namespace": "לייבא למרחב השם הזה. לא ניתן להשתמש בזה יחד עם <var>$1rootpage</var>.",
        "apihelp-import-param-rootpage": "לייבא בתור תת־משנה של הדף הזה. לא ניתן להשתמש בזה יחד עם <var>$1namespace</var>.",
        "apihelp-import-example-import": "לייבא את [[meta:Help:ParserFunctions]] למרחב השם 100 עם היסטוריה מלאה.",
-       "apihelp-login-description": "להיכנס ולקבל עוגיות אימות.\n\nבמקרה של כניסה מוצלחת, העוגיות הדרושות תיכללנה בכותרות תשובות של HTTP. במקרה של כניסה לא מוצלחת, הניסיונות הבאים עשויים להיות חנוקים כדי להגביל תקיפות ניחוש ססמה אוטומטי.",
+       "apihelp-linkaccount-description": "קישור חשבון של ספק צד־שלישי למשתמש הנוכחי.",
+       "apihelp-linkaccount-example-link": "תחילת תהליך הקישור לחשבון מ־<kbd>Example</kbd>.",
+       "apihelp-login-description": "להיכנס ולקבל עוגיות אימות.\n\nהפעולה הזאת צריכה לשמש רק בשילוב [[Special:BotPasswords]]; שימוש לכניסה לחשבון ראשי מיושן ועשוי להיכשל ללא אזהרה. כדי להיכנס בבטחה לחשבון הראשי, יש להשתמש ב־<kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nobotpasswords": "להיכנס ולקבל עוגיות אימות.\n\nהפעולה הזאת מיושנת ועשויה להיכשל ללא אזהרה. כדי להיכנס בבטחה, יש להשתמש ב־<kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nonauthmanager": "להיכנס ולקבל עוגיות אימות.\n\nבמקרה של כניסה מוצלחת, העוגיות המקוננות תיכללנה בכותרות תשובות ה־HTTP. במקרה של כניסה כושלת, הניסיונות הבאים יוגבלו למספר ניסויי ניחוש הססמה האוטומטיים.",
        "apihelp-login-param-name": "שם משתמש.",
        "apihelp-login-param-password": "ססמה.",
        "apihelp-login-param-domain": "שם מתחם (רשות).",
        "apihelp-query+allusers-param-activeusers": "לרשום רק משתמשים שהיו פעילים {{PLURAL:$1|ביום האחרון|ביומיים האחרונים|ב־$1 הימים האחרונים}}.",
        "apihelp-query+allusers-param-attachedwiki": "עם <kbd>$1prop=centralids</kbd>, לציין גם האם המשתמש משויך לוויקי עם המזהה הזה.",
        "apihelp-query+allusers-example-Y": "לרשום משתמשים שמתחילים ב־<kbd>Y</kbd>.",
+       "apihelp-query+authmanagerinfo-description": "אחזור מידע אודות מצב האימות הנוכחי.",
        "apihelp-query+backlinks-description": "מציאת כל הדפים שמקשרים לדף הנתון.",
        "apihelp-query+backlinks-param-title": "איזו כותרת לחפש. לא ניתן להשתמש בזה יחד עם <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "מזהה דף לחיפוש. לא ניתן להשתמש בזה יחד עם <var>$1title</var>.",
        "apihelp-query+watchlistraw-param-totitle": "באיזו כותרת (עם תחילית מרחב שם) להפסיק למנות.",
        "apihelp-query+watchlistraw-example-simple": "לרשום דפים ברשימת המעקב של המשתמש הנוכחי.",
        "apihelp-query+watchlistraw-example-generator": "אחזור מידע על הדפים עבור דפים ברשימת המעקב של המשתמש הנוכחי.",
+       "apihelp-removeauthenticationdata-description": "הסרת נתוני אימות עבור המשתמש הנוכחי.",
+       "apihelp-resetpassword-description": "שליחת דוא\"ל איפוס סיסמה למשתמש.",
+       "apihelp-resetpassword-param-email": "כתובת הדוא\"ל של המשתמש שהסיסמה שלו מאופסת.",
        "apihelp-revisiondelete-description": "מחיקה ושחזור ממחיקה של גרסאות.",
        "apihelp-revisiondelete-param-type": "סוג מחיקת הגרסה שמתבצע.",
        "apihelp-revisiondelete-param-target": "שם הדף למחיקת גרסה, אם זה נחוץ לסוג.",
        "apihelp-undelete-param-watchlist": "הוספה או הסרה של הדף ללא תנאי מרשימת המעקב של המשתמש הנוכחי, להשתמש בהעדפות או לא לשנות את המעקב.",
        "apihelp-undelete-example-page": "שחזור ממחיקה של הדף <kbd>Main Page</kbd>.",
        "apihelp-undelete-example-revisions": "שחזור שתי גרסאות של הדף <kbd>Main Page</kbd>.",
+       "apihelp-unlinkaccount-description": "ביטול קישור של חשבון צד־שלישי מהמשתמש הנוכחי.",
        "apihelp-upload-description": "העלאת קובץ, או קבלת מצב ההעלאות הממתינות.\n\nיש מספר שיטות:\n* להעלות את הקובץ ישירות, באמצעות הפרמטר <var>$1file</var>.\n* להעלות את הקובץ בחלקים, באמצעות הפרמטרים <var>$1filesize</var>‏, <var>$1chunk</var> ו־<var>$1offset</var>.\n* לגרום לשרת מדיה־ויקי לאחזר את הקובץ מ־URL באמצעות הפרמטר <var>$1url</var>.\n* להשלים העלאה קודמת שנכשלה בשל אזהרות באמצעות הפרמטר <var>$1filekey</var>.\nלתשומך לבך, יש לעשות את HTTP POST בתור העלאת קובץ (כלומר באמצעות <code>multipart/form-data</code>) בעת שליחת ה־<var>$1file</var>.",
        "apihelp-upload-param-filename": "שם קובץ היעד.",
        "apihelp-upload-param-comment": "הערת העלאה. משמש גם בתור טקסט הדף ההתחלתי עבור קבצים חדשים אם <var>$1text</var> אינו מצוין.",
index 480ad24..dae55de 100644 (file)
@@ -2,9 +2,14 @@
        "@metadata": {
                "authors": [
                        "WongKentir",
-                       "Beeyan"
+                       "Beeyan",
+                       "Rachmat.Wahidi",
+                       "Kenrick95"
                ]
        },
+       "apihelp-block-description": "Blokir pengguna.",
+       "apihelp-block-param-user": "Nama pengguna, alamat IP, atau rentang alamat IP untuk diblokir.",
        "apihelp-createaccount-param-name": "Nama pengguna",
+       "apihelp-login-example-login": "Masuk log.",
        "apihelp-revisiondelete-param-ids": "Penanda untuk perubahan yang akan dihapus"
 }
index 23a654d..6b0cab6 100644 (file)
        "apihelp-paraminfo-description": "Ottieni informazioni sui moduli API.",
        "apihelp-paraminfo-param-helpformat": "Formato delle stringhe di aiuto.",
        "apihelp-parse-param-summary": "Oggetto da analizzare.",
+       "apihelp-parse-param-redirects": "Se <var>$1page</var> o <var>$1pageid</var> è impostato come reindirizzamento, lo risolve.",
        "apihelp-parse-param-prop": "Quali pezzi di informazioni ottenere:",
        "apihelp-parse-example-text": "Analizza wikitext.",
        "apihelp-parse-example-texttitle": "Analizza wikitext, specificando il titolo della pagina.",
diff --git a/includes/api/i18n/jv.json b/includes/api/i18n/jv.json
new file mode 100644 (file)
index 0000000..a250db0
--- /dev/null
@@ -0,0 +1,11 @@
+{
+       "@metadata": {
+               "authors": [
+                       "NoiX180"
+               ]
+       },
+       "apihelp-delete-example-simple": "Busak <kbd>Tepas</kbd>.",
+       "apihelp-query+backlinks-example-simple": "Tuduhaké pranala menyang <kbd>Kaca utama</kbd>.",
+       "apihelp-query+backlinks-example-generator": "Deleng pratélan bab kaca-kaca sing nggayut <kbd>Kaca utama</kbd>.",
+       "apihelp-query+contributors-example-simple": "Tuduhaké para nyumbang <kbd>Kaca Utama</kbd>."
+}
index 0618a88..ccfc005 100644 (file)
@@ -11,7 +11,8 @@
                        "Kurousagi",
                        "Revi",
                        "Yearning",
-                       "Priviet"
+                       "Priviet",
+                       "Ykhwong"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|설명문서]]\n* [[mw:API:FAQ|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:오류와 경고]]를 참조하십시오.\n\n<strong>테스트하기:</strong> API 요청을 테스트의 편의를 위해, [[Special:ApiSandbox]]를 보세요.",
        "apihelp-checktoken-example-simple": "<kbd>csrf</kbd> 토큰의 유효성을 테스트합니다.",
        "apihelp-clearhasmsg-description": "현재 사용자의 <code>hasmsg</code> 플래그를 비웁니다.",
        "apihelp-clearhasmsg-example-1": "현재 계정의 <code>hasmsg</code> 플래그를 삭제합니다.",
+       "apihelp-compare-description": "두 문서 간의 차이를 가져옵니다.\n\n대상이 되는 두 문서의 판 번호나 문서 제목 또는 문서 ID를 지정해야 합니다.",
        "apihelp-compare-param-fromtitle": "비교할 첫 이름.",
        "apihelp-compare-param-fromid": "비교할 첫 문서 ID.",
        "apihelp-compare-param-fromrev": "비교할 첫 판.",
-       "apihelp-compare-param-totitle": "비교할 두번째 제목.",
-       "apihelp-compare-param-toid": "비교할 두번째 문서 ID.",
-       "apihelp-compare-param-torev": "비교할 두번째 판.",
+       "apihelp-compare-param-totitle": "비교할 두 번째 제목.",
+       "apihelp-compare-param-toid": "비교할 두 번째 문서 ID.",
+       "apihelp-compare-param-torev": "비교할 두 번째 판.",
        "apihelp-compare-example-1": "판 1과 2의 차이를 생성합니다.",
        "apihelp-createaccount-description": "새 사용자 계정을 만듭니다.",
        "apihelp-createaccount-param-name": "사용자 이름",
@@ -63,6 +65,9 @@
        "apihelp-createaccount-example-pass": "사용자 <kbd>testuser</kbd>를 만들고 비밀번호를 <kbd>test123</kbd>으로 설정합니다.",
        "apihelp-createaccount-example-mail": "사용자 <kbd>testmailuser</kbd>를 만들고 자동 생성된 비밀번호를 이메일로 보냅니다.",
        "apihelp-delete-description": "문서 삭제",
+       "apihelp-delete-param-pageid": "삭제할 문서의 ID. <var>$1title</var>과 함께 사용할 수 없습니다.",
+       "apihelp-delete-param-reason": "삭제의 이유. 설정하지 않으면 자동 생성되는 이유를 사용합니다.",
+       "apihelp-delete-param-watch": "문서를 현재 사용자의 주시문서 목록에 추가합니다.",
        "apihelp-delete-param-unwatch": "문서를 현재 사용자의 주시문서 목록에서 제거합니다.",
        "apihelp-delete-example-simple": "<kbd>Main Page</kbd>를 삭제합니다.",
        "apihelp-disabled-description": "이 모듈은 해제되었습니다.",
@@ -73,6 +78,8 @@
        "apihelp-edit-param-minor": "사소한 편집.",
        "apihelp-edit-param-notminor": "사소하지 않은 편집.",
        "apihelp-edit-param-bot": "이 편집을 봇으로 표시.",
+       "apihelp-edit-param-nocreate": "페이지가 존재하지 않으면 오류를 출력합니다.",
+       "apihelp-edit-param-watch": "문서를 현재 사용자의 주시문서 목록에 추가합니다.",
        "apihelp-edit-param-unwatch": "문서를 현재 사용자의 주시문서 목록에서 제거합니다.",
        "apihelp-edit-param-redirect": "자동으로 넘겨주기 처리하기.",
        "apihelp-edit-param-contentmodel": "새 콘텐츠의 콘텐츠 모델.",
        "apihelp-feedcontributions-param-deletedonly": "삭제된 기여만 봅니다.",
        "apihelp-feedcontributions-param-toponly": "최신 판인 편집만 봅니다.",
        "apihelp-feedrecentchanges-param-feedformat": "피드 포맷.",
+       "apihelp-feedrecentchanges-param-invert": "선택한 항목을 제외한 모든 이름공간.",
        "apihelp-feedrecentchanges-param-hideminor": "사소한 편집을 숨깁니다.",
        "apihelp-feedrecentchanges-param-hidebots": "봇의 편집을 숨깁니다.",
        "apihelp-feedrecentchanges-param-hideanons": "익명 사용자의 편집을 숨깁니다.",
        "apihelp-feedrecentchanges-example-30days": "30일간의 최근 바뀜을 봅니다.",
        "apihelp-filerevert-description": "파일을 이전 판으로 되돌립니다.",
        "apihelp-filerevert-example-revert": "<kbd>Wiki.png</kbd>를 <kbd>2011-03-05T15:27:40Z</kbd> 판으로 되돌립니다.",
+       "apihelp-help-param-helpformat": "도움말 출력 포맷.",
        "apihelp-import-param-xml": "업로드한 XML 파일.",
        "apihelp-login-param-name": "계정 이름.",
        "apihelp-login-param-password": "비밀번호.",
        "apihelp-login-param-domain": "도메인 (선택).",
        "apihelp-login-example-login": "로그인.",
+       "apihelp-mergehistory-param-reason": "문서 병합 이유.",
        "apihelp-move-description": "문서 이동하기.",
        "apihelp-move-param-reason": "제목을 변경하는 이유",
        "apihelp-move-param-movetalk": "토론 문서가 존재한다면, 토론 문서도 이름을 변경해주세요.",
        "apihelp-opensearch-description": "OpenSearch 프로토콜을 이용하여 위키 검색하기",
        "apihelp-opensearch-param-search": "문자열 검색",
        "apihelp-opensearch-param-limit": "반환할 결과의 최대 수",
+       "apihelp-opensearch-param-format": "출력 포맷.",
        "apihelp-options-param-reset": "사이트 기본으로 설정 초기화",
        "apihelp-options-example-reset": "모든 설정 초기화",
+       "apihelp-paraminfo-param-helpformat": "도움말 문자열 포맷.",
+       "apihelp-protect-param-reason": "보호 또는 보호 해제의 이유.",
        "apihelp-protect-example-protect": "문서 보호",
        "apihelp-query+allmessages-example-ipb": "<kbd>ipb-</kbd>로 시작하는 메시지를 보입니다.",
        "apihelp-query+allrevisions-description": "모든 판 표시.",
index 3cab41c..12ec506 100644 (file)
@@ -11,6 +11,7 @@
        "apihelp-createaccount-param-name": "Navê bikarhêner.",
        "apihelp-delete-description": "Rûpelekê jê bibe.",
        "apihelp-delete-example-simple": "<kbd>Main Page</kbd>ê jê bibe.",
+       "apihelp-edit-description": "Rûpelan çêke û biguherîne.",
        "apihelp-edit-param-sectiontitle": "Sernavê bo beşeke nû.",
        "apihelp-edit-param-text": "Naveroka rûpelê.",
        "apihelp-edit-param-minor": "Guhertina biçûk.",
        "apihelp-feedcontributions-param-deletedonly": "Tenê beşdariyên jêbirî nîşan bide.",
        "apihelp-feedrecentchanges-example-simple": "Guherandinên dawî nîşan bide.",
        "apihelp-feedrecentchanges-example-30days": "Guherandinên dawî yên 30 rojan nîşan bide",
+       "apihelp-filerevert-param-comment": "Şîroveyê bar bike.",
        "apihelp-help-example-recursive": "Hemû alîkarî di rûpelekê de.",
        "apihelp-login-param-name": "Navê bikarhêner.",
        "apihelp-login-param-password": "Şîfre.",
        "apihelp-login-example-login": "Têkeve.",
        "apihelp-move-param-reason": "Sedemê bo guherandina nav.",
        "apihelp-move-param-ignorewarnings": "Guh nede hişyariyan.",
+       "apihelp-opensearch-example-te": "Rûpelên ku bi <kbd>Te</kbd> dest pê dikin bibîne.",
+       "apihelp-parse-example-page": "Rûpelekê analîz bike.",
+       "apihelp-parse-example-summary": "Kurteyekê analîz bike",
+       "apihelp-protect-description": "Asta parastinê ya rûpelekê biguherîne.",
+       "apihelp-protect-example-protect": "Rûpelekê biparêze.",
        "apihelp-tag-param-reason": "Sedemê bo guherandinê."
 }
index 97de05f..8240e08 100644 (file)
        "apihelp-parse-example-text": "Расчлени викитекст.",
        "apihelp-parse-example-texttitle": "Расчлени страница, укажувајќи го насловот на страницата.",
        "apihelp-parse-example-summary": "Расчлени опис.",
-       "apihelp-patrol-description": "Испатролирај страница или ревизија.",
+       "apihelp-patrol-description": "Испатролирај страница или преработка.",
        "apihelp-patrol-param-rcid": "Назнака на спорешните промени за патролирање.",
        "apihelp-patrol-param-revid": "Назнака на преработката за патролирање.",
        "apihelp-patrol-example-rcid": "Испатролирај скорешна промена",
        "apihelp-upload-param-offset": "Зафатнина на делот во бајти.",
        "apihelp-upload-param-chunk": "Содржина на делот.",
        "apihelp-upload-param-async": "Направи ги работите со потенцијално големи податотеки неусогласени, кога е можно.",
-       "apihelp-upload-param-asyncdownload": "Направи го добивањето на URL-адреса неусогласено.",
-       "apihelp-upload-param-leavemessage": "Ако се користи неусогласено преземање, остави порака на страницата за разговор на корисникот ако е готово.",
-       "apihelp-upload-param-statuskey": "Дај ја состојбата на подигнатост за овој податотечен клуч (подигање по URL).",
        "apihelp-upload-param-checkstatus": "Дај ја состојбата на подигнатост само за дадениот податотечен клуч.",
        "apihelp-upload-example-url": "Подигни од URL",
        "apihelp-userrights-param-user": "Корисничко име.",
        "apihelp-watch-example-unwatch": "Отстрани ја страницата <kbd>Главна страница</kbd> од набљудуваните.",
        "apihelp-watch-example-generator": "Набљудувај ги првите неколку страници во главниот именски простор",
        "apihelp-format-example-generic": "Дај го исходот од барањето во $1-формат.",
-       "apihelp-dbg-description": "Давај го изводот во PHP-форматот <code>var_export()</code> .",
-       "apihelp-dbgfm-description": "Давај го изводот во PHP-форматот <code>var_export()</code> (подобрен испис во HTML).",
        "apihelp-json-description": "Давај го изводот во JSON-формат.",
        "apihelp-json-param-callback": "Ако е укажано, го обвива изводот во даден повик на функција. За безбедност, ќе се ограничат сите податоци што се однесуваат на корисниците.",
        "apihelp-json-param-utf8": "Ако е укажано, ги шифрира највеќето (но не сите) не-ASCII знаци како UTF-8 наместо да ги заменува со хексадецимални изводни низи. Ова е стандардно кога <var>formatversion</var> не е <kbd>1</kbd>.",
        "apihelp-php-param-formatversion": "Форматирање на изводот:\n;1:Назадно-складен формат (булови во XML-стил, клучеви <samp>*</samp> за содржински јазли и тн.).\n;2:Пробен современ формат. Поединостите може да се изменат!\n;најнов:Користење на најновиот формат (тековно <kbd>2</kbd>), може да се смени без предупредување.",
        "apihelp-phpfm-description": "Давај го изводот во серијализиран PHP-формат (подобрен испис во HTML).",
        "apihelp-rawfm-description": "Давај го изводот со елементи за отстранување грешки во JSON-формат (подобрен испис во HTML).",
-       "apihelp-txt-description": "Давај го изводот во PHP-форматот <code>print_r()</code>.",
-       "apihelp-txtfm-description": "Давај го изводот во PHP-форматот <code>print_r()</code> (подобрен испис во HTML).",
        "apihelp-xml-description": "Давај го изводот во XML-формат.",
        "apihelp-xml-param-xslt": "Ако е укажано, ја додава именуваната страница како XSL-стилска страница. Вредноста мора да биде наслов во именскиот простор „{{ns:mediawiki}}“ што ќе завршува со <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Ако е укажано, додава именски простор XML.",
        "apihelp-xmlfm-description": "Давај го изводот во XML-формат (подобрен испис во HTML).",
-       "apihelp-yaml-description": "Давај го изводот во YAML-формат.",
-       "apihelp-yamlfm-description": "Давај го изводот во YAML-формат (подобрен испис во HTML).",
        "api-format-title": "Резултат од Извршникот на МедијаВики",
        "api-format-prettyprint-header": "Ова е HTML-претстава на форматот $1. HTML е добар за отстранување на грешки, но не е погоден за употреба во извршник.\n\nУкажете го параметарот <var>format</var> за да го смените изводниот формат. За да ги видите претставите на форматот $1 вон HTML, задајте <kbd>format=$2</kbd>.\n\nПовеќе информации ќе најдете на [[mw:API|целосната документација]], или пак [[Special:ApiHelp/main|помош со извршникот]].",
-       "api-orm-param-props": "Полиња за пребарување.",
-       "api-orm-param-limit": "Макс. број на редови во изводот.",
        "api-pageset-param-titles": "Список на наслови на кои ќе се работи",
        "api-pageset-param-pageids": "Список на назнаки за страници на кои ќе се работи",
        "api-pageset-param-revids": "Список на назнаки на преработки на кои ќе се работи",
index 0c9f12f..c82512a 100644 (file)
        "apihelp-main-param-curtimestamp": "Dołącz obecny znacznik czasu do wyniku.",
        "apihelp-block-description": "Zablokuj użytkownika.",
        "apihelp-block-param-user": "Nazwa użytkownika, adres IP lub zakres adresów IP, które chcesz zablokować.",
+       "apihelp-block-param-expiry": "Czas trwania. Może być względny (np. <kbd>5 months</kbd> or <kbd>2 weeks</kbd>) lub konkretny (np. <kbd>2014-09-18T12:34:56Z</kbd>). Jeśli jest ustawiony na <kbd>infinite</kbd>, <kbd>indefinite</kbd>, lub <kbd>never</kbd>, blokada nigdy nie wygaśnie.",
        "apihelp-block-param-reason": "Powód blokady.",
        "apihelp-block-param-anononly": "Blokuj tylko anonimowych użytkowników (blokuje anonimowe edycje z tego adresu IP).",
        "apihelp-block-param-nocreate": "Zapobiegnij utworzeniu konta.",
        "apihelp-block-param-autoblock": "Zablokuj ostatni adres IP tego użytkownika i automatycznie wszystkie kolejne, z których będzie się logował.",
+       "apihelp-block-param-noemail": "Uniemożliwia użytkownikowi wysyłanie wiadomości e-mail za pośrednictwem interfejsu wiki. (Wymagane uprawnienie <code>blockemail</code>).",
        "apihelp-block-param-hidename": "Ukryj nazwę użytkownika z rejestru blokad. (Wymagane uprawnienia <code>hideuser</code>)",
+       "apihelp-block-param-allowusertalk": "Pozwala użytkownikowi edytować własną stronę dyskusji (zależy od <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
        "apihelp-block-param-reblock": "Jeżeli ten użytkownik jest już zablokowany, nadpisz blokadę.",
        "apihelp-block-param-watchuser": "Obserwuj stronę użytkownika i jego IP oraz ich strony dyskusji.",
        "apihelp-block-example-ip-simple": "Zablokuj IP <kbd>192.0.2.5</kbd> na 3 dni za <kbd>Pierwszy atak</kbd>.",
        "apihelp-query+watchlist-paramvalue-prop-comment": "Dodaje komentarz do edycji.",
        "apihelp-query+watchlist-paramvalue-prop-timestamp": "Dodaje znacznik czasu edycji.",
        "apihelp-query+watchlist-paramvalue-prop-sizes": "Dodaje starą i nową długość strony.",
+       "apihelp-resetpassword-description": "Wyślij użytkownikowi e-mail do resetowania hasła.",
        "apihelp-stashedit-param-title": "Tytuł edytowanej strony.",
        "apihelp-stashedit-param-sectiontitle": "Tytuł nowej sekcji.",
        "apihelp-stashedit-param-text": "Zawartość strony.",
        "api-help-param-multi-max": "Maksymalna liczba wartości to {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} dla botów).",
        "api-help-param-default": "Domyślnie: $1",
        "api-help-param-default-empty": "Domyślnie: <span class=\"apihelp-empty\">(puste)</span>",
+       "api-help-param-token": "Token \"$1\" zdobyty z [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
        "api-help-param-no-description": "<span class=\"apihelp-empty\">(bez opisu)</span>",
        "api-help-examples": "{{PLURAL:$1|Przykład|Przykłady}}:",
        "api-help-permissions": "{{PLURAL:$2|Uprawnienie|Uprawnienia}}:",
index 4ded4aa..6137457 100644 (file)
@@ -41,6 +41,8 @@
        "apihelp-block-param-watchuser": "{{doc-apihelp-param|block|watchuser}}",
        "apihelp-block-example-ip-simple": "{{doc-apihelp-example|block}}",
        "apihelp-block-example-user-complex": "{{doc-apihelp-example|block}}",
+       "apihelp-changeauthenticationdata-description": "{{doc-apihelp-description|changeauthenticationdata}}",
+       "apihelp-changeauthenticationdata-example-password": "{{doc-apihelp-example|changeauthenticationdata}}",
        "apihelp-checktoken-description": "{{doc-apihelp-description|checktoken}}",
        "apihelp-checktoken-param-type": "{{doc-apihelp-param|checktoken|type}}",
        "apihelp-checktoken-param-token": "{{doc-apihelp-param|checktoken|token}}",
@@ -48,6 +50,9 @@
        "apihelp-checktoken-example-simple": "{{doc-apihelp-example|checktoken}}",
        "apihelp-clearhasmsg-description": "{{doc-apihelp-description|clearhasmsg}}",
        "apihelp-clearhasmsg-example-1": "{{doc-apihelp-example|clearhasmsg}}",
+       "apihelp-clientlogin-description": "{{doc-apihelp-description|clientlogin}}",
+       "apihelp-clientlogin-example-login": "{{doc-apihelp-example|clientlogin}}",
+       "apihelp-clientlogin-example-login2": "{{doc-apihelp-example|clientlogin}}",
        "apihelp-compare-description": "{{doc-apihelp-description|compare}}",
        "apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}",
        "apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}",
@@ -57,6 +62,7 @@
        "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
        "apihelp-compare-example-1": "{{doc-apihelp-example|compare}}",
        "apihelp-createaccount-description": "{{doc-apihelp-description|createaccount}}",
+       "apihelp-createaccount-example-create": "{{doc-apihelp-example|createaccount}}",
        "apihelp-createaccount-param-name": "{{doc-apihelp-param|createaccount|name}}\n{{Identical|Username}}",
        "apihelp-createaccount-param-password": "{{doc-apihelp-param|createaccount|password}}",
        "apihelp-createaccount-param-domain": "{{doc-apihelp-param|createaccount|domain}}",
        "apihelp-import-param-namespace": "{{doc-apihelp-param|import|namespace}}",
        "apihelp-import-param-rootpage": "{{doc-apihelp-param|import|rootpage}}",
        "apihelp-import-example-import": "{{doc-apihelp-example|import}}",
-       "apihelp-login-description": "{{doc-apihelp-description|login}}",
+       "apihelp-linkaccount-description": "{{doc-apihelp-description|linkaccount}}",
+       "apihelp-linkaccount-example-link": "{{doc-apihelp-example|linkaccount}}",
+       "apihelp-login-description": "{{doc-apihelp-description|login|info=This message is used when <code>$wgEnableBotPasswords</code> is true.|seealso=* {{msg-mw|apihelp-login-description-nobotpasswords}}}}",
+       "apihelp-login-description-nobotpasswords": "{{doc-apihelp-description|login|info=This message is used when <code>$wgEnableBotPasswords</code> is false.|seealso=* {{msg-mw|apihelp-login-description}}}}",
+       "apihelp-login-description-nonauthmanager": "{{doc-apihelp-description|login|info=This message is used when <code>$wgDisableAuthManager</code> is true.|seealso=* {{msg-mw|apihelp-login-description}}}}",
        "apihelp-login-param-name": "{{doc-apihelp-param|login|name}}\n{{Identical|Username}}",
        "apihelp-login-param-password": "{{doc-apihelp-param|login|password}}\n{{Identical|Password}}",
        "apihelp-login-param-domain": "{{doc-apihelp-param|login|domain}}",
        "apihelp-query+allusers-param-activeusers": "{{doc-apihelp-param|query+allusers|activeusers|params=* $1 - Value of [[mw:Manual:$wgActiveUserDays]]|paramstart=2}}",
        "apihelp-query+allusers-param-attachedwiki": "{{doc-apihelp-param|query+allusers|attachedwiki}}",
        "apihelp-query+allusers-example-Y": "{{doc-apihelp-example|query+allusers}}",
+       "apihelp-query+authmanagerinfo-description": "{{doc-apihelp-description|query+authmanagerinfo}}",
+       "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "{{doc-apihelp-param|query+authmanagerinfo|securitysensitiveoperation}}",
+       "apihelp-query+authmanagerinfo-param-requestsfor": "{{doc-apihelp-param|query+authmanagerinfo|requestsfor}}",
+       "apihelp-query+filerepoinfo-example-login": "{{doc-apihelp-example|query+filerepoinfo}}",
+       "apihelp-query+filerepoinfo-example-login-merged": "{{doc-apihelp-example|query+filerepoinfo}}",
+       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "{{doc-apihelp-example|query+filerepoinfo}}",
        "apihelp-query+backlinks-description": "{{doc-apihelp-description|query+backlinks}}",
        "apihelp-query+backlinks-param-title": "{{doc-apihelp-param|query+backlinks|title}}",
        "apihelp-query+backlinks-param-pageid": "{{doc-apihelp-param|query+backlinks|pageid}}",
        "apihelp-query+siteinfo-paramvalue-prop-variables": "{{doc-apihelp-paramvalue|query+siteinfo|prop|variables}}",
        "apihelp-query+siteinfo-paramvalue-prop-protocols": "{{doc-apihelp-paramvalue|query+siteinfo|prop|protocols}}",
        "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "{{doc-apihelp-paramvalue|query+siteinfo|prop|defaultoptions}}",
+       "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "{{doc-apihelp-paramvalue|query+siteinfo|prop|uploaddialog}}",
        "apihelp-query+siteinfo-param-filteriw": "{{doc-apihelp-param|query+siteinfo|filteriw}}",
        "apihelp-query+siteinfo-param-showalldb": "{{doc-apihelp-param|query+siteinfo|showalldb}}",
        "apihelp-query+siteinfo-param-numberingroup": "{{doc-apihelp-param|query+siteinfo|numberingroup}}",
        "apihelp-query+users-paramvalue-prop-emailable": "{{doc-apihelp-paramvalue|query+users|prop|emailable}}",
        "apihelp-query+users-paramvalue-prop-gender": "{{doc-apihelp-paramvalue|query+users|prop|gender}}",
        "apihelp-query+users-paramvalue-prop-centralids": "{{doc-apihelp-paramvalue|query+users|prop|centralids}}",
+       "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-token": "{{doc-apihelp-param|query+users|token}}",
        "apihelp-query+watchlistraw-param-totitle": "{{doc-apihelp-param|query+watchlistraw|totitle}}",
        "apihelp-query+watchlistraw-example-simple": "{{doc-apihelp-example|query+watchlistraw}}",
        "apihelp-query+watchlistraw-example-generator": "{{doc-apihelp-example|query+watchlistraw}}",
+       "apihelp-removeauthenticationdata-description": "{{doc-apihelp-description|removeauthenticationdata}}",
+       "apihelp-removeauthenticationdata-example-simple": "{{doc-apihelp-example|removeauthenticationdata}}",
+       "apihelp-resetpassword-description": "{{doc-apihelp-description|resetpassword|seealso=* {{msg-mw|apihelp-resetpassword-description-noroutes}}}}",
+       "apihelp-resetpassword-description-noroutes": "{{doc-apihelp-description|resetpassword|info=This message is used when no known routes are enabled in <var>[[mw:Manual:$wgPasswordResetRoutes|$wgPasswordResetRoutes]]</var>.|seealso={{msg-mw|apihelp-resetpassword-description}}}}",
+       "apihelp-resetpassword-param-user": "{{doc-apihelp-param|resetpassword|user}}",
+       "apihelp-resetpassword-param-email": "{{doc-apihelp-param|resetpassword|email}}",
+       "apihelp-resetpassword-param-capture": "{{doc-apihelp-param|resetpassword|capture}}",
+       "apihelp-resetpassword-example-user": "{{doc-apihelp-example|resetpassword}}",
+       "apihelp-resetpassword-example-email": "{{doc-apihelp-example|resetpassword}}",
        "apihelp-revisiondelete-description": "{{doc-apihelp-description|revisiondelete}}",
        "apihelp-revisiondelete-param-type": "{{doc-apihelp-param|revisiondelete|type}}",
        "apihelp-revisiondelete-param-target": "{{doc-apihelp-param|revisiondelete|target}}",
        "apihelp-undelete-param-watchlist": "{{doc-apihelp-param|undelete|watchlist}}",
        "apihelp-undelete-example-page": "{{doc-apihelp-example|undelete}}",
        "apihelp-undelete-example-revisions": "{{doc-apihelp-example|undelete}}",
+       "apihelp-unlinkaccount-description": "{{doc-apihelp-description|unlinkaccount}}",
+       "apihelp-unlinkaccount-example-simple": "{{doc-apihelp-example|unlinkaccount}}",
        "apihelp-upload-description": "{{doc-apihelp-description|upload}}",
        "apihelp-upload-param-filename": "{{doc-apihelp-param|upload|filename}}",
        "apihelp-upload-param-comment": "{{doc-apihelp-param|upload|comment}}",
        "api-help-permissions-granted-to": "Used to introduce the list of groups each permission is assigned to.\n\nParameters:\n* $1 - Number of groups\n* $2 - List of group names, comma-separated",
        "api-help-right-apihighlimits": "{{technical}}{{doc-right|apihighlimits|prefix=api-help}}\nThis message is used instead of {{msg-mw|right-apihighlimits}} in the API help to display the actual limits.\n\nParameters:\n* $1 - Limit for slow queries\n* $2 - Limit for fast queries",
        "api-help-open-in-apisandbox": "Text for the link to open an API example in [[Special:ApiSandbox]].",
+       "api-help-authmanager-general-usage": "Text giving a brief overview of how to use an AuthManager-using API module. Parameters:\n* $1 - Module parameter prefix, e.g. \"login\"\n* $2 - Module name, e.g. \"clientlogin\"\n* $3 - Module path, e.g. \"clientlogin\"\n* $4 - AuthManager action to use with this module.\n* $5 - Token type needed by the module.",
+       "api-help-authmanagerhelper-requests": "{{doc-apihelp-param|description=the \"requests\" parameter for AuthManager-using API modules|params=* $1 - AuthManager action used by this module|paramstart=2|noseealso=1}}",
+       "api-help-authmanagerhelper-request": "{{doc-apihelp-param|description=the \"request\" parameter for AuthManager-using API modules|params=* $1 - AuthManager action used by this module|paramstart=2|noseealso=1}}",
+       "api-help-authmanagerhelper-messageformat": "{{doc-apihelp-param|description=the \"messageformat\" parameter for AuthManager-using API modules|noseealso=1}}",
+       "api-help-authmanagerhelper-mergerequestfields": "{{doc-apihelp-param|description=the \"mergerequestfields\" parameter for AuthManager-using API modules|noseealso=1}}",
+       "api-help-authmanagerhelper-preservestate": "{{doc-apihelp-param|description=the \"preservestate\" parameter for AuthManager-using API modules|noseealso=1}}",
+       "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\"",
        "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 14e32ba..5de1dee 100644 (file)
        "apihelp-managetags-example-delete": "Radera <kbd>vandalims</kbd> taggen med andledningen: <kbd>Felstavat</kbd>",
        "apihelp-managetags-example-activate": "Aktivera en tagg med namn <kbd>spam</kbd> med anledningen: <kbd>For use in edit patrolling</kbd>",
        "apihelp-managetags-example-deactivate": "Inaktivera en tagg vid namn <kbd>spam</kbd> med anledningen: <kbd>No longer required</kbd>",
+       "apihelp-mergehistory-description": "Sammanfoga sidhistoriker.",
+       "apihelp-mergehistory-param-reason": "Orsaken till sammanfogning av historik.",
+       "apihelp-mergehistory-example-merge": "Sammanfoga hela historiken för <kbd>Oldpage</kbd> i <kbd>Newpage</kbd>.",
+       "apihelp-mergehistory-example-merge-timestamp": "Sammanfoga den sidversion av <kbd>Oldpage</kbd> daterad fram till <kbd>2015-12-31T04:37:41Z</kbd> till <kbd>Newpage</kbd>.",
        "apihelp-move-description": "Flytta en sida.",
        "apihelp-move-param-from": "Titeln på sidan du vill flytta. Kan inte användas tillsammans med <var>$1fromid</var>.",
        "apihelp-move-param-fromid": "Sid-ID för sidan att byta namn. Kan inte användas tillsammans med <var>$1from</var>.",
        "apihelp-query+allcategories-param-min": "Returnera endast kategorier med minst så här många medlemmar.",
        "apihelp-query+allcategories-param-max": "Returnera endast kategorier med som mest så här många medlemmar.",
        "apihelp-query+allcategories-param-limit": "Hur många kategorier att returnera.",
+       "apihelp-query+allcategories-paramvalue-prop-size": "Lägger till antal sidor i kategorin.",
+       "apihelp-query+allcategories-paramvalue-prop-hidden": "Märker kategorier som är dolda med <code>_&#95;HIDDENCAT_&#95;</code>.",
        "apihelp-query+alldeletedrevisions-description": "Lista alla raderade revisioner av en användare or inom en namnrymd.",
        "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Kan endast användas med <var>$3user</var>.",
        "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Kan inte användas med <var>$3user</var>.",
index 6e2ff5a..4a4989f 100644 (file)
@@ -15,6 +15,8 @@
        "apihelp-block-param-reason": "Lý do cấm.",
        "apihelp-block-param-nocreate": "Cấm tạo tài khoản.",
        "apihelp-block-param-reblock": "Nếu người dùng này đã bị cấm, ghi đè lên vụ cấm đã tồn tại.",
+       "apihelp-block-param-watchuser": "Xem người dùng hoặc địa chỉ IP của người dùng và trang thảo luận.",
+       "apihelp-block-example-ip-simple": "Khóa địa chỉ IP <kbd>192.0.2.5</kbd> trong ba ngày với lý do <kbd>khiển trách lần đầu</kbd>.",
        "apihelp-checktoken-param-type": "Kiểu dấu hiệu được kiểm thử.",
        "apihelp-checktoken-param-token": "Dấu hiệu để kiểm thử.",
        "apihelp-checktoken-example-simple": "Kiểm thử dấu hiệu <kbd>csrf</kbd> có hợp lệ hay không.",
@@ -62,6 +64,7 @@
        "apihelp-edit-param-undo": "Hoàn tác sửa đổi này. Ghi đè $1text, $1prependtext và $ 1appendtext.",
        "apihelp-edit-param-undoafter": "Hoàn tác tất cả các sửa đổi từ $1undo cho tới sửa đổi này. Nếu không được thiết lập, chỉ cần lùi lại một sửa đổi.",
        "apihelp-edit-param-redirect": "Tự động giải quyết các chuyển hướng.",
+       "apihelp-edit-param-contentmodel": "Mô hình nội dung của nội dung mới.",
        "apihelp-edit-example-edit": "Sửa đổi trang",
        "apihelp-edit-example-prepend": "Đưa <kbd>_&#95;NOTOC_&#95;</kbd> vào đầu trang",
        "apihelp-edit-example-undo": "Lùi sửa các thay đổi 13579–13585 và tự động tóm lược",
        "apihelp-login-param-token": "Dấu hiệu đăng nhập được lấy trong yêu cầu đầu tiên.",
        "apihelp-login-example-gettoken": "Lấy dấu hiệu đăng nhập",
        "apihelp-login-example-login": "Đăng nhập",
+       "apihelp-logout-description": "Thoát ra và xóa dữ liệu phiên làm việc.",
        "apihelp-logout-example-logout": "Đăng xuất người dùng hiện tại",
        "apihelp-mergehistory-description": "Hợp nhất lịch sử trang.",
        "apihelp-mergehistory-param-reason": "Lý do hợp nhất lịch sử.",
        "apihelp-move-description": "Di chuyển trang.",
+       "apihelp-move-param-to": "Đặt tiêu đề để đổi tên trang.",
        "apihelp-move-param-reason": "Lý do đổi tên.",
+       "apihelp-move-param-movetalk": "Đổi tên trang thảo luận, nếu nó tồn tại.",
        "apihelp-move-param-movesubpages": "Đổi tên trang con, nếu có thể áp dụng.",
        "apihelp-move-param-noredirect": "Không tạo trang đổi hướng.",
        "apihelp-move-param-ignorewarnings": "Bỏ qua tất cả các cảnh báo.",
        "apihelp-opensearch-param-search": "Chuỗi tìm kiếm.",
        "apihelp-opensearch-param-limit": "Đa số kết quả để cho ra.",
        "apihelp-opensearch-param-namespace": "Không gian tên để tìm kiếm.",
+       "apihelp-opensearch-param-suggest": "Không làm gì nếu <var> [[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> sai/lỗi.",
        "apihelp-opensearch-param-format": "Định dạng kết quả được cho ra.",
        "apihelp-opensearch-example-te": "Tìm trang bắt đầu với <kbd>Te</kbd>.",
        "apihelp-options-example-reset": "Mặc định lại các tùy chọn",
        "apihelp-paraminfo-description": "Lấy thông tin về các module API.",
        "apihelp-paraminfo-param-helpformat": "Định dạng chuỗi trợ giúp.",
        "apihelp-parse-param-summary": "Lời tóm lược để phân tích.",
+       "apihelp-parse-param-prop": "Những mẩu thông tin nào muốn có:",
        "apihelp-parse-param-section": "Chỉ phân tích nội dung của số phần này.\n\nNếu có <kbd>new</kbd> thì phân tích <var>$1text</var> và <var>$1sectiontitle</var> như thể thêm phần mới vào trang.\n\nPhần <kbd>new</kbd> chỉ được chấp nhận khi định rõ <var>text</var>.",
        "apihelp-parse-param-disablelimitreport": "Bỏ qua thông báo bộ tiền xử lý (“NewPP limit report”) khi cho ra kết quả bộ xử lý.",
        "apihelp-parse-example-page": "Phân tích trang.",
        "apihelp-query-param-prop": "Các thuộc tính để lấy khi truy vấn các trang.",
        "apihelp-query-param-list": "Các danh sách để lấy.",
        "apihelp-query-param-meta": "Siêu dữ liệu để lấy.",
+       "apihelp-query+allcategories-description": "Liệt kê tất cả các thể loại.",
+       "apihelp-query+allcategories-param-from": "Chọn thể loại để bắt đầu đếm.",
+       "apihelp-query+allcategories-param-to": "Chọn thể loại để dừng đếm.",
        "apihelp-query+allcategories-param-dir": "Hướng xếp loại.",
+       "apihelp-query+allcategories-param-limit": "Có bao nhiêu thể loại được trả về.",
+       "apihelp-query+allfileusages-param-limit": "Có bao nhiêu số hạng mục được trả về.",
+       "apihelp-query+allimages-param-limit": "Có bao nhiêu hình ảnh trong tổng số được trả về.",
+       "apihelp-query+alllinks-param-limit": "Có bao nhiêu số hạng mục được trả về.",
+       "apihelp-query+allpages-param-limit": "Có bao nhiêu trang được trả về.",
+       "apihelp-query+allredirects-param-limit": "Có bao nhiêu số hạng mục được trả về.",
+       "apihelp-query+mystashedfiles-param-limit": "Có bao nhiêu tập tin nhận được.",
+       "apihelp-query+alltransclusions-param-limit": "Có bao nhiêu số hạng mục được trả về.",
+       "apihelp-query+allusers-param-limit": "Có bao nhiêu tên người dùng được trả về.",
+       "apihelp-query+backlinks-param-limit": "Tất cả có bao nhiêu trang trả về. Nếu <var>$1redirect</var> được kích hoạt, giới hạn áp dụng cho mỗi cấp độ riêng biệt (có nghĩa là lên đến 2*<var>$1limit</var> kết quả có thể được trả lại).",
+       "apihelp-query+categories-param-limit": "Có bao nhiêu thể loại được trả về.",
+       "apihelp-query+extlinks-param-limit": "Có bao nhiêu liên kết được trả về.",
+       "apihelp-query+exturlusage-param-limit": "Có bao nhiêu trang được trả về.",
+       "apihelp-query+filearchive-param-limit": "Tổng cộng có bao nhiêu hình ảnh được trả về.",
+       "apihelp-query+fileusage-param-limit": "Có bao nhiêu được trả về.",
+       "apihelp-query+images-param-limit": "Có bao nhiêu tập tin được trả về.",
+       "apihelp-query+langbacklinks-param-limit": "Tổng cộng có bao nhiêu trang được trả về.",
+       "apihelp-query+links-param-limit": "Có bao nhiêu liên kết được trả về.",
+       "apihelp-query+linkshere-param-limit": "Có bao nhiêu được trả về.",
+       "apihelp-query+logevents-param-limit": "Tổng cộng có bao nhiêu bài viết sự kiện được trả về.",
+       "apihelp-query+transcludedin-param-limit": "Có bao nhiêu được trả về.",
+       "apihelp-query+watchlist-param-limit": "Cả bao nhiêu kết quả được trả về trên mỗi yêu cầu.",
        "apihelp-rollback-description": "Lùi lại sửa đổi cuối cùng của trang này.\n\nNếu người dùng cuối cùng đã sửa đổi trang này nhiều lần, tất cả chúng sẽ được lùi lại cùng một lúc.",
        "apihelp-format-example-generic": "Cho ra kết quả truy vấn dưới dạng $1.",
        "apihelp-json-description": "Cho ra dữ liệu dưới dạng JSON.",
index c3ea00a..810062f 100644 (file)
@@ -47,6 +47,7 @@
        "apihelp-block-param-watchuser": "监视用户或该 IP 的用户页和讨论页。",
        "apihelp-block-example-ip-simple": "封禁IP地址<kbd>192.0.2.5</kbd>三天,原因<kbd>First strike</kbd>。",
        "apihelp-block-example-user-complex": "无限期封禁用户<kbd>Vandal</kbd>,原因<kbd>Vandalism</kbd>,并阻止新账户创建和电子邮件发送。",
+       "apihelp-changeauthenticationdata-description": "更改当前用户的身份验证数据。",
        "apihelp-checktoken-description": "从<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>检查令牌有效性。",
        "apihelp-checktoken-param-type": "已开始测试的令牌类型。",
        "apihelp-checktoken-param-token": "要测试的令牌。",
@@ -54,6 +55,7 @@
        "apihelp-checktoken-example-simple": "测试<kbd>csrf</kbd>令牌的有效性。",
        "apihelp-clearhasmsg-description": "清除当前用户的<code>hasmsg</code>标记。",
        "apihelp-clearhasmsg-example-1": "清除当前用户的<code>hasmsg</code>标记。",
+       "apihelp-clientlogin-description": "使用交互式流登录wiki。",
        "apihelp-compare-description": "获取2个页面之间的差别。\n\n用于“from”和“to”的修订版本号、页面标题或页面 ID 必须获得通过。",
        "apihelp-compare-param-fromtitle": "要比较的第一个标题。",
        "apihelp-compare-param-fromid": "要比较的第一个页面 ID。",
@@ -63,6 +65,7 @@
        "apihelp-compare-param-torev": "要比较的第二个修订版本。",
        "apihelp-compare-example-1": "在版本1和2中创建差异。",
        "apihelp-createaccount-description": "创建一个新用户账户。",
+       "apihelp-createaccount-example-create": "开始创建用户<kbd>Example</kbd>和密码<kbd>ExamplePassword</kbd>的流程。",
        "apihelp-createaccount-param-name": "用户名。",
        "apihelp-createaccount-param-password": "密码(如果设置<var>$1mailpassword</var>则忽略)。",
        "apihelp-createaccount-param-domain": "外部身份验证域 (可选)。",
        "apihelp-import-param-namespace": "导入至此名字空间。不能与<var>$1rootpage</var>一起使用。",
        "apihelp-import-param-rootpage": "作为此页面的子页面导入。不能与<var>$1namespace</var>一起使用。",
        "apihelp-import-example-import": "将页面[[meta:Help:ParserFunctions]]连带完整历史导入至100名字空间。",
+       "apihelp-linkaccount-description": "将来自第三方提供商的账户链接至当前用户。",
        "apihelp-login-description": "登录并获得身份验证Cookie。\n\n在成功登录的情况下,所需的Cookie将包含在HTTP响应头中。在登录失败的情况下,进一步的尝试可能会被自动密码猜解攻击的限制所遏制。",
+       "apihelp-login-description-nonauthmanager": "登录并获得身份验证Cookie。\n\n在成功登录的情况下,所需的Cookie将包含在HTTP响应头中。在登录失败的情况下,进一步的尝试可能会被自动密码猜解攻击的限制所遏制。",
        "apihelp-login-param-name": "用户名。",
        "apihelp-login-param-password": "密码。",
        "apihelp-login-param-domain": "域名(可选)。",
        "apihelp-mergehistory-param-fromid": "将被合并历史的页面的页面ID。不能与<var>$1from</var>一起使用。",
        "apihelp-mergehistory-param-to": "将要合并历史的页面的标题。不能与<var>$1toid</var>一起使用。",
        "apihelp-mergehistory-param-toid": "将要合并历史的页面的页面ID。不能与<var>$1to</var>一起使用。",
+       "apihelp-mergehistory-param-timestamp": "指定时间戳,决定源页面的哪些修订历史被移动到目标页面的历史中。如果省略,源页面的所有历史记录都将被合并到目标页面。",
        "apihelp-mergehistory-param-reason": "历史合并的原因。",
        "apihelp-mergehistory-example-merge": "将<kbd>Oldpage</kbd>的完整历史合并至<kbd>Newpage</kbd>。",
        "apihelp-mergehistory-example-merge-timestamp": "将<kbd>Oldpage</kbd>直到<kbd>2015-12-31T04:37:41Z</kbd>的页面修订版本合并至<kbd>Newpage</kbd>。",
        "apihelp-query+pageswithprop-param-dir": "排序的方向。",
        "apihelp-query+pageswithprop-example-simple": "列出前10个使用<code>&#123;&#123;DISPLAYTITLE:&#125;&#125;</code>的页面。",
        "apihelp-query+pageswithprop-example-generator": "获取有关前10个使用<code>_&#95;NOTOC_&#95;</code>的页面的额外信息。",
-       "apihelp-query+prefixsearch-description": "为页面标题执行前缀搜索。\n\nDespite the similarity in names, this module is not intended to be equivalent to [[Special:PrefixIndex]]; for that, see <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd> with the <kbd>apprefix</kbd> parameter. The purpose of this module is similar to <kbd>[[Special:ApiHelp/opensearch|action=opensearch]]</kbd>: to take user input and provide the best-matching titles. Depending on the search engine backend, this might include typo correction, redirect avoidance, or other heuristics.",
+       "apihelp-query+prefixsearch-description": "执行页面标题的带前缀搜索。\n\n尽管名称类似,但此模块不等于[[Special:PrefixIndex]];详见<kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>中的<kbd>apprefix</kbd>参数。此模块的目的类似<kbd>[[Special:ApiHelp/opensearch|action=opensearch]]</kbd>:基于用户的输入提供最佳匹配的标题。取决于搜索引擎后端,这可能包括错拼纠正、避免重定向和其他启发性行为。",
        "apihelp-query+prefixsearch-param-search": "搜索字符串。",
        "apihelp-query+prefixsearch-param-namespace": "搜索的名字空间。",
        "apihelp-query+prefixsearch-param-limit": "要返回的结果最大数。",
        "apihelp-query+watchlistraw-param-totitle": "要列举的最终标题(带名字空间前缀)。",
        "apihelp-query+watchlistraw-example-simple": "列出当前用户的监视列表中的页面。",
        "apihelp-query+watchlistraw-example-generator": "检索当前用户监视列表上的页面的页面信息。",
+       "apihelp-resetpassword-description": "向用户发送密码重置邮件。",
+       "apihelp-resetpassword-param-user": "正在重置的用户。",
+       "apihelp-resetpassword-param-email": "正在重置用户的电子邮件地址。",
+       "apihelp-resetpassword-param-capture": "返回已发送的临时密码。需要<code>passwordreset</code>用户权限。",
+       "apihelp-resetpassword-example-user": "向用户<kbd>Example</kbd>发送密码重置邮件。",
+       "apihelp-resetpassword-example-email": "向所有电子邮件地址为<kbd>user@example.com</kbd>的用户发送密码重置邮件。",
        "apihelp-revisiondelete-description": "删除和恢复修订版本。",
        "apihelp-revisiondelete-param-type": "正在执行的修订版本删除类型。",
        "apihelp-revisiondelete-param-target": "要进行修订版本删除的页面标题,如果对某一类型需要。",
        "apihelp-upload-param-sessionkey": "与$1filekey相同,基于向后兼容而维护。",
        "apihelp-upload-param-stash": "如果设置,服务器将临时藏匿文件而不是加入存储库。",
        "apihelp-upload-param-filesize": "全部上传的文件大小。",
-       "apihelp-upload-param-offset": "块的偏移量(字节)。",
+       "apihelp-upload-param-offset": "数据块的偏移量(字节)。",
        "apihelp-upload-param-chunk": "大块内容。",
        "apihelp-upload-param-async": "在可能的情况下,使潜在的大文件操作异步进行。",
        "apihelp-upload-param-checkstatus": "只检索指定文件密钥的上传状态。",
        "api-help-permissions-granted-to": "{{PLURAL:$1|授予}}:$2",
        "api-help-right-apihighlimits": "在API查询中使用更高的上限(慢查询:$1;快查询:$2)。慢查询的限制也适用于多值参数。",
        "api-help-open-in-apisandbox": "<small>[在沙盒中打开]</small>",
+       "api-help-authmanagerhelper-messageformat": "返回消息使用的格式。",
        "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/。"
 }
diff --git a/includes/auth/AbstractAuthenticationProvider.php b/includes/auth/AbstractAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..9e38ecc
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Config;
+use Psr\Log\LoggerInterface;
+
+/**
+ * A base class that implements some of the boilerplate for an AuthenticationProvider
+ * @ingroup Auth
+ * @since 1.27
+ */
+abstract class AbstractAuthenticationProvider implements AuthenticationProvider {
+       /** @var LoggerInterface */
+       protected $logger;
+       /** @var AuthManager */
+       protected $manager;
+       /** @var Config */
+       protected $config;
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       public function setManager( AuthManager $manager ) {
+               $this->manager = $manager;
+       }
+
+       public function setConfig( Config $config ) {
+               $this->config = $config;
+       }
+
+       /**
+        * @inheritdoc
+        * @note Override this if it makes sense to support more than one instance
+        */
+       public function getUniqueId() {
+               return static::class;
+       }
+}
diff --git a/includes/auth/AbstractPasswordPrimaryAuthenticationProvider.php b/includes/auth/AbstractPasswordPrimaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..900d2e5
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Password;
+use PasswordFactory;
+use Status;
+
+/**
+ * Basic framework for a primary authentication provider that uses passwords
+ * @ingroup Auth
+ * @since 1.27
+ */
+abstract class AbstractPasswordPrimaryAuthenticationProvider
+       extends AbstractPrimaryAuthenticationProvider
+{
+       /** @var bool Whether this provider should ABSTAIN (false) or FAIL (true) on password failure */
+       protected $authoritative = true;
+
+       private $passwordFactory = null;
+
+       /**
+        * @param array $params Settings
+        *  - authoritative: Whether this provider should ABSTAIN (false) or FAIL
+        *    (true) on password failure
+        */
+       public function __construct( array $params = [] ) {
+               $this->authoritative = !isset( $params['authoritative'] ) || (bool)$params['authoritative'];
+       }
+
+       /**
+        * Get the PasswordFactory
+        * @return PasswordFactory
+        */
+       protected function getPasswordFactory() {
+               if ( $this->passwordFactory === null ) {
+                       $this->passwordFactory = new PasswordFactory();
+                       $this->passwordFactory->init( $this->config );
+               }
+               return $this->passwordFactory;
+       }
+
+       /**
+        * Get a Password object from the hash
+        * @param string $hash
+        * @return Password
+        */
+       protected function getPassword( $hash ) {
+               $passwordFactory = $this->getPasswordFactory();
+               try {
+                       return $passwordFactory->newFromCiphertext( $hash );
+               } catch ( \PasswordError $e ) {
+                       $class = static::class;
+                       $this->logger->debug( "Invalid password hash in {$class}::getPassword()" );
+                       return $passwordFactory->newFromCiphertext( null );
+               }
+       }
+
+       /**
+        * Return the appropriate response for failure
+        * @param PasswordAuthenticationRequest $req
+        * @return AuthenticationResponse
+        */
+       protected function failResponse( PasswordAuthenticationRequest $req ) {
+               if ( $this->authoritative ) {
+                       return AuthenticationResponse::newFail(
+                               wfMessage( $req->password === '' ? 'wrongpasswordempty' : 'wrongpassword' )
+                       );
+               } else {
+                       return AuthenticationResponse::newAbstain();
+               }
+       }
+
+       /**
+        * Check that the password is valid
+        *
+        * This should be called *before* validating the password. If the result is
+        * not ok, login should fail immediately.
+        *
+        * @param string $username
+        * @param string $password
+        * @return Status
+        */
+       protected function checkPasswordValidity( $username, $password ) {
+               return \User::newFromName( $username )->checkPasswordValidity( $password );
+       }
+
+       /**
+        * Check if the password should be reset
+        *
+        * This should be called after a successful login. It sets 'reset-pass'
+        * authentication data if necessary, see
+        * ResetPassSecondaryAuthenticationProvider.
+        *
+        * @param string $username
+        * @param Status $status From $this->checkPasswordValidity()
+        * @param mixed $data Passed through to $this->getPasswordResetData()
+        */
+       protected function setPasswordResetFlag( $username, Status $status, $data = null ) {
+               $reset = $this->getPasswordResetData( $username, $data );
+
+               if ( !$reset && $this->config->get( 'InvalidPasswordReset' ) && !$status->isGood() ) {
+                       $reset = (object)[
+                               'msg' => $status->getMessage( 'resetpass-validity-soft' ),
+                               'hard' => false,
+                       ];
+               }
+
+               if ( $reset ) {
+                       $this->manager->setAuthenticationSessionData( 'reset-pass', $reset );
+               }
+       }
+
+       /**
+        * Get password reset data, if any
+        *
+        * @param string $username
+        * @param mixed $data
+        * @return object|null { 'hard' => bool, 'msg' => Message }
+        */
+       protected function getPasswordResetData( $username, $data ) {
+               return null;
+       }
+
+       /**
+        * Get expiration date for a new password, if any
+        *
+        * @param string $username
+        * @return string|null
+        */
+       protected function getNewPasswordExpiry( $username ) {
+               $days = $this->config->get( 'PasswordExpirationDays' );
+               $expires = $days ? wfTimestamp( TS_MW, time() + $days * 86400 ) : null;
+
+               // Give extensions a chance to force an expiration
+               \Hooks::run( 'ResetPasswordExpiration', [ \User::newFromName( $username ), &$expires ] );
+
+               return $expires;
+       }
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               switch ( $action ) {
+                       case AuthManager::ACTION_LOGIN:
+                       case AuthManager::ACTION_REMOVE:
+                       case AuthManager::ACTION_CREATE:
+                       case AuthManager::ACTION_CHANGE:
+                               return [ new PasswordAuthenticationRequest() ];
+                       default:
+                               return [];
+               }
+       }
+}
diff --git a/includes/auth/AbstractPreAuthenticationProvider.php b/includes/auth/AbstractPreAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..48a9c88
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * A base class that implements some of the boilerplate for a PreAuthenticationProvider
+ * @ingroup Auth
+ * @since 1.27
+ */
+abstract class AbstractPreAuthenticationProvider extends AbstractAuthenticationProvider
+       implements PreAuthenticationProvider
+{
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               return [];
+       }
+
+       public function testForAuthentication( array $reqs ) {
+               return \StatusValue::newGood();
+       }
+
+       public function postAuthentication( $user, AuthenticationResponse $response ) {
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               return \StatusValue::newGood();
+       }
+
+       public function testUserForCreation( $user, $autocreate ) {
+               return \StatusValue::newGood();
+       }
+
+       public function postAccountCreation( $user, $creator, AuthenticationResponse $response ) {
+       }
+
+       public function testForAccountLink( $user ) {
+               return \StatusValue::newGood();
+       }
+
+       public function postAccountLink( $user, AuthenticationResponse $response ) {
+       }
+
+}
diff --git a/includes/auth/AbstractPrimaryAuthenticationProvider.php b/includes/auth/AbstractPrimaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..2e0d669
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use User;
+
+/**
+ * A base class that implements some of the boilerplate for a PrimaryAuthenticationProvider
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+abstract class AbstractPrimaryAuthenticationProvider extends AbstractAuthenticationProvider
+       implements PrimaryAuthenticationProvider
+{
+
+       public function continuePrimaryAuthentication( array $reqs ) {
+               throw new \BadMethodCallException( __METHOD__ . ' is not implemented.' );
+       }
+
+       public function postAuthentication( $user, AuthenticationResponse $response ) {
+       }
+
+       public function testUserCanAuthenticate( $username ) {
+               // Assume it can authenticate if it exists
+               return $this->testUserExists( $username );
+       }
+
+       /**
+        * @inheritdoc
+        * @note Reimplement this if you do anything other than
+        *  User::getCanonicalName( $req->username ) to determine the user being
+        *  authenticated.
+        */
+       public function providerNormalizeUsername( $username ) {
+               $name = User::getCanonicalName( $username );
+               return $name === false ? null : $name;
+       }
+
+       /**
+        * @inheritdoc
+        * @note Reimplement this if self::getAuthenticationRequests( AuthManager::ACTION_REMOVE )
+        *  doesn't return requests that will revoke all access for the user.
+        */
+       public function providerRevokeAccessForUser( $username ) {
+               $reqs = $this->getAuthenticationRequests(
+                       AuthManager::ACTION_REMOVE, [ 'username' => $username ]
+               );
+               foreach ( $reqs as $req ) {
+                       $req->username = $username;
+                       $req->action = AuthManager::ACTION_REMOVE;
+                       $this->providerChangeAuthenticationData( $req );
+               }
+       }
+
+       public function providerAllowsPropertyChange( $property ) {
+               return true;
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               return \StatusValue::newGood();
+       }
+
+       public function continuePrimaryAccountCreation( $user, $creator, array $reqs ) {
+               throw new \BadMethodCallException( __METHOD__ . ' is not implemented.' );
+       }
+
+       public function finishAccountCreation( $user, $creator, AuthenticationResponse $response ) {
+               return null;
+       }
+
+       public function postAccountCreation( $user, $creator, AuthenticationResponse $response ) {
+       }
+
+       public function testUserForCreation( $user, $autocreate ) {
+               return \StatusValue::newGood();
+       }
+
+       public function autoCreatedAccount( $user, $source ) {
+       }
+
+       public function beginPrimaryAccountLink( $user, array $reqs ) {
+               if ( $this->accountCreationType() === self::TYPE_LINK ) {
+                       throw new \BadMethodCallException( __METHOD__ . ' is not implemented.' );
+               } else {
+                       throw new \BadMethodCallException(
+                               __METHOD__ . ' should not be called on a non-link provider.'
+                       );
+               }
+       }
+
+       public function continuePrimaryAccountLink( $user, array $reqs ) {
+               throw new \BadMethodCallException( __METHOD__ . ' is not implemented.' );
+       }
+
+       public function postAccountLink( $user, AuthenticationResponse $response ) {
+       }
+
+}
diff --git a/includes/auth/AbstractSecondaryAuthenticationProvider.php b/includes/auth/AbstractSecondaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..89fd6f9
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * A base class that implements some of the boilerplate for a SecondaryAuthenticationProvider
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+abstract class AbstractSecondaryAuthenticationProvider extends AbstractAuthenticationProvider
+       implements SecondaryAuthenticationProvider
+{
+
+       public function continueSecondaryAuthentication( $user, array $reqs ) {
+               throw new \BadMethodCallException( __METHOD__ . ' is not implemented.' );
+       }
+
+       public function postAuthentication( $user, AuthenticationResponse $response ) {
+       }
+
+       public function providerAllowsPropertyChange( $property ) {
+               return true;
+       }
+
+       /**
+        * @inheritdoc
+        * @note Reimplement this if self::getAuthenticationRequests( AuthManager::ACTION_REMOVE )
+        *  doesn't return requests that will revoke all access for the user.
+        */
+       public function providerRevokeAccessForUser( $username ) {
+               $reqs = $this->getAuthenticationRequests(
+                       AuthManager::ACTION_REMOVE, [ 'username' => $username ]
+               );
+               foreach ( $reqs as $req ) {
+                       $req->username = $username;
+                       $this->providerChangeAuthenticationData( $req );
+               }
+       }
+
+       public function providerAllowsAuthenticationDataChange(
+               AuthenticationRequest $req, $checkData = true
+       ) {
+               return \StatusValue::newGood( 'ignored' );
+       }
+
+       public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               return \StatusValue::newGood();
+       }
+
+       public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               throw new \BadMethodCallException( __METHOD__ . ' is not implemented.' );
+       }
+
+       public function postAccountCreation( $user, $creator, AuthenticationResponse $response ) {
+       }
+
+       public function testUserForCreation( $user, $autocreate ) {
+               return \StatusValue::newGood();
+       }
+
+       public function autoCreatedAccount( $user, $source ) {
+       }
+}
diff --git a/includes/auth/AuthManager.php b/includes/auth/AuthManager.php
new file mode 100644 (file)
index 0000000..efee53c
--- /dev/null
@@ -0,0 +1,2386 @@
+<?php
+/**
+ * Authentication (and possibly Authorization in the future) system entry point
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Config;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Status;
+use StatusValue;
+use User;
+use WebRequest;
+
+/**
+ * This serves as the entry point to the authentication system.
+ *
+ * In the future, it may also serve as the entry point to the authorization
+ * system.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class AuthManager implements LoggerAwareInterface {
+       /** Log in with an existing (not necessarily local) user */
+       const ACTION_LOGIN = 'login';
+       /** Continue a login process that was interrupted by the need for user input or communication
+        * with an external provider */
+       const ACTION_LOGIN_CONTINUE = 'login-continue';
+       /** Create a new user */
+       const ACTION_CREATE = 'create';
+       /** Continue a user creation process that was interrupted by the need for user input or
+        * communication with an external provider */
+       const ACTION_CREATE_CONTINUE = 'create-continue';
+       /** Link an existing user to a third-party account */
+       const ACTION_LINK = 'link';
+       /** Continue a user linking process that was interrupted by the need for user input or
+        * communication with an external provider */
+       const ACTION_LINK_CONTINUE = 'link-continue';
+       /** Change a user's credentials */
+       const ACTION_CHANGE = 'change';
+       /** Remove a user's credentials */
+       const ACTION_REMOVE = 'remove';
+       /** Like ACTION_REMOVE but for linking providers only */
+       const ACTION_UNLINK = 'unlink';
+
+       /** Security-sensitive operations are ok. */
+       const SEC_OK = 'ok';
+       /** Security-sensitive operations should re-authenticate. */
+       const SEC_REAUTH = 'reauth';
+       /** Security-sensitive should not be performed. */
+       const SEC_FAIL = 'fail';
+
+       /** Auto-creation is due to SessionManager */
+       const AUTOCREATE_SOURCE_SESSION = \MediaWiki\Session\SessionManager::class;
+
+       /** @var AuthManager|null */
+       private static $instance = null;
+
+       /** @var WebRequest */
+       private $request;
+
+       /** @var Config */
+       private $config;
+
+       /** @var LoggerInterface */
+       private $logger;
+
+       /** @var AuthenticationProvider[] */
+       private $allAuthenticationProviders = [];
+
+       /** @var PreAuthenticationProvider[] */
+       private $preAuthenticationProviders = null;
+
+       /** @var PrimaryAuthenticationProvider[] */
+       private $primaryAuthenticationProviders = null;
+
+       /** @var SecondaryAuthenticationProvider[] */
+       private $secondaryAuthenticationProviders = null;
+
+       /** @var CreatedAccountAuthenticationRequest[] */
+       private $createdAccountAuthenticationRequests = [];
+
+       /**
+        * Get the global AuthManager
+        * @return AuthManager
+        */
+       public static function singleton() {
+               global $wgDisableAuthManager;
+
+               if ( $wgDisableAuthManager ) {
+                       throw new \BadMethodCallException( '$wgDisableAuthManager is set' );
+               }
+
+               if ( self::$instance === null ) {
+                       self::$instance = new self(
+                               \RequestContext::getMain()->getRequest(),
+                               \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+                       );
+               }
+               return self::$instance;
+       }
+
+       /**
+        * @param WebRequest $request
+        * @param Config $config
+        */
+       public function __construct( WebRequest $request, Config $config ) {
+               $this->request = $request;
+               $this->config = $config;
+               $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' ) );
+       }
+
+       /**
+        * @param LoggerInterface $logger
+        */
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * @return WebRequest
+        */
+       public function getRequest() {
+               return $this->request;
+       }
+
+       /**
+        * Force certain PrimaryAuthenticationProviders
+        * @deprecated For backwards compatibility only
+        * @param PrimaryAuthenticationProvider[] $providers
+        * @param string $why
+        */
+       public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
+               $this->logger->warning( "Overriding AuthManager primary authn because $why" );
+
+               if ( $this->primaryAuthenticationProviders !== null ) {
+                       $this->logger->warning(
+                               'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
+                       );
+
+                       $this->allAuthenticationProviders = array_diff_key(
+                               $this->allAuthenticationProviders,
+                               $this->primaryAuthenticationProviders
+                       );
+                       $session = $this->request->getSession();
+                       $session->remove( 'AuthManager::authnState' );
+                       $session->remove( 'AuthManager::accountCreationState' );
+                       $session->remove( 'AuthManager::accountLinkState' );
+                       $this->createdAccountAuthenticationRequests = [];
+               }
+
+               $this->primaryAuthenticationProviders = [];
+               foreach ( $providers as $provider ) {
+                       if ( !$provider instanceof PrimaryAuthenticationProvider ) {
+                               throw new \RuntimeException(
+                                       'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
+                                               get_class( $provider )
+                               );
+                       }
+                       $provider->setLogger( $this->logger );
+                       $provider->setManager( $this );
+                       $provider->setConfig( $this->config );
+                       $id = $provider->getUniqueId();
+                       if ( isset( $this->allAuthenticationProviders[$id] ) ) {
+                               throw new \RuntimeException(
+                                       "Duplicate specifications for id $id (classes " .
+                                               get_class( $provider ) . ' and ' .
+                                               get_class( $this->allAuthenticationProviders[$id] ) . ')'
+                               );
+                       }
+                       $this->allAuthenticationProviders[$id] = $provider;
+                       $this->primaryAuthenticationProviders[$id] = $provider;
+               }
+       }
+
+       /**
+        * Call a legacy AuthPlugin method, if necessary
+        * @codeCoverageIgnore
+        * @deprecated For backwards compatibility only, should be avoided in new code
+        * @param string $method AuthPlugin method to call
+        * @param array $params Parameters to pass
+        * @param mixed $return Return value if AuthPlugin wasn't called
+        * @return mixed Return value from the AuthPlugin method, or $return
+        */
+       public static function callLegacyAuthPlugin( $method, array $params, $return = null ) {
+               global $wgAuth;
+
+               if ( $wgAuth && !$wgAuth instanceof AuthManagerAuthPlugin ) {
+                       return call_user_func_array( [ $wgAuth, $method ], $params );
+               } else {
+                       return $return;
+               }
+       }
+
+       /**
+        * @name Authentication
+        * @{
+        */
+
+       /**
+        * Indicate whether user authentication is possible
+        *
+        * It may not be if the session is provided by something like OAuth
+        * for which each individual request includes authentication data.
+        *
+        * @return bool
+        */
+       public function canAuthenticateNow() {
+               return $this->request->getSession()->canSetUser();
+       }
+
+       /**
+        * Start an authentication flow
+        * @param AuthenticationRequest[] $reqs
+        * @param string $returnToUrl Url that REDIRECT responses should eventually
+        *  return to.
+        * @return AuthenticationResponse See self::continueAuthentication()
+        */
+       public function beginAuthentication( array $reqs, $returnToUrl ) {
+               $session = $this->request->getSession();
+               if ( !$session->canSetUser() ) {
+                       // Caller should have called canAuthenticateNow()
+                       $session->remove( 'AuthManager::authnState' );
+                       throw new \LogicException( 'Authentication is not possible now' );
+               }
+
+               $guessUserName = null;
+               foreach ( $reqs as $req ) {
+                       $req->returnToUrl = $returnToUrl;
+                       // @codeCoverageIgnoreStart
+                       if ( $req->username !== null && $req->username !== '' ) {
+                               if ( $guessUserName === null ) {
+                                       $guessUserName = $req->username;
+                               } elseif ( $guessUserName !== $req->username ) {
+                                       $guessUserName = null;
+                                       break;
+                               }
+                       }
+                       // @codeCoverageIgnoreEnd
+               }
+
+               // Check for special-case login of a just-created account
+               $req = AuthenticationRequest::getRequestByClass(
+                       $reqs, CreatedAccountAuthenticationRequest::class
+               );
+               if ( $req ) {
+                       if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
+                               throw new \LogicException(
+                                       'CreatedAccountAuthenticationRequests are only valid on ' .
+                                               'the same AuthManager that created the account'
+                               );
+                       }
+
+                       $user = User::newFromName( $req->username );
+                       // @codeCoverageIgnoreStart
+                       if ( !$user ) {
+                               throw new \UnexpectedValueException(
+                                       "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
+                               );
+                       } elseif ( $user->getId() != $req->id ) {
+                               throw new \UnexpectedValueException(
+                                       "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
+                               );
+                       }
+                       // @codeCoverageIgnoreEnd
+
+                       $this->logger->info( 'Logging in {user} after account creation', [
+                               'user' => $user->getName(),
+                       ] );
+                       $ret = AuthenticationResponse::newPass( $user->getName() );
+                       $this->setSessionDataForUser( $user );
+                       $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
+                       $session->remove( 'AuthManager::authnState' );
+                       \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
+                       return $ret;
+               }
+
+               $this->removeAuthenticationSessionData( null );
+
+               foreach ( $this->getPreAuthenticationProviders() as $provider ) {
+                       $status = $provider->testForAuthentication( $reqs );
+                       if ( !$status->isGood() ) {
+                               $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
+                               $ret = AuthenticationResponse::newFail(
+                                       Status::wrap( $status )->getMessage()
+                               );
+                               $this->callMethodOnProviders( 7, 'postAuthentication',
+                                       [ User::newFromName( $guessUserName ) ?: null, $ret ]
+                               );
+                               \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, null, $guessUserName ] );
+                               return $ret;
+                       }
+               }
+
+               $state = [
+                       'reqs' => $reqs,
+                       'returnToUrl' => $returnToUrl,
+                       'guessUserName' => $guessUserName,
+                       'primary' => null,
+                       'primaryResponse' => null,
+                       'secondary' => [],
+                       'maybeLink' => [],
+                       'continueRequests' => [],
+               ];
+
+               // Preserve state from a previous failed login
+               $req = AuthenticationRequest::getRequestByClass(
+                       $reqs, CreateFromLoginAuthenticationRequest::class
+               );
+               if ( $req ) {
+                       $state['maybeLink'] = $req->maybeLink;
+               }
+
+               $session = $this->request->getSession();
+               $session->setSecret( 'AuthManager::authnState', $state );
+               $session->persist();
+
+               return $this->continueAuthentication( $reqs );
+       }
+
+       /**
+        * Continue an authentication flow
+        *
+        * Return values are interpreted as follows:
+        * - status FAIL: Authentication failed. If $response->createRequest is
+        *   set, that may be passed to self::beginAuthentication() or to
+        *   self::beginAccountCreation() (after adding a username, if necessary)
+        *   to preserve state.
+        * - status REDIRECT: The client should be redirected to the contained URL,
+        *   new AuthenticationRequests should be made (if any), then
+        *   AuthManager::continueAuthentication() should be called.
+        * - status UI: The client should be presented with a user interface for
+        *   the fields in the specified AuthenticationRequests, then new
+        *   AuthenticationRequests should be made, then
+        *   AuthManager::continueAuthentication() should be called.
+        * - status RESTART: The user logged in successfully with a third-party
+        *   service, but the third-party credentials aren't attached to any local
+        *   account. This could be treated as a UI or a FAIL.
+        * - status PASS: Authentication was successful.
+        *
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse
+        */
+       public function continueAuthentication( array $reqs ) {
+               $session = $this->request->getSession();
+               try {
+                       if ( !$session->canSetUser() ) {
+                               // Caller should have called canAuthenticateNow()
+                               // @codeCoverageIgnoreStart
+                               throw new \LogicException( 'Authentication is not possible now' );
+                               // @codeCoverageIgnoreEnd
+                       }
+
+                       $state = $session->getSecret( 'AuthManager::authnState' );
+                       if ( !is_array( $state ) ) {
+                               return AuthenticationResponse::newFail(
+                                       wfMessage( 'authmanager-authn-not-in-progress' )
+                               );
+                       }
+                       $state['continueRequests'] = [];
+
+                       $guessUserName = $state['guessUserName'];
+
+                       foreach ( $reqs as $req ) {
+                               $req->returnToUrl = $state['returnToUrl'];
+                       }
+
+                       // Step 1: Choose an primary authentication provider, and call it until it succeeds.
+
+                       if ( $state['primary'] === null ) {
+                               // We haven't picked a PrimaryAuthenticationProvider yet
+                               // @codeCoverageIgnoreStart
+                               $guessUserName = null;
+                               foreach ( $reqs as $req ) {
+                                       if ( $req->username !== null && $req->username !== '' ) {
+                                               if ( $guessUserName === null ) {
+                                                       $guessUserName = $req->username;
+                                               } elseif ( $guessUserName !== $req->username ) {
+                                                       $guessUserName = null;
+                                                       break;
+                                               }
+                                       }
+                               }
+                               $state['guessUserName'] = $guessUserName;
+                               // @codeCoverageIgnoreEnd
+                               $state['reqs'] = $reqs;
+
+                               foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
+                                       $res = $provider->beginPrimaryAuthentication( $reqs );
+                                       switch ( $res->status ) {
+                                               case AuthenticationResponse::PASS;
+                                                       $state['primary'] = $id;
+                                                       $state['primaryResponse'] = $res;
+                                                       $this->logger->debug( "Primary login with $id succeeded" );
+                                                       break 2;
+                                               case AuthenticationResponse::FAIL;
+                                                       $this->logger->debug( "Login failed in primary authentication by $id" );
+                                                       if ( $res->createRequest || $state['maybeLink'] ) {
+                                                               $res->createRequest = new CreateFromLoginAuthenticationRequest(
+                                                                       $res->createRequest, $state['maybeLink']
+                                                               );
+                                                       }
+                                                       $this->callMethodOnProviders( 7, 'postAuthentication',
+                                                               [ User::newFromName( $guessUserName ) ?: null, $res ]
+                                                       );
+                                                       $session->remove( 'AuthManager::authnState' );
+                                                       \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
+                                                       return $res;
+                                               case AuthenticationResponse::ABSTAIN;
+                                                       // Continue loop
+                                                       break;
+                                               case AuthenticationResponse::REDIRECT;
+                                               case AuthenticationResponse::UI;
+                                                       $this->logger->debug( "Primary login with $id returned $res->status" );
+                                                       $state['primary'] = $id;
+                                                       $state['continueRequests'] = $res->neededRequests;
+                                                       $session->setSecret( 'AuthManager::authnState', $state );
+                                                       return $res;
+
+                                                       // @codeCoverageIgnoreStart
+                                               default:
+                                                       throw new \DomainException(
+                                                               get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
+                                                       );
+                                                       // @codeCoverageIgnoreEnd
+                                       }
+                               }
+                               if ( $state['primary'] === null ) {
+                                       $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
+                                       $ret = AuthenticationResponse::newFail(
+                                               wfMessage( 'authmanager-authn-no-primary' )
+                                       );
+                                       $this->callMethodOnProviders( 7, 'postAuthentication',
+                                               [ User::newFromName( $guessUserName ) ?: null, $ret ]
+                                       );
+                                       $session->remove( 'AuthManager::authnState' );
+                                       return $ret;
+                               }
+                       } elseif ( $state['primaryResponse'] === null ) {
+                               $provider = $this->getAuthenticationProvider( $state['primary'] );
+                               if ( !$provider instanceof PrimaryAuthenticationProvider ) {
+                                       // Configuration changed? Force them to start over.
+                                       // @codeCoverageIgnoreStart
+                                       $ret = AuthenticationResponse::newFail(
+                                               wfMessage( 'authmanager-authn-not-in-progress' )
+                                       );
+                                       $this->callMethodOnProviders( 7, 'postAuthentication',
+                                               [ User::newFromName( $guessUserName ) ?: null, $ret ]
+                                       );
+                                       $session->remove( 'AuthManager::authnState' );
+                                       return $ret;
+                                       // @codeCoverageIgnoreEnd
+                               }
+                               $id = $provider->getUniqueId();
+                               $res = $provider->continuePrimaryAuthentication( $reqs );
+                               switch ( $res->status ) {
+                                       case AuthenticationResponse::PASS;
+                                               $state['primaryResponse'] = $res;
+                                               $this->logger->debug( "Primary login with $id succeeded" );
+                                               break;
+                                       case AuthenticationResponse::FAIL;
+                                               $this->logger->debug( "Login failed in primary authentication by $id" );
+                                               if ( $res->createRequest || $state['maybeLink'] ) {
+                                                       $res->createRequest = new CreateFromLoginAuthenticationRequest(
+                                                               $res->createRequest, $state['maybeLink']
+                                                       );
+                                               }
+                                               $this->callMethodOnProviders( 7, 'postAuthentication',
+                                                       [ User::newFromName( $guessUserName ) ?: null, $res ]
+                                               );
+                                               $session->remove( 'AuthManager::authnState' );
+                                               \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
+                                               return $res;
+                                       case AuthenticationResponse::REDIRECT;
+                                       case AuthenticationResponse::UI;
+                                               $this->logger->debug( "Primary login with $id returned $res->status" );
+                                               $state['continueRequests'] = $res->neededRequests;
+                                               $session->setSecret( 'AuthManager::authnState', $state );
+                                               return $res;
+                                       default:
+                                               throw new \DomainException(
+                                                       get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
+                                               );
+                               }
+                       }
+
+                       $res = $state['primaryResponse'];
+                       if ( $res->username === null ) {
+                               $provider = $this->getAuthenticationProvider( $state['primary'] );
+                               if ( !$provider instanceof PrimaryAuthenticationProvider ) {
+                                       // Configuration changed? Force them to start over.
+                                       // @codeCoverageIgnoreStart
+                                       $ret = AuthenticationResponse::newFail(
+                                               wfMessage( 'authmanager-authn-not-in-progress' )
+                                       );
+                                       $this->callMethodOnProviders( 7, 'postAuthentication',
+                                               [ User::newFromName( $guessUserName ) ?: null, $ret ]
+                                       );
+                                       $session->remove( 'AuthManager::authnState' );
+                                       return $ret;
+                                       // @codeCoverageIgnoreEnd
+                               }
+
+                               if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
+                                       $res->linkRequest &&
+                                        // don't confuse the user with an incorrect message if linking is disabled
+                                       $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
+                               ) {
+                                       $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
+                                       $msg = 'authmanager-authn-no-local-user-link';
+                               } else {
+                                       $msg = 'authmanager-authn-no-local-user';
+                               }
+                               $this->logger->debug(
+                                       "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
+                               );
+                               $ret = AuthenticationResponse::newRestart( wfMessage( $msg ) );
+                               $ret->neededRequests = $this->getAuthenticationRequestsInternal(
+                                       self::ACTION_LOGIN,
+                                       [],
+                                       $this->getPrimaryAuthenticationProviders() + $this->getSecondaryAuthenticationProviders()
+                               );
+                               if ( $res->createRequest || $state['maybeLink'] ) {
+                                       $ret->createRequest = new CreateFromLoginAuthenticationRequest(
+                                               $res->createRequest, $state['maybeLink']
+                                       );
+                                       $ret->neededRequests[] = $ret->createRequest;
+                               }
+                               $session->setSecret( 'AuthManager::authnState', [
+                                       'reqs' => [], // Will be filled in later
+                                       'primary' => null,
+                                       'primaryResponse' => null,
+                                       'secondary' => [],
+                                       'continueRequests' => $ret->neededRequests,
+                               ] + $state );
+                               return $ret;
+                       }
+
+                       // Step 2: Primary authentication succeeded, create the User object
+                       // (and add the user locally if necessary)
+
+                       $user = User::newFromName( $res->username, 'usable' );
+                       if ( !$user ) {
+                               throw new \DomainException(
+                                       get_class( $provider ) . " returned an invalid username: {$res->username}"
+                               );
+                       }
+                       if ( $user->getId() === 0 ) {
+                               // User doesn't exist locally. Create it.
+                               $this->logger->info( 'Auto-creating {user} on login', [
+                                       'user' => $user->getName(),
+                               ] );
+                               $status = $this->autoCreateUser( $user, $state['primary'], false );
+                               if ( !$status->isGood() ) {
+                                       $ret = AuthenticationResponse::newFail(
+                                               Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
+                                       );
+                                       $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
+                                       $session->remove( 'AuthManager::authnState' );
+                                       \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
+                                       return $ret;
+                               }
+                       }
+
+                       // Step 3: Iterate over all the secondary authentication providers.
+
+                       $beginReqs = $state['reqs'];
+
+                       foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
+                               if ( !isset( $state['secondary'][$id] ) ) {
+                                       // This provider isn't started yet, so we pass it the set
+                                       // of reqs from beginAuthentication instead of whatever
+                                       // might have been used by a previous provider in line.
+                                       $func = 'beginSecondaryAuthentication';
+                                       $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
+                               } elseif ( !$state['secondary'][$id] ) {
+                                       $func = 'continueSecondaryAuthentication';
+                                       $res = $provider->continueSecondaryAuthentication( $user, $reqs );
+                               } else {
+                                       continue;
+                               }
+                               switch ( $res->status ) {
+                                       case AuthenticationResponse::PASS;
+                                               $this->logger->debug( "Secondary login with $id succeeded" );
+                                               // fall through
+                                       case AuthenticationResponse::ABSTAIN;
+                                               $state['secondary'][$id] = true;
+                                               break;
+                                       case AuthenticationResponse::FAIL;
+                                               $this->logger->debug( "Login failed in secondary authentication by $id" );
+                                               $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
+                                               $session->remove( 'AuthManager::authnState' );
+                                               \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, $user, $user->getName() ] );
+                                               return $res;
+                                       case AuthenticationResponse::REDIRECT;
+                                       case AuthenticationResponse::UI;
+                                               $this->logger->debug( "Secondary login with $id returned " . $res->status );
+                                               $state['secondary'][$id] = false;
+                                               $state['continueRequests'] = $res->neededRequests;
+                                               $session->setSecret( 'AuthManager::authnState', $state );
+                                               return $res;
+
+                                               // @codeCoverageIgnoreStart
+                                       default:
+                                               throw new \DomainException(
+                                                       get_class( $provider ) . "::{$func}() returned $res->status"
+                                               );
+                                               // @codeCoverageIgnoreEnd
+                               }
+                       }
+
+                       // Step 4: Authentication complete! Set the user in the session and
+                       // clean up.
+
+                       $this->logger->info( 'Login for {user} succeeded', [
+                               'user' => $user->getName(),
+                       ] );
+                       $req = AuthenticationRequest::getRequestByClass(
+                               $beginReqs, RememberMeAuthenticationRequest::class
+                       );
+                       $this->setSessionDataForUser( $user, $req && $req->rememberMe );
+                       $ret = AuthenticationResponse::newPass( $user->getName() );
+                       $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
+                       $session->remove( 'AuthManager::authnState' );
+                       $this->removeAuthenticationSessionData( null );
+                       \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
+                       return $ret;
+               } catch ( \Exception $ex ) {
+                       $session->remove( 'AuthManager::authnState' );
+                       throw $ex;
+               }
+       }
+
+       /**
+        * Whether security-sensitive operations should proceed.
+        *
+        * A "security-sensitive operation" is something like a password or email
+        * change, that would normally have a "reenter your password to confirm"
+        * box if we only supported password-based authentication.
+        *
+        * @param string $operation Operation being checked. This should be a
+        *  message-key-like string such as 'change-password' or 'change-email'.
+        * @return string One of the SEC_* constants.
+        */
+       public function securitySensitiveOperationStatus( $operation ) {
+               $status = self::SEC_OK;
+
+               $this->logger->debug( __METHOD__ . ": Checking $operation" );
+
+               $session = $this->request->getSession();
+               $aId = $session->getUser()->getId();
+               if ( $aId === 0 ) {
+                       // User isn't authenticated. DWIM?
+                       $status = $this->canAuthenticateNow() ? self::SEC_REAUTH : self::SEC_FAIL;
+                       $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
+                       return $status;
+               }
+
+               if ( $session->canSetUser() ) {
+                       $id = $session->get( 'AuthManager:lastAuthId' );
+                       $last = $session->get( 'AuthManager:lastAuthTimestamp' );
+                       if ( $id !== $aId || $last === null ) {
+                               $timeSinceLogin = PHP_INT_MAX; // Forever ago
+                       } else {
+                               $timeSinceLogin = max( 0, time() - $last );
+                       }
+
+                       $thresholds = $this->config->get( 'ReauthenticateTime' );
+                       if ( isset( $thresholds[$operation] ) ) {
+                               $threshold = $thresholds[$operation];
+                       } elseif ( isset( $thresholds['default'] ) ) {
+                               $threshold = $thresholds['default'];
+                       } else {
+                               throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
+                       }
+
+                       if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
+                               $status = self::SEC_REAUTH;
+                       }
+               } else {
+                       $timeSinceLogin = -1;
+
+                       $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
+                       if ( isset( $pass[$operation] ) ) {
+                               $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
+                       } elseif ( isset( $pass['default'] ) ) {
+                               $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
+                       } else {
+                               throw new \UnexpectedValueException(
+                                       '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
+                               );
+                       }
+               }
+
+               \Hooks::run( 'SecuritySensitiveOperationStatus', [
+                       &$status, $operation, $session, $timeSinceLogin
+               ] );
+
+               // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
+               if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
+                       $status = self::SEC_FAIL;
+               }
+
+               $this->logger->info( __METHOD__ . ": $operation is $status" );
+
+               return $status;
+       }
+
+       /**
+        * Determine whether a username can authenticate
+        *
+        * @param string $username
+        * @return bool
+        */
+       public function userCanAuthenticate( $username ) {
+               foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
+                       if ( $provider->testUserCanAuthenticate( $username ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Provide normalized versions of the username for security checks
+        *
+        * Since different providers can normalize the input in different ways,
+        * this returns an array of all the different ways the name might be
+        * normalized for authentication.
+        *
+        * The returned strings should not be revealed to the user, as that might
+        * leak private information (e.g. an email address might be normalized to a
+        * username).
+        *
+        * @param string $username
+        * @return string[]
+        */
+       public function normalizeUsername( $username ) {
+               $ret = [];
+               foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
+                       $normalized = $provider->providerNormalizeUsername( $username );
+                       if ( $normalized !== null ) {
+                               $ret[$normalized] = true;
+                       }
+               }
+               return array_keys( $ret );
+       }
+
+       /**@}*/
+
+       /**
+        * @name Authentication data changing
+        * @{
+        */
+
+       /**
+        * Revoke any authentication credentials for a user
+        *
+        * After this, the user should no longer be able to log in.
+        *
+        * @param string $username
+        */
+       public function revokeAccessForUser( $username ) {
+               $this->logger->info( 'Revoking access for {user}', [
+                       'user' => $username,
+               ] );
+               $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
+       }
+
+       /**
+        * Validate a change of authentication data (e.g. passwords)
+        * @param AuthenticationRequest $req
+        * @param bool $checkData If false, $req hasn't been loaded from the
+        *  submission so checks on user-submitted fields should be skipped. $req->username is
+        *  considered user-submitted for this purpose, even if it cannot be changed via
+        *  $req->loadFromSubmission.
+        * @return Status
+        */
+       public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
+               $any = false;
+               $providers = $this->getPrimaryAuthenticationProviders() +
+                       $this->getSecondaryAuthenticationProviders();
+               foreach ( $providers as $provider ) {
+                       $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
+                       if ( !$status->isGood() ) {
+                               return Status::wrap( $status );
+                       }
+                       $any = $any || $status->value !== 'ignored';
+               }
+               if ( !$any ) {
+                       $status = Status::newGood( 'ignored' );
+                       $status->warning( 'authmanager-change-not-supported' );
+                       return $status;
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * Change authentication data (e.g. passwords)
+        *
+        * If $req was returned for AuthManager::ACTION_CHANGE, using $req should
+        * result in a successful login in the future.
+        *
+        * If $req was returned for AuthManager::ACTION_REMOVE, using $req should
+        * no longer result in a successful login.
+        *
+        * @param AuthenticationRequest $req
+        */
+       public function changeAuthenticationData( AuthenticationRequest $req ) {
+               $this->logger->info( 'Changing authentication data for {user} class {what}', [
+                       'user' => is_string( $req->username ) ? $req->username : '<no name>',
+                       'what' => get_class( $req ),
+               ] );
+
+               $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
+
+               // When the main account's authentication data is changed, invalidate
+               // all BotPasswords too.
+               \BotPassword::invalidateAllPasswordsForUser( $req->username );
+       }
+
+       /**@}*/
+
+       /**
+        * @name Account creation
+        * @{
+        */
+
+       /**
+        * Determine whether accounts can be created
+        * @return bool
+        */
+       public function canCreateAccounts() {
+               foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
+                       switch ( $provider->accountCreationType() ) {
+                               case PrimaryAuthenticationProvider::TYPE_CREATE:
+                               case PrimaryAuthenticationProvider::TYPE_LINK:
+                                       return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Determine whether a particular account can be created
+        * @param string $username
+        * @param int $flags Bitfield of User:READ_* constants
+        * @return Status
+        */
+       public function canCreateAccount( $username, $flags = User::READ_NORMAL ) {
+               if ( !$this->canCreateAccounts() ) {
+                       return Status::newFatal( 'authmanager-create-disabled' );
+               }
+
+               if ( $this->userExists( $username, $flags ) ) {
+                       return Status::newFatal( 'userexists' );
+               }
+
+               $user = User::newFromName( $username, 'creatable' );
+               if ( !is_object( $user ) ) {
+                       return Status::newFatal( 'noname' );
+               } else {
+                       $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
+                       if ( $user->getId() !== 0 ) {
+                               return Status::newFatal( 'userexists' );
+                       }
+               }
+
+               // Denied by providers?
+               $providers = $this->getPreAuthenticationProviders() +
+                       $this->getPrimaryAuthenticationProviders() +
+                       $this->getSecondaryAuthenticationProviders();
+               foreach ( $providers as $provider ) {
+                       $status = $provider->testUserForCreation( $user, false );
+                       if ( !$status->isGood() ) {
+                               return Status::wrap( $status );
+                       }
+               }
+
+               return Status::newGood();
+       }
+
+       /**
+        * Basic permissions checks on whether a user can create accounts
+        * @param User $creator User doing the account creation
+        * @return Status
+        */
+       public function checkAccountCreatePermissions( User $creator ) {
+               // Wiki is read-only?
+               if ( wfReadOnly() ) {
+                       return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+               }
+
+               // This is awful, this permission check really shouldn't go through Title.
+               $permErrors = \SpecialPage::getTitleFor( 'CreateAccount' )
+                       ->getUserPermissionsErrors( 'createaccount', $creator, 'secure' );
+               if ( $permErrors ) {
+                       $status = Status::newGood();
+                       foreach ( $permErrors as $args ) {
+                               call_user_func_array( [ $status, 'fatal' ], $args );
+                       }
+                       return $status;
+               }
+
+               $block = $creator->isBlockedFromCreateAccount();
+               if ( $block ) {
+                       $errorParams = [
+                               $block->getTarget(),
+                               $block->mReason ?: wfMessage( 'blockednoreason' )->text(),
+                               $block->getByName()
+                       ];
+
+                       if ( $block->getType() === \Block::TYPE_RANGE ) {
+                               $errorMessage = 'cantcreateaccount-range-text';
+                               $errorParams[] = $this->getRequest()->getIP();
+                       } else {
+                               $errorMessage = 'cantcreateaccount-text';
+                       }
+
+                       return Status::newFatal( wfMessage( $errorMessage, $errorParams ) );
+               }
+
+               $ip = $this->getRequest()->getIP();
+               if ( $creator->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
+                       return Status::newFatal( 'sorbs_create_account_reason' );
+               }
+
+               return Status::newGood();
+       }
+
+       /**
+        * Start an account creation flow
+        * @param User $creator User doing the account creation
+        * @param AuthenticationRequest[] $reqs
+        * @param string $returnToUrl Url that REDIRECT responses should eventually
+        *  return to.
+        * @return AuthenticationResponse
+        */
+       public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
+               $session = $this->request->getSession();
+               if ( !$this->canCreateAccounts() ) {
+                       // Caller should have called canCreateAccounts()
+                       $session->remove( 'AuthManager::accountCreationState' );
+                       throw new \LogicException( 'Account creation is not possible' );
+               }
+
+               try {
+                       $username = AuthenticationRequest::getUsernameFromRequests( $reqs );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $username = null;
+               }
+               if ( $username === null ) {
+                       $this->logger->debug( __METHOD__ . ': No username provided' );
+                       return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
+               }
+
+               // Permissions check
+               $status = $this->checkAccountCreatePermissions( $creator );
+               if ( !$status->isGood() ) {
+                       $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
+                               'user' => $username,
+                               'creator' => $creator->getName(),
+                               'reason' => $status->getWikiText( null, null, 'en' )
+                       ] );
+                       return AuthenticationResponse::newFail( $status->getMessage() );
+               }
+
+               $status = $this->canCreateAccount( $username, User::READ_LOCKING );
+               if ( !$status->isGood() ) {
+                       $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
+                               'user' => $username,
+                               'creator' => $creator->getName(),
+                               'reason' => $status->getWikiText( null, null, 'en' )
+                       ] );
+                       return AuthenticationResponse::newFail( $status->getMessage() );
+               }
+
+               $user = User::newFromName( $username, 'creatable' );
+               foreach ( $reqs as $req ) {
+                       $req->username = $username;
+                       $req->returnToUrl = $returnToUrl;
+                       if ( $req instanceof UserDataAuthenticationRequest ) {
+                               $status = $req->populateUser( $user );
+                               if ( !$status->isGood() ) {
+                                       $status = Status::wrap( $status );
+                                       $session->remove( 'AuthManager::accountCreationState' );
+                                       $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
+                                               'user' => $user->getName(),
+                                               'creator' => $creator->getName(),
+                                               'reason' => $status->getWikiText( null, null, 'en' ),
+                                       ] );
+                                       return AuthenticationResponse::newFail( $status->getMessage() );
+                               }
+                       }
+               }
+
+               $this->removeAuthenticationSessionData( null );
+
+               $state = [
+                       'username' => $username,
+                       'userid' => 0,
+                       'creatorid' => $creator->getId(),
+                       'creatorname' => $creator->getName(),
+                       'reqs' => $reqs,
+                       'returnToUrl' => $returnToUrl,
+                       'primary' => null,
+                       'primaryResponse' => null,
+                       'secondary' => [],
+                       'continueRequests' => [],
+                       'maybeLink' => [],
+                       'ranPreTests' => false,
+               ];
+
+               // Special case: converting a login to an account creation
+               $req = AuthenticationRequest::getRequestByClass(
+                       $reqs, CreateFromLoginAuthenticationRequest::class
+               );
+               if ( $req ) {
+                       $state['maybeLink'] = $req->maybeLink;
+
+                       // If we get here, the user didn't submit a form with any of the
+                       // usual AuthenticationRequests that are needed for an account
+                       // creation. So we need to determine if there are any and return a
+                       // UI response if so.
+                       if ( $req->createRequest ) {
+                               // We have a createRequest from a
+                               // PrimaryAuthenticationProvider, so don't ask.
+                               $providers = $this->getPreAuthenticationProviders() +
+                                       $this->getSecondaryAuthenticationProviders();
+                       } else {
+                               // We're only preserving maybeLink, so ask for primary fields
+                               // too.
+                               $providers = $this->getPreAuthenticationProviders() +
+                                       $this->getPrimaryAuthenticationProviders() +
+                                       $this->getSecondaryAuthenticationProviders();
+                       }
+                       $reqs = $this->getAuthenticationRequestsInternal(
+                               self::ACTION_CREATE,
+                               [],
+                               $providers
+                       );
+                       // See if we need any requests to begin
+                       foreach ( (array)$reqs as $r ) {
+                               if ( !$r instanceof UsernameAuthenticationRequest &&
+                                       !$r instanceof UserDataAuthenticationRequest &&
+                                       !$r instanceof CreationReasonAuthenticationRequest
+                               ) {
+                                       // Needs some reqs, so request them
+                                       $reqs[] = new CreateFromLoginAuthenticationRequest( $req->createRequest, [] );
+                                       $state['continueRequests'] = $reqs;
+                                       $session->setSecret( 'AuthManager::accountCreationState', $state );
+                                       $session->persist();
+                                       return AuthenticationResponse::newUI( $reqs, wfMessage( 'authmanager-create-from-login' ) );
+                               }
+                       }
+                       // No reqs needed, so we can just continue.
+                       $req->createRequest->returnToUrl = $returnToUrl;
+                       $reqs = [ $req->createRequest ];
+               }
+
+               $session->setSecret( 'AuthManager::accountCreationState', $state );
+               $session->persist();
+
+               return $this->continueAccountCreation( $reqs );
+       }
+
+       /**
+        * Continue an account creation flow
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse
+        */
+       public function continueAccountCreation( array $reqs ) {
+               $session = $this->request->getSession();
+               try {
+                       if ( !$this->canCreateAccounts() ) {
+                               // Caller should have called canCreateAccounts()
+                               $session->remove( 'AuthManager::accountCreationState' );
+                               throw new \LogicException( 'Account creation is not possible' );
+                       }
+
+                       $state = $session->getSecret( 'AuthManager::accountCreationState' );
+                       if ( !is_array( $state ) ) {
+                               return AuthenticationResponse::newFail(
+                                       wfMessage( 'authmanager-create-not-in-progress' )
+                               );
+                       }
+                       $state['continueRequests'] = [];
+
+                       // Step 0: Prepare and validate the input
+
+                       $user = User::newFromName( $state['username'], 'creatable' );
+                       if ( !is_object( $user ) ) {
+                               $session->remove( 'AuthManager::accountCreationState' );
+                               $this->logger->debug( __METHOD__ . ': Invalid username', [
+                                       'user' => $state['username'],
+                               ] );
+                               return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
+                       }
+
+                       if ( $state['creatorid'] ) {
+                               $creator = User::newFromId( $state['creatorid'] );
+                       } else {
+                               $creator = new User;
+                               $creator->setName( $state['creatorname'] );
+                       }
+
+                       // Avoid account creation races on double submissions
+                       $cache = \ObjectCache::getLocalClusterInstance();
+                       $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
+                       if ( !$lock ) {
+                               // Don't clear AuthManager::accountCreationState for this code
+                               // path because the process that won the race owns it.
+                               $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
+                                       'user' => $user->getName(),
+                                       'creator' => $creator->getName(),
+                               ] );
+                               return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
+                       }
+
+                       // Permissions check
+                       $status = $this->checkAccountCreatePermissions( $creator );
+                       if ( !$status->isGood() ) {
+                               $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
+                                       'user' => $user->getName(),
+                                       'creator' => $creator->getName(),
+                                       'reason' => $status->getWikiText( null, null, 'en' )
+                               ] );
+                               $ret = AuthenticationResponse::newFail( $status->getMessage() );
+                               $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                               $session->remove( 'AuthManager::accountCreationState' );
+                               return $ret;
+                       }
+
+                       // Load from master for existence check
+                       $user->load( User::READ_LOCKING );
+
+                       if ( $state['userid'] === 0 ) {
+                               if ( $user->getId() != 0 ) {
+                                       $this->logger->debug( __METHOD__ . ': User exists locally', [
+                                               'user' => $user->getName(),
+                                               'creator' => $creator->getName(),
+                                       ] );
+                                       $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
+                                       $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                                       $session->remove( 'AuthManager::accountCreationState' );
+                                       return $ret;
+                               }
+                       } else {
+                               if ( $user->getId() == 0 ) {
+                                       $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
+                                               'user' => $user->getName(),
+                                               'creator' => $creator->getName(),
+                                               'expected_id' => $state['userid'],
+                                       ] );
+                                       throw new \UnexpectedValueException(
+                                               "User \"{$state['username']}\" should exist now, but doesn't!"
+                                       );
+                               }
+                               if ( $user->getId() != $state['userid'] ) {
+                                       $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
+                                               'user' => $user->getName(),
+                                               'creator' => $creator->getName(),
+                                               'expected_id' => $state['userid'],
+                                               'actual_id' => $user->getId(),
+                                       ] );
+                                       throw new \UnexpectedValueException(
+                                               "User \"{$state['username']}\" exists, but " .
+                                                       "ID {$user->getId()} != {$state['userid']}!"
+                                       );
+                               }
+                       }
+                       foreach ( $state['reqs'] as $req ) {
+                               if ( $req instanceof UserDataAuthenticationRequest ) {
+                                       $status = $req->populateUser( $user );
+                                       if ( !$status->isGood() ) {
+                                               // This should never happen...
+                                               $status = Status::wrap( $status );
+                                               $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                                       'reason' => $status->getWikiText( null, null, 'en' ),
+                                               ] );
+                                               $ret = AuthenticationResponse::newFail( $status->getMessage() );
+                                               $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                                               $session->remove( 'AuthManager::accountCreationState' );
+                                               return $ret;
+                                       }
+                               }
+                       }
+
+                       foreach ( $reqs as $req ) {
+                               $req->returnToUrl = $state['returnToUrl'];
+                               $req->username = $state['username'];
+                       }
+
+                       // If we're coming in from a create-from-login UI response, we need
+                       // to extract the createRequest (if any).
+                       $req = AuthenticationRequest::getRequestByClass(
+                               $reqs, CreateFromLoginAuthenticationRequest::class
+                       );
+                       if ( $req && $req->createRequest ) {
+                               $reqs[] = $req->createRequest;
+                       }
+
+                       // Run pre-creation tests, if we haven't already
+                       if ( !$state['ranPreTests'] ) {
+                               $providers = $this->getPreAuthenticationProviders() +
+                                       $this->getPrimaryAuthenticationProviders() +
+                                       $this->getSecondaryAuthenticationProviders();
+                               foreach ( $providers as $id => $provider ) {
+                                       $status = $provider->testForAccountCreation( $user, $creator, $reqs );
+                                       if ( !$status->isGood() ) {
+                                               $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                               ] );
+                                               $ret = AuthenticationResponse::newFail(
+                                                       Status::wrap( $status )->getMessage()
+                                               );
+                                               $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                                               $session->remove( 'AuthManager::accountCreationState' );
+                                               return $ret;
+                                       }
+                               }
+
+                               $state['ranPreTests'] = true;
+                       }
+
+                       // Step 1: Choose a primary authentication provider and call it until it succeeds.
+
+                       if ( $state['primary'] === null ) {
+                               // We haven't picked a PrimaryAuthenticationProvider yet
+                               foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
+                                       if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
+                                               continue;
+                                       }
+                                       $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
+                                       switch ( $res->status ) {
+                                               case AuthenticationResponse::PASS;
+                                                       $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
+                                                               'user' => $user->getName(),
+                                                               'creator' => $creator->getName(),
+                                                       ] );
+                                                       $state['primary'] = $id;
+                                                       $state['primaryResponse'] = $res;
+                                                       break 2;
+                                               case AuthenticationResponse::FAIL;
+                                                       $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
+                                                               'user' => $user->getName(),
+                                                               'creator' => $creator->getName(),
+                                                       ] );
+                                                       $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
+                                                       $session->remove( 'AuthManager::accountCreationState' );
+                                                       return $res;
+                                               case AuthenticationResponse::ABSTAIN;
+                                                       // Continue loop
+                                                       break;
+                                               case AuthenticationResponse::REDIRECT;
+                                               case AuthenticationResponse::UI;
+                                                       $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
+                                                               'user' => $user->getName(),
+                                                               'creator' => $creator->getName(),
+                                                       ] );
+                                                       $state['primary'] = $id;
+                                                       $state['continueRequests'] = $res->neededRequests;
+                                                       $session->setSecret( 'AuthManager::accountCreationState', $state );
+                                                       return $res;
+
+                                                       // @codeCoverageIgnoreStart
+                                               default:
+                                                       throw new \DomainException(
+                                                               get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
+                                                       );
+                                                       // @codeCoverageIgnoreEnd
+                                       }
+                               }
+                               if ( $state['primary'] === null ) {
+                                       $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
+                                               'user' => $user->getName(),
+                                               'creator' => $creator->getName(),
+                                       ] );
+                                       $ret = AuthenticationResponse::newFail(
+                                               wfMessage( 'authmanager-create-no-primary' )
+                                       );
+                                       $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                                       $session->remove( 'AuthManager::accountCreationState' );
+                                       return $ret;
+                               }
+                       } elseif ( $state['primaryResponse'] === null ) {
+                               $provider = $this->getAuthenticationProvider( $state['primary'] );
+                               if ( !$provider instanceof PrimaryAuthenticationProvider ) {
+                                       // Configuration changed? Force them to start over.
+                                       // @codeCoverageIgnoreStart
+                                       $ret = AuthenticationResponse::newFail(
+                                               wfMessage( 'authmanager-create-not-in-progress' )
+                                       );
+                                       $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                                       $session->remove( 'AuthManager::accountCreationState' );
+                                       return $ret;
+                                       // @codeCoverageIgnoreEnd
+                               }
+                               $id = $provider->getUniqueId();
+                               $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
+                               switch ( $res->status ) {
+                                       case AuthenticationResponse::PASS;
+                                               $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                               ] );
+                                               $state['primaryResponse'] = $res;
+                                               break;
+                                       case AuthenticationResponse::FAIL;
+                                               $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                               ] );
+                                               $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
+                                               $session->remove( 'AuthManager::accountCreationState' );
+                                               return $res;
+                                       case AuthenticationResponse::REDIRECT;
+                                       case AuthenticationResponse::UI;
+                                               $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                               ] );
+                                               $state['continueRequests'] = $res->neededRequests;
+                                               $session->setSecret( 'AuthManager::accountCreationState', $state );
+                                               return $res;
+                                       default:
+                                               throw new \DomainException(
+                                                       get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
+                                               );
+                               }
+                       }
+
+                       // Step 2: Primary authentication succeeded, create the User object
+                       // and add the user locally.
+
+                       if ( $state['userid'] === 0 ) {
+                               $this->logger->info( 'Creating user {user} during account creation', [
+                                       'user' => $user->getName(),
+                                       'creator' => $creator->getName(),
+                               ] );
+                               $status = $user->addToDatabase();
+                               if ( !$status->isOk() ) {
+                                       // @codeCoverageIgnoreStart
+                                       $ret = AuthenticationResponse::newFail( $status->getMessage() );
+                                       $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                                       $session->remove( 'AuthManager::accountCreationState' );
+                                       return $ret;
+                                       // @codeCoverageIgnoreEnd
+                               }
+                               $this->setDefaultUserOptions( $user, $creator->isAnon() );
+                               \Hooks::run( 'LocalUserCreated', [ $user, false ] );
+                               $user->saveSettings();
+                               $state['userid'] = $user->getId();
+
+                               // Update user count
+                               \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
+
+                               // Watch user's userpage and talk page
+                               $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
+
+                               // Inform the provider
+                               $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
+
+                               // Log the creation
+                               if ( $this->config->get( 'NewUserLog' ) ) {
+                                       $isAnon = $creator->isAnon();
+                                       $logEntry = new \ManualLogEntry(
+                                               'newusers',
+                                               $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
+                                       );
+                                       $logEntry->setPerformer( $isAnon ? $user : $creator );
+                                       $logEntry->setTarget( $user->getUserPage() );
+                                       $req = AuthenticationRequest::getRequestByClass(
+                                               $state['reqs'], CreationReasonAuthenticationRequest::class
+                                       );
+                                       $logEntry->setComment( $req ? $req->reason : '' );
+                                       $logEntry->setParameters( [
+                                               '4::userid' => $user->getId(),
+                                       ] );
+                                       $logid = $logEntry->insert();
+                                       $logEntry->publish( $logid );
+                               }
+                       }
+
+                       // Step 3: Iterate over all the secondary authentication providers.
+
+                       $beginReqs = $state['reqs'];
+
+                       foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
+                               if ( !isset( $state['secondary'][$id] ) ) {
+                                       // This provider isn't started yet, so we pass it the set
+                                       // of reqs from beginAuthentication instead of whatever
+                                       // might have been used by a previous provider in line.
+                                       $func = 'beginSecondaryAccountCreation';
+                                       $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
+                               } elseif ( !$state['secondary'][$id] ) {
+                                       $func = 'continueSecondaryAccountCreation';
+                                       $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
+                               } else {
+                                       continue;
+                               }
+                               switch ( $res->status ) {
+                                       case AuthenticationResponse::PASS;
+                                               $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                               ] );
+                                               // fall through
+                                       case AuthenticationResponse::ABSTAIN;
+                                               $state['secondary'][$id] = true;
+                                               break;
+                                       case AuthenticationResponse::REDIRECT;
+                                       case AuthenticationResponse::UI;
+                                               $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
+                                                       'user' => $user->getName(),
+                                                       'creator' => $creator->getName(),
+                                               ] );
+                                               $state['secondary'][$id] = false;
+                                               $state['continueRequests'] = $res->neededRequests;
+                                               $session->setSecret( 'AuthManager::accountCreationState', $state );
+                                               return $res;
+                                       case AuthenticationResponse::FAIL;
+                                               throw new \DomainException(
+                                                       get_class( $provider ) . "::{$func}() returned $res->status." .
+                                                       ' Secondary providers are not allowed to fail account creation, that' .
+                                                       ' should have been done via testForAccountCreation().'
+                                               );
+                                                       // @codeCoverageIgnoreStart
+                                       default:
+                                               throw new \DomainException(
+                                                       get_class( $provider ) . "::{$func}() returned $res->status"
+                                               );
+                                                       // @codeCoverageIgnoreEnd
+                               }
+                       }
+
+                       $id = $user->getId();
+                       $name = $user->getName();
+                       $req = new CreatedAccountAuthenticationRequest( $id, $name );
+                       $ret = AuthenticationResponse::newPass( $name );
+                       $ret->loginRequest = $req;
+                       $this->createdAccountAuthenticationRequests[] = $req;
+
+                       $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
+                               'user' => $user->getName(),
+                               'creator' => $creator->getName(),
+                       ] );
+
+                       $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
+                       $session->remove( 'AuthManager::accountCreationState' );
+                       $this->removeAuthenticationSessionData( null );
+                       return $ret;
+               } catch ( \Exception $ex ) {
+                       $session->remove( 'AuthManager::accountCreationState' );
+                       throw $ex;
+               }
+       }
+
+       /**
+        * Auto-create an account, and log into that account
+        * @param User $user User to auto-create
+        * @param string $source What caused the auto-creation? This must be the ID
+        *  of a PrimaryAuthenticationProvider or the constant self::AUTOCREATE_SOURCE_SESSION.
+        * @param bool $login Whether to also log the user in
+        * @return Status Good if user was created, Ok if user already existed, otherwise Fatal
+        */
+       public function autoCreateUser( User $user, $source, $login = true ) {
+               if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
+                       !$this->getAuthenticationProvider( $source ) instanceof PrimaryAuthenticationProvider
+               ) {
+                       throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
+               }
+
+               $username = $user->getName();
+
+               // Try the local user from the slave DB
+               $localId = User::idFromName( $username );
+               $flags = User::READ_NORMAL;
+
+               // Fetch the user ID from the master, so that we don't try to create the user
+               // when they already exist, due to replication lag
+               // @codeCoverageIgnoreStart
+               if ( !$localId && wfGetLB()->getReaderIndex() != 0 ) {
+                       $localId = User::idFromName( $username, User::READ_LATEST );
+                       $flags = User::READ_LATEST;
+               }
+               // @codeCoverageIgnoreEnd
+
+               if ( $localId ) {
+                       $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
+                               'username' => $username,
+                       ] );
+                       $user->setId( $localId );
+                       $user->loadFromId( $flags );
+                       if ( $login ) {
+                               $this->setSessionDataForUser( $user );
+                       }
+                       $status = Status::newGood();
+                       $status->warning( 'userexists' );
+                       return $status;
+               }
+
+               // Wiki is read-only?
+               if ( wfReadOnly() ) {
+                       $this->logger->debug( __METHOD__ . ': denied by wfReadOnly(): {reason}', [
+                               'username' => $username,
+                               'reason' => wfReadOnlyReason(),
+                       ] );
+                       $user->setId( 0 );
+                       $user->loadFromId();
+                       return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+               }
+
+               // Check the session, if we tried to create this user already there's
+               // no point in retrying.
+               $session = $this->request->getSession();
+               if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
+                       $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
+                               'username' => $username,
+                               'sessionid' => $session->getId(),
+                       ] );
+                       $user->setId( 0 );
+                       $user->loadFromId();
+                       $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
+                       if ( $reason instanceof StatusValue ) {
+                               return Status::wrap( $reason );
+                       } else {
+                               return Status::newFatal( $reason );
+                       }
+               }
+
+               // Is the username creatable?
+               if ( !User::isCreatableName( $username ) ) {
+                       $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
+                               'username' => $username,
+                       ] );
+                       $session->set( 'AuthManager::AutoCreateBlacklist', 'noname', 600 );
+                       $user->setId( 0 );
+                       $user->loadFromId();
+                       return Status::newFatal( 'noname' );
+               }
+
+               // Is the IP user able to create accounts?
+               $anon = new User;
+               if ( !$anon->isAllowedAny( 'createaccount', 'autocreateaccount' ) ) {
+                       $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
+                               'username' => $username,
+                               'ip' => $anon->getName(),
+                       ] );
+                       $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm', 600 );
+                       $session->persist();
+                       $user->setId( 0 );
+                       $user->loadFromId();
+                       return Status::newFatal( 'authmanager-autocreate-noperm' );
+               }
+
+               // Avoid account creation races on double submissions
+               $cache = \ObjectCache::getLocalClusterInstance();
+               $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
+               if ( !$lock ) {
+                       $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
+                               'user' => $username,
+                       ] );
+                       $user->setId( 0 );
+                       $user->loadFromId();
+                       return Status::newFatal( 'usernameinprogress' );
+               }
+
+               // Denied by providers?
+               $providers = $this->getPreAuthenticationProviders() +
+                       $this->getPrimaryAuthenticationProviders() +
+                       $this->getSecondaryAuthenticationProviders();
+               foreach ( $providers as $provider ) {
+                       $status = $provider->testUserForCreation( $user, $source );
+                       if ( !$status->isGood() ) {
+                               $ret = Status::wrap( $status );
+                               $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
+                                       'username' => $username,
+                                       'reason' => $ret->getWikiText( null, null, 'en' ),
+                               ] );
+                               $session->set( 'AuthManager::AutoCreateBlacklist', $status, 600 );
+                               $user->setId( 0 );
+                               $user->loadFromId();
+                               return $ret;
+                       }
+               }
+
+               // Ignore warnings about master connections/writes...hard to avoid here
+               \Profiler::instance()->getTransactionProfiler()->resetExpectations();
+
+               $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
+               if ( $cache->get( $backoffKey ) ) {
+                       $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
+                               'username' => $username,
+                       ] );
+                       $user->setId( 0 );
+                       $user->loadFromId();
+                       return Status::newFatal( 'authmanager-autocreate-exception' );
+               }
+
+               // Checks passed, create the user...
+               $from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI';
+               $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
+                       'username' => $username,
+                       'from' => $from,
+               ] );
+
+               try {
+                       $status = $user->addToDatabase();
+                       if ( !$status->isOk() ) {
+                               // double-check for a race condition (T70012)
+                               $localId = User::idFromName( $username, User::READ_LATEST );
+                               if ( $localId ) {
+                                       $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
+                                               'username' => $username,
+                                       ] );
+                                       $user->setId( $localId );
+                                       $user->loadFromId( User::READ_LATEST );
+                                       if ( $login ) {
+                                               $this->setSessionDataForUser( $user );
+                                       }
+                                       $status = Status::newGood();
+                                       $status->warning( 'userexists' );
+                               } else {
+                                       $this->logger->error( __METHOD__ . ': {username} failed with message {message}', [
+                                               'username' => $username,
+                                               'message' => $status->getWikiText( null, null, 'en' )
+                                       ] );
+                                       $user->setId( 0 );
+                                       $user->loadFromId();
+                               }
+                               return $status;
+                       }
+               } catch ( \Exception $ex ) {
+                       $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
+                               'username' => $username,
+                               'exception' => $ex,
+                       ] );
+                       // Do not keep throwing errors for a while
+                       $cache->set( $backoffKey, 1, 600 );
+                       // Bubble up error; which should normally trigger DB rollbacks
+                       throw $ex;
+               }
+
+               $this->setDefaultUserOptions( $user, true );
+
+               // Inform the providers
+               $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
+
+               \Hooks::run( 'AuthPluginAutoCreate', [ $user ], '1.27' );
+               \Hooks::run( 'LocalUserCreated', [ $user, true ] );
+               $user->saveSettings();
+
+               // Update user count
+               \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
+
+               // Watch user's userpage and talk page
+               $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
+
+               // Log the creation
+               if ( $this->config->get( 'NewUserLog' ) ) {
+                       $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
+                       $logEntry->setPerformer( $user );
+                       $logEntry->setTarget( $user->getUserPage() );
+                       $logEntry->setComment( '' );
+                       $logEntry->setParameters( [
+                               '4::userid' => $user->getId(),
+                       ] );
+                       $logid = $logEntry->insert();
+               }
+
+               if ( $login ) {
+                       $this->setSessionDataForUser( $user );
+               }
+
+               return Status::newGood();
+       }
+
+       /**@}*/
+
+       /**
+        * @name Account linking
+        * @{
+        */
+
+       /**
+        * Determine whether accounts can be linked
+        * @return bool
+        */
+       public function canLinkAccounts() {
+               foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
+                       if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Start an account linking flow
+        *
+        * @param User $user User being linked
+        * @param AuthenticationRequest[] $reqs
+        * @param string $returnToUrl Url that REDIRECT responses should eventually
+        *  return to.
+        * @return AuthenticationResponse
+        */
+       public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
+               $session = $this->request->getSession();
+               $session->remove( 'AuthManager::accountLinkState' );
+
+               if ( !$this->canLinkAccounts() ) {
+                       // Caller should have called canLinkAccounts()
+                       throw new \LogicException( 'Account linking is not possible' );
+               }
+
+               if ( $user->getId() === 0 ) {
+                       if ( !User::isUsableName( $user->getName() ) ) {
+                               $msg = wfMessage( 'noname' );
+                       } else {
+                               $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
+                       }
+                       return AuthenticationResponse::newFail( $msg );
+               }
+               foreach ( $reqs as $req ) {
+                       $req->username = $user->getName();
+                       $req->returnToUrl = $returnToUrl;
+               }
+
+               $this->removeAuthenticationSessionData( null );
+
+               $providers = $this->getPreAuthenticationProviders();
+               foreach ( $providers as $id => $provider ) {
+                       $status = $provider->testForAccountLink( $user );
+                       if ( !$status->isGood() ) {
+                               $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
+                                       'user' => $user->getName(),
+                               ] );
+                               $ret = AuthenticationResponse::newFail(
+                                       Status::wrap( $status )->getMessage()
+                               );
+                               $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
+                               return $ret;
+                       }
+               }
+
+               $state = [
+                       'username' => $user->getName(),
+                       'userid' => $user->getId(),
+                       'returnToUrl' => $returnToUrl,
+                       'primary' => null,
+                       'continueRequests' => [],
+               ];
+
+               $providers = $this->getPrimaryAuthenticationProviders();
+               foreach ( $providers as $id => $provider ) {
+                       if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
+                               continue;
+                       }
+
+                       $res = $provider->beginPrimaryAccountLink( $user, $reqs );
+                       switch ( $res->status ) {
+                               case AuthenticationResponse::PASS;
+                                       $this->logger->info( "Account linked to {user} by $id", [
+                                               'user' => $user->getName(),
+                                       ] );
+                                       $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
+                                       return $res;
+
+                               case AuthenticationResponse::FAIL;
+                                       $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
+                                               'user' => $user->getName(),
+                                       ] );
+                                       $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
+                                       return $res;
+
+                               case AuthenticationResponse::ABSTAIN;
+                                       // Continue loop
+                                       break;
+
+                               case AuthenticationResponse::REDIRECT;
+                               case AuthenticationResponse::UI;
+                                       $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
+                                               'user' => $user->getName(),
+                                       ] );
+                                       $state['primary'] = $id;
+                                       $state['continueRequests'] = $res->neededRequests;
+                                       $session->setSecret( 'AuthManager::accountLinkState', $state );
+                                       $session->persist();
+                                       return $res;
+
+                                       // @codeCoverageIgnoreStart
+                               default:
+                                       throw new \DomainException(
+                                               get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
+                                       );
+                                       // @codeCoverageIgnoreEnd
+                       }
+               }
+
+               $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
+                       'user' => $user->getName(),
+               ] );
+               $ret = AuthenticationResponse::newFail(
+                       wfMessage( 'authmanager-link-no-primary' )
+               );
+               $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
+               return $ret;
+       }
+
+       /**
+        * Continue an account linking flow
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse
+        */
+       public function continueAccountLink( array $reqs ) {
+               $session = $this->request->getSession();
+               try {
+                       if ( !$this->canLinkAccounts() ) {
+                               // Caller should have called canLinkAccounts()
+                               $session->remove( 'AuthManager::accountLinkState' );
+                               throw new \LogicException( 'Account linking is not possible' );
+                       }
+
+                       $state = $session->getSecret( 'AuthManager::accountLinkState' );
+                       if ( !is_array( $state ) ) {
+                               return AuthenticationResponse::newFail(
+                                       wfMessage( 'authmanager-link-not-in-progress' )
+                               );
+                       }
+                       $state['continueRequests'] = [];
+
+                       // Step 0: Prepare and validate the input
+
+                       $user = User::newFromName( $state['username'], 'usable' );
+                       if ( !is_object( $user ) ) {
+                               $session->remove( 'AuthManager::accountLinkState' );
+                               return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
+                       }
+                       if ( $user->getId() != $state['userid'] ) {
+                               throw new \UnexpectedValueException(
+                                       "User \"{$state['username']}\" is valid, but " .
+                                               "ID {$user->getId()} != {$state['userid']}!"
+                               );
+                       }
+
+                       foreach ( $reqs as $req ) {
+                               $req->username = $state['username'];
+                               $req->returnToUrl = $state['returnToUrl'];
+                       }
+
+                       // Step 1: Call the primary again until it succeeds
+
+                       $provider = $this->getAuthenticationProvider( $state['primary'] );
+                       if ( !$provider instanceof PrimaryAuthenticationProvider ) {
+                               // Configuration changed? Force them to start over.
+                               // @codeCoverageIgnoreStart
+                               $ret = AuthenticationResponse::newFail(
+                                       wfMessage( 'authmanager-link-not-in-progress' )
+                               );
+                               $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
+                               $session->remove( 'AuthManager::accountLinkState' );
+                               return $ret;
+                               // @codeCoverageIgnoreEnd
+                       }
+                       $id = $provider->getUniqueId();
+                       $res = $provider->continuePrimaryAccountLink( $user, $reqs );
+                       switch ( $res->status ) {
+                               case AuthenticationResponse::PASS;
+                                       $this->logger->info( "Account linked to {user} by $id", [
+                                               'user' => $user->getName(),
+                                       ] );
+                                       $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
+                                       $session->remove( 'AuthManager::accountLinkState' );
+                                       return $res;
+                               case AuthenticationResponse::FAIL;
+                                       $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
+                                               'user' => $user->getName(),
+                                       ] );
+                                       $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
+                                       $session->remove( 'AuthManager::accountLinkState' );
+                                       return $res;
+                               case AuthenticationResponse::REDIRECT;
+                               case AuthenticationResponse::UI;
+                                       $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
+                                               'user' => $user->getName(),
+                                       ] );
+                                       $state['continueRequests'] = $res->neededRequests;
+                                       $session->setSecret( 'AuthManager::accountLinkState', $state );
+                                       return $res;
+                               default:
+                                       throw new \DomainException(
+                                               get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
+                                       );
+                       }
+               } catch ( \Exception $ex ) {
+                       $session->remove( 'AuthManager::accountLinkState' );
+                       throw $ex;
+               }
+       }
+
+       /**@}*/
+
+       /**
+        * @name Information methods
+        * @{
+        */
+
+       /**
+        * Return the applicable list of AuthenticationRequests
+        *
+        * Possible values for $action:
+        *  - ACTION_LOGIN: Valid for passing to beginAuthentication
+        *  - ACTION_LOGIN_CONTINUE: Valid for passing to continueAuthentication in the current state
+        *  - ACTION_CREATE: Valid for passing to beginAccountCreation
+        *  - ACTION_CREATE_CONTINUE: Valid for passing to continueAccountCreation in the current state
+        *  - ACTION_LINK: Valid for passing to beginAccountLink
+        *  - ACTION_LINK_CONTINUE: Valid for passing to continueAccountLink in the current state
+        *  - ACTION_CHANGE: Valid for passing to changeAuthenticationData to change credentials
+        *  - ACTION_REMOVE: Valid for passing to changeAuthenticationData to remove credentials.
+        *  - ACTION_UNLINK: Same as ACTION_REMOVE, but limited to linked accounts.
+        *
+        * @param string $action One of the AuthManager::ACTION_* constants
+        * @param User|null $user User being acted on, instead of the current user.
+        * @return AuthenticationRequest[]
+        */
+       public function getAuthenticationRequests( $action, User $user = null ) {
+               $options = [];
+               $providerAction = $action;
+
+               // Figure out which providers to query
+               switch ( $action ) {
+                       case self::ACTION_LOGIN:
+                       case self::ACTION_CREATE:
+                               $providers = $this->getPreAuthenticationProviders() +
+                                       $this->getPrimaryAuthenticationProviders() +
+                                       $this->getSecondaryAuthenticationProviders();
+                               break;
+
+                       case self::ACTION_LOGIN_CONTINUE:
+                               $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
+                               return is_array( $state ) ? $state['continueRequests'] : [];
+
+                       case self::ACTION_CREATE_CONTINUE:
+                               $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
+                               return is_array( $state ) ? $state['continueRequests'] : [];
+
+                       case self::ACTION_LINK:
+                               $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
+                                       return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
+                               } );
+                               break;
+
+                       case self::ACTION_UNLINK:
+                               $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
+                                       return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
+                               } );
+
+                               // To providers, unlink and remove are identical.
+                               $providerAction = self::ACTION_REMOVE;
+                               break;
+
+                       case self::ACTION_LINK_CONTINUE:
+                               $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
+                               return is_array( $state ) ? $state['continueRequests'] : [];
+
+                       case self::ACTION_CHANGE:
+                       case self::ACTION_REMOVE:
+                               $providers = $this->getPrimaryAuthenticationProviders() +
+                                       $this->getSecondaryAuthenticationProviders();
+                               break;
+
+                       // @codeCoverageIgnoreStart
+                       default:
+                               throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
+               }
+               // @codeCoverageIgnoreEnd
+
+               return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
+       }
+
+       /**
+        * Internal request lookup for self::getAuthenticationRequests
+        *
+        * @param string $providerAction Action to pass to providers
+        * @param array $options Options to pass to providers
+        * @param AuthenticationProvider[] $providers
+        * @param User|null $user
+        * @return AuthenticationRequest[]
+        */
+       private function getAuthenticationRequestsInternal(
+               $providerAction, array $options, array $providers, User $user = null
+       ) {
+               $user = $user ?: \RequestContext::getMain()->getUser();
+               $options['username'] = $user->isAnon() ? null : $user->getName();
+
+               // Query them and merge results
+               $reqs = [];
+               $allPrimaryRequired = null;
+               foreach ( $providers as $provider ) {
+                       $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
+                       $thisRequired = [];
+                       foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
+                               $id = $req->getUniqueId();
+
+                               // If it's from a Primary, mark it as "primary-required" but
+                               // track it for later.
+                               if ( $isPrimary ) {
+                                       if ( $req->required ) {
+                                               $thisRequired[$id] = true;
+                                               $req->required = AuthenticationRequest::PRIMARY_REQUIRED;
+                                       }
+                               }
+
+                               if ( !isset( $reqs[$id] ) || $req->required === AuthenticationRequest::REQUIRED ) {
+                                       $reqs[$id] = $req;
+                               }
+                       }
+
+                       // Track which requests are required by all primaries
+                       if ( $isPrimary ) {
+                               $allPrimaryRequired = $allPrimaryRequired === null
+                                       ? $thisRequired
+                                       : array_intersect_key( $allPrimaryRequired, $thisRequired );
+                       }
+               }
+               // Any requests that were required by all primaries are required.
+               foreach ( (array)$allPrimaryRequired as $id => $dummy ) {
+                       $reqs[$id]->required = AuthenticationRequest::REQUIRED;
+               }
+
+               // AuthManager has its own req for some actions
+               switch ( $providerAction ) {
+                       case self::ACTION_LOGIN:
+                               $reqs[] = new RememberMeAuthenticationRequest;
+                               break;
+
+                       case self::ACTION_CREATE:
+                               $reqs[] = new UsernameAuthenticationRequest;
+                               $reqs[] = new UserDataAuthenticationRequest;
+                               if ( $options['username'] !== null ) {
+                                       $reqs[] = new CreationReasonAuthenticationRequest;
+                                       $options['username'] = null; // Don't fill in the username below
+                               }
+                               break;
+               }
+
+               // Fill in reqs data
+               foreach ( $reqs as $req ) {
+                       $req->action = $providerAction;
+                       if ( $req->username === null ) {
+                               $req->username = $options['username'];
+                       }
+               }
+
+               // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
+               if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
+                       $reqs = array_filter( $reqs, function ( $req ) {
+                               return $this->allowsAuthenticationDataChange( $req, false )->isGood();
+                       } );
+               }
+
+               return array_values( $reqs );
+       }
+
+       /**
+        * Determine whether a username exists
+        * @param string $username
+        * @param int $flags Bitfield of User:READ_* constants
+        * @return bool
+        */
+       public function userExists( $username, $flags = User::READ_NORMAL ) {
+               foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
+                       if ( $provider->testUserExists( $username, $flags ) ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Determine whether a user property should be allowed to be changed.
+        *
+        * Supported properties are:
+        *  - emailaddress
+        *  - realname
+        *  - nickname
+        *
+        * @param string $property
+        * @return bool
+        */
+       public function allowsPropertyChange( $property ) {
+               $providers = $this->getPrimaryAuthenticationProviders() +
+                       $this->getSecondaryAuthenticationProviders();
+               foreach ( $providers as $provider ) {
+                       if ( !$provider->providerAllowsPropertyChange( $property ) ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**@}*/
+
+       /**
+        * @name Internal methods
+        * @{
+        */
+
+       /**
+        * Store authentication in the current session
+        * @protected For use by AuthenticationProviders
+        * @param string $key
+        * @param mixed $data Must be serializable
+        */
+       public function setAuthenticationSessionData( $key, $data ) {
+               $session = $this->request->getSession();
+               $arr = $session->getSecret( 'authData' );
+               if ( !is_array( $arr ) ) {
+                       $arr = [];
+               }
+               $arr[$key] = $data;
+               $session->setSecret( 'authData', $arr );
+       }
+
+       /**
+        * Fetch authentication data from the current session
+        * @protected For use by AuthenticationProviders
+        * @param string $key
+        * @param mixed $default
+        * @return mixed
+        */
+       public function getAuthenticationSessionData( $key, $default = null ) {
+               $arr = $this->request->getSession()->getSecret( 'authData' );
+               if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
+                       return $arr[$key];
+               } else {
+                       return $default;
+               }
+       }
+
+       /**
+        * Remove authentication data
+        * @protected For use by AuthenticationProviders
+        * @param string|null $key If null, all data is removed
+        */
+       public function removeAuthenticationSessionData( $key ) {
+               $session = $this->request->getSession();
+               if ( $key === null ) {
+                       $session->remove( 'authData' );
+               } else {
+                       $arr = $session->getSecret( 'authData' );
+                       if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
+                               unset( $arr[$key] );
+                               $session->setSecret( 'authData', $arr );
+                       }
+               }
+       }
+
+       /**
+        * Create an array of AuthenticationProviders from an array of ObjectFactory specs
+        * @param string $class
+        * @param array[] $specs
+        * @return AuthenticationProvider[]
+        */
+       protected function providerArrayFromSpecs( $class, array $specs ) {
+               $i = 0;
+               foreach ( $specs as &$spec ) {
+                       $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
+               }
+               unset( $spec );
+               usort( $specs, function ( $a, $b ) {
+                       return ( (int)$a['sort'] ) - ( (int)$b['sort'] )
+                               ?: $a['sort2'] - $b['sort2'];
+               } );
+
+               $ret = [];
+               foreach ( $specs as $spec ) {
+                       $provider = \ObjectFactory::getObjectFromSpec( $spec );
+                       if ( !$provider instanceof $class ) {
+                               throw new \RuntimeException(
+                                       "Expected instance of $class, got " . get_class( $provider )
+                               );
+                       }
+                       $provider->setLogger( $this->logger );
+                       $provider->setManager( $this );
+                       $provider->setConfig( $this->config );
+                       $id = $provider->getUniqueId();
+                       if ( isset( $this->allAuthenticationProviders[$id] ) ) {
+                               throw new \RuntimeException(
+                                       "Duplicate specifications for id $id (classes " .
+                                       get_class( $provider ) . ' and ' .
+                                       get_class( $this->allAuthenticationProviders[$id] ) . ')'
+                               );
+                       }
+                       $this->allAuthenticationProviders[$id] = $provider;
+                       $ret[$id] = $provider;
+               }
+               return $ret;
+       }
+
+       /**
+        * Get the configuration
+        * @return array
+        */
+       private function getConfiguration() {
+               return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
+       }
+
+       /**
+        * Get the list of PreAuthenticationProviders
+        * @return PreAuthenticationProvider[]
+        */
+       protected function getPreAuthenticationProviders() {
+               if ( $this->preAuthenticationProviders === null ) {
+                       $conf = $this->getConfiguration();
+                       $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
+                               PreAuthenticationProvider::class, $conf['preauth']
+                       );
+               }
+               return $this->preAuthenticationProviders;
+       }
+
+       /**
+        * Get the list of PrimaryAuthenticationProviders
+        * @return PrimaryAuthenticationProvider[]
+        */
+       protected function getPrimaryAuthenticationProviders() {
+               if ( $this->primaryAuthenticationProviders === null ) {
+                       $conf = $this->getConfiguration();
+                       $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
+                               PrimaryAuthenticationProvider::class, $conf['primaryauth']
+                       );
+               }
+               return $this->primaryAuthenticationProviders;
+       }
+
+       /**
+        * Get the list of SecondaryAuthenticationProviders
+        * @return SecondaryAuthenticationProvider[]
+        */
+       protected function getSecondaryAuthenticationProviders() {
+               if ( $this->secondaryAuthenticationProviders === null ) {
+                       $conf = $this->getConfiguration();
+                       $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
+                               SecondaryAuthenticationProvider::class, $conf['secondaryauth']
+                       );
+               }
+               return $this->secondaryAuthenticationProviders;
+       }
+
+       /**
+        * Get a provider by ID
+        * @param string $id
+        * @return AuthenticationProvider|null
+        */
+       protected function getAuthenticationProvider( $id ) {
+               // Fast version
+               if ( isset( $this->allAuthenticationProviders[$id] ) ) {
+                       return $this->allAuthenticationProviders[$id];
+               }
+
+               // Slow version: instantiate each kind and check
+               $providers = $this->getPrimaryAuthenticationProviders();
+               if ( isset( $providers[$id] ) ) {
+                       return $providers[$id];
+               }
+               $providers = $this->getSecondaryAuthenticationProviders();
+               if ( isset( $providers[$id] ) ) {
+                       return $providers[$id];
+               }
+               $providers = $this->getPreAuthenticationProviders();
+               if ( isset( $providers[$id] ) ) {
+                       return $providers[$id];
+               }
+
+               return null;
+       }
+
+       /**
+        * @param User $user
+        * @param bool|null $remember
+        */
+       private function setSessionDataForUser( $user, $remember = null ) {
+               $session = $this->request->getSession();
+               $delay = $session->delaySave();
+
+               $session->resetId();
+               if ( $session->canSetUser() ) {
+                       $session->setUser( $user );
+               }
+               if ( $remember !== null ) {
+                       $session->setRememberUser( $remember );
+               }
+               $session->set( 'AuthManager:lastAuthId', $user->getId() );
+               $session->set( 'AuthManager:lastAuthTimestamp', time() );
+               $session->persist();
+
+               \ScopedCallback::consume( $delay );
+
+               \Hooks::run( 'UserLoggedIn', [ $user ] );
+       }
+
+       /**
+        * @param User $user
+        * @param bool $useContextLang Use 'uselang' to set the user's language
+        */
+       private function setDefaultUserOptions( User $user, $useContextLang ) {
+               global $wgContLang;
+
+               \MediaWiki\Session\SessionManager::singleton()->invalidateSessionsForUser( $user );
+
+               $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $wgContLang;
+               $user->setOption( 'language', $lang->getPreferredVariant() );
+
+               if ( $wgContLang->hasVariants() ) {
+                       $user->setOption( 'variant', $wgContLang->getPreferredVariant() );
+               }
+       }
+
+       /**
+        * @param int $which Bitmask: 1 = pre, 2 = primary, 4 = secondary
+        * @param string $method
+        * @param array $args
+        */
+       private function callMethodOnProviders( $which, $method, array $args ) {
+               $providers = [];
+               if ( $which & 1 ) {
+                       $providers += $this->getPreAuthenticationProviders();
+               }
+               if ( $which & 2 ) {
+                       $providers += $this->getPrimaryAuthenticationProviders();
+               }
+               if ( $which & 4 ) {
+                       $providers += $this->getSecondaryAuthenticationProviders();
+               }
+               foreach ( $providers as $provider ) {
+                       call_user_func_array( [ $provider, $method ], $args );
+               }
+       }
+
+       /**
+        * Reset the internal caching for unit testing
+        */
+       public static function resetCache() {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       // @codeCoverageIgnoreStart
+                       throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
+                       // @codeCoverageIgnoreEnd
+               }
+
+               self::$instance = null;
+       }
+
+       /**@}*/
+
+}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
diff --git a/includes/auth/AuthManagerAuthPlugin.php b/includes/auth/AuthManagerAuthPlugin.php
new file mode 100644 (file)
index 0000000..bf1e021
--- /dev/null
@@ -0,0 +1,251 @@
+<?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
+ */
+
+namespace MediaWiki\Auth;
+
+use User;
+
+/**
+ * Backwards-compatibility wrapper for AuthManager via $wgAuth
+ * @since 1.27
+ * @deprecated since 1.27
+ */
+class AuthManagerAuthPlugin extends \AuthPlugin {
+       /** @var string|null */
+       protected $domain = null;
+
+       /** @var \\Psr\\Log\\LoggerInterface */
+       protected $logger = null;
+
+       public function __construct() {
+               $this->logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' );
+       }
+
+       public function userExists( $name ) {
+               return AuthManager::singleton()->userExists( $name );
+       }
+
+       public function authenticate( $username, $password ) {
+               $data = [
+                       'username' => $username,
+                       'password' => $password,
+               ];
+               if ( $this->domain !== null && $this->domain !== '' ) {
+                       $data['domain'] = $this->domain;
+               }
+               $reqs = AuthManager::singleton()->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
+               $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
+
+               $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
+               switch ( $res->status ) {
+                       case AuthenticationResponse::PASS:
+                               return true;
+                       case AuthenticationResponse::FAIL:
+                               // Hope it's not a PreAuthenticationProvider that failed...
+                               $msg = $res->message instanceof \Message ? $res->message : new \Message( $res->message );
+                               $this->logger->info( __METHOD__ . ': Authentication failed: ' . $msg->plain() );
+                               return false;
+                       default:
+                               throw new \BadMethodCallException(
+                                       'AuthManager does not support such simplified authentication'
+                               );
+               }
+       }
+
+       public function modifyUITemplate( &$template, &$type ) {
+               // AuthManager does not support direct UI screwing-around-with
+       }
+
+       public function setDomain( $domain ) {
+               $this->domain = $domain;
+       }
+
+       public function getDomain() {
+               if ( isset( $this->domain ) ) {
+                       return $this->domain;
+               } else {
+                       return 'invaliddomain';
+               }
+       }
+
+       public function validDomain( $domain ) {
+               $domainList = $this->domainList();
+               return $domainList ? in_array( $domain, $domainList, true ) : $domain === '';
+       }
+
+       public function updateUser( &$user ) {
+               \Hooks::run( 'UserLoggedIn', [ $user ] );
+               return true;
+       }
+
+       public function autoCreate() {
+               return true;
+       }
+
+       public function allowPropChange( $prop = '' ) {
+               return AuthManager::singleton()->allowsPropertyChange( $prop );
+       }
+
+       public function allowPasswordChange() {
+               $reqs = AuthManager::singleton()->getAuthenticationRequests( AuthManager::ACTION_CHANGE );
+               foreach ( $reqs as $req ) {
+                       if ( $req instanceof PasswordAuthenticationRequest ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       public function allowSetLocalPassword() {
+               // There should be a PrimaryAuthenticationProvider that does this, if necessary
+               return false;
+       }
+
+       public function setPassword( $user, $password ) {
+               $data = [
+                       'username' => $user->getName(),
+                       'password' => $password,
+               ];
+               if ( $this->domain !== null && $this->domain !== '' ) {
+                       $data['domain'] = $this->domain;
+               }
+               $reqs = AuthManager::singleton()->getAuthenticationRequests( AuthManager::ACTION_CHANGE );
+               $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
+               foreach ( $reqs as $req ) {
+                       $status = AuthManager::singleton()->allowsAuthenticationDataChange( $req );
+                       if ( !$status->isOk() ) {
+                               $this->logger->info( __METHOD__ . ': Password change rejected: {reason}', [
+                                       'username' => $data['username'],
+                                       'reason' => $status->getWikiText( null, null, 'en' ),
+                               ] );
+                               return false;
+                       }
+               }
+               foreach ( $reqs as $req ) {
+                       AuthManager::singleton()->changeAuthenticationData( $req );
+               }
+               return true;
+       }
+
+       public function updateExternalDB( $user ) {
+               // This fires the necessary hook
+               $user->saveSettings();
+               return true;
+       }
+
+       public function updateExternalDBGroups( $user, $addgroups, $delgroups = [] ) {
+               \Hooks::run( 'UserGroupsChanged', [ $user, $addgroups, $delgroups ] );
+               return true;
+       }
+
+       public function canCreateAccounts() {
+               return AuthManager::singleton()->canCreateAccounts();
+       }
+
+       public function addUser( $user, $password, $email = '', $realname = '' ) {
+               global $wgUser;
+
+               $data = [
+                       'username' => $user->getName(),
+                       'password' => $password,
+                       'retype' => $password,
+                       'email' => $email,
+                       'realname' => $realname,
+               ];
+               if ( $this->domain !== null && $this->domain !== '' ) {
+                       $data['domain'] = $this->domain;
+               }
+               $reqs = AuthManager::singleton()->getAuthenticationRequests( AuthManager::ACTION_CREATE );
+               $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
+
+               $res = AuthManager::singleton()->beginAccountCreation( $wgUser, $reqs, 'null:' );
+               switch ( $res->status ) {
+                       case AuthenticationResponse::PASS:
+                               return true;
+                       case AuthenticationResponse::FAIL:
+                               // Hope it's not a PreAuthenticationProvider that failed...
+                               $msg = $res->message instanceof \Message ? $res->message : new \Message( $res->message );
+                               $this->logger->info( __METHOD__ . ': Authentication failed: ' . $msg->plain() );
+                               return false;
+                       default:
+                               throw new \BadMethodCallException(
+                                       'AuthManager does not support such simplified account creation'
+                               );
+               }
+       }
+
+       public function strict() {
+               // There should be a PrimaryAuthenticationProvider that does this, if necessary
+               return true;
+       }
+
+       public function strictUserAuth( $username ) {
+               // There should be a PrimaryAuthenticationProvider that does this, if necessary
+               return true;
+       }
+
+       public function initUser( &$user, $autocreate = false ) {
+               \Hooks::run( 'LocalUserCreated', [ $user, $autocreate ] );
+       }
+
+       public function getCanonicalName( $username ) {
+               // AuthManager doesn't support restrictions beyond MediaWiki's
+               return $username;
+       }
+
+       public function getUserInstance( User &$user ) {
+               return new AuthManagerAuthPluginUser( $user );
+       }
+
+       public function domainList() {
+               return [];
+       }
+}
+
+/**
+ * @since 1.27
+ * @deprecated since 1.27
+ */
+class AuthManagerAuthPluginUser extends \AuthPluginUser {
+       /** @var User */
+       private $user;
+
+       function __construct( $user ) {
+               $this->user = $user;
+       }
+
+       public function getId() {
+               return $this->user->getId();
+       }
+
+       public function isLocked() {
+               return $this->user->isLocked();
+       }
+
+       public function isHidden() {
+               return $this->user->isHidden();
+       }
+
+       public function resetAuthToken() {
+               \MediaWiki\Session\SessionManager::singleton()->invalidateSessionsForUser( $this->user );
+               return true;
+       }
+}
diff --git a/includes/auth/AuthPluginPrimaryAuthenticationProvider.php b/includes/auth/AuthPluginPrimaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..9746637
--- /dev/null
@@ -0,0 +1,429 @@
+<?php
+/**
+ * Primary authentication provider wrapper for AuthPlugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use AuthPlugin;
+use User;
+
+/**
+ * Primary authentication provider wrapper for AuthPlugin
+ * @warning If anything depends on the wrapped AuthPlugin being $wgAuth, it won't work with this!
+ * @ingroup Auth
+ * @since 1.27
+ * @deprecated since 1.27
+ */
+class AuthPluginPrimaryAuthenticationProvider
+       extends AbstractPasswordPrimaryAuthenticationProvider
+{
+       private $auth;
+       private $hasDomain;
+       private $requestType = null;
+
+       /**
+        * @param AuthPlugin $auth AuthPlugin to wrap
+        * @param string|null $requestType Class name of the
+        *  PasswordAuthenticationRequest to use. If $auth->domainList() returns
+        *  more than one domain, this must be a PasswordDomainAuthenticationRequest.
+        */
+       public function __construct( AuthPlugin $auth, $requestType = null ) {
+               parent::__construct();
+
+               if ( $auth instanceof AuthManagerAuthPlugin ) {
+                       throw new \InvalidArgumentException(
+                               'Trying to wrap AuthManagerAuthPlugin in AuthPluginPrimaryAuthenticationProvider ' .
+                                       'makes no sense.'
+                       );
+               }
+
+               $need = count( $auth->domainList() ) > 1
+                       ? PasswordDomainAuthenticationRequest::class
+                       : PasswordAuthenticationRequest::class;
+               if ( $requestType === null ) {
+                       $requestType = $need;
+               } elseif ( $requestType !== $need && !is_subclass_of( $requestType, $need ) ) {
+                       throw new \InvalidArgumentException( "$requestType is not a $need" );
+               }
+
+               $this->auth = $auth;
+               $this->requestType = $requestType;
+               $this->hasDomain = (
+                       $requestType === PasswordDomainAuthenticationRequest::class ||
+                       is_subclass_of( $requestType, PasswordDomainAuthenticationRequest::class )
+               );
+               $this->authoritative = $auth->strict();
+
+               // Registering hooks from core is unusual, but is needed here to be
+               // able to call the AuthPlugin methods those hooks replace.
+               \Hooks::register( 'UserSaveSettings', [ $this, 'onUserSaveSettings' ] );
+               \Hooks::register( 'UserGroupsChanged', [ $this, 'onUserGroupsChanged' ] );
+               \Hooks::register( 'UserLoggedIn', [ $this, 'onUserLoggedIn' ] );
+               \Hooks::register( 'LocalUserCreated', [ $this, 'onLocalUserCreated' ] );
+       }
+
+       /**
+        * Create an appropriate AuthenticationRequest
+        * @return PasswordAuthenticationRequest
+        */
+       protected function makeAuthReq() {
+               $class = $this->requestType;
+               if ( $this->hasDomain ) {
+                       return new $class( $this->auth->domainList() );
+               } else {
+                       return new $class();
+               }
+       }
+
+       /**
+        * Call $this->auth->setDomain()
+        * @param PasswordAuthenticationRequest $req
+        */
+       protected function setDomain( $req ) {
+               if ( $this->hasDomain ) {
+                       $domain = $req->domain;
+               } else {
+                       // Just grab the first one.
+                       $domainList = $this->auth->domainList();
+                       $domain = reset( $domainList );
+               }
+
+               // Special:UserLogin does this. Strange.
+               if ( !$this->auth->validDomain( $domain ) ) {
+                       $domain = $this->auth->getDomain();
+               }
+               $this->auth->setDomain( $domain );
+       }
+
+       /**
+        * Hook function to call AuthPlugin::updateExternalDB()
+        * @param User $user
+        * @codeCoverageIgnore
+        */
+       public function onUserSaveSettings( $user ) {
+               // No way to know the domain, just hope the provider handles that.
+               $this->auth->updateExternalDB( $user );
+       }
+
+       /**
+        * Hook function to call AuthPlugin::updateExternalDBGroups()
+        * @param User $user
+        * @param array $added
+        * @param array $removed
+        */
+       public function onUserGroupsChanged( $user, $added, $removed ) {
+               // No way to know the domain, just hope the provider handles that.
+               $this->auth->updateExternalDBGroups( $user, $added, $removed );
+       }
+
+       /**
+        * Hook function to call AuthPlugin::updateUser()
+        * @param User $user
+        */
+       public function onUserLoggedIn( $user ) {
+               $hookUser = $user;
+               // No way to know the domain, just hope the provider handles that.
+               $this->auth->updateUser( $hookUser );
+               if ( $hookUser !== $user ) {
+                       throw new \UnexpectedValueException(
+                               get_class( $this->auth ) . '::updateUser() tried to replace $user!'
+                       );
+               }
+       }
+
+       /**
+        * Hook function to call AuthPlugin::initUser()
+        * @param User $user
+        * @param bool $autocreated
+        */
+       public function onLocalUserCreated( $user, $autocreated ) {
+               // For $autocreated, see self::autoCreatedAccount()
+               if ( !$autocreated ) {
+                       $hookUser = $user;
+                       // No way to know the domain, just hope the provider handles that.
+                       $this->auth->initUser( $hookUser, $autocreated );
+                       if ( $hookUser !== $user ) {
+                               throw new \UnexpectedValueException(
+                                       get_class( $this->auth ) . '::initUser() tried to replace $user!'
+                               );
+                       }
+               }
+       }
+
+       public function getUniqueId() {
+               return parent::getUniqueId() . ':' . get_class( $this->auth );
+       }
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               switch ( $action ) {
+                       case AuthManager::ACTION_LOGIN:
+                       case AuthManager::ACTION_CREATE:
+                               return [ $this->makeAuthReq() ];
+
+                       case AuthManager::ACTION_CHANGE:
+                       case AuthManager::ACTION_REMOVE:
+                               // No way to know the domain, just hope the provider handles that.
+                               return $this->auth->allowPasswordChange() ? [ $this->makeAuthReq() ] : [];
+
+                       default:
+                               return [];
+               }
+       }
+
+       public function beginPrimaryAuthentication( array $reqs ) {
+               $req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType );
+               if ( !$req || $req->username === null || $req->password === null ||
+                       ( $this->hasDomain && $req->domain === null )
+               ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $username = User::getCanonicalName( $req->username, 'usable' );
+               if ( $username === false ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $this->setDomain( $req );
+               if ( $this->testUserCanAuthenticateInternal( User::newFromName( $username ) ) &&
+                       $this->auth->authenticate( $username, $req->password )
+               ) {
+                       return AuthenticationResponse::newPass( $username );
+               } else {
+                       $this->authoritative = $this->auth->strict() || $this->auth->strictUserAuth( $username );
+                       return $this->failResponse( $req );
+               }
+       }
+
+       public function testUserCanAuthenticate( $username ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return false;
+               }
+
+               // We have to check every domain, because at least LdapAuthentication
+               // interprets AuthPlugin::userExists() as applying only to the current
+               // domain.
+               $curDomain = $this->auth->getDomain();
+               $domains = $this->auth->domainList() ?: [ '' ];
+               foreach ( $domains as $domain ) {
+                       $this->auth->setDomain( $domain );
+                       if ( $this->testUserCanAuthenticateInternal( User::newFromName( $username ) ) ) {
+                               $this->auth->setDomain( $curDomain );
+                               return true;
+                       }
+               }
+               $this->auth->setDomain( $curDomain );
+               return false;
+       }
+
+       /**
+        * @see self::testUserCanAuthenticate
+        * @note The caller is responsible for calling $this->auth->setDomain()
+        * @param User $user
+        * @return bool
+        */
+       private function testUserCanAuthenticateInternal( $user ) {
+               if ( $this->auth->userExists( $user->getName() ) ) {
+                       return !$this->auth->getUserInstance( $user )->isLocked();
+               } else {
+                       return false;
+               }
+       }
+
+       public function providerRevokeAccessForUser( $username ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return;
+               }
+               $user = User::newFromName( $username );
+               if ( $user ) {
+                       // Reset the password on every domain.
+                       $curDomain = $this->auth->getDomain();
+                       $domains = $this->auth->domainList() ?: [ '' ];
+                       $failed = [];
+                       foreach ( $domains as $domain ) {
+                               $this->auth->setDomain( $domain );
+                               if ( $this->testUserCanAuthenticateInternal( $user ) &&
+                                       !$this->auth->setPassword( $user, null )
+                               ) {
+                                       $failed[] = $domain === '' ? '(default)' : $domain;
+                               }
+                       }
+                       $this->auth->setDomain( $curDomain );
+                       if ( $failed ) {
+                               throw new \UnexpectedValueException(
+                                       "AuthPlugin failed to reset password for $username in the following domains: "
+                                               . join( ' ', $failed )
+                               );
+                       }
+               }
+       }
+
+       public function testUserExists( $username, $flags = User::READ_NORMAL ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return false;
+               }
+
+               // We have to check every domain, because at least LdapAuthentication
+               // interprets AuthPlugin::userExists() as applying only to the current
+               // domain.
+               $curDomain = $this->auth->getDomain();
+               $domains = $this->auth->domainList() ?: [ '' ];
+               foreach ( $domains as $domain ) {
+                       $this->auth->setDomain( $domain );
+                       if ( $this->auth->userExists( $username ) ) {
+                               $this->auth->setDomain( $curDomain );
+                               return true;
+                       }
+               }
+               $this->auth->setDomain( $curDomain );
+               return false;
+       }
+
+       public function providerAllowsPropertyChange( $property ) {
+               // No way to know the domain, just hope the provider handles that.
+               return $this->auth->allowPropChange( $property );
+       }
+
+       public function providerAllowsAuthenticationDataChange(
+               AuthenticationRequest $req, $checkData = true
+       ) {
+               if ( get_class( $req ) !== $this->requestType ) {
+                       return \StatusValue::newGood( 'ignored' );
+               }
+
+               // Hope it works, AuthPlugin gives us no way to do this.
+               $curDomain = $this->auth->getDomain();
+               $this->setDomain( $req );
+               try {
+                       // If !$checkData the domain might be wrong. Nothing we can do about that.
+                       if ( !$this->auth->allowPasswordChange() ) {
+                               return \StatusValue::newFatal( 'authmanager-authplugin-setpass-denied' );
+                       }
+
+                       if ( !$checkData ) {
+                               return \StatusValue::newGood();
+                       }
+
+                       if ( $this->hasDomain ) {
+                               if ( $req->domain === null ) {
+                                       return \StatusValue::newGood( 'ignored' );
+                               }
+                               if ( !$this->auth->validDomain( $domain ) ) {
+                                       return \StatusValue::newFatal( 'authmanager-authplugin-setpass-bad-domain' );
+                               }
+                       }
+
+                       $username = User::getCanonicalName( $req->username, 'usable' );
+                       if ( $username !== false ) {
+                               $sv = \StatusValue::newGood();
+                               if ( $req->password !== null ) {
+                                       if ( $req->password !== $req->retype ) {
+                                               $sv->fatal( 'badretype' );
+                                       } else {
+                                               $sv->merge( $this->checkPasswordValidity( $username, $req->password ) );
+                                       }
+                               }
+                               return $sv;
+                       } else {
+                               return \StatusValue::newGood( 'ignored' );
+                       }
+               } finally {
+                       $this->auth->setDomain( $curDomain );
+               }
+       }
+
+       public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
+               if ( get_class( $req ) === $this->requestType ) {
+                       $username = $req->username !== null ? User::getCanonicalName( $req->username, 'usable' ) : false;
+                       if ( $username === false ) {
+                               return;
+                       }
+
+                       if ( $this->hasDomain && $req->domain === null ) {
+                               return;
+                       }
+
+                       $this->setDomain( $req );
+                       $user = User::newFromName( $username );
+                       if ( !$this->auth->setPassword( $user, $req->password ) ) {
+                               // This is totally unfriendly and leaves other
+                               // AuthenticationProviders in an uncertain state, but what else
+                               // can we do?
+                               throw new \ErrorPageError(
+                                       'authmanager-authplugin-setpass-failed-title',
+                                       'authmanager-authplugin-setpass-failed-message'
+                               );
+                       }
+               }
+       }
+
+       public function accountCreationType() {
+               // No way to know the domain, just hope the provider handles that.
+               return $this->auth->canCreateAccounts() ? self::TYPE_CREATE : self::TYPE_NONE;
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               return \StatusValue::newGood();
+       }
+
+       public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
+               if ( $this->accountCreationType() === self::TYPE_NONE ) {
+                       throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
+               }
+
+               $req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType );
+               if ( !$req || $req->username === null || $req->password === null ||
+                       ( $this->hasDomain && $req->domain === null )
+               ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $username = User::getCanonicalName( $req->username, 'usable' );
+               if ( $username === false ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $this->setDomain( $req );
+               if ( $this->auth->addUser(
+                       $user, $req->password, $user->getEmail(), $user->getRealName()
+               ) ) {
+                       return AuthenticationResponse::newPass();
+               } else {
+                       return AuthenticationResponse::newFail(
+                               new \Message( 'authmanager-authplugin-create-fail' )
+                       );
+               }
+       }
+
+       public function autoCreatedAccount( $user, $source ) {
+               $hookUser = $user;
+               // No way to know the domain, just hope the provider handles that.
+               $this->auth->initUser( $hookUser, true );
+               if ( $hookUser !== $user ) {
+                       throw new \UnexpectedValueException(
+                               get_class( $this->auth ) . '::initUser() tried to replace $user!'
+                       );
+               }
+       }
+}
diff --git a/includes/auth/AuthenticationProvider.php b/includes/auth/AuthenticationProvider.php
new file mode 100644 (file)
index 0000000..4db0a84
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Authentication provider interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Config;
+use Psr\Log\LoggerAwareInterface;
+
+/**
+ * An AuthenticationProvider is used by AuthManager when authenticating users.
+ * @ingroup Auth
+ * @since 1.27
+ */
+interface AuthenticationProvider extends LoggerAwareInterface {
+
+       /**
+        * Set AuthManager
+        * @param AuthManager $manager
+        */
+       public function setManager( AuthManager $manager );
+
+       /**
+        * Set configuration
+        * @param Config $config
+        */
+       public function setConfig( Config $config );
+
+       /**
+        * Return a unique identifier for this instance
+        *
+        * This must be the same across requests. If multiple instances return the
+        * same ID, exceptions will be thrown from AuthManager.
+        *
+        * @return string
+        */
+       public function getUniqueId();
+
+       /**
+        * Return the applicable list of AuthenticationRequests
+        *
+        * Possible values for $action depend on whether the implementing class is
+        * also a PreAuthenticationProvider, PrimaryAuthenticationProvider, or
+        * SecondaryAuthenticationProvider.
+        *  - ACTION_LOGIN: Valid for passing to beginAuthentication. Called on all
+        *    providers.
+        *  - ACTION_CREATE: Valid for passing to beginAccountCreation. Called on
+        *    all providers.
+        *  - ACTION_LINK: Valid for passing to beginAccountLink. Called on linking
+        *    primary providers only.
+        *  - ACTION_CHANGE: Valid for passing to AuthManager::changeAuthenticationData
+        *    to change credentials. Called on primary and secondary providers.
+        *  - ACTION_REMOVE: Valid for passing to AuthManager::changeAuthenticationData
+        *    to remove credentials. Must work without additional user input (i.e.
+        *    without calling loadFromSubmission). Called on primary and secondary
+        *    providers.
+        *
+        * @see AuthManager::getAuthenticationRequests()
+        * @param string $action
+        * @param array $options Options are:
+        *  - username: User name related to the action, or null/unset if anon.
+        *    - ACTION_LOGIN: The currently logged-in user, if any.
+        *    - ACTION_CREATE: The account creator, if non-anonymous.
+        *    - ACTION_LINK: The local user being linked to.
+        *    - ACTION_CHANGE: The user having data changed.
+        *    - ACTION_REMOVE: The user having data removed.
+        *    This does not need to be copied into the returned requests, you only
+        *    need to pay attention to it if the set of requests differs based on
+        *    the user.
+        * @return AuthenticationRequest[]
+        */
+       public function getAuthenticationRequests( $action, array $options );
+
+}
diff --git a/includes/auth/AuthenticationRequest.php b/includes/auth/AuthenticationRequest.php
new file mode 100644 (file)
index 0000000..3c19b87
--- /dev/null
@@ -0,0 +1,338 @@
+<?php
+/**
+ * Authentication request value object
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Message;
+
+/**
+ * This is a value object for authentication requests.
+ *
+ * An AuthenticationRequest represents a set of form fields that are needed on
+ * and provided from the login, account creation, or password change forms.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+abstract class AuthenticationRequest {
+
+       /** Indicates that the request is not required for authentication to proceed. */
+       const OPTIONAL = 0;
+
+       /** Indicates that the request is required for authentication to proceed. */
+       const REQUIRED = 1;
+
+       /** Indicates that the request is required by a primary authentication
+        * provdier, but other primary authentication providers do not require it. */
+       const PRIMARY_REQUIRED = 2;
+
+       /** @var string|null The AuthManager::ACTION_* constant this request was
+        * created to be used for. The *_CONTINUE constants are not used here, the
+        * corresponding "begin" constant is used instead.
+        */
+       public $action = null;
+
+       /** @var int For login, continue, and link actions, one of self::OPTIONAL,
+        * self::REQUIRED, or self::PRIMARY_REQUIRED */
+       public $required = self::REQUIRED;
+
+       /** @var string|null Return-to URL, in case of redirect */
+       public $returnToUrl = null;
+
+       /** @var string|null Username. May not be used by all subclasses. */
+       public $username = null;
+
+       /**
+        * Supply a unique key for deduplication
+        *
+        * When the AuthenticationRequests instances returned by the providers are
+        * merged, the value returned here is used for keeping only one copy of
+        * duplicate requests.
+        *
+        * Subclasses should override this if multiple distinct instances would
+        * make sense, i.e. the request class has internal state of some sort.
+        *
+        * This value might be exposed to the user in web forms so it should not
+        * contain private information.
+        *
+        * @return string
+        */
+       public function getUniqueId() {
+               return get_called_class();
+       }
+
+       /**
+        * Fetch input field info
+        *
+        * The field info is an associative array mapping field names to info
+        * arrays. The info arrays have the following keys:
+        *  - type: (string) Type of input. Types and equivalent HTML widgets are:
+        *     - string: <input type="text">
+        *     - password: <input type="password">
+        *     - select: <select>
+        *     - checkbox: <input type="checkbox">
+        *     - multiselect: More a grid of checkboxes than <select multi>
+        *     - button: <input type="image"> if 'image' is set, otherwise <input type="submit">
+        *       (uses 'label' as button text)
+        *     - hidden: Not visible to the user, but needs to be preserved for the next request
+        *     - null: No widget, just display the 'label' message.
+        *  - options: (array) Maps option values to Messages for the
+        *      'select' and 'multiselect' types.
+        *  - value: (string) Value (for 'null' and 'hidden') or default value (for other types).
+        *  - image: (string) URL of an image to use in connection with the input
+        *  - label: (Message) Text suitable for a label in an HTML form
+        *  - help: (Message) Text suitable as a description of what the field is
+        *  - optional: (bool) If set and truthy, the field may be left empty
+        *
+        * @return array As above
+        */
+       abstract public function getFieldInfo();
+
+       /**
+        * Returns metadata about this request.
+        *
+        * This is mainly for the benefit of API clients which need more detailed render hints
+        * than what's available through getFieldInfo(). Semantics are unspecified and left to the
+        * individual subclasses, but the contents of the array should be primitive types so that they
+        * can be transformed into JSON or similar formats.
+        *
+        * @return array A (possibly nested) array with primitive types
+        */
+       public function getMetadata() {
+               return [];
+       }
+
+       /**
+        * Initialize form submitted form data.
+        *
+        * Should always return false if self::getFieldInfo() returns an empty
+        * array.
+        *
+        * @param array $data Submitted data as an associative array
+        * @return bool Whether the request data was successfully loaded
+        */
+       public function loadFromSubmission( array $data ) {
+               $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
+                       return $info['type'] !== 'null';
+               } );
+               if ( !$fields ) {
+                       return false;
+               }
+
+               foreach ( $fields as $field => $info ) {
+                       // Checkboxes and buttons are special. Depending on the method used
+                       // to populate $data, they might be unset meaning false or they
+                       // might be boolean. Further, image buttons might submit the
+                       // coordinates of the click rather than the expected value.
+                       if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
+                               $this->$field = isset( $data[$field] ) && $data[$field] !== false
+                                       || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
+                               if ( !$this->$field && empty( $info['optional'] ) ) {
+                                       return false;
+                               }
+                               continue;
+                       }
+
+                       // Multiselect are too, slightly
+                       if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
+                               $data[$field] = [];
+                       }
+
+                       if ( !isset( $data[$field] ) ) {
+                               return false;
+                       }
+                       if ( $data[$field] === '' || $data[$field] === [] ) {
+                               if ( empty( $info['optional'] ) ) {
+                                       return false;
+                               }
+                       } else {
+                               switch ( $info['type'] ) {
+                                       case 'select':
+                                               if ( !isset( $info['options'][$data[$field]] ) ) {
+                                                       return false;
+                                               }
+                                               break;
+
+                                       case 'multiselect':
+                                               $data[$field] = (array)$data[$field];
+                                               $allowed = array_keys( $info['options'] );
+                                               if ( array_diff( $data[$field], $allowed ) !== [] ) {
+                                                       return false;
+                                               }
+                                               break;
+                               }
+                       }
+
+                       $this->$field = $data[$field];
+               }
+
+               return true;
+       }
+
+       /**
+        * Describe the credentials represented by this request
+        *
+        * This is used on requests returned by
+        * AuthenticationProvider::getAuthenticationRequests() for ACTION_LINK
+        * and ACTION_REMOVE and for requests returned in
+        * AuthenticationResponse::$linkRequest to create useful user interfaces.
+        *
+        * @return Message[] with the following keys:
+        *  - provider: A Message identifying the service that provides
+        *    the credentials, e.g. the name of the third party authentication
+        *    service.
+        *  - account: A Message identifying the credentials themselves,
+        *    e.g. the email address used with the third party authentication
+        *    service.
+        */
+       public function describeCredentials() {
+               return [
+                       'provider' => new \RawMessage( '$1', [ get_called_class() ] ),
+                       'account' => new \RawMessage( '$1', [ $this->getUniqueId() ] ),
+               ];
+       }
+
+       /**
+        * Update a set of requests with form submit data, discarding ones that fail
+        * @param AuthenticationRequest[] $reqs
+        * @param array $data
+        * @return AuthenticationRequest[]
+        */
+       public static function loadRequestsFromSubmission( array $reqs, array $data ) {
+               return array_values( array_filter( $reqs, function ( $req ) use ( $data ) {
+                       return $req->loadFromSubmission( $data );
+               } ) );
+       }
+
+       /**
+        * Select a request by class name.
+        * @param AuthenticationRequest[] $reqs
+        * @param string $class Class name
+        * @param bool $allowSubclasses If true, also returns any request that's a subclass of the given
+        *   class.
+        * @return AuthenticationRequest|null Returns null if there is not exactly
+        *  one matching request.
+        */
+       public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
+               $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
+                       if ( $allowSubclasses ) {
+                               return is_a( $req, $class, false );
+                       } else {
+                               return get_class( $req ) === $class;
+                       }
+               } );
+               return count( $requests ) === 1 ? reset( $requests ) : null;
+       }
+
+       /**
+        * Get the username from the set of requests
+        *
+        * Only considers requests that have a "username" field.
+        *
+        * @param AuthenticationRequest[] $requests
+        * @return string|null
+        * @throws \UnexpectedValueException If multiple different usernames are present.
+        */
+       public static function getUsernameFromRequests( array $reqs ) {
+               $username = null;
+               $otherClass = null;
+               foreach ( $reqs as $req ) {
+                       $info = $req->getFieldInfo();
+                       if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
+                               if ( $username === null ) {
+                                       $username = $req->username;
+                                       $otherClass = get_class( $req );
+                               } elseif ( $username !== $req->username ) {
+                                       $requestClass = get_class( $req );
+                                       throw new \UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
+                                               . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
+                               }
+                       }
+               }
+               return $username;
+       }
+
+       /**
+        * Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
+        * @param AuthenticationRequest[] $reqs
+        * @return array
+        * @throws \UnexpectedValueException If fields cannot be merged
+        */
+       public static function mergeFieldInfo( array $reqs ) {
+               $merged = [];
+
+               foreach ( $reqs as $req ) {
+                       $info = $req->getFieldInfo();
+                       if ( !$info ) {
+                               continue;
+                       }
+
+                       foreach ( $info as $name => $options ) {
+                               if ( $req->required !== self::REQUIRED ) {
+                                       // If the request isn't required, its fields aren't required either.
+                                       $options['optional'] = true;
+                               } else {
+                                       $options['optional'] = !empty( $options['optional'] );
+                               }
+
+                               if ( !array_key_exists( $name, $merged ) ) {
+                                       $merged[$name] = $options;
+                               } elseif ( $merged[$name]['type'] !== $options['type'] ) {
+                                       throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
+                                               "\"{$merged[$name]['type']}\" vs \"{$options['type']}\""
+                                       );
+                               } else {
+                                       if ( isset( $options['options'] ) ) {
+                                               if ( isset( $merged[$name]['options'] ) ) {
+                                                       $merged[$name]['options'] += $options['options'];
+                                               } else {
+                                                       // @codeCoverageIgnoreStart
+                                                       $merged[$name]['options'] = $options['options'];
+                                                       // @codeCoverageIgnoreEnd
+                                               }
+                                       }
+
+                                       $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
+
+                                       // No way to merge 'value', 'image', 'help', or 'label', so just use
+                                       // the value from the first request.
+                               }
+                       }
+               }
+
+               return $merged;
+       }
+
+       /**
+        * Implementing this mainly for use from the unit tests.
+        * @param array $data
+        * @return AuthenticationRequest
+        */
+       public static function __set_state( $data ) {
+               $ret = new static();
+               foreach ( $data as $k => $v ) {
+                       $ret->$k = $v;
+               }
+               return $ret;
+       }
+}
diff --git a/includes/auth/AuthenticationResponse.php b/includes/auth/AuthenticationResponse.php
new file mode 100644 (file)
index 0000000..db01825
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Authentication response value object
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Message;
+
+/**
+ * This is a value object to hold authentication response data
+ * @ingroup Auth
+ * @since 1.27
+ */
+class AuthenticationResponse {
+       /** Indicates that the authentication succeeded. */
+       const PASS = 'PASS';
+
+       /** Indicates that the authentication failed. */
+       const FAIL = 'FAIL';
+
+       /** Indicates that third-party authentication succeeded but no user exists.
+        * Either treat this like a UI response or pass $this->createRequest to
+        * AuthManager::beginCreateAccount().
+        */
+       const RESTART = 'RESTART';
+
+       /** Indicates that the authentication provider does not handle this request. */
+       const ABSTAIN = 'ABSTAIN';
+
+       /** Indicates that the authentication needs further user input of some sort. */
+       const UI = 'UI';
+
+       /** Indicates that the authentication needs to be redirected to a third party to proceed. */
+       const REDIRECT = 'REDIRECT';
+
+       /** @var string One of the constants above */
+       public $status;
+
+       /** @var string|null URL to redirect to for a REDIRECT response */
+       public $redirectTarget = null;
+
+       /**
+        * @var mixed Data for a REDIRECT response that a client might use to
+        * query the remote site via its API rather than by following $redirectTarget.
+        * Value must be something acceptable to ApiResult::addValue().
+        */
+       public $redirectApiData = null;
+
+       /**
+        * @var AuthenticationRequest[] Needed AuthenticationRequests to continue
+        * after a UI or REDIRECT response
+        */
+       public $neededRequests = [];
+
+       /** @var Message|null I18n message to display in case of UI or FAIL */
+       public $message = null;
+
+       /**
+        * @var string|null Local user name from authentication.
+        * May be null if the authentication passed but no local user is known.
+        */
+       public $username = null;
+
+       /**
+        * @var AuthenticationRequest|null
+        *
+        * Returned with a PrimaryAuthenticationProvider login FAIL, this holds a
+        * request that should result in a PASS when passed to that provider's
+        * PrimaryAuthenticationProvider::beginPrimaryAccountCreation().
+        *
+        * Returned with an AuthManager login FAIL or RESTART, this holds a request
+        * that may be passed to AuthManager::beginCreateAccount() after setting
+        * its ->returnToUrl property. It may also be passed to
+        * AuthManager::beginAuthentication() to preserve state.
+        */
+       public $createRequest = null;
+
+       /**
+        * @var AuthenticationRequest|null Returned with a PrimaryAuthenticationProvider
+        *  login PASS with no username, this holds a request to pass to
+        *  AuthManager::changeAuthenticationData() to link the account once the
+        *  local user has been determined.
+        */
+       public $linkRequest = null;
+
+       /**
+        * @var AuthenticationRequest|null Returned with an AuthManager account
+        *  creation PASS, this holds a request to pass to AuthManager::beginAuthentication()
+        *  to immediately log into the created account.
+        */
+       public $loginRequest = null;
+
+       /**
+        * @param string|null $username Local username
+        * @return AuthenticationResponse
+        */
+       public static function newPass( $username = null ) {
+               $ret = new AuthenticationResponse;
+               $ret->status = AuthenticationResponse::PASS;
+               $ret->username = $username;
+               return $ret;
+       }
+
+       /**
+        * @param Message $msg
+        * @return AuthenticationResponse
+        */
+       public static function newFail( Message $msg ) {
+               $ret = new AuthenticationResponse;
+               $ret->status = AuthenticationResponse::FAIL;
+               $ret->message = $msg;
+               return $ret;
+       }
+
+       /**
+        * @param Message $msg
+        * @return AuthenticationResponse
+        */
+       public static function newRestart( Message $msg ) {
+               $ret = new AuthenticationResponse;
+               $ret->status = AuthenticationResponse::RESTART;
+               $ret->message = $msg;
+               return $ret;
+       }
+
+       /**
+        * @return AuthenticationResponse
+        */
+       public static function newAbstain() {
+               $ret = new AuthenticationResponse;
+               $ret->status = AuthenticationResponse::ABSTAIN;
+               return $ret;
+       }
+
+       /**
+        * @param AuthenticationRequest[] $reqs AuthenticationRequests needed to continue
+        * @param Message $msg
+        * @return AuthenticationResponse
+        */
+       public static function newUI( array $reqs, Message $msg ) {
+               if ( !$reqs ) {
+                       throw new \InvalidArgumentException( '$reqs may not be empty' );
+               }
+
+               $ret = new AuthenticationResponse;
+               $ret->status = AuthenticationResponse::UI;
+               $ret->neededRequests = $reqs;
+               $ret->message = $msg;
+               return $ret;
+       }
+
+       /**
+        * @param AuthenticationRequest[] $reqs AuthenticationRequests needed to continue
+        * @param string $redirectTarget URL
+        * @param mixed $redirectApiData Data suitable for adding to an ApiResult
+        * @return AuthenticationResponse
+        */
+       public static function newRedirect( array $reqs, $redirectTarget, $redirectApiData = null ) {
+               if ( !$reqs ) {
+                       throw new \InvalidArgumentException( '$reqs may not be empty' );
+               }
+
+               $ret = new AuthenticationResponse;
+               $ret->status = AuthenticationResponse::REDIRECT;
+               $ret->neededRequests = $reqs;
+               $ret->redirectTarget = $redirectTarget;
+               $ret->redirectApiData = $redirectApiData;
+               return $ret;
+       }
+
+}
diff --git a/includes/auth/ButtonAuthenticationRequest.php b/includes/auth/ButtonAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..055d7ea
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Message;
+
+/**
+ * This is an authentication request that just implements a simple button.
+ * @ingroup Auth
+ * @since 1.27
+ */
+class ButtonAuthenticationRequest extends AuthenticationRequest {
+       /** @var string */
+       protected $name;
+
+       /** @var Message */
+       protected $label;
+
+       /** @var Message */
+       protected $help;
+
+       /**
+        * @param string $name Button name
+        * @param Message $label Button label
+        * @param Message $help Button help
+        * @param bool $required The button is required for authentication to proceed.
+        */
+       public function __construct( $name, Message $label, Message $help, $required = false ) {
+               $this->name = $name;
+               $this->label = $label;
+               $this->help = $help;
+               $this->required = $required ? self::REQUIRED : self::OPTIONAL;
+       }
+
+       public function getUniqueId() {
+               return parent::getUniqueId() . ':' . $this->name;
+       }
+
+       public function getFieldInfo() {
+               return [
+                       $this->name => [
+                               'type' => 'button',
+                               'label' => $this->label,
+                               'help' => $this->help,
+                       ]
+               ];
+       }
+
+       /**
+        * Fetch a ButtonAuthenticationRequest or subclass by name
+        * @param AuthenticationRequest[] $reqs Requests to search
+        * @param string $name Name to look for
+        * @return ButtonAuthenticationRequest|null Returns null if there is not
+        *  exactly one matching request.
+        */
+       public static function getRequestByName( array $reqs, $name ) {
+               $requests = array_filter( $reqs, function ( $req ) use ( $name ) {
+                       return $req instanceof ButtonAuthenticationRequest && $req->name === $name;
+               } );
+               return count( $requests ) === 1 ? reset( $requests ) : null;
+       }
+
+       /**
+        * @codeCoverageIgnore
+        */
+       public static function __set_state( $data ) {
+               if ( !isset( $data['label'] ) ) {
+                       $data['label'] = new \RawMessage( '$1', $data['name'] );
+               } elseif ( is_string( $data['label'] ) ) {
+                       $data['label'] = new \Message( $data['label'] );
+               } elseif ( is_array( $data['label'] ) ) {
+                       $data['label'] = call_user_func_array( 'Message::newFromKey', $data['label'] );
+               }
+               if ( !isset( $data['help'] ) ) {
+                       $data['help'] = new \RawMessage( '$1', $data['name'] );
+               } elseif ( is_string( $data['help'] ) ) {
+                       $data['help'] = new \Message( $data['help'] );
+               } elseif ( is_array( $data['help'] ) ) {
+                       $data['help'] = call_user_func_array( 'Message::newFromKey', $data['help'] );
+               }
+               $ret = new static( $data['name'], $data['label'], $data['help'] );
+               foreach ( $data as $k => $v ) {
+                       $ret->$k = $v;
+               }
+               return $ret;
+       }
+}
diff --git a/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php b/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..070da9f
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use Config;
+use StatusValue;
+use User;
+
+/**
+ * Check if the user is blocked, and prevent authentication if so.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class CheckBlocksSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
+
+       /** @var bool */
+       protected $blockDisablesLogin = null;
+
+       /**
+        * @param array $params
+        *  - blockDisablesLogin: (bool) Whether blocked accounts can log in,
+        *    defaults to $wgBlockDisablesLogin
+        */
+       public function __construct( $params = [] ) {
+               if ( isset( $params['blockDisablesLogin'] ) ) {
+                       $this->blockDisablesLogin = (bool)$params['blockDisablesLogin'];
+               }
+       }
+
+       public function setConfig( Config $config ) {
+               parent::setConfig( $config );
+
+               if ( $this->blockDisablesLogin === null ) {
+                       $this->blockDisablesLogin = $this->config->get( 'BlockDisablesLogin' );
+               }
+       }
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               return [];
+       }
+
+       public function beginSecondaryAuthentication( $user, array $reqs ) {
+               if ( !$this->blockDisablesLogin ) {
+                       return AuthenticationResponse::newAbstain();
+               } elseif ( $user->isBlocked() ) {
+                       return AuthenticationResponse::newFail(
+                               new \Message( 'login-userblocked', [ $user->getName() ] )
+                       );
+               } else {
+                       return AuthenticationResponse::newPass();
+               }
+       }
+
+       public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               return AuthenticationResponse::newAbstain();
+       }
+
+       public function testUserForCreation( $user, $autocreate ) {
+               $block = $user->isBlockedFromCreateAccount();
+               if ( $block ) {
+                       $errorParams = [
+                               $block->getTarget(),
+                               $block->mReason ?: \Message::newFromKey( 'blockednoreason' )->text(),
+                               $block->getByName()
+                       ];
+
+                       if ( $block->getType() === \Block::TYPE_RANGE ) {
+                               $errorMessage = 'cantcreateaccount-range-text';
+                               $errorParams[] = $this->manager->getRequest()->getIP();
+                       } else {
+                               $errorMessage = 'cantcreateaccount-text';
+                       }
+
+                       return StatusValue::newFatal(
+                               new \Message( $errorMessage, $errorParams )
+                       );
+               } else {
+                       return StatusValue::newGood();
+               }
+       }
+
+}
diff --git a/includes/auth/ConfirmLinkAuthenticationRequest.php b/includes/auth/ConfirmLinkAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..b82914f
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+class ConfirmLinkAuthenticationRequest extends AuthenticationRequest {
+       /** @var AuthenticationRequest[] */
+       protected $linkRequests;
+
+       /** @var string[] List of unique IDs of the confirmed accounts. */
+       public $confirmedLinkIDs = [];
+
+       /**
+        * @param AuthenticationRequest[] $linkRequests A list of autolink requests
+        *  which need to be confirmed.
+        */
+       public function __construct( array $linkRequests ) {
+               if ( !$linkRequests ) {
+                       throw new \InvalidArgumentException( '$linkRequests must not be empty' );
+               }
+               $this->linkRequests = $linkRequests;
+       }
+
+       public function getFieldInfo() {
+               $options = [];
+               foreach ( $this->linkRequests as $req ) {
+                       $description = $req->describeCredentials();
+                       $options[$req->getUniqueId()] = wfMessage(
+                               'authprovider-confirmlink-option',
+                               $description['provider']->text(), $description['account']->text()
+                       );
+               }
+               return [
+                       'confirmedLinkIDs' => [
+                               'type' => 'multiselect',
+                               'options' => $options,
+                               'label' => wfMessage( 'authprovider-confirmlink-request-label' ),
+                               'help' => wfMessage( 'authprovider-confirmlink-request-help' ),
+                               'optional' => true,
+                       ]
+               ];
+       }
+
+       public function getUniqueId() {
+               return parent::getUniqueId() . ':' . implode( '|', array_map( function ( $req ) {
+                       return $req->getUniqueId();
+               }, $this->linkRequests ) );
+       }
+
+       /**
+        * Implementing this mainly for use from the unit tests.
+        * @param array $data
+        * @return AuthenticationRequest
+        */
+       public static function __set_state( $data ) {
+               $ret = new static( $data['linkRequests'] );
+               foreach ( $data as $k => $v ) {
+                       $ret->$k = $v;
+               }
+               return $ret;
+       }
+}
diff --git a/includes/auth/ConfirmLinkSecondaryAuthenticationProvider.php b/includes/auth/ConfirmLinkSecondaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..180aaae
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+use StatusValue;
+use User;
+
+/**
+ * Links third-party authentication to the user's account
+ *
+ * If the user logged into linking provider accounts that aren't linked to a
+ * local user, this provider will prompt the user to link them after a
+ * successful login or account creation.
+ *
+ * To avoid confusing behavior, this provider should be later in the
+ * configuration list than any provider that can abort the authentication
+ * process, so that it is only invoked for successful authentication.
+ */
+class ConfirmLinkSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               return [];
+       }
+
+       public function beginSecondaryAuthentication( $user, array $reqs ) {
+               return $this->beginLinkAttempt( $user, 'AuthManager::authnState' );
+       }
+
+       public function continueSecondaryAuthentication( $user, array $reqs ) {
+               return $this->continueLinkAttempt( $user, 'AuthManager::authnState', $reqs );
+       }
+
+       public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               return $this->beginLinkAttempt( $user, 'AuthManager::accountCreationState' );
+       }
+
+       public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               return $this->continueLinkAttempt( $user, 'AuthManager::accountCreationState', $reqs );
+       }
+
+       /**
+        * Begin the link attempt
+        * @param User $user
+        * @param string $key Session key to look in
+        * @return AuthenticationResponse
+        */
+       protected function beginLinkAttempt( $user, $key ) {
+               $session = $this->manager->getRequest()->getSession();
+               $state = $session->getSecret( $key );
+               if ( !is_array( $state ) ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+               $maybeLink = $state['maybeLink'];
+               if ( !$maybeLink ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $req = new ConfirmLinkAuthenticationRequest( $maybeLink );
+               return AuthenticationResponse::newUI(
+                       [ $req ],
+                       wfMessage( 'authprovider-confirmlink-message' )
+               );
+       }
+
+       /**
+        * Continue the link attempt
+        * @param User $user
+        * @param string $key Session key to look in
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse
+        */
+       protected function continueLinkAttempt( $user, $key, array $reqs ) {
+               $req = ButtonAuthenticationRequest::getRequestByName( $reqs, 'linkOk' );
+               if ( $req ) {
+                       return AuthenticationResponse::newPass();
+               }
+
+               $req = AuthenticationRequest::getRequestByClass( $reqs, ConfirmLinkAuthenticationRequest::class );
+               if ( !$req ) {
+                       // WTF? Retry.
+                       return $this->beginLinkAttempt( $user, $key );
+               }
+
+               $session = $this->manager->getRequest()->getSession();
+               $state = $session->getSecret( $key );
+               if ( !is_array( $state ) ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $maybeLink = [];
+               foreach ( $state['maybeLink'] as $linkReq ) {
+                       $maybeLink[$linkReq->getUniqueId()] = $linkReq;
+               }
+               if ( !$maybeLink ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $state['maybeLink'] = [];
+               $session->setSecret( $key, $state );
+
+               $statuses = [];
+               $anyFailed = false;
+               foreach ( $req->confirmedLinkIDs as $id ) {
+                       if ( isset( $maybeLink[$id] ) ) {
+                               $req = $maybeLink[$id];
+                               $req->username = $user->getName();
+                               if ( !$req->action ) {
+                                       // Make sure the action is set, but don't override it if
+                                       // the provider filled it in.
+                                       $req->action = AuthManager::ACTION_CHANGE;
+                               }
+                               $status = $this->manager->allowsAuthenticationDataChange( $req );
+                               $statuses[] = [ $req, $status ];
+                               if ( $status->isGood() ) {
+                                       $this->manager->changeAuthenticationData( $req );
+                               } else {
+                                       $anyFailed = true;
+                               }
+                       }
+               }
+               if ( !$anyFailed ) {
+                       return AuthenticationResponse::newPass();
+               }
+
+               $combinedStatus = \Status::newGood();
+               foreach ( $statuses as $data ) {
+                       list( $req, $status ) = $data;
+                       $descriptionInfo = $req->describeCredentials();
+                       $description = wfMessage(
+                               'authprovider-confirmlink-option',
+                               $descriptionInfo['provider']->text(), $descriptionInfo['account']->text()
+                       )->text();
+                       if ( $status->isGood() ) {
+                               $combinedStatus->error( wfMessage( 'authprovider-confirmlink-success-line', $description ) );
+                       } else {
+                               $combinedStatus->error( wfMessage(
+                                       'authprovider-confirmlink-failure-line', $description, $status->getMessage()->text()
+                               ) );
+                       }
+               }
+               return AuthenticationResponse::newUI(
+                       [
+                               new ButtonAuthenticationRequest(
+                                       'linkOk', wfMessage( 'ok' ), wfMessage( 'authprovider-confirmlink-ok-help' )
+                               )
+                       ],
+                       $combinedStatus->getMessage( 'authprovider-confirmlink-failed' )
+               );
+       }
+}
diff --git a/includes/auth/CreateFromLoginAuthenticationRequest.php b/includes/auth/CreateFromLoginAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..949302d
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * This transfers state between the login and account creation flows.
+ *
+ * AuthManager::getAuthenticationRequests() won't return this type, but it
+ * may be passed to AuthManager::beginAccountCreation() anyway.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class CreateFromLoginAuthenticationRequest extends AuthenticationRequest {
+       public $required = self::OPTIONAL;
+
+       /** @var AuthenticationRequest|null */
+       public $createRequest;
+
+       /** @var AuthenticationRequest[] */
+       public $maybeLink = [];
+
+       /**
+        * @param AuthenticationRequest|null $createRequest A request to use to
+        *  begin creating the account
+        * @param AuthenticationRequest[] $maybeLink Additional accounts to link
+        *  after creation.
+        */
+       public function __construct(
+               AuthenticationRequest $createRequest = null, array $maybeLink = []
+       ) {
+               $this->createRequest = $createRequest;
+               $this->maybeLink = $maybeLink;
+       }
+
+       public function getFieldInfo() {
+               return [];
+       }
+
+       public function loadFromSubmission( array $data ) {
+               return true;
+       }
+}
diff --git a/includes/auth/CreatedAccountAuthenticationRequest.php b/includes/auth/CreatedAccountAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..48a6e1d
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * Returned from account creation to allow for logging into the created account
+ * @ingroup Auth
+ * @since 1.27
+ */
+class CreatedAccountAuthenticationRequest extends AuthenticationRequest {
+
+       public $required = self::OPTIONAL;
+
+       /** @var int User id */
+       public $id;
+
+       public function getFieldInfo() {
+               return [];
+       }
+
+       /**
+        * @param int $id User id
+        * @param string $name Username
+        */
+       public function __construct( $id, $name ) {
+               $this->id = $id;
+               $this->username = $name;
+       }
+}
diff --git a/includes/auth/CreationReasonAuthenticationRequest.php b/includes/auth/CreationReasonAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..1711aec
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * Authentication request for the reason given for account creation.
+ * Used in logs and for notification.
+ */
+class CreationReasonAuthenticationRequest extends AuthenticationRequest {
+       /** @var string Account creation reason (only used when creating for someone else) */
+       public $reason;
+
+       public function getFieldInfo() {
+               return [
+                       'reason' => [
+                               'type' => 'string',
+                               'label' => wfMessage( 'createacct-reason' ),
+                               'help' => wfMessage( 'createacct-reason-help' ),
+                       ],
+               ];
+       }
+}
diff --git a/includes/auth/EmailNotificationSecondaryAuthenticationProvider.php b/includes/auth/EmailNotificationSecondaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..c632e3c
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+use Config;
+use StatusValue;
+
+/**
+ * Handles email notification / email address confirmation for account creation.
+ *
+ * Set 'no-email' to true (via AuthManager::setAuthenticationSessionData) to skip this provider.
+ * Primary providers doing so are expected to take care of email address confirmation.
+ */
+class EmailNotificationSecondaryAuthenticationProvider
+       extends AbstractSecondaryAuthenticationProvider
+{
+       /** @var bool */
+       protected $sendConfirmationEmail;
+
+       /**
+        * @param array $params
+        *  - sendConfirmationEmail: (bool) send an email asking the user to confirm their email
+        *    address after a successful registration
+        */
+       public function __construct( $params = [] ) {
+               if ( isset( $params['sendConfirmationEmail'] ) ) {
+                       $this->sendConfirmationEmail = (bool)$params['sendConfirmationEmail'];
+               }
+       }
+
+       public function setConfig( Config $config ) {
+               parent::setConfig( $config );
+
+               if ( $this->sendConfirmationEmail === null ) {
+                       $this->sendConfirmationEmail = $this->config->get( 'EnableEmail' )
+                               && $this->config->get( 'EmailAuthentication' );
+               }
+       }
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               return [];
+       }
+
+       public function beginSecondaryAuthentication( $user, array $reqs ) {
+               return AuthenticationResponse::newAbstain();
+       }
+
+       public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               if (
+                       $this->sendConfirmationEmail
+                       && $user->getEmail()
+                       && !$this->manager->getAuthenticationSessionData( 'no-email' )
+               ) {
+                       $status = $user->sendConfirmationMail();
+                       $user->saveSettings();
+                       if ( $status->isGood() ) {
+                               // TODO show 'confirmemail_oncreate' success message
+                       } else {
+                               // TODO show 'confirmemail_sendfailed' error message
+                               $this->logger->warning( 'Could not send confirmation email: ' .
+                                       $status->getWikiText( false, false, 'en' ) );
+                       }
+               }
+
+               return AuthenticationResponse::newPass();
+       }
+}
diff --git a/includes/auth/LegacyHookPreAuthenticationProvider.php b/includes/auth/LegacyHookPreAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..1a8a758
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use LoginForm;
+use StatusValue;
+use User;
+
+/**
+ * A pre-authentication provider to call some legacy hooks.
+ * @ingroup Auth
+ * @since 1.27
+ * @deprecated since 1.27
+ */
+class LegacyHookPreAuthenticationProvider extends AbstractPreAuthenticationProvider {
+
+       public function testForAuthentication( array $reqs ) {
+               $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
+               if ( $req ) {
+                       $user = User::newFromName( $req->username );
+                       $password = $req->password;
+               } else {
+                       $user = null;
+                       foreach ( $reqs as $req ) {
+                               if ( $req->username !== null ) {
+                                       $user = User::newFromName( $req->username );
+                                       break;
+                               }
+                       }
+                       if ( !$user ) {
+                               $this->logger->debug( __METHOD__ . ': No username in $reqs, skipping hooks' );
+                               return StatusValue::newGood();
+                       }
+
+                       // Something random for the 'AbortLogin' hook.
+                       $password = wfRandomString( 32 );
+               }
+
+               $msg = null;
+               if ( !\Hooks::run( 'LoginUserMigrated', [ $user, &$msg ] ) ) {
+                       return $this->makeFailResponse(
+                               $user, null, LoginForm::USER_MIGRATED, $msg, 'LoginUserMigrated'
+                       );
+               }
+
+               $abort = LoginForm::ABORTED;
+               $msg = null;
+               if ( !\Hooks::run( 'AbortLogin', [ $user, $password, &$abort, &$msg ] ) ) {
+                       return $this->makeFailResponse( $user, null, $abort, $msg, 'AbortLogin' );
+               }
+
+               return StatusValue::newGood();
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               $abortError = '';
+               $abortStatus = null;
+               if ( !\Hooks::run( 'AbortNewAccount', [ $user, &$abortError, &$abortStatus ] ) ) {
+                       // Hook point to add extra creation throttles and blocks
+                       $this->logger->debug( __METHOD__ . ': a hook blocked creation' );
+                       if ( $abortStatus === null ) {
+                               // Report back the old string as a raw message status.
+                               // This will report the error back as 'createaccount-hook-aborted'
+                               // with the given string as the message.
+                               // To return a different error code, return a StatusValue object.
+                               $msg = wfMessage( 'createaccount-hook-aborted' )->rawParams( $abortError );
+                               return StatusValue::newFatal( $msg );
+                       } else {
+                               // For MediaWiki 1.23+ and updated hooks, return the Status object
+                               // returned from the hook.
+                               $ret = StatusValue::newGood();
+                               $ret->merge( $abortStatus );
+                               return $ret;
+                       }
+               }
+
+               return StatusValue::newGood();
+       }
+
+       public function testUserForCreation( $user, $autocreate ) {
+               if ( $autocreate !== false ) {
+                       $abortError = '';
+                       if ( !\Hooks::run( 'AbortAutoAccount', [ $user, &$abortError ] ) ) {
+                               // Hook point to add extra creation throttles and blocks
+                               $this->logger->debug( __METHOD__ . ": a hook blocked auto-creation: $abortError\n" );
+                               return $this->makeFailResponse(
+                                       $user, $user, LoginForm::ABORTED, $abortError, 'AbortAutoAccount'
+                               );
+                       }
+               } else {
+                       $abortError = '';
+                       $abortStatus = null;
+                       if ( !\Hooks::run( 'AbortNewAccount', [ $user, &$abortError, &$abortStatus ] ) ) {
+                               // Hook point to add extra creation throttles and blocks
+                               $this->logger->debug( __METHOD__ . ': a hook blocked creation' );
+                               if ( $abortStatus === null ) {
+                                       // Report back the old string as a raw message status.
+                                       // This will report the error back as 'createaccount-hook-aborted'
+                                       // with the given string as the message.
+                                       // To return a different error code, return a StatusValue object.
+                                       $msg = wfMessage( 'createaccount-hook-aborted' )->rawParams( $abortError );
+                                       return StatusValue::newFatal( $msg );
+                               } else {
+                                       // For MediaWiki 1.23+ and updated hooks, return the Status object
+                                       // returned from the hook.
+                                       $ret = StatusValue::newGood();
+                                       $ret->merge( $abortStatus );
+                                       return $ret;
+                               }
+                       }
+               }
+
+               return StatusValue::newGood();
+       }
+
+       /**
+        * Construct an appropriate failure response
+        * @param User $user
+        * @param User|null $creator
+        * @param int $constant LoginForm constant
+        * @param string|null $msg Message
+        * @param string $hook Hook
+        * @return StatusValue
+        */
+       protected function makeFailResponse( $user, $creator, $constant, $msg, $hook ) {
+               switch ( $constant ) {
+                       case LoginForm::SUCCESS:
+                               // WTF?
+                               $this->logger->debug( "$hook is SUCCESS?!" );
+                               return StatusValue::newGood();
+
+                       case LoginForm::NEED_TOKEN:
+                               return StatusValue::newFatal( $msg ?: 'nocookiesforlogin' );
+
+                       case LoginForm::WRONG_TOKEN:
+                               return StatusValue::newFatal( $msg ?: 'sessionfailure' );
+
+                       case LoginForm::NO_NAME:
+                       case LoginForm::ILLEGAL:
+                               return StatusValue::newFatal( $msg ?: 'noname' );
+
+                       case LoginForm::WRONG_PLUGIN_PASS:
+                       case LoginForm::WRONG_PASS:
+                               return StatusValue::newFatal( $msg ?: 'wrongpassword' );
+
+                       case LoginForm::NOT_EXISTS:
+                               return StatusValue::newFatal( $msg ?: 'nosuchusershort', wfEscapeWikiText( $user->getName() ) );
+
+                       case LoginForm::EMPTY_PASS:
+                               return StatusValue::newFatal( $msg ?: 'wrongpasswordempty' );
+
+                       case LoginForm::RESET_PASS:
+                               return StatusValue::newFatal( $msg ?: 'resetpass_announce' );
+
+                       case LoginForm::THROTTLED:
+                               $throttle = $this->config->get( 'PasswordAttemptThrottle' );
+                               return StatusValue::newFatal(
+                                       $msg ?: 'login-throttled',
+                                       \Message::durationParam( $throttle['seconds'] )
+                               );
+
+                       case LoginForm::USER_BLOCKED:
+                               return StatusValue::newFatal(
+                                       $msg ?: 'login-userblocked', wfEscapeWikiText( $user->getName() )
+                               );
+
+                       case LoginForm::ABORTED:
+                               return StatusValue::newFatal(
+                                       $msg ?: 'login-abort-generic', wfEscapeWikiText( $user->getName() )
+                               );
+
+                       case LoginForm::USER_MIGRATED:
+                               $error = $msg ?: 'login-migrated-generic';
+                               return call_user_func_array( 'StatusValue::newFatal', (array)$error );
+
+                       // @codeCoverageIgnoreStart
+                       case LoginForm::CREATE_BLOCKED: // Can never happen
+                       default:
+                               throw new \DomainException( __METHOD__ . ": Unhandled case value from $hook" );
+               }
+                       // @codeCoverageIgnoreEnd
+       }
+}
diff --git a/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php b/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..5f5ef79
--- /dev/null
@@ -0,0 +1,314 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use User;
+
+/**
+ * A primary authentication provider that uses the password field in the 'user' table.
+ * @ingroup Auth
+ * @since 1.27
+ */
+class LocalPasswordPrimaryAuthenticationProvider
+       extends AbstractPasswordPrimaryAuthenticationProvider
+{
+
+       /** @var bool If true, this instance is for legacy logins only. */
+       protected $loginOnly = false;
+
+       /**
+        * @param array $params Settings
+        *  - loginOnly: If true, the local passwords are for legacy logins only:
+        *    the local password will be invalidated when authentication is changed
+        *    and new users will not have a valid local password set.
+        */
+       public function __construct( $params = [] ) {
+               parent::__construct( $params );
+               $this->loginOnly = !empty( $params['loginOnly'] );
+       }
+
+       protected function getPasswordResetData( $username, $row ) {
+               $now = wfTimestamp();
+               $expiration = wfTimestampOrNull( TS_UNIX, $row->user_password_expires );
+               if ( $expiration === null || $expiration >= $now ) {
+                       return null;
+               }
+
+               $grace = $this->config->get( 'PasswordExpireGrace' );
+               if ( $expiration + $grace < $now ) {
+                       $data = [
+                               'hard' => true,
+                               'msg' => \Status::newFatal( 'resetpass-expired' )->getMessage(),
+                       ];
+               } else {
+                       $data = [
+                               'hard' => false,
+                               'msg' => \Status::newFatal( 'resetpass-expired-soft' )->getMessage(),
+                       ];
+               }
+
+               return (object)$data;
+       }
+
+       public function beginPrimaryAuthentication( array $reqs ) {
+               $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
+               if ( !$req ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               if ( $req->username === null || $req->password === null ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $username = User::getCanonicalName( $req->username, 'usable' );
+               if ( $username === false ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $fields = [
+                       'user_id', 'user_password', 'user_password_expires',
+               ];
+
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
+                       'user',
+                       $fields,
+                       [ 'user_name' => $username ],
+                       __METHOD__
+               );
+               if ( !$row ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               // Check for *really* old password hashes that don't even have a type
+               // The old hash format was just an md5 hex hash, with no type information
+               if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) {
+                       if ( $this->config->get( 'PasswordSalt' ) ) {
+                               $row->user_password = ":A:{$row->user_id}:{$row->user_password}";
+                       } else {
+                               $row->user_password = ":A:{$row->user_password}";
+                       }
+               }
+
+               $status = $this->checkPasswordValidity( $username, $req->password );
+               if ( !$status->isOk() ) {
+                       // Fatal, can't log in
+                       return AuthenticationResponse::newFail( $status->getMessage() );
+               }
+
+               $pwhash = $this->getPassword( $row->user_password );
+               if ( !$pwhash->equals( $req->password ) ) {
+                       if ( $this->config->get( 'LegacyEncoding' ) ) {
+                               // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
+                               // Check for this with iconv
+                               $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $req->password );
+                               if ( $cp1252Password === $req->password || !$pwhash->equals( $cp1252Password ) ) {
+                                       return $this->failResponse( $req );
+                               }
+                       } else {
+                               return $this->failResponse( $req );
+                       }
+               }
+
+               // @codeCoverageIgnoreStart
+               if ( $this->getPasswordFactory()->needsUpdate( $pwhash ) ) {
+                       $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
+                       $dbw->update(
+                               'user',
+                               [ 'user_password' => $pwhash->toString() ],
+                               [ 'user_id' => $row->user_id ],
+                               __METHOD__
+                       );
+               }
+               // @codeCoverageIgnoreEnd
+
+               $this->setPasswordResetFlag( $username, $status, $row );
+
+               return AuthenticationResponse::newPass( $username );
+       }
+
+       public function testUserCanAuthenticate( $username ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return false;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
+                       'user',
+                       [ 'user_password' ],
+                       [ 'user_name' => $username ],
+                       __METHOD__
+               );
+               if ( !$row ) {
+                       return false;
+               }
+
+               // Check for *really* old password hashes that don't even have a type
+               // The old hash format was just an md5 hex hash, with no type information
+               if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) {
+                       return true;
+               }
+
+               return !$this->getPassword( $row->user_password ) instanceof \InvalidPassword;
+       }
+
+       public function testUserExists( $username, $flags = User::READ_NORMAL ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return false;
+               }
+
+               list( $db, $options ) = \DBAccessObjectUtils::getDBOptions( $flags );
+               return (bool)wfGetDB( $db )->selectField(
+                       [ 'user' ],
+                       [ 'user_id' ],
+                       [ 'user_name' => $username ],
+                       __METHOD__,
+                       $options
+               );
+       }
+
+       public function providerAllowsAuthenticationDataChange(
+               AuthenticationRequest $req, $checkData = true
+       ) {
+               // We only want to blank the password if something else will accept the
+               // new authentication data, so return 'ignore' here.
+               if ( $this->loginOnly ) {
+                       return \StatusValue::newGood( 'ignored' );
+               }
+
+               if ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
+                       if ( !$checkData ) {
+                               return \StatusValue::newGood();
+                       }
+
+                       $username = User::getCanonicalName( $req->username, 'usable' );
+                       if ( $username !== false ) {
+                               $row = wfGetDB( DB_MASTER )->selectRow(
+                                       'user',
+                                       [ 'user_id' ],
+                                       [ 'user_name' => $username ],
+                                       __METHOD__
+                               );
+                               if ( $row ) {
+                                       $sv = \StatusValue::newGood();
+                                       if ( $req->password !== null ) {
+                                               if ( $req->password !== $req->retype ) {
+                                                       $sv->fatal( 'badretype' );
+                                               } else {
+                                                       $sv->merge( $this->checkPasswordValidity( $username, $req->password ) );
+                                               }
+                                       }
+                                       return $sv;
+                               }
+                       }
+               }
+
+               return \StatusValue::newGood( 'ignored' );
+       }
+
+       public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
+               $username = $req->username !== null ? User::getCanonicalName( $req->username, 'usable' ) : false;
+               if ( $username === false ) {
+                       return;
+               }
+
+               $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 ( $pwhash ) {
+                       $dbw = wfGetDB( DB_MASTER );
+                       $dbw->update(
+                               'user',
+                               [
+                                       'user_password' => $pwhash->toString(),
+                                       'user_password_expires' => $dbw->timestampOrNull( $expiry ),
+                               ],
+                               [ 'user_name' => $username ],
+                               __METHOD__
+                       );
+               }
+       }
+
+       public function accountCreationType() {
+               return $this->loginOnly ? self::TYPE_NONE : self::TYPE_CREATE;
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
+
+               $ret = \StatusValue::newGood();
+               if ( !$this->loginOnly && $req && $req->username !== null && $req->password !== null ) {
+                       if ( $req->password !== $req->retype ) {
+                               $ret->fatal( 'badretype' );
+                       } else {
+                               $ret->merge(
+                                       $this->checkPasswordValidity( $user->getName(), $req->password )
+                               );
+                       }
+               }
+               return $ret;
+       }
+
+       public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
+               if ( $this->accountCreationType() === self::TYPE_NONE ) {
+                       throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
+               }
+
+               $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
+               if ( $req ) {
+                       if ( $req->username !== null && $req->password !== null ) {
+                               // Nothing we can do besides claim it, because the user isn't in
+                               // the DB yet
+                               if ( $req->username !== $user->getName() ) {
+                                       $req = clone( $req );
+                                       $req->username = $user->getName();
+                               }
+                               $ret = AuthenticationResponse::newPass( $req->username );
+                               $ret->createRequest = $req;
+                               return $ret;
+                       }
+               }
+               return AuthenticationResponse::newAbstain();
+       }
+
+       public function finishAccountCreation( $user, $creator, AuthenticationResponse $res ) {
+               if ( $this->accountCreationType() === self::TYPE_NONE ) {
+                       throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
+               }
+
+               // Now that the user is in the DB, set the password on it.
+               $this->providerChangeAuthenticationData( $res->createRequest );
+
+               return null;
+       }
+}
diff --git a/includes/auth/PasswordAuthenticationRequest.php b/includes/auth/PasswordAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..187c29a
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * This is a value object for authentication requests with a username and password
+ * @ingroup Auth
+ * @since 1.27
+ */
+class PasswordAuthenticationRequest extends AuthenticationRequest {
+       /** @var string Password */
+       public $password = null;
+
+       /** @var string Password, again */
+       public $retype = null;
+
+       public function getFieldInfo() {
+               if ( $this->action === AuthManager::ACTION_REMOVE ) {
+                       return [];
+               }
+
+               // for password change it's nice to make extra clear that we are asking for the new password
+               $forNewPassword = $this->action === AuthManager::ACTION_CHANGE;
+               $passwordLabel = $forNewPassword ? 'newpassword' : 'userlogin-yourpassword';
+               $retypeLabel = $forNewPassword ? 'retypenew' : 'yourpasswordagain';
+
+               $ret = [
+                       'username' => [
+                               'type' => 'string',
+                               'label' => wfMessage( 'userlogin-yourname' ),
+                               'help' => wfMessage( 'authmanager-username-help' ),
+                       ],
+                       'password' => [
+                               'type' => 'password',
+                               'label' => wfMessage( $passwordLabel ),
+                               'help' => wfMessage( 'authmanager-password-help' ),
+                       ],
+               ];
+
+               switch ( $this->action ) {
+                       case AuthManager::ACTION_CHANGE:
+                       case AuthManager::ACTION_REMOVE:
+                               unset( $ret['username'] );
+                               break;
+               }
+
+               if ( $this->action !== AuthManager::ACTION_LOGIN ) {
+                       $ret['retype'] = [
+                               'type' => 'password',
+                               'label' => wfMessage( $retypeLabel ),
+                               'help' => wfMessage( 'authmanager-retype-help' ),
+                       ];
+               }
+
+               return $ret;
+       }
+
+       public function describeCredentials() {
+               return [
+                       'provider' => wfMessage( 'authmanager-provider-password' ),
+                       'account' => new \RawMessage( '$1', [ $this->username ] ),
+               ];
+       }
+}
diff --git a/includes/auth/PasswordDomainAuthenticationRequest.php b/includes/auth/PasswordDomainAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..ddad54b
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * This is a value object for authentication requests with a username, password, and domain
+ * @ingroup Auth
+ * @since 1.27
+ */
+class PasswordDomainAuthenticationRequest extends PasswordAuthenticationRequest {
+       /** @var string[] Domains available */
+       private $domainList;
+
+       /** @var string Domain */
+       public $domain = null;
+
+       /**
+        * @param string[] $domainList List of available domains
+        */
+       public function __construct( array $domainList ) {
+               $this->domainList = $domainList;
+       }
+
+       public function getFieldInfo() {
+               $ret = parent::getFieldInfo();
+
+               // Only add a domain field if we have the username field included
+               if ( isset( $ret['username'] ) ) {
+                       $ret['domain'] = [
+                               'type' => 'select',
+                               'options' => [],
+                               'label' => wfMessage( 'yourdomainname' ),
+                               'help' => wfMessage( 'authmanager-domain-help' ),
+                       ];
+                       foreach ( $this->domainList as $domain ) {
+                               $ret['domain']['options'][$domain] = new \RawMessage( '$1', [ $domain ] );
+                       }
+               }
+
+               return $ret;
+       }
+
+       public function describeCredentials() {
+               return [
+                       'provider' => wfMessage( 'authmanager-provider-password-domain' ),
+                       'account' => wfMessage(
+                               'authmanager-account-password-domain', [ $this->username, $this->domain ]
+                       ),
+               ];
+       }
+
+       /**
+        * @codeCoverageIgnore
+        */
+       public static function __set_state( $data ) {
+               $ret = new static( $data['domainList'] );
+               foreach ( $data as $k => $v ) {
+                       if ( $k !== 'domainList' ) {
+                               $ret->$k = $v;
+                       }
+               }
+               return $ret;
+       }
+}
diff --git a/includes/auth/PreAuthenticationProvider.php b/includes/auth/PreAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..846d16e
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Pre-authentication provider interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use StatusValue;
+use User;
+
+/**
+ * A pre-authentication provider is a check that must pass for authentication
+ * to proceed.
+ *
+ * A PreAuthenticationProvider is used to supply arbitrary checks to be
+ * performed before the PrimaryAuthenticationProviders are consulted during the
+ * login process. Possible uses include checking that a per-IP throttle has not
+ * been reached or that a captcha has been solved.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+interface PreAuthenticationProvider extends AuthenticationProvider {
+
+       /**
+        * Determine whether an authentication may begin
+        *
+        * Called from AuthManager::beginAuthentication()
+        *
+        * @param AuthenticationRequest[] $reqs
+        * @return StatusValue
+        */
+       public function testForAuthentication( array $reqs );
+
+       /**
+        * Post-login callback
+        * @param User|null $user User that was attempted to be logged in, if known.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAuthentication( $user, AuthenticationResponse $response );
+
+       /**
+        * Determine whether an account creation may begin
+        *
+        * Called from AuthManager::beginAccountCreation()
+        *
+        * @note No need to test if the account exists, AuthManager checks that
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return StatusValue
+        */
+       public function testForAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Determine whether an account may be created
+        *
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param bool|string $autocreate False if this is not an auto-creation, or
+        *  the source of the auto-creation passed to AuthManager::autoCreateUser().
+        * @return StatusValue
+        */
+       public function testUserForCreation( $user, $autocreate );
+
+       /**
+        * Post-creation callback
+        * @param User $user User that was attempted to be created.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAccountCreation( $user, $creator, AuthenticationResponse $response );
+
+       /**
+        * Determine whether an account may linked to another authentication method
+        *
+        * @param User $user User being linked.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @return StatusValue
+        */
+       public function testForAccountLink( $user );
+
+       /**
+        * Post-link callback
+        * @param User $user User that was attempted to be linked.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAccountLink( $user, AuthenticationResponse $response );
+
+}
diff --git a/includes/auth/PrimaryAuthenticationProvider.php b/includes/auth/PrimaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..169e7f1
--- /dev/null
@@ -0,0 +1,334 @@
+<?php
+/**
+ * Primary authentication provider interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use StatusValue;
+use User;
+
+/**
+ * A primary authentication provider determines which user is trying to log in.
+ *
+ * A PrimaryAuthenticationProvider is used as part of presenting a login form
+ * to authenticate a user. In particular, the PrimaryAuthenticationProvider
+ * takes form data and determines the authenticated user (if any) corresponds
+ * to that form data. It might do this on the basis of a username and password
+ * in that data, or by interacting with an external authentication service
+ * (e.g. using OpenID), or by some other mechanism.
+ *
+ * A PrimaryAuthenticationProvider would not be appropriate for something like
+ * HTTP authentication, OAuth, or SSL client certificates where each HTTP
+ * request contains all the information needed to identify the user. In that
+ * case you'll want to be looking at a \\MediaWiki\\Session\\SessionProvider
+ * instead.
+ *
+ * This interface also provides methods for changing authentication data such
+ * as passwords and for creating new users who can later be authenticated with
+ * this provider.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+interface PrimaryAuthenticationProvider extends AuthenticationProvider {
+       /** Provider can create accounts */
+       const TYPE_CREATE = 'create';
+       /** Provider can link to existing accounts elsewhere */
+       const TYPE_LINK = 'link';
+       /** Provider cannot create or link to accounts */
+       const TYPE_NONE = 'none';
+
+       /**
+        * Start an authentication flow
+        *
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user is authenticated. Secondary providers will now run.
+        *  - FAIL: The user is not authenticated. Fail the authentication process.
+        *  - ABSTAIN: These $reqs are not handled. Some other primary provider may handle it.
+        *  - UI: The $reqs are accepted, no other primary provider will run.
+        *    Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: The $reqs are accepted, no other primary provider will run.
+        *    Redirection to a third party is needed to complete the process.
+        */
+       public function beginPrimaryAuthentication( array $reqs );
+
+       /**
+        * Continue an authentication flow
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user is authenticated. Secondary providers will now run.
+        *  - FAIL: The user is not authenticated. Fail the authentication process.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function continuePrimaryAuthentication( array $reqs );
+
+       /**
+        * Post-login callback
+        * @param User|null $user User that was attempted to be logged in, if known.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAuthentication( $user, AuthenticationResponse $response );
+
+       /**
+        * Test whether the named user exists
+        * @param string $username
+        * @param int $flags Bitfield of User:READ_* constants
+        * @return bool
+        */
+       public function testUserExists( $username, $flags = User::READ_NORMAL );
+
+       /**
+        * Test whether the named user can authenticate with this provider
+        * @param string $username
+        * @return bool
+        */
+       public function testUserCanAuthenticate( $username );
+
+       /**
+        * Normalize the username for authentication
+        *
+        * Any two inputs that would result in the same user being authenticated
+        * should return the same string here, while inputs that would result in
+        * different users should return different strings.
+        *
+        * If possible, the best thing to do here is to return the canonicalized
+        * name of the local user account that would be used. If not, return
+        * something that would be invalid as a local username (e.g. wrap an email
+        * address in "<>", or append "#servicename" to the username passed to a
+        * third-party service).
+        *
+        * If the provider doesn't use a username at all in its
+        * AuthenticationRequests, return null. If the name is syntactically
+        * invalid, it's probably best to return null.
+        *
+        * @param string $username
+        * @return string|null
+        */
+       public function providerNormalizeUsername( $username );
+
+       /**
+        * Revoke the user's credentials
+        *
+        * This may cause the user to no longer exist for the provider, or the user
+        * may continue to exist in a "disabled" state.
+        *
+        * The intention is that the named account will never again be usable for
+        * normal login (i.e. there is no way to undo the revocation of access).
+        *
+        * @param string $username
+        */
+       public function providerRevokeAccessForUser( $username );
+
+       /**
+        * Determine whether a property can change
+        * @see AuthManager::allowsPropertyChange()
+        * @param string $property
+        * @return bool
+        */
+       public function providerAllowsPropertyChange( $property );
+
+       /**
+        * Validate a change of authentication data (e.g. passwords)
+        *
+        * Return StatusValue::newGood( 'ignored' ) if you don't support this
+        * AuthenticationRequest type.
+        *
+        * @param AuthenticationRequest $req
+        * @param bool $checkData If false, $req hasn't been loaded from the
+        *  submission so checks on user-submitted fields should be skipped.
+        *  $req->username is considered user-submitted for this purpose, even
+        *  if it cannot be changed via $req->loadFromSubmission.
+        * @return StatusValue
+        */
+       public function providerAllowsAuthenticationDataChange(
+               AuthenticationRequest $req, $checkData = true
+       );
+
+       /**
+        * Change or remove authentication data (e.g. passwords)
+        *
+        * If $req was returned for AuthManager::ACTION_CHANGE, the corresponding
+        * credentials should result in a successful login in the future.
+        *
+        * If $req was returned for AuthManager::ACTION_REMOVE, the corresponding
+        * credentials should no longer result in a successful login.
+        *
+        * @param AuthenticationRequest $req
+        */
+       public function providerChangeAuthenticationData( AuthenticationRequest $req );
+
+       /**
+        * Fetch the account-creation type
+        * @return string One of the TYPE_* constants
+        */
+       public function accountCreationType();
+
+       /**
+        * Determine whether an account creation may begin
+        *
+        * Called from AuthManager::beginAccountCreation()
+        *
+        * @note No need to test if the account exists, AuthManager checks that
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return StatusValue
+        */
+       public function testForAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Start an account creation flow
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user may be created. Secondary providers will now run.
+        *  - FAIL: The user may not be created. Fail the creation process.
+        *  - ABSTAIN: These $reqs are not handled. Some other primary provider may handle it.
+        *  - UI: The $reqs are accepted, no other primary provider will run.
+        *    Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: The $reqs are accepted, no other primary provider will run.
+        *    Redirection to a third party is needed to complete the process.
+        */
+       public function beginPrimaryAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Continue an account creation flow
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user may be created. Secondary providers will now run.
+        *  - FAIL: The user may not be created. Fail the creation process.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function continuePrimaryAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Post-creation callback
+        *
+        * Called after the user is added to the database, before secondary
+        * authentication providers are run.
+        *
+        * @param User $user User being created (has been added to the database now).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationResponse $response PASS response returned earlier
+        * @return string|null 'newusers' log subtype to use for logging the
+        *   account creation. If null, either 'create' or 'create2' will be used
+        *   depending on $creator.
+        */
+       public function finishAccountCreation( $user, $creator, AuthenticationResponse $response );
+
+       /**
+        * Post-creation callback
+        *
+        * Called when the account creation process ends.
+        *
+        * @param User $user User that was attempted to be created.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAccountCreation( $user, $creator, AuthenticationResponse $response );
+
+       /**
+        * Determine whether an account may be created
+        *
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param bool|string $autocreate False if this is not an auto-creation, or
+        *  the source of the auto-creation passed to AuthManager::autoCreateUser().
+        * @return StatusValue
+        */
+       public function testUserForCreation( $user, $autocreate );
+
+       /**
+        * Post-auto-creation callback
+        * @param User $user User being created (has been added to the database now).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param string $source The source of the auto-creation passed to
+        *  AuthManager::autoCreateUser().
+        */
+       public function autoCreatedAccount( $user, $source );
+
+       /**
+        * Start linking an account to an existing user
+        * @param User $user User being linked.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user is linked.
+        *  - FAIL: The user is not linked. Fail the linking process.
+        *  - ABSTAIN: These $reqs are not handled. Some other primary provider may handle it.
+        *  - UI: The $reqs are accepted, no other primary provider will run.
+        *    Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: The $reqs are accepted, no other primary provider will run.
+        *    Redirection to a third party is needed to complete the process.
+        */
+       public function beginPrimaryAccountLink( $user, array $reqs );
+
+       /**
+        * Continue linking an account to an existing user
+        * @param User $user User being linked.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user is linked.
+        *  - FAIL: The user is not linked. Fail the linking process.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function continuePrimaryAccountLink( $user, array $reqs );
+
+       /**
+        * Post-link callback
+        * @param User $user User that was attempted to be linked.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAccountLink( $user, AuthenticationResponse $response );
+
+}
diff --git a/includes/auth/RememberMeAuthenticationRequest.php b/includes/auth/RememberMeAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..d487e31
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use MediaWiki\Session\SessionManager;
+use MediaWiki\Session\SessionProvider;
+
+/**
+ * This is an authentication request added by AuthManager to show a "remember
+ * me" checkbox. When checked, it will take more time for the authenticated session to expire.
+ * @ingroup Auth
+ * @since 1.27
+ */
+class RememberMeAuthenticationRequest extends AuthenticationRequest {
+
+       public $required = self::OPTIONAL;
+
+       /** @var int How long the user will be remembered, in seconds */
+       protected $expiration = null;
+
+       /** @var bool */
+       public $rememberMe = false;
+
+       public function __construct() {
+               /** @var SessionProvider $provider */
+               $provider = SessionManager::getGlobalSession()->getProvider();
+               $this->expiration = $provider->getRememberUserDuration();
+       }
+
+       public function getFieldInfo() {
+               if ( !$this->expiration ) {
+                       return [];
+               }
+
+               $expirationDays = ceil( $this->expiration / ( 3600 * 24 ) );
+               return [
+                       'rememberMe' => [
+                               'type' => 'checkbox',
+                               'label' => wfMessage( 'userlogin-remembermypassword' )->numParams( $expirationDays ),
+                               'help' => wfMessage( 'authmanager-userlogin-remembermypassword-help' ),
+                               'optional' => true,
+                       ]
+               ];
+       }
+}
diff --git a/includes/auth/ResetPasswordSecondaryAuthenticationProvider.php b/includes/auth/ResetPasswordSecondaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..2e51cf2
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * Reset the local password, if signalled via $this->manager->setAuthenticationSessionData()
+ *
+ * The authentication data key is 'reset-pass'; the data is an object with the
+ * following properties:
+ * - msg: Message object to display to the user
+ * - hard: Boolean, if true the reset cannot be skipped.
+ * - req: Optional PasswordAuthenticationRequest to use to actually reset the
+ *   password. Won't be displayed to the user.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class ResetPasswordSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               return [];
+       }
+
+       public function beginSecondaryAuthentication( $user, array $reqs ) {
+               return $this->tryReset( $user, $reqs );
+       }
+
+       public function continueSecondaryAuthentication( $user, array $reqs ) {
+               return $this->tryReset( $user, $reqs );
+       }
+
+       public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               return $this->tryReset( $user, $reqs );
+       }
+
+       public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
+               return $this->tryReset( $user, $reqs );
+       }
+
+       /**
+        * Try to reset the password
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse
+        */
+       protected function tryReset( \User $user, array $reqs ) {
+               $data = $this->manager->getAuthenticationSessionData( 'reset-pass' );
+               if ( !$data ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               if ( is_array( $data ) ) {
+                       $data = (object)$data;
+               }
+               if ( !is_object( $data ) ) {
+                       throw new \UnexpectedValueException( 'reset-pass is not valid' );
+               }
+
+               if ( !isset( $data->msg ) ) {
+                       throw new \UnexpectedValueException( 'reset-pass msg is missing' );
+               } elseif ( !$data->msg instanceof \Message ) {
+                       throw new \UnexpectedValueException( 'reset-pass msg is not valid' );
+               } elseif ( !isset( $data->hard ) ) {
+                       throw new \UnexpectedValueException( 'reset-pass hard is missing' );
+               } elseif ( isset( $data->req ) && (
+                       !$data->req instanceof PasswordAuthenticationRequest ||
+                       !array_key_exists( 'retype', $data->req->getFieldInfo() )
+               ) ) {
+                       throw new \UnexpectedValueException( 'reset-pass req is not valid' );
+               }
+
+               if ( !$data->hard ) {
+                       $req = ButtonAuthenticationRequest::getRequestByName( $reqs, 'skipReset' );
+                       if ( $req ) {
+                               $this->manager->removeAuthenticationSessionData( 'reset-pass' );
+                               return AuthenticationResponse::newPass();
+                       }
+               }
+
+               if ( isset( $data->req ) ) {
+                       $needReq = $data->req;
+               } else {
+                       $needReq = new PasswordAuthenticationRequest();
+                       $needReq->action = AuthManager::ACTION_CHANGE;
+               }
+               $needReqs = [ $needReq ];
+               if ( !$data->hard ) {
+                       $needReqs[] = new ButtonAuthenticationRequest(
+                               'skipReset',
+                               wfMessage( 'authprovider-resetpass-skip-label' ),
+                               wfMessage( 'authprovider-resetpass-skip-help' )
+                       );
+               }
+
+               $req = AuthenticationRequest::getRequestByClass( $reqs, get_class( $needReq ) );
+               if ( !$req || !array_key_exists( 'retype', $req->getFieldInfo() ) ) {
+                       return AuthenticationResponse::newUI( $needReqs, $data->msg );
+               }
+
+               if ( $req->password !== $req->retype ) {
+                       return AuthenticationResponse::newUI( $needReqs, new \Message( 'badretype' ) );
+               }
+
+               $req->username = $user->getName();
+               $status = $this->manager->allowsAuthenticationDataChange( $req );
+               if ( !$status->isGood() ) {
+                       return AuthenticationResponse::newUI( $needReqs, $status->getMessage() );
+               }
+               $this->manager->changeAuthenticationData( $req );
+
+               $this->manager->removeAuthenticationSessionData( 'reset-pass' );
+               return AuthenticationResponse::newPass();
+       }
+}
diff --git a/includes/auth/SecondaryAuthenticationProvider.php b/includes/auth/SecondaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..0d52d25
--- /dev/null
@@ -0,0 +1,217 @@
+<?php
+/**
+ * Secondary authentication provider interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use StatusValue;
+use User;
+
+/**
+ * A secondary authentication provider performs additional authentication steps
+ * after a PrimaryAuthenticationProvider has done its thing.
+ *
+ * A SecondaryAuthenticationProvider is used to perform arbitrary checks on an
+ * authentication request after the user itself has been authenticated. For
+ * example, it might implement a password reset, request the second factor for
+ * two-factor auth, or prevent the login if the account is blocked.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+interface SecondaryAuthenticationProvider extends AuthenticationProvider {
+
+       /**
+        * Start an authentication flow
+        *
+        * Note that this may be called for a user even if
+        * beginSecondaryAccountCreation() was never called. The module should take
+        * the opportunity to do any necessary setup in that case.
+        *
+        * @param User $user User being authenticated. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user is authenticated. Additional secondary providers may run.
+        *  - FAIL: The user is not authenticated. Fail the authentication process.
+        *  - ABSTAIN: Additional secondary providers may run.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function beginSecondaryAuthentication( $user, array $reqs );
+
+       /**
+        * Continue an authentication flow
+        * @param User $user User being authenticated. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user is authenticated. Additional secondary providers may run.
+        *  - FAIL: The user is not authenticated. Fail the authentication process.
+        *  - ABSTAIN: Additional secondary providers may run.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function continueSecondaryAuthentication( $user, array $reqs );
+
+       /**
+        * Post-login callback
+        * @param User|null $user User that was attempted to be logged in, if known.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAuthentication( $user, AuthenticationResponse $response );
+
+       /**
+        * Revoke the user's credentials
+        *
+        * This may cause the user to no longer exist for the provider, or the user
+        * may continue to exist in a "disabled" state.
+        *
+        * The intention is that the named account will never again be usable for
+        * normal login (i.e. there is no way to undo the revocation of access).
+        *
+        * @param string $username
+        */
+       public function providerRevokeAccessForUser( $username );
+
+       /**
+        * Determine whether a property can change
+        * @see AuthManager::allowsPropertyChange()
+        * @param string $property
+        * @return bool
+        */
+       public function providerAllowsPropertyChange( $property );
+
+       /**
+        * Validate a change of authentication data (e.g. passwords)
+        *
+        * Return StatusValue::newGood( 'ignored' ) if you don't support this
+        * AuthenticationRequest type.
+        *
+        * @param AuthenticationRequest $req
+        * @param bool $checkData If false, $req hasn't been loaded from the
+        *  submission so checks on user-submitted fields should be skipped.
+        *  $req->username is considered user-submitted for this purpose, even
+        *  if it cannot be changed via $req->loadFromSubmission.
+        * @return StatusValue
+        */
+       public function providerAllowsAuthenticationDataChange(
+               AuthenticationRequest $req, $checkData = true
+       );
+
+       /**
+        * Change or remove authentication data (e.g. passwords)
+        *
+        * If $req was returned for AuthManager::ACTION_CHANGE, the corresponding
+        * credentials should result in a successful login in the future.
+        *
+        * If $req was returned for AuthManager::ACTION_REMOVE, the corresponding
+        * credentials should no longer result in a successful login.
+        *
+        * @param AuthenticationRequest $req
+        */
+       public function providerChangeAuthenticationData( AuthenticationRequest $req );
+
+       /**
+        * Determine whether an account creation may begin
+        *
+        * Called from AuthManager::beginAccountCreation()
+        *
+        * @note No need to test if the account exists, AuthManager checks that
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return StatusValue
+        */
+       public function testForAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Start an account creation flow
+        * @param User $user User being created (has been added to the database).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user creation is ok. Additional secondary providers may run.
+        *  - ABSTAIN: Additional secondary providers may run.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function beginSecondaryAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Continue an authentication flow
+        * @param User $user User being created (has been added to the database).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationRequest[] $reqs
+        * @return AuthenticationResponse Expected responses:
+        *  - PASS: The user creation is ok. Additional secondary providers may run.
+        *  - ABSTAIN: Additional secondary providers may run.
+        *  - UI: Additional AuthenticationRequests are needed to complete the process.
+        *  - REDIRECT: Redirection to a third party is needed to complete the process.
+        */
+       public function continueSecondaryAccountCreation( $user, $creator, array $reqs );
+
+       /**
+        * Post-creation callback
+        * @param User $user User that was attempted to be created.
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param User $creator User doing the creation. This may become a
+        *   "UserValue" in the future, or User may be refactored into such.
+        * @param AuthenticationResponse $response Authentication response that will be returned
+        */
+       public function postAccountCreation( $user, $creator, AuthenticationResponse $response );
+
+       /**
+        * Determine whether an account may be created
+        *
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param bool|string $autocreate False if this is not an auto-creation, or
+        *  the source of the auto-creation passed to AuthManager::autoCreateUser().
+        * @return StatusValue
+        */
+       public function testUserForCreation( $user, $autocreate );
+
+       /**
+        * Post-auto-creation callback
+        * @param User $user User being created (has been added to the database now).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @param string $source The source of the auto-creation passed to
+        *  AuthManager::autoCreateUser().
+        */
+       public function autoCreatedAccount( $user, $source );
+
+}
diff --git a/includes/auth/TemporaryPasswordAuthenticationRequest.php b/includes/auth/TemporaryPasswordAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..42f0e70
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * This represents the intention to set a temporary password for the user.
+ * @ingroup Auth
+ * @since 1.27
+ */
+class TemporaryPasswordAuthenticationRequest extends AuthenticationRequest {
+       /** @var string|null Temporary password */
+       public $password;
+
+       /** @var bool Email password to the user. */
+       public $mailpassword = false;
+
+       /**
+        * @var bool Do not fail certain operations if the password cannot be mailed, there is a
+        *   backchannel present.
+        */
+       public $hasBackchannel = false;
+
+       /** @var string Username or IP address of the caller */
+       public $caller;
+
+       public function getFieldInfo() {
+               return [
+                       'mailpassword' => [
+                               'type' => 'checkbox',
+                               'label' => wfMessage( 'createaccountmail' ),
+                               'help' => wfMessage( 'createaccountmail-help' ),
+                       ],
+               ];
+       }
+
+       /**
+        * @param string|null $password
+        */
+       public function __construct( $password = null ) {
+               $this->password = $password;
+               if ( $password ) {
+                       $this->mailpassword = true;
+               }
+       }
+
+       /**
+        * Return an instance with a new, random password
+        * @return TemporaryPasswordAuthenticationRequest
+        */
+       public static function newRandom() {
+               $config = \ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+
+               // get the min password length
+               $minLength = $config->get( 'MinimalPasswordLength' );
+               $policy = $config->get( 'PasswordPolicy' );
+               foreach ( $policy['policies'] as $p ) {
+                       if ( isset( $p['MinimalPasswordLength'] ) ) {
+                               $minLength = max( $minLength, $p['MinimalPasswordLength'] );
+                       }
+                       if ( isset( $p['MinimalPasswordLengthToLogin'] ) ) {
+                               $minLength = max( $minLength, $p['MinimalPasswordLengthToLogin'] );
+                       }
+               }
+
+               $password = \PasswordFactory::generateRandomPasswordString( $minLength );
+
+               return new self( $password );
+       }
+
+       /**
+        * Return an instance with an invalid password
+        * @return TemporaryPasswordAuthenticationRequest
+        */
+       public static function newInvalid() {
+               $request = new self( null );
+               return $request;
+       }
+
+       public function describeCredentials() {
+               return [
+                       'provider' => wfMessage( 'authmanager-provider-temporarypassword' ),
+                       'account' => new \RawMessage( '$1', [ $this->username ] ),
+               ] + parent::describeCredentials();
+       }
+
+}
diff --git a/includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php b/includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..46cbab5
--- /dev/null
@@ -0,0 +1,454 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use User;
+
+/**
+ * A primary authentication provider that uses the temporary password field in
+ * the 'user' table.
+ *
+ * A successful login will force a password reset.
+ *
+ * @note For proper operation, this should generally come before any other
+ *  password-based authentication providers.
+ * @ingroup Auth
+ * @since 1.27
+ */
+class TemporaryPasswordPrimaryAuthenticationProvider
+       extends AbstractPasswordPrimaryAuthenticationProvider
+{
+       /** @var bool */
+       protected $emailEnabled = null;
+
+       /** @var int */
+       protected $newPasswordExpiry = null;
+
+       /** @var int */
+       protected $passwordReminderResendTime = null;
+
+       /**
+        * @param array $params
+        *  - emailEnabled: (bool) must be true for the option to email passwords to be present
+        *  - newPasswordExpiry: (int) expiraton time of temporary passwords, in seconds
+        *  - passwordReminderResendTime: (int) cooldown period in hours until a password reminder can
+        *    be sent to the same user again,
+        */
+       public function __construct( $params = [] ) {
+               parent::__construct( $params );
+
+               if ( isset( $params['emailEnabled'] ) ) {
+                       $this->emailEnabled = (bool)$params['emailEnabled'];
+               }
+               if ( isset( $params['newPasswordExpiry'] ) ) {
+                       $this->newPasswordExpiry = (int)$params['newPasswordExpiry'];
+               }
+               if ( isset( $params['passwordReminderResendTime'] ) ) {
+                       $this->passwordReminderResendTime = $params['passwordReminderResendTime'];
+               }
+       }
+
+       public function setConfig( \Config $config ) {
+               parent::setConfig( $config );
+
+               if ( $this->emailEnabled === null ) {
+                       $this->emailEnabled = $this->config->get( 'EnableEmail' );
+               }
+               if ( $this->newPasswordExpiry === null ) {
+                       $this->newPasswordExpiry = $this->config->get( 'NewPasswordExpiry' );
+               }
+               if ( $this->passwordReminderResendTime === null ) {
+                       $this->passwordReminderResendTime = $this->config->get( 'PasswordReminderResendTime' );
+               }
+       }
+
+       protected function getPasswordResetData( $username, $data ) {
+               // Always reset
+               return (object)[
+                       'msg' => wfMessage( 'resetpass-temp-emailed' ),
+                       'hard' => true,
+               ];
+       }
+
+       public function getAuthenticationRequests( $action, array $options ) {
+               switch ( $action ) {
+                       case AuthManager::ACTION_LOGIN:
+                               return [ new PasswordAuthenticationRequest() ];
+
+                       case AuthManager::ACTION_CHANGE:
+                               return [ TemporaryPasswordAuthenticationRequest::newRandom() ];
+
+                       case AuthManager::ACTION_CREATE:
+                               if ( isset( $options['username'] ) && $this->emailEnabled ) {
+                                       // Creating an account for someone else
+                                       return [ TemporaryPasswordAuthenticationRequest::newRandom() ];
+                               } else {
+                                       // It's not terribly likely that an anonymous user will
+                                       // be creating an account for someone else.
+                                       return [];
+                               }
+
+                       case AuthManager::ACTION_REMOVE:
+                               return [ new TemporaryPasswordAuthenticationRequest ];
+
+                       default:
+                               return [];
+               }
+       }
+
+       public function beginPrimaryAuthentication( array $reqs ) {
+               $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
+               if ( !$req || $req->username === null || $req->password === null ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $username = User::getCanonicalName( $req->username, 'usable' );
+               if ( $username === false ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
+                       'user',
+                       [
+                               'user_id', 'user_newpassword', 'user_newpass_time',
+                       ],
+                       [ 'user_name' => $username ],
+                       __METHOD__
+               );
+               if ( !$row ) {
+                       return AuthenticationResponse::newAbstain();
+               }
+
+               $status = $this->checkPasswordValidity( $username, $req->password );
+               if ( !$status->isOk() ) {
+                       // Fatal, can't log in
+                       return AuthenticationResponse::newFail( $status->getMessage() );
+               }
+
+               $pwhash = $this->getPassword( $row->user_newpassword );
+               if ( !$pwhash->equals( $req->password ) ) {
+                       return $this->failResponse( $req );
+               }
+
+               if ( !$this->isTimestampValid( $row->user_newpass_time ) ) {
+                       return $this->failResponse( $req );
+               }
+
+               $this->setPasswordResetFlag( $username, $status );
+
+               return AuthenticationResponse::newPass( $username );
+       }
+
+       public function testUserCanAuthenticate( $username ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return false;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
+                       'user',
+                       [ 'user_newpassword', 'user_newpass_time' ],
+                       [ 'user_name' => $username ],
+                       __METHOD__
+               );
+               if ( !$row ) {
+                       return false;
+               }
+
+               if ( $this->getPassword( $row->user_newpassword ) instanceof \InvalidPassword ) {
+                       return false;
+               }
+
+               if ( !$this->isTimestampValid( $row->user_newpass_time ) ) {
+                       return false;
+               }
+
+               return true;
+       }
+
+       public function testUserExists( $username, $flags = User::READ_NORMAL ) {
+               $username = User::getCanonicalName( $username, 'usable' );
+               if ( $username === false ) {
+                       return false;
+               }
+
+               list( $db, $options ) = \DBAccessObjectUtils::getDBOptions( $flags );
+               return (bool)wfGetDB( $db )->selectField(
+                       [ 'user' ],
+                       [ 'user_id' ],
+                       [ 'user_name' => $username ],
+                       __METHOD__,
+                       $options
+               );
+       }
+
+       public function providerAllowsAuthenticationDataChange(
+               AuthenticationRequest $req, $checkData = true
+       ) {
+               if ( get_class( $req ) !== TemporaryPasswordAuthenticationRequest::class ) {
+                       // We don't really ignore it, but this is what the caller expects.
+                       return \StatusValue::newGood( 'ignored' );
+               }
+
+               if ( !$checkData ) {
+                       return \StatusValue::newGood();
+               }
+
+               $username = User::getCanonicalName( $req->username, 'usable' );
+               if ( $username === false ) {
+                       return \StatusValue::newGood( 'ignored' );
+               }
+
+               $row = wfGetDB( DB_MASTER )->selectRow(
+                       'user',
+                       [ 'user_id', 'user_newpass_time' ],
+                       [ 'user_name' => $username ],
+                       __METHOD__
+               );
+
+               if ( !$row ) {
+                       return \StatusValue::newGood( 'ignored' );
+               }
+
+               $sv = \StatusValue::newGood();
+               if ( $req->password !== null ) {
+                       $sv->merge( $this->checkPasswordValidity( $username, $req->password ) );
+
+                       if ( $req->mailpassword ) {
+                               if ( !$this->emailEnabled && !$req->hasBackchannel ) {
+                                       return \StatusValue::newFatal( 'passwordreset-emaildisabled' );
+                               }
+
+                               // We don't check whether the user has an email address;
+                               // that information should not be exposed to the caller.
+
+                               // do not allow temporary password creation within
+                               // $wgPasswordReminderResendTime from the last attempt
+                               if (
+                                       $this->passwordReminderResendTime
+                                       && $row->user_newpass_time
+                                       && time() < wfTimestamp( TS_UNIX, $row->user_newpass_time )
+                                               + $this->passwordReminderResendTime * 3600
+                               ) {
+                                       // Round the time in hours to 3 d.p., in case someone is specifying
+                                       // minutes or seconds.
+                                       return \StatusValue::newFatal( 'throttled-mailpassword',
+                                               round( $this->passwordReminderResendTime, 3 ) );
+                               }
+
+                               if ( !$req->caller ) {
+                                       return \StatusValue::newFatal( 'passwordreset-nocaller' );
+                               }
+                               if ( !\IP::isValid( $req->caller ) ) {
+                                       $caller = User::newFromName( $req->caller );
+                                       if ( !$caller ) {
+                                               return \StatusValue::newFatal( 'passwordreset-nosuchcaller', $req->caller );
+                                       }
+                               }
+                       }
+               }
+               return $sv;
+       }
+
+       public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
+               $username = $req->username !== null ? User::getCanonicalName( $req->username, 'usable' ) : false;
+               if ( $username === false ) {
+                       return;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $sendMail = false;
+               if ( $req->action !== AuthManager::ACTION_REMOVE &&
+                       get_class( $req ) === TemporaryPasswordAuthenticationRequest::class
+               ) {
+                       $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
+                       $newpassTime = $dbw->timestamp();
+                       $sendMail = $req->mailpassword;
+               } else {
+                       // Invalidate the temporary password when any other auth is reset, or when removing
+                       $pwhash = $this->getPasswordFactory()->newFromCiphertext( null );
+                       $newpassTime = null;
+               }
+
+               $dbw->update(
+                       'user',
+                       [
+                               'user_newpassword' => $pwhash->toString(),
+                               'user_newpass_time' => $newpassTime,
+                       ],
+                       [ 'user_name' => $username ],
+                       __METHOD__
+               );
+
+               if ( $sendMail ) {
+                       $this->sendPasswordResetEmail( $req );
+               }
+       }
+
+       public function accountCreationType() {
+               return self::TYPE_CREATE;
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               /** @var TemporaryPasswordAuthenticationRequest $req */
+               $req = AuthenticationRequest::getRequestByClass(
+                       $reqs, TemporaryPasswordAuthenticationRequest::class
+               );
+
+               $ret = \StatusValue::newGood();
+               if ( $req ) {
+                       if ( $req->mailpassword && !$req->hasBackchannel ) {
+                               if ( !$this->emailEnabled ) {
+                                       $ret->merge( \StatusValue::newFatal( 'emaildisabled' ) );
+                               } elseif ( !$user->getEmail() ) {
+                                       $ret->merge( \StatusValue::newFatal( 'noemailcreate' ) );
+                               }
+                       }
+
+                       $ret->merge(
+                               $this->checkPasswordValidity( $user->getName(), $req->password )
+                       );
+               }
+               return $ret;
+       }
+
+       public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
+               /** @var TemporaryPasswordAuthenticationRequest $req */
+               $req = AuthenticationRequest::getRequestByClass(
+                       $reqs, TemporaryPasswordAuthenticationRequest::class
+               );
+               if ( $req ) {
+                       if ( $req->username !== null && $req->password !== null ) {
+                               // Nothing we can do yet, because the user isn't in the DB yet
+                               if ( $req->username !== $user->getName() ) {
+                                       $req = clone( $req );
+                                       $req->username = $user->getName();
+                               }
+
+                               if ( $req->mailpassword ) {
+                                       // prevent EmailNotificationSecondaryAuthenticationProvider from sending another mail
+                                       $this->manager->setAuthenticationSessionData( 'no-email', true );
+                               }
+
+                               $ret = AuthenticationResponse::newPass( $req->username );
+                               $ret->createRequest = $req;
+                               return $ret;
+                       }
+               }
+               return AuthenticationResponse::newAbstain();
+       }
+
+       public function finishAccountCreation( $user, $creator, AuthenticationResponse $res ) {
+               /** @var TemporaryPasswordAuthenticationRequest $req */
+               $req = $res->createRequest;
+               $mailpassword = $req->mailpassword;
+               $req->mailpassword = false; // providerChangeAuthenticationData would send the wrong email
+
+               // Now that the user is in the DB, set the password on it.
+               $this->providerChangeAuthenticationData( $req );
+
+               if ( $mailpassword ) {
+                       $this->sendNewAccountEmail( $user, $creator, $req->password );
+               }
+
+               return $mailpassword ? 'byemail' : null;
+       }
+
+       /**
+        * Check that a temporary password is still valid (hasn't expired).
+        * @param string $timestamp A timestamp in MediaWiki (TS_MW) format
+        * @return bool
+        */
+       protected function isTimestampValid( $timestamp ) {
+               $time = wfTimestampOrNull( TS_MW, $timestamp );
+               if ( $time !== null ) {
+                       $expiry = wfTimestamp( TS_UNIX, $time ) + $this->newPasswordExpiry;
+                       if ( time() >= $expiry ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Send an email about the new account creation and the temporary password.
+        * @param User $user The new user account
+        * @param User $creatingUser The user who created the account (can be anonymous)
+        * @param string $password The temporary password
+        * @return \Status
+        */
+       protected function sendNewAccountEmail( User $user, User $creatingUser, $password ) {
+               $ip = $creatingUser->getRequest()->getIP();
+               // @codeCoverageIgnoreStart
+               if ( !$ip ) {
+                       return \Status::newFatal( 'badipaddress' );
+               }
+               // @codeCoverageIgnoreEnd
+
+               \Hooks::run( 'User::mailPasswordInternal', [ &$creatingUser, &$ip, &$user ] );
+
+               $mainPageUrl = \Title::newMainPage()->getCanonicalURL();
+               $userLanguage = $user->getOption( 'language' );
+               $subjectMessage = wfMessage( 'createaccount-title' )->inLanguage( $userLanguage );
+               $bodyMessage = wfMessage( 'createaccount-text', $ip, $user->getName(), $password,
+                       '<' . $mainPageUrl . '>', round( $this->newPasswordExpiry / 86400 ) )
+                       ->inLanguage( $userLanguage );
+
+               $status = $user->sendMail( $subjectMessage->text(), $bodyMessage->text() );
+
+               // TODO show 'mailerror' message on error, 'accmailtext' success message otherwise?
+               // @codeCoverageIgnoreStart
+               if ( !$status->isGood() ) {
+                       $this->logger->warning( 'Could not send account creation email: ' .
+                               $status->getWikiText( false, false, 'en' ) );
+               }
+               // @codeCoverageIgnoreEnd
+
+               return $status;
+       }
+
+       /**
+        * @param TemporaryPasswordAuthenticationRequest $req
+        * @return \Status
+        */
+       protected function sendPasswordResetEmail( TemporaryPasswordAuthenticationRequest $req ) {
+                       $user = User::newFromName( $req->username );
+                       if ( !$user ) {
+                               return \Status::newFatal( 'noname' );
+                       }
+                       $userLanguage = $user->getOption( 'language' );
+                       $callerIsAnon = \IP::isValid( $req->caller );
+                       $callerName = $callerIsAnon ? $req->caller : User::newFromName( $req->caller )->getName();
+                       $passwordMessage = wfMessage( 'passwordreset-emailelement', $user->getName(),
+                               $req->password )->inLanguage( $userLanguage );
+                       $emailMessage = wfMessage( $callerIsAnon ? 'passwordreset-emailtext-ip'
+                               : 'passwordreset-emailtext-user' )->inLanguage( $userLanguage );
+                       $emailMessage->params( $callerName, $passwordMessage->text(), 1,
+                               '<' . \Title::newMainPage()->getCanonicalURL() . '>',
+                               round( $this->newPasswordExpiry / 86400 ) );
+                       $emailTitle = wfMessage( 'passwordreset-emailtitle' )->inLanguage( $userLanguage );
+                       return $user->sendMail( $emailTitle->text(), $emailMessage->text() );
+       }
+}
diff --git a/includes/auth/ThrottlePreAuthenticationProvider.php b/includes/auth/ThrottlePreAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..e2123ef
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use BagOStuff;
+use Config;
+
+/**
+ * A pre-authentication provider to throttle authentication actions.
+ *
+ * Adding this provider will throttle account creations and primary authentication attempts
+ * (more specifically, any authentication that returns FAIL on failure). Secondary authentication
+ * cannot be easily throttled on a framework level (since it would typically return UI on failure);
+ * secondary providers are expected to do their own throttling.
+ * @ingroup Auth
+ * @since 1.27
+ */
+class ThrottlePreAuthenticationProvider extends AbstractPreAuthenticationProvider {
+       /** @var array */
+       protected $throttleSettings;
+
+       /** @var Throttler */
+       protected $accountCreationThrottle;
+
+       /** @var Throttler */
+       protected $passwordAttemptThrottle;
+
+       /** @var BagOStuff */
+       protected $cache;
+
+       /**
+        * @param array $params
+        *  - accountCreationThrottle: (array) Condition array for the account creation throttle; an array
+        *    of arrays in a format like $wgPasswordAttemptThrottle, passed to the Throttler constructor.
+        *  - passwordAttemptThrottle: (array) Condition array for the password attempt throttle, in the
+        *    same format as accountCreationThrottle.
+        *  - cache: (BagOStuff) Where to store the throttle, defaults to the local cluster instance.
+        */
+       public function __construct( $params = [] ) {
+               $this->throttleSettings = array_intersect_key( $params,
+                       [ 'accountCreationThrottle' => true, 'passwordAttemptThrottle' => true ] );
+               $this->cache = isset( $params['cache'] ) ? $params['cache'] :
+                       \ObjectCache::getLocalClusterInstance();
+       }
+
+       public function setConfig( Config $config ) {
+               parent::setConfig( $config );
+
+               // @codeCoverageIgnoreStart
+               $this->throttleSettings += [
+               // @codeCoverageIgnoreEnd
+                       'accountCreationThrottle' => [ [
+                               'count' => $this->config->get( 'AccountCreationThrottle' ),
+                               'seconds' => 86400,
+                       ] ],
+                       'passwordAttemptThrottle' => $this->config->get( 'PasswordAttemptThrottle' ),
+               ];
+
+               if ( !empty( $this->throttleSettings['accountCreationThrottle'] ) ) {
+                       $this->accountCreationThrottle = new Throttler(
+                               $this->throttleSettings['accountCreationThrottle'], [
+                                       'type' => 'acctcreate',
+                                       'cache' => $this->cache,
+                               ]
+                       );
+               }
+               if ( !empty( $this->throttleSettings['passwordAttemptThrottle'] ) ) {
+                       $this->passwordAttemptThrottle = new Throttler(
+                               $this->throttleSettings['passwordAttemptThrottle'], [
+                                       'type' => 'password',
+                                       'cache' => $this->cache,
+                               ]
+                       );
+               }
+       }
+
+       public function testForAccountCreation( $user, $creator, array $reqs ) {
+               if ( !$this->accountCreationThrottle || !$creator->isPingLimitable() ) {
+                       return \StatusValue::newGood();
+               }
+
+               $ip = $this->manager->getRequest()->getIP();
+
+               if ( !\Hooks::run( 'ExemptFromAccountCreationThrottle', [ $ip ] ) ) {
+                       $this->logger->debug( __METHOD__ . ": a hook allowed account creation w/o throttle\n" );
+                       return \StatusValue::newGood();
+               }
+
+               $result = $this->accountCreationThrottle->increase( null, $ip, __METHOD__ );
+               if ( $result ) {
+                       return \StatusValue::newFatal( 'acct_creation_throttle_hit', $result['count'] );
+               }
+
+               return \StatusValue::newGood();
+       }
+
+       public function testForAuthentication( array $reqs ) {
+               if ( !$this->passwordAttemptThrottle ) {
+                       return \StatusValue::newGood();
+               }
+
+               $ip = $this->manager->getRequest()->getIP();
+               try {
+                       $username = AuthenticationRequest::getUsernameFromRequests( $reqs );
+               } catch ( \UnexpectedValueException $e ) {
+                       $username = '';
+               }
+
+               // Get everything this username could normalize to, and throttle each one individually.
+               // If nothing uses usernames, just throttle by IP.
+               $usernames = $this->manager->normalizeUsername( $username );
+               $result = false;
+               foreach ( $usernames as $name ) {
+                       $r = $this->passwordAttemptThrottle->increase( $name, $ip, __METHOD__ );
+                       if ( $r && ( !$result || $result['wait'] < $r['wait'] ) ) {
+                               $result = $r;
+                       }
+               }
+
+               if ( $result ) {
+                       $message = wfMessage( 'login-throttled' )->durationParams( $result['wait'] );
+                       return \StatusValue::newFatal( $message );
+               } else {
+                       $this->manager->setAuthenticationSessionData( 'LoginThrottle',
+                               [ 'users' => $usernames, 'ip' => $ip ] );
+                       return \StatusValue::newGood();
+               }
+       }
+
+       /**
+        * @param null|\User $user
+        * @param AuthenticationResponse $response
+        */
+       public function postAuthentication( $user, AuthenticationResponse $response ) {
+               if ( $response->status !== AuthenticationResponse::PASS ) {
+                       return;
+               } elseif ( !$this->passwordAttemptThrottle ) {
+                       return;
+               }
+
+               $data = $this->manager->getAuthenticationSessionData( 'LoginThrottle' );
+               if ( !$data ) {
+                       $this->logger->error( 'throttler data not found for {user}', [ 'user' => $user->getName() ] );
+                       return;
+               }
+
+               foreach ( $data['users'] as $name ) {
+                       $this->passwordAttemptThrottle->clear( $name, $data['ip'] );
+               }
+       }
+}
diff --git a/includes/auth/Throttler.php b/includes/auth/Throttler.php
new file mode 100644 (file)
index 0000000..5b14a3b
--- /dev/null
@@ -0,0 +1,210 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use BagOStuff;
+use Config;
+use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
+use User;
+
+/**
+ * A helper class for throttling authentication attempts.
+ * @package MediaWiki\Auth
+ * @ingroup Auth
+ * @since 1.27
+ */
+class Throttler implements LoggerAwareInterface {
+       /** @var string */
+       protected $type;
+       /**
+        * See documentation of $wgPasswordAttemptThrottle for format. Old (pre-1.27) format is not
+        * allowed here.
+        * @var array
+        * @see https://www.mediawiki.org/wiki/Manual:$wgPasswordAttemptThrottle
+        */
+       protected $conditions;
+       /** @var BagOStuff */
+       protected $cache;
+       /** @var LoggerInterface */
+       protected $logger;
+       /** @var int|float */
+       protected $warningLimit;
+
+       /**
+        * @param array $conditions An array of arrays describing throttling conditions.
+        *     Defaults to $wgPasswordAttemptThrottle. See documentation of that variable for format.
+        * @param array $params Parameters (all optional):
+        *   - type: throttle type, used as a namespace for counters,
+        *   - cache: a BagOStuff object where throttle counters are stored.
+        *   - warningLimit: the log level will be raised to warning when rejecting an attempt after
+        *     no less than this many failures.
+        */
+       public function __construct( array $conditions = null, array $params = [] ) {
+               $invalidParams = array_diff_key( $params,
+                       array_fill_keys( [ 'type', 'cache', 'warningLimit' ], true ) );
+               if ( $invalidParams ) {
+                       throw new \InvalidArgumentException( 'unrecognized parameters: '
+                               . implode( ', ', array_keys( $invalidParams ) ) );
+               }
+
+               if ( $conditions === null ) {
+                       $config = \ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+                       $conditions = $config->get( 'PasswordAttemptThrottle' );
+                       $params += [
+                               'type' => 'password',
+                               'cache' => \ObjectCache::getLocalClusterInstance(),
+                               'warningLimit' => 50,
+                       ];
+               } else {
+                       $params += [
+                               'type' => 'custom',
+                               'cache' => \ObjectCache::getLocalClusterInstance(),
+                               'warningLimit' => INF,
+                       ];
+               }
+
+               $this->type = $params['type'];
+               $this->conditions = static::normalizeThrottleConditions( $conditions );
+               $this->cache = $params['cache'];
+               $this->warningLimit = $params['warningLimit'];
+
+               $this->setLogger( LoggerFactory::getInstance( 'throttler' ) );
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * Increase the throttle counter and return whether the attempt should be throttled.
+        *
+        * Should be called before an authentication attempt.
+        *
+        * @param string|null $username
+        * @param string|null $ip
+        * @param string|null $caller The authentication method from which we were called.
+        * @return array|false False if the attempt should not be throttled, an associative array
+        *   with three keys otherwise:
+        *   - throttleIndex: which throttle condition was met (a key of the conditions array)
+        *   - count: throttle count (ie. number of failed attempts)
+        *   - wait: time in seconds until authentication can be attempted
+        */
+       public function increase( $username = null, $ip = null, $caller = null ) {
+               if ( $username === null && $ip === null ) {
+                       throw new \InvalidArgumentException( 'Either username or IP must be set for throttling' );
+               }
+
+               $userKey = $username ? md5( $username ) : null;
+               foreach ( $this->conditions as $index => $throttleCondition ) {
+                       $ipKey = isset( $throttleCondition['allIPs'] ) ? null : $ip;
+                       $count = $throttleCondition['count'];
+                       $expiry = $throttleCondition['seconds'];
+
+                       // a limit of 0 is used as a disable flag in some throttling configuration settings
+                       // throttling the whole world is probably a bad idea
+                       if ( !$count || $userKey === null && $ipKey === null ) {
+                               continue;
+                       }
+
+                       $throttleKey = wfGlobalCacheKey( 'throttler', $this->type, $index, $ipKey, $userKey );
+                       $throttleCount = $this->cache->get( $throttleKey );
+
+                       if ( !$throttleCount ) {  // counter not started yet
+                               $this->cache->add( $throttleKey, 1, $expiry );
+                       } elseif ( $throttleCount < $count ) { // throttle limited not yet reached
+                               $this->cache->incr( $throttleKey );
+                       } else { // throttled
+                               $this->logRejection( [
+                                       'type' => $this->type,
+                                       'index' => $index,
+                                       'ip' => $ipKey,
+                                       'username' => $username,
+                                       'count' => $count,
+                                       'expiry' => $expiry,
+                                       // @codeCoverageIgnoreStart
+                                       'method' => $caller ?: __METHOD__,
+                                       // @codeCoverageIgnoreEnd
+                               ] );
+
+                               return [
+                                       'throttleIndex' => $index,
+                                       'count' => $count,
+                                       'wait' => $expiry,
+                               ];
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Clear the throttle counter.
+        *
+        * Should be called after a successful authentication attempt.
+        *
+        * @param string|null $username
+        * @param string|null $ip
+        * @throws \MWException
+        */
+       public function clear( $username = null, $ip = null ) {
+               $userKey = $username ? md5( $username ) : null;
+               foreach ( $this->conditions as $index => $specificThrottle ) {
+                       $ipKey = isset( $specificThrottle['allIPs'] ) ? null : $ip;
+                       $throttleKey = wfGlobalCacheKey( 'throttler', $this->type, $index, $ipKey, $userKey );
+                       $this->cache->delete( $throttleKey );
+               }
+       }
+
+       /**
+        * Handles B/C for $wgPasswordAttemptThrottle.
+        * @param array $throttleConditions
+        * @return array
+        * @see $wgPasswordAttemptThrottle for structure
+        */
+       protected static function normalizeThrottleConditions( $throttleConditions ) {
+               if ( !is_array( $throttleConditions ) ) {
+                       return [];
+               }
+               if ( isset( $throttleConditions['count'] ) ) { // old style
+                       $throttleConditions = [ $throttleConditions ];
+               }
+               return $throttleConditions;
+       }
+
+       protected function logRejection( array $context ) {
+               $logMsg = 'Throttle {type} hit, throttled for {expiry} seconds due to {count} attempts '
+                       . 'from username {username} and IP {ip}';
+
+               // If we are hitting a throttle for >= warningLimit attempts, it is much more likely to be
+               // an attack than someone simply forgetting their password, so log it at a higher level.
+               $level = $context['count'] >= $this->warningLimit ? LogLevel::WARNING : LogLevel::INFO;
+
+               // It should be noted that once the throttle is hit, every attempt to login will
+               // generate the log message until the throttle expires, not just the attempt that
+               // puts the throttle over the top.
+               $this->logger->log( $level, $logMsg, $context );
+       }
+
+}
diff --git a/includes/auth/UserDataAuthenticationRequest.php b/includes/auth/UserDataAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..ee77d7b
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use StatusValue;
+use User;
+
+/**
+ * This represents additional user data requested on the account creation form
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class UserDataAuthenticationRequest extends AuthenticationRequest {
+       /** @var string|null Email address */
+       public $email;
+
+       /** @var string|null Real name */
+       public $realname;
+
+       public function getFieldInfo() {
+               $config = \ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+               $ret = [
+                       'email' => [
+                               'type' => 'string',
+                               'label' => wfMessage( 'authmanager-email-label' ),
+                               'help' => wfMessage( 'authmanager-email-help' ),
+                               'optional' => true,
+                       ],
+                       'realname' => [
+                               'type' => 'string',
+                               'label' => wfMessage( 'authmanager-realname-label' ),
+                               'help' => wfMessage( 'authmanager-realname-help' ),
+                               'optional' => true,
+                       ],
+               ];
+
+               if ( !$config->get( 'EnableEmail' ) ) {
+                       unset( $ret['email'] );
+               }
+
+               if ( in_array( 'realname', $config->get( 'HiddenPrefs' ), true ) ) {
+                       unset( $ret['realname'] );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Add data to the User object
+        * @param User $user User being created (not added to the database yet).
+        *   This may become a "UserValue" in the future, or User may be refactored
+        *   into such.
+        * @return StatusValue
+        */
+       public function populateUser( $user ) {
+               if ( $this->email !== null && $this->email !== '' ) {
+                       if ( !\Sanitizer::validateEmail( $this->email ) ) {
+                               return StatusValue::newFatal( 'invalidemailaddress' );
+                       }
+                       $user->setEmail( $this->email );
+               }
+               if ( $this->realname !== null && $this->realname !== '' ) {
+                       $user->setRealName( $this->realname );
+               }
+               return StatusValue::newGood();
+       }
+
+}
diff --git a/includes/auth/UsernameAuthenticationRequest.php b/includes/auth/UsernameAuthenticationRequest.php
new file mode 100644 (file)
index 0000000..7bf8f13
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+/**
+ * AuthenticationRequest to ensure something with a username is present
+ * @ingroup Auth
+ * @since 1.27
+ */
+class UsernameAuthenticationRequest extends AuthenticationRequest {
+       public function getFieldInfo() {
+               return [
+                       'username' => [
+                               'type' => 'string',
+                               'label' => wfMessage( 'userlogin-yourname' ),
+                               'help' => wfMessage( 'authmanager-username-help' ),
+                       ],
+               ];
+       }
+}
index 2d29d86..a59ba97 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Cache
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * This class stores an arbitrary value along with its dependencies.
@@ -244,6 +245,34 @@ class GlobalDependency extends CacheDependency {
        }
 }
 
+/**
+ * @ingroup Cache
+ */
+class MainConfigDependency extends CacheDependency {
+       private $name;
+       private $value;
+
+       function __construct( $name ) {
+               $this->name = $name;
+               $this->value = $this->getConfig()->get( $this->name );
+       }
+
+       private function getConfig() {
+               return MediaWikiServices::getInstance()->getMainConfig();
+       }
+
+       /**
+        * @return bool
+        */
+       function isExpired() {
+               if ( !$this->getConfig()->has( $this->name ) ) {
+                       return true;
+               }
+
+               return $this->getConfig()->get( $this->name ) != $this->value;
+       }
+}
+
 /**
  * @ingroup Cache
  */
index 19695df..80f04ce 100644 (file)
@@ -21,6 +21,7 @@
  * @author Niklas Laxström
  * @ingroup Cache
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Caches user genders when needed to use correct namespace aliases.
@@ -34,18 +35,11 @@ class GenderCache {
        protected $missLimit = 1000;
 
        /**
+        * @deprecated in 1.28 see MediaWikiServices::getInstance()->getGenderCache()
         * @return GenderCache
         */
        public static function singleton() {
-               static $that = null;
-               if ( $that === null ) {
-                       $that = new self();
-               }
-
-               return $that;
-       }
-
-       protected function __construct() {
+               return MediaWikiServices::getInstance()->getGenderCache();
        }
 
        /**
index c5bd290..a7dd570 100644 (file)
@@ -179,8 +179,6 @@ class LinkBatch {
         * @return bool|ResultWrapper
         */
        public function doQuery() {
-               global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
-
                if ( $this->isEmpty() ) {
                        return false;
                }
@@ -188,15 +186,10 @@ class LinkBatch {
                // This is similar to LinkHolderArray::replaceInternal
                $dbr = wfGetDB( DB_SLAVE );
                $table = 'page';
-               $fields = [ 'page_id', 'page_namespace', 'page_title', 'page_len',
-                       'page_is_redirect', 'page_latest' ];
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'page_content_model';
-               }
-               if ( $wgPageLanguageUseDB ) {
-                       $fields[] = 'page_lang';
-               }
+               $fields = array_merge(
+                       LinkCache::getSelectFields(),
+                       [ 'page_namespace', 'page_title' ]
+               );
 
                $conds = $this->constructSet( 'page', $dbr );
 
index b8f2329..de44f9b 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  * @ingroup Cache
  */
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Cache for article titles (prefixed DB keys) and ids linked from one source
@@ -38,56 +40,30 @@ class LinkCache {
        private $mForUpdate = false;
 
        /**
-        * How many Titles to store. There are two caches, so the amount actually
-        * stored in memory can be up to twice this.
+        * @var TitleFormatter
         */
-       const MAX_SIZE = 10000;
+       private $titleFormatter;
 
        /**
-        * @var LinkCache
+        * How many Titles to store. There are two caches, so the amount actually
+        * stored in memory can be up to twice this.
         */
-       protected static $instance;
+       const MAX_SIZE = 10000;
 
-       public function __construct() {
+       public function __construct( TitleFormatter $titleFormatter ) {
                $this->mGoodLinks = new HashBagOStuff( [ 'maxKeys' => self::MAX_SIZE ] );
                $this->mBadLinks = new HashBagOStuff( [ 'maxKeys' => self::MAX_SIZE ] );
+               $this->titleFormatter = $titleFormatter;
        }
 
        /**
         * Get an instance of this class.
         *
         * @return LinkCache
+        * @deprecated since 1.28, use MediaWikiServices instead
         */
        public static function singleton() {
-               if ( !self::$instance ) {
-                       self::$instance = new LinkCache;
-               }
-
-               return self::$instance;
-       }
-
-       /**
-        * Destroy the singleton instance
-        *
-        * A new one will be created next time singleton() is called.
-        *
-        * @since 1.22
-        */
-       public static function destroySingleton() {
-               self::$instance = null;
-       }
-
-       /**
-        * Set the singleton instance to a given object.
-        *
-        * Since we do not have an interface for LinkCache, you have to be sure the
-        * given object implements all the LinkCache public methods.
-        *
-        * @param LinkCache $instance
-        * @since 1.22
-        */
-       public static function setSingleton( LinkCache $instance ) {
-               self::$instance = $instance;
+               return MediaWikiServices::getInstance()->getLinkCache();
        }
 
        /**
@@ -119,12 +95,12 @@ class LinkCache {
        /**
         * Get a field of a title object from cache.
         * If this link is not a cached good title, it will return NULL.
-        * @param Title $title
+        * @param LinkTarget $target
         * @param string $field ('length','redirect','revision','model')
         * @return string|int|null
         */
-       public function getGoodLinkFieldObj( Title $title, $field ) {
-               $dbkey = $title->getPrefixedDBkey();
+       public function getGoodLinkFieldObj( LinkTarget $target, $field ) {
+               $dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
                $info = $this->mGoodLinks->get( $dbkey );
                if ( !$info ) {
                        return null;
@@ -145,17 +121,17 @@ class LinkCache {
         * Add a link for the title to the link cache
         *
         * @param int $id Page's ID
-        * @param Title $title
+        * @param LinkTarget $target
         * @param int $len Text's length
         * @param int $redir Whether the page is a redirect
         * @param int $revision Latest revision's ID
         * @param string|null $model Latest revision's content model ID
         * @param string|null $lang Language code of the page, if not the content language
         */
-       public function addGoodLinkObj( $id, Title $title, $len = -1, $redir = null,
+       public function addGoodLinkObj( $id, LinkTarget $target, $len = -1, $redir = null,
                $revision = 0, $model = null, $lang = null
        ) {
-               $dbkey = $title->getPrefixedDBkey();
+               $dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
                $this->mGoodLinks->set( $dbkey, [
                        'id' => (int)$id,
                        'length' => (int)$len,
@@ -169,12 +145,12 @@ class LinkCache {
        /**
         * Same as above with better interface.
         * @since 1.19
-        * @param Title $title
+        * @param LinkTarget $target
         * @param stdClass $row Object which has the fields page_id, page_is_redirect,
         *  page_latest and page_content_model
         */
-       public function addGoodLinkObjFromRow( Title $title, $row ) {
-               $dbkey = $title->getPrefixedDBkey();
+       public function addGoodLinkObjFromRow( LinkTarget $target, $row ) {
+               $dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
                $this->mGoodLinks->set( $dbkey, [
                        'id' => intval( $row->page_id ),
                        'length' => intval( $row->page_len ),
@@ -186,10 +162,10 @@ class LinkCache {
        }
 
        /**
-        * @param Title $title
+        * @param LinkTarget $target
         */
-       public function addBadLinkObj( Title $title ) {
-               $dbkey = $title->getPrefixedDBkey();
+       public function addBadLinkObj( LinkTarget $target ) {
+               $dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
                if ( !$this->isBadLink( $dbkey ) ) {
                        $this->mBadLinks->set( $dbkey, 1 );
                }
@@ -203,10 +179,10 @@ class LinkCache {
        }
 
        /**
-        * @param Title $title
+        * @param LinkTarget $target
         */
-       public function clearLink( Title $title ) {
-               $dbkey = $title->getPrefixedDBkey();
+       public function clearLink( LinkTarget $target ) {
+               $dbkey = $this->titleFormatter->getPrefixedDBkey( $target );
                $this->mBadLinks->delete( $dbkey );
                $this->mGoodLinks->delete( $dbkey );
        }
@@ -214,6 +190,7 @@ class LinkCache {
        /**
         * Add a title to the link cache, return the page_id or zero if non-existent
         *
+        * @deprecated since 1.27, unused
         * @param string $title Prefixed DB key
         * @return int Page ID or zero
         */
@@ -226,15 +203,33 @@ class LinkCache {
        }
 
        /**
-        * Add a title to the link cache, return the page_id or zero if non-existent
+        * Fields that LinkCache needs to select
         *
-        * @param Title $nt Title object to add
-        * @return int Page ID or zero
+        * @since 1.28
+        * @return array
         */
-       public function addLinkObj( Title $nt ) {
+       public static function getSelectFields() {
                global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
 
-               $key = $nt->getPrefixedDBkey();
+               $fields = [ 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ];
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'page_content_model';
+               }
+               if ( $wgPageLanguageUseDB ) {
+                       $fields[] = 'page_lang';
+               }
+
+               return $fields;
+       }
+
+       /**
+        * Add a title to the link cache, return the page_id or zero if non-existent
+        *
+        * @param LinkTarget $nt LinkTarget object to add
+        * @return int Page ID or zero
+        */
+       public function addLinkObj( LinkTarget $nt ) {
+               $key = $this->titleFormatter->getPrefixedDBkey( $nt );
                if ( $this->isBadLink( $key ) || $nt->isExternal() ) {
                        return 0;
                }
@@ -250,15 +245,7 @@ class LinkCache {
                // Some fields heavily used for linking...
                $db = $this->mForUpdate ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 
-               $fields = [ 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ];
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'page_content_model';
-               }
-               if ( $wgPageLanguageUseDB ) {
-                       $fields[] = 'page_lang';
-               }
-
-               $row = $db->selectRow( 'page', $fields,
+               $row = $db->selectRow( 'page', self::getSelectFields(),
                        [ 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ],
                        __METHOD__
                );
index dd7d81a..0fb9ed8 100644 (file)
@@ -23,6 +23,7 @@
 use Cdb\Reader as CdbReader;
 use Cdb\Writer as CdbWriter;
 use CLDRPluralRuleParser\Evaluator;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class for caching the contents of localisation files, Messages*.php
@@ -802,12 +803,15 @@ class LocalisationCache {
         * @return array
         */
        public function getMessagesDirs() {
-               global $wgMessagesDirs, $IP;
+               global $IP;
+
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+               $messagesDirs = $config->get( 'MessagesDirs' );
                return [
                        'core' => "$IP/languages/i18n",
                        'api' => "$IP/includes/api/i18n",
                        'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
-               ] + $wgMessagesDirs;
+               ] + $messagesDirs;
        }
 
        /**
@@ -958,8 +962,9 @@ class LocalisationCache {
 
                # Add cache dependencies for any referenced globals
                $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
-               // $wgMessagesDirs is used in LocalisationCache::getMessagesDirs()
-               $deps['wgMessagesDirs'] = new GlobalDependency( 'wgMessagesDirs' );
+               // The 'MessagesDirs' config setting is used in LocalisationCache::getMessagesDirs().
+               // We use the key 'wgMessagesDirs' for historical reasons.
+               $deps['wgMessagesDirs'] = new MainConfigDependency( 'MessagesDirs' );
                $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
 
                # Add dependencies to the cache entry
index a808516..1070877 100644 (file)
@@ -262,6 +262,7 @@ class EnhancedChangesList extends ChangesList {
                        if ( !$line ) {
                                // completely ignore this RC entry if we don't want to render it
                                unset( $block[$i] );
+                               continue;
                        }
 
                        // Roll up flags
@@ -286,6 +287,7 @@ class EnhancedChangesList extends ChangesList {
 
                        $lines[] = $line;
                }
+
                // Further down are some assumptions that $block is a 0-indexed array
                // with (count-1) as last key. Let's make sure it is.
                $block = array_values( $block );
index 9db1697..a2945af 100644 (file)
@@ -1055,8 +1055,8 @@ class ChangeTags {
                $tagUsage = self::tagUsageStatistics();
 
                if ( !is_null( $user ) ) {
-                       if ( !$user->isAllowed( 'managechangetags' ) ) {
-                               return Status::newFatal( 'tags-manage-no-permission' );
+                       if ( !$user->isAllowed( 'deletechangetags' ) ) {
+                               return Status::newFatal( 'tags-delete-no-permission' );
                        } elseif ( $user->isBlocked() ) {
                                return Status::newFatal( 'tags-manage-blocked' );
                        }
@@ -1134,7 +1134,7 @@ class ChangeTags {
        public static function listExtensionActivatedTags() {
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'active-tags' ),
-                       300,
+                       WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) {
                                $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
 
@@ -1145,8 +1145,8 @@ class ChangeTags {
                        },
                        [
                                'checkKeys' => [ wfMemcKey( 'active-tags' ) ],
-                               'lockTSE' => 300,
-                               'pcTTL' => 30
+                               'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
+                               'pcTTL' => WANObjectCache::TTL_PROC_LONG
                        ]
                );
        }
@@ -1179,7 +1179,7 @@ class ChangeTags {
 
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-db' ),
-                       300,
+                       WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
                                $dbr = wfGetDB( DB_SLAVE );
 
@@ -1191,8 +1191,8 @@ class ChangeTags {
                        },
                        [
                                'checkKeys' => [ wfMemcKey( 'valid-tags-db' ) ],
-                               'lockTSE' => 300,
-                               'pcTTL' => 30
+                               'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
+                               'pcTTL' => WANObjectCache::TTL_PROC_LONG
                        ]
                );
        }
@@ -1209,7 +1209,7 @@ class ChangeTags {
        public static function listExtensionDefinedTags() {
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-hook' ),
-                       300,
+                       WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) {
                                $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
 
@@ -1219,8 +1219,8 @@ class ChangeTags {
                        },
                        [
                                'checkKeys' => [ wfMemcKey( 'valid-tags-hook' ) ],
-                               'lockTSE' => 300,
-                               'pcTTL' => 30
+                               'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
+                               'pcTTL' => WANObjectCache::TTL_PROC_LONG
                        ]
                );
        }
@@ -1264,7 +1264,7 @@ class ChangeTags {
                $fname = __METHOD__;
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'change-tag-statistics' ),
-                       300,
+                       WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
                                $dbr = wfGetDB( DB_SLAVE, 'vslow' );
 
@@ -1287,8 +1287,8 @@ class ChangeTags {
                        },
                        [
                                'checkKeys' => [ wfMemcKey( 'change-tag-statistics' ) ],
-                               'lockTSE' => 300,
-                               'pcTTL' => 30
+                               'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
+                               'pcTTL' => WANObjectCache::TTL_PROC_LONG
                        ]
                );
        }
index 942036b..a374b13 100644 (file)
@@ -234,32 +234,33 @@ class IcuCollation extends Collation {
 
        /**
         * @since 1.16.3
+        * @return array
         */
        public function getFirstLetterData() {
-               if ( $this->firstLetterData !== null ) {
-                       return $this->firstLetterData;
-               }
-
-               $cache = ObjectCache::getLocalServerInstance( CACHE_ANYTHING );
-               $cacheKey = $cache->makeKey(
-                       'first-letters',
-                       $this->locale,
-                       $this->digitTransformLanguage->getCode(),
-                       self::getICUVersion()
-               );
-               $cacheEntry = $cache->get( $cacheKey );
-
-               if ( $cacheEntry && isset( $cacheEntry['version'] )
-                       && $cacheEntry['version'] == self::FIRST_LETTER_VERSION
-               ) {
-                       $this->firstLetterData = $cacheEntry;
-                       return $this->firstLetterData;
+               if ( $this->firstLetterData === null ) {
+                       $cache = ObjectCache::getLocalServerInstance( CACHE_ANYTHING );
+                       $cacheKey = $cache->makeKey(
+                               'first-letters',
+                               $this->locale,
+                               $this->digitTransformLanguage->getCode(),
+                               self::getICUVersion(),
+                               self::FIRST_LETTER_VERSION
+                       );
+                       $this->firstLetterData = $cache->getWithSetCallback( $cacheKey, $cache::TTL_WEEK, function () {
+                               return $this->fetchFirstLetterData();
+                       } );
                }
+               return $this->firstLetterData;
+       }
 
+       /**
+        * @return array
+        * @throws MWException
+        */
+       private function fetchFirstLetterData() {
                // Generate data from serialized data file
-
                if ( isset( self::$tailoringFirstLetters[$this->locale] ) ) {
-                       $letters = wfGetPrecompiledData( "first-letters-root.ser" );
+                       $letters = wfGetPrecompiledData( 'first-letters-root.ser' );
                        // Append additional characters
                        $letters = array_merge( $letters, self::$tailoringFirstLetters[$this->locale] );
                        // Remove unnecessary ones, if any
@@ -374,15 +375,11 @@ class IcuCollation extends Collation {
                $data = [
                        'chars' => array_values( $letterMap ),
                        'keys' => array_keys( $letterMap ),
-                       'version' => self::FIRST_LETTER_VERSION,
                ];
 
                // Reduce memory usage before caching
                unset( $letterMap );
 
-               // Save to cache
-               $this->firstLetterData = $data;
-               $cache->set( $cacheKey, $data, $cache::TTL_WEEK );
                return $data;
        }
 
@@ -390,30 +387,21 @@ class IcuCollation extends Collation {
         * @since 1.16.3
         */
        public function getLetterByIndex( $index ) {
-               if ( $this->firstLetterData === null ) {
-                       $this->getFirstLetterData();
-               }
-               return $this->firstLetterData['chars'][$index];
+               return $this->getFirstLetterData()['chars'][$index];
        }
 
        /**
         * @since 1.16.3
         */
        public function getSortKeyByLetterIndex( $index ) {
-               if ( $this->firstLetterData === null ) {
-                       $this->getFirstLetterData();
-               }
-               return $this->firstLetterData['keys'][$index];
+               return $this->getFirstLetterData()['keys'][$index];
        }
 
        /**
         * @since 1.16.3
         */
        public function getFirstLetterCount() {
-               if ( $this->firstLetterData === null ) {
-                       $this->getFirstLetterData();
-               }
-               return count( $this->firstLetterData['chars'] );
+               return count( $this->getFirstLetterData()['chars'] );
        }
 
        /**
index 0bc8d0f..8791e4c 100644 (file)
@@ -30,8 +30,8 @@ use UtfNormal\Utils;
 /**
  * Return UTF-8 sequence for a given Unicode code point.
  *
- * @param $codepoint Integer:
- * @return String
+ * @param int $codepoint
+ * @return string
  * @throws InvalidArgumentException if fed out of range data.
  * @public
  * @deprecated since 1.25, use UtfNormal\Utils directly
@@ -45,8 +45,8 @@ function codepointToUtf8( $codepoint ) {
  * Unicode code points and return a UTF-8 string composed of those
  * characters. Used by UTF-8 data generation and testing routines.
  *
- * @param $sequence String
- * @return String
+ * @param string $sequence
+ * @return string
  * @throws InvalidArgumentException if fed out of range data.
  * @private
  * @deprecated since 1.25, use UtfNormal\Utils directly
@@ -77,8 +77,8 @@ function utf8ToHexSequence( $str ) {
  * Determine the Unicode codepoint of a single-character UTF-8 sequence.
  * Does not check for invalid input data.
  *
- * @param $char String
- * @return Integer
+ * @param string $char
+ * @return int
  * @public
  * @deprecated since 1.25, use UtfNormal\Utils directly
  */
index 20290ef..9f60394 100644 (file)
@@ -1,8 +1,8 @@
 <?php
 
 use Composer\Package\Link;
-use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\Package;
+use Composer\Semver\Constraint\Constraint;
 
 /**
  * @licence GNU GPL v2+
@@ -50,7 +50,7 @@ class ComposerPackageModifier {
                $mvVersion = $this->versionFetcher->fetchVersion();
                $mvVersion = $this->versionNormalizer->normalizeSuffix( $mvVersion );
 
-               $version = new VersionConstraint(
+               $version = new Constraint(
                        '==',
                        $this->versionNormalizer->normalizeLevelCount( $mvVersion )
                );
index 4b803d8..cd25352 100644 (file)
  *
  * @file
  */
+use MediaWiki\Services\SalvageableService;
+use Wikimedia\Assert\Assert;
 
 /**
  * Factory class to create Config objects
  *
  * @since 1.23
  */
-class ConfigFactory {
+class ConfigFactory implements SalvageableService {
 
        /**
         * Map of config name => callback
@@ -51,16 +53,62 @@ class ConfigFactory {
        }
 
        /**
-        * Register a new config factory function
-        * Will override if it's already registered
+        * Re-uses existing Cache objects from $other. Cache objects are only re-used if the
+        * registered factory function for both is the same. Cache config is not copied,
+        * and only instances of caches defined on this instance with the same config
+        * are copied.
+        *
+        * @see SalvageableService::salvage()
+        *
+        * @param SalvageableService $other The object to salvage state from. $other must have the
+        * exact same type as $this.
+        */
+       public function salvage( SalvageableService $other ) {
+               Assert::parameterType( self::class, $other, '$other' );
+
+               /** @var ConfigFactory $other */
+               foreach ( $other->factoryFunctions as $name => $otherFunc ) {
+                       if ( !isset( $this->factoryFunctions[$name] ) ) {
+                               continue;
+                       }
+
+                       // if the callback function is the same, salvage the Cache object
+                       // XXX: Closures are never equal!
+                       if ( isset( $other->configs[$name] )
+                               && $this->factoryFunctions[$name] == $otherFunc
+                       ) {
+                               $this->configs[$name] = $other->configs[$name];
+                               unset( $other->configs[$name] );
+                       }
+               }
+
+               // disable $other
+               $other->factoryFunctions = [];
+               $other->configs = [];
+       }
+
+       /**
+        * @return string[]
+        */
+       public function getConfigNames() {
+               return array_keys( $this->factoryFunctions );
+       }
+
+       /**
+        * Register a new config factory function.
+        * Will override if it's already registered.
+        * Use "*" for $name to provide a fallback config for all unknown names.
         * @param string $name
-        * @param callable $callback That takes this ConfigFactory as an argument
+        * @param callable|Config $callback A factory callabck that takes this ConfigFactory
+        *        as an argument and returns a Config instance, or an existing Config instance.
         * @throws InvalidArgumentException If an invalid callback is provided
         */
        public function register( $name, $callback ) {
-               if ( !is_callable( $callback ) ) {
+               if ( !is_callable( $callback ) && !( $callback instanceof Config ) ) {
                        throw new InvalidArgumentException( 'Invalid callback provided' );
                }
+
+               unset( $this->configs[$name] );
                $this->factoryFunctions[$name] = $callback;
        }
 
@@ -75,10 +123,20 @@ class ConfigFactory {
         */
        public function makeConfig( $name ) {
                if ( !isset( $this->configs[$name] ) ) {
-                       if ( !isset( $this->factoryFunctions[$name] ) ) {
+                       $key = $name;
+                       if ( !isset( $this->factoryFunctions[$key] ) ) {
+                               $key = '*';
+                       }
+                       if ( !isset( $this->factoryFunctions[$key] ) ) {
                                throw new ConfigException( "No registered builder available for $name." );
                        }
-                       $conf = call_user_func( $this->factoryFunctions[$name], $this );
+
+                       if ( $this->factoryFunctions[$key] instanceof Config ) {
+                               $conf = $this->factoryFunctions[$key];
+                       } else {
+                               $conf = call_user_func( $this->factoryFunctions[$key], $this );
+                       }
+
                        if ( $conf instanceof Config ) {
                                $this->configs[$name] = $conf;
                        } else {
@@ -88,4 +146,5 @@ class ConfigFactory {
 
                return $this->configs[$name];
        }
+
 }
index 7430caf..e225fb7 100644 (file)
@@ -641,7 +641,12 @@ abstract class ContentHandler {
         *
         * @since 1.21
         *
-        * @return array Always an empty array.
+        * @return array An array mapping action names (typically "view", "edit", "history" etc.) to
+        *  either the full qualified class name of an Action class, a callable taking ( Page $page,
+        *  IContextSource $context = null ) as parameters and returning an Action object, or an actual
+        *  Action object. An empty array in this default implementation.
+        *
+        * @see Action::factory
         */
        public function getActionOverrides() {
                return [];
@@ -892,7 +897,7 @@ abstract class ContentHandler {
         * have it / want it.
         */
        public function getAutoDeleteReason( Title $title, &$hasHistory ) {
-               $dbw = wfGetDB( DB_MASTER );
+               $dbr = wfGetDB( DB_SLAVE );
 
                // Get the last revision
                $rev = Revision::newFromTitle( $title );
@@ -922,10 +927,10 @@ abstract class ContentHandler {
 
                // Find out if there was only one contributor
                // Only scan the last 20 revisions
-               $res = $dbw->select( 'revision', 'rev_user_text',
+               $res = $dbr->select( 'revision', 'rev_user_text',
                        [
                                'rev_page' => $title->getArticleID(),
-                               $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
+                               $dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
                        ],
                        __METHOD__,
                        [ 'LIMIT' => 20 ]
@@ -937,7 +942,7 @@ abstract class ContentHandler {
                }
 
                $hasHistory = ( $res->numRows() > 1 );
-               $row = $dbw->fetchObject( $res );
+               $row = $dbr->fetchObject( $res );
 
                if ( $row ) { // $row is false if the only contributor is hidden
                        $onlyAuthor = $row->rev_user_text;
index c36cfdb..92e89b0 100644 (file)
@@ -1847,7 +1847,7 @@ abstract class DatabaseBase implements IDatabase {
                if ( !$alias || (string)$alias === (string)$name ) {
                        return $name;
                } else {
-                       return $name . ' AS ' . $alias; // PostgreSQL needs AS
+                       return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
                }
        }
 
@@ -2589,17 +2589,13 @@ abstract class DatabaseBase implements IDatabase {
                        } elseif ( !$this->mTrxAutomatic ) {
                                // We want to warn about inadvertently nested begin/commit pairs, but not about
                                // auto-committing implicit transactions that were started by query() via DBO_TRX
-                               $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
-                                       " performing implicit commit!";
-                               wfWarn( $msg );
-                               wfLogDBError( $msg,
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__,
-                                               'fname' => $fname,
-                                       ] )
+                               throw new DBUnexpectedError(
+                                       $this,
+                                       "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
+                                               " performing implicit commit!"
                                );
                        } else {
-                               // if the transaction was automatic and has done write operations
+                               // The transaction was automatic and has done write operations
                                if ( $this->mTrxDoneWrites ) {
                                        wfDebug( "$fname: Automatic transaction with writes in progress" .
                                                " (from {$this->mTrxFname}), performing implicit commit!\n"
index 5c46c1a..33f8162 100644 (file)
@@ -38,6 +38,7 @@ class DatabaseMssql extends Database {
        protected $mBinaryColumnCache = null;
        protected $mBitColumnCache = null;
        protected $mIgnoreDupKeyErrors = false;
+       protected $mIgnoreErrors = [];
 
        protected $mPort;
 
@@ -206,35 +207,31 @@ class DatabaseMssql extends Database {
                        $success = (bool)$stmt;
                }
 
+               // make a copy so that anything we add below does not get reflected in future queries
+               $ignoreErrors = $this->mIgnoreErrors;
+
                if ( $this->mIgnoreDupKeyErrors ) {
-                       // ignore duplicate key errors, but nothing else
+                       // ignore duplicate key errors
                        // this emulates INSERT IGNORE in MySQL
-                       if ( $success === false ) {
-                               $errors = sqlsrv_errors( SQLSRV_ERR_ERRORS );
-                               $success = true;
-
-                               foreach ( $errors as $err ) {
-                                       if ( $err['SQLSTATE'] == '23000' && $err['code'] == '2601' ) {
-                                               continue; // duplicate key error caused by unique index
-                                       } elseif ( $err['SQLSTATE'] == '23000' && $err['code'] == '2627' ) {
-                                               continue; // duplicate key error caused by primary key
-                                       } elseif ( $err['SQLSTATE'] == '01000' && $err['code'] == '3621' ) {
-                                               continue; // generic "the statement has been terminated" error
-                                       }
+                       $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
+                       $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
+                       $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
+               }
 
-                                       $success = false; // getting here means we got an error we weren't expecting
-                                       break;
-                               }
+               if ( $success === false ) {
+                       $errors = sqlsrv_errors();
+                       $success = true;
 
-                               if ( $success ) {
-                                       $this->mAffectedRows = 0;
-                                       return $stmt;
+                       foreach ( $errors as $err ) {
+                               if ( !in_array( $err['code'], $ignoreErrors ) ) {
+                                       $success = false;
+                                       break;
                                }
                        }
-               }
 
-               if ( $success === false ) {
-                       return false;
+                       if ( $success === false ) {
+                               return false;
+                       }
                }
                // remember number of rows affected
                $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
@@ -276,7 +273,15 @@ class DatabaseMssql extends Database {
                        $res = $res->result;
                }
 
-               return sqlsrv_num_rows( $res );
+               $ret = sqlsrv_num_rows( $res );
+
+               if ( $ret === false ) {
+                       // we cannot get an amount of rows from this cursor type
+                       // has_rows returns bool true/false if the result has rows
+                       $ret = (int)sqlsrv_has_rows( $res );
+               }
+
+               return $ret;
        }
 
        /**
@@ -536,8 +541,9 @@ class DatabaseMssql extends Database {
                # This does not return the same info as MYSQL would, but that's OK
                # because MediaWiki never uses the returned value except to check for
                # the existance of indexes.
-               $sql = "sp_helpindex '" . $table . "'";
+               $sql = "sp_helpindex '" . $this->tableName( $table ) . "'";
                $res = $this->query( $sql, $fname );
+
                if ( !$res ) {
                        return null;
                }
@@ -696,6 +702,12 @@ class DatabaseMssql extends Database {
                                $row = $ret->fetchObject();
                                if ( is_object( $row ) ) {
                                        $this->mInsertId = $row->$identity;
+
+                                       // it seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is used
+                                       // if we got an identity back, we know for sure a row was affected, so adjust that here
+                                       if ( $this->mAffectedRows == -1 ) {
+                                               $this->mAffectedRows = 1;
+                                       }
                                }
                        }
                }
@@ -1351,6 +1363,24 @@ class DatabaseMssql extends Database {
                return $table;
        }
 
+       /**
+        * Delete a table
+        * @param string $tableName
+        * @param string $fName
+        * @return bool|ResultWrapper
+        * @since 1.18
+        */
+       public function dropTable( $tableName, $fName = __METHOD__ ) {
+               if ( !$this->tableExists( $tableName, $fName ) ) {
+                       return false;
+               }
+
+               // parent function incorrectly appends CASCADE, which we don't want
+               $sql = "DROP TABLE " . $this->tableName( $tableName );
+
+               return $this->query( $sql, $fName );
+       }
+
        /**
         * Called in the installer and updater.
         * Probably doesn't need to be called anywhere else in the codebase.
@@ -1370,6 +1400,16 @@ class DatabaseMssql extends Database {
        public function scrollableCursor( $value = null ) {
                return wfSetVar( $this->mScrollableCursor, $value );
        }
+
+       /**
+        * Called in the installer and updater.
+        * Probably doesn't need to be called anywhere else in the codebase.
+        * @param array|null $value
+        * @return array|null
+        */
+       public function ignoreErrors( array $value = null ) {
+               return wfSetVar( $this->mIgnoreErrors, $value );
+       }
 } // end DatabaseMssql class
 
 /**
index f39596b..b78793f 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Database
  */
 
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Services\DestructibleService;
 use Psr\Log\LoggerInterface;
 use MediaWiki\Logger\LoggerFactory;
 
@@ -28,7 +30,8 @@ use MediaWiki\Logger\LoggerFactory;
  * An interface for generating database load balancers
  * @ingroup Database
  */
-abstract class LBFactory {
+abstract class LBFactory implements DestructibleService {
+
        /** @var ChronologyProtector */
        protected $chronProt;
 
@@ -38,9 +41,6 @@ abstract class LBFactory {
        /** @var LoggerInterface */
        protected $logger;
 
-       /** @var LBFactory */
-       private static $instance;
-
        /** @var string|bool Reason all LBs are read-only or false if not */
        protected $readOnlyReason = false;
 
@@ -60,38 +60,40 @@ abstract class LBFactory {
                $this->logger = LoggerFactory::getInstance( 'DBTransaction' );
        }
 
+       /**
+        * Disables all load balancers. All connections are closed, and any attempt to
+        * open a new connection will result in a DBAccessError.
+        * @see LoadBalancer::disable()
+        */
+       public function destroy() {
+               $this->shutdown();
+               $this->forEachLBCallMethod( 'disable' );
+       }
+
        /**
         * Disables all access to the load balancer, will cause all database access
         * to throw a DBAccessError
         */
        public static function disableBackend() {
-               global $wgLBFactoryConf;
-               self::$instance = new LBFactoryFake( $wgLBFactoryConf );
+               MediaWikiServices::disableStorageBackend();
        }
 
        /**
         * Get an LBFactory instance
         *
+        * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
+        *
         * @return LBFactory
         */
        public static function singleton() {
-               global $wgLBFactoryConf;
-
-               if ( is_null( self::$instance ) ) {
-                       $class = self::getLBFactoryClass( $wgLBFactoryConf );
-                       $config = $wgLBFactoryConf;
-                       if ( !isset( $config['readOnlyReason'] ) ) {
-                               $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
-                       }
-                       self::$instance = new $class( $config );
-               }
-
-               return self::$instance;
+               return MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
        }
 
        /**
         * Returns the LBFactory class to use and the load balancer configuration.
         *
+        * @todo instead of this, use a ServiceContainer for managing the different implementations.
+        *
         * @param array $config (e.g. $wgLBFactoryConf)
         * @return string Class name
         */
@@ -120,23 +122,11 @@ abstract class LBFactory {
 
        /**
         * Shut down, close connections and destroy the cached instance.
-        */
-       public static function destroyInstance() {
-               if ( self::$instance ) {
-                       self::$instance->shutdown();
-                       self::$instance->forEachLBCallMethod( 'closeAll' );
-                       self::$instance = null;
-               }
-       }
-
-       /**
-        * Set the instance to be the given object
         *
-        * @param LBFactory $instance
+        * @deprecated since 1.27, use LBFactory::destroy()
         */
-       public static function setInstance( $instance ) {
-               self::destroyInstance();
-               self::$instance = $instance;
+       public static function destroyInstance() {
+               self::singleton()->destroy();
        }
 
        /**
@@ -470,7 +460,7 @@ abstract class LBFactory {
 class DBAccessError extends MWException {
        public function __construct() {
                parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
-                       "This is not allowed." );
+                       "This is not allowed, because database access has been disabled." );
        }
 }
 
index 741999c..5578099 100644 (file)
@@ -77,6 +77,11 @@ class LoadBalancer {
        /** @var integer Max time to wait for a slave to catch up (e.g. ChronologyProtector) */
        const POS_WAIT_TIMEOUT = 10;
 
+       /**
+        * @var boolean
+        */
+       private $disabled = false;
+
        /**
         * @param array $params Array with keys:
         *  - servers : Required. Array of server info structures.
@@ -666,6 +671,8 @@ class LoadBalancer {
         * On error, returns false, and the connection which caused the
         * error will be available via $this->mErrorConnection.
         *
+        * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+        *
         * @param int $i Server index
         * @param string|bool $wiki Wiki ID, or false for the current wiki
         * @return DatabaseBase|bool Returns false on errors
@@ -716,6 +723,8 @@ class LoadBalancer {
         * On error, returns false, and the connection which caused the
         * error will be available via $this->mErrorConnection.
         *
+        * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+        *
         * @param int $i Server index
         * @param string $wiki Wiki ID to open
         * @return DatabaseBase
@@ -803,6 +812,10 @@ class LoadBalancer {
         * @return DatabaseBase
         */
        protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+               if ( $this->disabled ) {
+                       throw new DBAccessError();
+               }
+
                if ( !is_array( $server ) ) {
                        throw new MWException( 'You must update your load-balancing configuration. ' .
                                'See DefaultSettings.php entry for $wgDBservers.' );
@@ -976,6 +989,17 @@ class LoadBalancer {
                return false;
        }
 
+       /**
+        * Disable this load balancer. All connections are closed, and any attempt to
+        * open a new connection will result in a DBAccessError.
+        *
+        * @since 1.27
+        */
+       public function disable() {
+               $this->closeAll();
+               $this->disabled = true;
+       }
+
        /**
         * Close all open connections
         */
index 13d25a8..d90ef8a 100644 (file)
@@ -75,6 +75,15 @@ class MWDebug {
                self::$enabled = true;
        }
 
+       /**
+        * Disable the debugger.
+        *
+        * @since 1.28
+        */
+       public static function deinit() {
+               self::$enabled = false;
+       }
+
        /**
         * Add ResourceLoader modules to the OutputPage object if debugging is
         * enabled.
index 65ff9f3..4ce9e62 100644 (file)
@@ -112,12 +112,18 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
                // Reliably broadcast the purge to all edge nodes
                $relayer = MediaWikiServices::getInstance()->getEventRelayerGroup()
                                        ->getRelayer( 'cdn-url-purges' );
-               $relayer->notify(
+               $ts = microtime( true );
+               $relayer->notifyMulti(
                        'cdn-url-purges',
-                       [
-                               'urls' => array_values( $urlArr ), // JSON array
-                               'timestamp' => microtime( true )
-                       ]
+                       array_map(
+                               function ( $url ) use ( $ts ) {
+                                       return [
+                                               'url' => $url,
+                                               'timestamp' => $ts,
+                                       ];
+                               },
+                               $urlArr
+                       )
                );
 
                // Send lossy UDP broadcasting if enabled
index 249b207..e3b7570 100644 (file)
@@ -144,7 +144,12 @@ class DeferredUpdates {
                        }
 
                        // Delegate DataUpdate execution to the DataUpdate class
-                       DataUpdate::runUpdates( $dataUpdates, $mode );
+                       try {
+                               DataUpdate::runUpdates( $dataUpdates, $mode );
+                       } catch ( Exception $e ) {
+                               // Let the other updates occur if these had to rollback
+                               MWExceptionHandler::logException( $e );
+                       }
                        // Execute the non-DataUpdate tasks
                        foreach ( $otherUpdates as $update ) {
                                try {
index 1770639..65a8c0e 100644 (file)
@@ -49,6 +49,9 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
        public function doUpdate() {
                # Page may already be deleted, so don't just getId()
                $id = $this->pageId;
+               // Make sure all links update threads see the changes of each other.
+               // This handles the case when updates have to batched into several COMMITs.
+               $scopedLock = LinksUpdate::acquirePageLock( $this->mDb, $id );
 
                # Delete restrictions for it
                $this->mDb->delete( 'page_restrictions', [ 'pr_page' => $id ], __METHOD__ );
@@ -101,6 +104,11 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
                                $this->mDb->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
                        }
                }
+
+               $this->mDb->onTransactionIdle( function() use ( &$scopedLock ) {
+                       // Release the lock *after* the final COMMIT for correctness
+                       ScopedCallback::consume( $scopedLock );
+               } );
        }
 
        public function getAsJobSpecification() {
index 4215ed0..1f7f3b0 100644 (file)
  */
 
 /**
- * See docs/deferred.txt
+ * Class the manages updates of *_link tables as well as similar extension-managed tables
+ *
+ * @note: LinksUpdate is managed by DeferredUpdates::execute(). Do not run this in a transaction.
  *
- * @todo document (e.g. one-sentence top-level class description).
+ * See docs/deferred.txt
  */
 class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
        // @todo make members protected, but make sure extensions don't break
@@ -55,6 +57,9 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
        /** @var array Map of language codes to titles */
        public $mInterlangs;
 
+       /** @var array 2-D map of (prefix => DBK => 1) */
+       public $mInterwikis;
+
        /** @var array Map of arbitrary name to value */
        public $mProperties;
 
@@ -79,6 +84,8 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
         */
        private $user;
 
+       const BATCH_SIZE = 500; // try to keep typical updates in a single transaction
+
        /**
         * Constructor
         *
@@ -88,7 +95,8 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
         * @throws MWException
         */
        function __construct( Title $title, ParserOutput $parserOutput, $recursive = true ) {
-               parent::__construct( false ); // no implicit transaction
+               // Implicit transactions are disabled as they interfere with batching
+               parent::__construct( false );
 
                $this->mTitle = $title;
                $this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE );
@@ -138,16 +146,46 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
 
        /**
         * Update link tables with outgoing links from an updated article
+        *
+        * @note: this is managed by DeferredUpdates::execute(). Do not run this in a transaction.
         */
        public function doUpdate() {
+               // Make sure all links update threads see the changes of each other.
+               // This handles the case when updates have to batched into several COMMITs.
+               $scopedLock = self::acquirePageLock( $this->mDb, $this->mId );
+
                Hooks::run( 'LinksUpdate', [ &$this ] );
                $this->doIncrementalUpdate();
 
-               $this->mDb->onTransactionIdle( function() {
+               $this->mDb->onTransactionIdle( function() use ( &$scopedLock ) {
                        Hooks::run( 'LinksUpdateComplete', [ &$this ] );
+                       // Release the lock *after* the final COMMIT for correctness
+                       ScopedCallback::consume( $scopedLock );
                } );
        }
 
+       /**
+        * Acquire a lock for performing link table updates for a page on a DB
+        *
+        * @param IDatabase $dbw
+        * @param integer $pageId
+        * @return ScopedCallback|null Returns null on failure
+        * @throws RuntimeException
+        * @since 1.27
+        */
+       public static function acquirePageLock( IDatabase $dbw, $pageId ) {
+               $scopedLock = $dbw->getScopedLockAndFlush(
+                       "LinksUpdate:pageid:$pageId",
+                       __METHOD__,
+                       15
+               );
+               if ( !$scopedLock ) {
+                       throw new RuntimeException( "Could not acquire lock on page #$pageId." );
+               }
+
+               return $scopedLock;
+       }
+
        protected function doIncrementalUpdate() {
                # Page links
                $existing = $this->getExistingLinks();
@@ -157,7 +195,6 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
 
                # Image links
                $existing = $this->getExistingImages();
-
                $imageDeletes = $this->getImageDeletions( $existing );
                $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes,
                        $this->getImageInsertions( $existing ) );
@@ -188,9 +225,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
 
                # Category links
                $existing = $this->getExistingCategories();
-
                $categoryDeletes = $this->getCategoryDeletions( $existing );
-
                $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes,
                        $this->getCategoryInsertions( $existing ) );
 
@@ -202,9 +237,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
 
                # Page properties
                $existing = $this->getExistingProperties();
-
                $propertiesDeletes = $this->getPropertyDeletions( $existing );
-
                $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes,
                        $this->getPropertyInsertions( $existing ) );
 
@@ -304,44 +337,69 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
         * @param array $deletions
         * @param array $insertions Rows to insert
         */
-       function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
-               if ( $table == 'page_props' ) {
+       private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
+               if ( $table === 'page_props' ) {
                        $fromField = 'pp_page';
                } else {
                        $fromField = "{$prefix}_from";
                }
-               $where = [ $fromField => $this->mId ];
-               if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) {
-                       if ( $table == 'iwlinks' ) {
-                               $baseKey = 'iwl_prefix';
-                       } else {
-                               $baseKey = "{$prefix}_namespace";
+
+               $deleteWheres = []; // list of WHERE clause arrays for each DB delete() call
+               if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) {
+                       $baseKey =  ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
+
+                       $curBatchSize = 0;
+                       $curDeletionBatch = [];
+                       $deletionBatches = [];
+                       foreach ( $deletions as $ns => $dbKeys ) {
+                               foreach ( $dbKeys as $dbKey => $unused ) {
+                                       $curDeletionBatch[$ns][$dbKey] = 1;
+                                       if ( ++$curBatchSize >= self::BATCH_SIZE ) {
+                                               $deletionBatches[] = $curDeletionBatch;
+                                               $curDeletionBatch = [];
+                                               $curBatchSize = 0;
+                                       }
+                               }
                        }
-                       $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" );
-                       if ( $clause ) {
-                               $where[] = $clause;
-                       } else {
-                               $where = false;
+                       if ( $curDeletionBatch ) {
+                               $deletionBatches[] = $curDeletionBatch;
+                       }
+
+                       foreach ( $deletionBatches as $deletionBatch ) {
+                               $deleteWheres[] = [
+                                       $fromField => $this->mId,
+                                       $this->mDb->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
+                               ];
                        }
                } else {
-                       if ( $table == 'langlinks' ) {
+                       if ( $table === 'langlinks' ) {
                                $toField = 'll_lang';
-                       } elseif ( $table == 'page_props' ) {
+                       } elseif ( $table === 'page_props' ) {
                                $toField = 'pp_propname';
                        } else {
                                $toField = $prefix . '_to';
                        }
-                       if ( count( $deletions ) ) {
-                               $where[$toField] = array_keys( $deletions );
-                       } else {
-                               $where = false;
+
+                       $deletionBatches = array_chunk( array_keys( $deletions ), self::BATCH_SIZE );
+                       foreach ( $deletionBatches as $deletionBatch ) {
+                               $deleteWheres[] = [ $fromField => $this->mId, $toField => $deletionBatch ];
                        }
                }
-               if ( $where ) {
-                       $this->mDb->delete( $table, $where, __METHOD__ );
+
+               foreach ( $deleteWheres as $deleteWhere ) {
+                       $this->mDb->delete( $table, $deleteWhere, __METHOD__ );
+                       $this->mDb->commit( __METHOD__, 'flush' );
+                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+               }
+
+               $insertBatches = array_chunk( $insertions, self::BATCH_SIZE );
+               foreach ( $insertBatches as $insertBatch ) {
+                       $this->mDb->insert( $table, $insertBatch, __METHOD__, 'IGNORE' );
+                       $this->mDb->commit( __METHOD__, 'flush' );
+                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
                }
+
                if ( count( $insertions ) ) {
-                       $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
                        Hooks::run( 'LinksUpdateAfterInsert', [ $this, $table, $insertions ] );
                }
        }
index e5e082f..dbb32e6 100644 (file)
@@ -191,244 +191,6 @@ class DiffOpChange extends DiffOp {
        }
 }
 
-/**
- * Class used internally by Diff to actually compute the diffs.
- *
- * The algorithm used here is mostly lifted from the perl module
- * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
- *     http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
- *
- * More ideas are taken from:
- *     http://www.ics.uci.edu/~eppstein/161/960229.html
- *
- * Some ideas (and a bit of code) are from analyze.c, from GNU
- * diffutils-2.7, which can be found at:
- *     ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
- *
- * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
- * are my own.
- *
- * Line length limits for robustness added by Tim Starling, 2005-08-31
- * Alternative implementation added by Guy Van den Broeck, 2008-07-30
- *
- * @author Geoffrey T. Dairiki, Tim Starling, Guy Van den Broeck
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffEngine {
-       const MAX_XREF_LENGTH = 10000;
-
-       protected $xchanged, $ychanged;
-
-       protected $xv = [], $yv = [];
-       protected $xind = [], $yind = [];
-
-       protected $seq = [], $in_seq = [];
-
-       protected $lcs = 0;
-
-       /**
-        * @param string[] $from_lines
-        * @param string[] $to_lines
-        *
-        * @return DiffOp[]
-        */
-       public function diff( $from_lines, $to_lines ) {
-
-               // Diff and store locally
-               $this->diffLocal( $from_lines, $to_lines );
-
-               // Merge edits when possible
-               $this->shiftBoundaries( $from_lines, $this->xchanged, $this->ychanged );
-               $this->shiftBoundaries( $to_lines, $this->ychanged, $this->xchanged );
-
-               // Compute the edit operations.
-               $n_from = count( $from_lines );
-               $n_to = count( $to_lines );
-
-               $edits = [];
-               $xi = $yi = 0;
-               while ( $xi < $n_from || $yi < $n_to ) {
-                       assert( $yi < $n_to || $this->xchanged[$xi] );
-                       assert( $xi < $n_from || $this->ychanged[$yi] );
-
-                       // Skip matching "snake".
-                       $copy = [];
-                       while ( $xi < $n_from && $yi < $n_to
-                               && !$this->xchanged[$xi] && !$this->ychanged[$yi]
-                       ) {
-                               $copy[] = $from_lines[$xi++];
-                               ++$yi;
-                       }
-                       if ( $copy ) {
-                               $edits[] = new DiffOpCopy( $copy );
-                       }
-
-                       // Find deletes & adds.
-                       $delete = [];
-                       while ( $xi < $n_from && $this->xchanged[$xi] ) {
-                               $delete[] = $from_lines[$xi++];
-                       }
-
-                       $add = [];
-                       while ( $yi < $n_to && $this->ychanged[$yi] ) {
-                               $add[] = $to_lines[$yi++];
-                       }
-
-                       if ( $delete && $add ) {
-                               $edits[] = new DiffOpChange( $delete, $add );
-                       } elseif ( $delete ) {
-                               $edits[] = new DiffOpDelete( $delete );
-                       } elseif ( $add ) {
-                               $edits[] = new DiffOpAdd( $add );
-                       }
-               }
-
-               return $edits;
-       }
-
-       /**
-        * @param string[] $from_lines
-        * @param string[] $to_lines
-        */
-       private function diffLocal( $from_lines, $to_lines ) {
-               $wikidiff3 = new WikiDiff3();
-               $wikidiff3->diff( $from_lines, $to_lines );
-               $this->xchanged = $wikidiff3->removed;
-               $this->ychanged = $wikidiff3->added;
-       }
-
-       /**
-        * Adjust inserts/deletes of identical lines to join changes
-        * as much as possible.
-        *
-        * We do something when a run of changed lines include a
-        * line at one end and has an excluded, identical line at the other.
-        * We are free to choose which identical line is included.
-        * `compareseq' usually chooses the one at the beginning,
-        * but usually it is cleaner to consider the following identical line
-        * to be the "change".
-        *
-        * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
-        */
-       private function shiftBoundaries( $lines, &$changed, $other_changed ) {
-               $i = 0;
-               $j = 0;
-
-               assert( count( $lines ) == count( $changed ) );
-               $len = count( $lines );
-               $other_len = count( $other_changed );
-
-               while ( 1 ) {
-                       /*
-                        * Scan forwards to find beginning of another run of changes.
-                        * Also keep track of the corresponding point in the other file.
-                        *
-                        * Throughout this code, $i and $j are adjusted together so that
-                        * the first $i elements of $changed and the first $j elements
-                        * of $other_changed both contain the same number of zeros
-                        * (unchanged lines).
-                        * Furthermore, $j is always kept so that $j == $other_len or
-                        * $other_changed[$j] == false.
-                        */
-                       while ( $j < $other_len && $other_changed[$j] ) {
-                               $j++;
-                       }
-
-                       while ( $i < $len && !$changed[$i] ) {
-                               assert( $j < $other_len && ! $other_changed[$j] );
-                               $i++;
-                               $j++;
-                               while ( $j < $other_len && $other_changed[$j] ) {
-                                       $j++;
-                               }
-                       }
-
-                       if ( $i == $len ) {
-                               break;
-                       }
-
-                       $start = $i;
-
-                       // Find the end of this run of changes.
-                       while ( ++$i < $len && $changed[$i] ) {
-                               continue;
-                       }
-
-                       do {
-                               /*
-                                * Record the length of this run of changes, so that
-                                * we can later determine whether the run has grown.
-                                */
-                               $runlength = $i - $start;
-
-                               /*
-                                * Move the changed region back, so long as the
-                                * previous unchanged line matches the last changed one.
-                                * This merges with previous changed regions.
-                                */
-                               while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) {
-                                       $changed[--$start] = 1;
-                                       $changed[--$i] = false;
-                                       while ( $start > 0 && $changed[$start - 1] ) {
-                                               $start--;
-                                       }
-                                       assert( $j > 0 );
-                                       while ( $other_changed[--$j] ) {
-                                               continue;
-                                       }
-                                       assert( $j >= 0 && !$other_changed[$j] );
-                               }
-
-                               /*
-                                * Set CORRESPONDING to the end of the changed run, at the last
-                                * point where it corresponds to a changed run in the other file.
-                                * CORRESPONDING == LEN means no such point has been found.
-                                */
-                               $corresponding = $j < $other_len ? $i : $len;
-
-                               /*
-                                * Move the changed region forward, so long as the
-                                * first changed line matches the following unchanged one.
-                                * This merges with following changed regions.
-                                * Do this second, so that if there are no merges,
-                                * the changed region is moved forward as far as possible.
-                                */
-                               while ( $i < $len && $lines[$start] == $lines[$i] ) {
-                                       $changed[$start++] = false;
-                                       $changed[$i++] = 1;
-                                       while ( $i < $len && $changed[$i] ) {
-                                               $i++;
-                                       }
-
-                                       assert( $j < $other_len && ! $other_changed[$j] );
-                                       $j++;
-                                       if ( $j < $other_len && $other_changed[$j] ) {
-                                               $corresponding = $i;
-                                               while ( $j < $other_len && $other_changed[$j] ) {
-                                                       $j++;
-                                               }
-                                       }
-                               }
-                       } while ( $runlength != $i - $start );
-
-                       /*
-                        * If possible, move the fully-merged run of changes
-                        * back to a corresponding run in the other file.
-                        */
-                       while ( $corresponding < $i ) {
-                               $changed[--$start] = 1;
-                               $changed[--$i] = 0;
-                               assert( $j > 0 );
-                               while ( $other_changed[--$j] ) {
-                                       continue;
-                               }
-                               assert( $j >= 0 && !$other_changed[$j] );
-                       }
-               }
-       }
-}
-
 /**
  * Class representing a 'diff' between two sequences of strings.
  * @todo document
@@ -559,236 +321,7 @@ class Diff {
 }
 
 /**
- * @todo document, bad name.
- * @private
- * @ingroup DifferenceEngine
- */
-class MappedDiff extends Diff {
-       /**
-        * Constructor.
-        *
-        * Computes diff between sequences of strings.
-        *
-        * This can be used to compute things like
-        * case-insensitve diffs, or diffs which ignore
-        * changes in white-space.
-        *
-        * @param string[] $from_lines An array of strings.
-        *   Typically these are lines from a file.
-        * @param string[] $to_lines An array of strings.
-        * @param string[] $mapped_from_lines This array should
-        *   have the same size number of elements as $from_lines.
-        *   The elements in $mapped_from_lines and
-        *   $mapped_to_lines are what is actually compared
-        *   when computing the diff.
-        * @param string[] $mapped_to_lines This array should
-        *   have the same number of elements as $to_lines.
-        */
-       public function __construct( $from_lines, $to_lines,
-               $mapped_from_lines, $mapped_to_lines ) {
-
-               assert( count( $from_lines ) == count( $mapped_from_lines ) );
-               assert( count( $to_lines ) == count( $mapped_to_lines ) );
-
-               parent::__construct( $mapped_from_lines, $mapped_to_lines );
-
-               $xi = $yi = 0;
-               $editCount = count( $this->edits );
-               for ( $i = 0; $i < $editCount; $i++ ) {
-                       $orig = &$this->edits[$i]->orig;
-                       if ( is_array( $orig ) ) {
-                               $orig = array_slice( $from_lines, $xi, count( $orig ) );
-                               $xi += count( $orig );
-                       }
-
-                       $closing = &$this->edits[$i]->closing;
-                       if ( is_array( $closing ) ) {
-                               $closing = array_slice( $to_lines, $yi, count( $closing ) );
-                               $yi += count( $closing );
-                       }
-               }
-       }
-}
-
-/**
- * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
+ * @deprecated Alias for WordAccumulator, to be soon removed
  */
-
-/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class HWLDFWordAccumulator {
-       public $insClass = ' class="diffchange diffchange-inline"';
-       public $delClass = ' class="diffchange diffchange-inline"';
-
-       private $lines = [];
-       private $line = '';
-       private $group = '';
-       private $tag = '';
-
-       /**
-        * @param string $new_tag
-        */
-       private function flushGroup( $new_tag ) {
-               if ( $this->group !== '' ) {
-                       if ( $this->tag == 'ins' ) {
-                               $this->line .= "<ins{$this->insClass}>" .
-                                       htmlspecialchars( $this->group ) . '</ins>';
-                       } elseif ( $this->tag == 'del' ) {
-                               $this->line .= "<del{$this->delClass}>" .
-                                       htmlspecialchars( $this->group ) . '</del>';
-                       } else {
-                               $this->line .= htmlspecialchars( $this->group );
-                       }
-               }
-               $this->group = '';
-               $this->tag = $new_tag;
-       }
-
-       /**
-        * @param string $new_tag
-        */
-       private function flushLine( $new_tag ) {
-               $this->flushGroup( $new_tag );
-               if ( $this->line != '' ) {
-                       array_push( $this->lines, $this->line );
-               } else {
-                       # make empty lines visible by inserting an NBSP
-                       array_push( $this->lines, '&#160;' );
-               }
-               $this->line = '';
-       }
-
-       /**
-        * @param string[] $words
-        * @param string $tag
-        */
-       public function addWords( $words, $tag = '' ) {
-               if ( $tag != $this->tag ) {
-                       $this->flushGroup( $tag );
-               }
-
-               foreach ( $words as $word ) {
-                       // new-line should only come as first char of word.
-                       if ( $word == '' ) {
-                               continue;
-                       }
-                       if ( $word[0] == "\n" ) {
-                               $this->flushLine( $tag );
-                               $word = substr( $word, 1 );
-                       }
-                       assert( !strstr( $word, "\n" ) );
-                       $this->group .= $word;
-               }
-       }
-
-       /**
-        * @return string[]
-        */
-       public function getLines() {
-               $this->flushLine( '~done' );
-
-               return $this->lines;
-       }
-}
-
-/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class WordLevelDiff extends MappedDiff {
-       const MAX_LINE_LENGTH = 10000;
-
-       /**
-        * @param string[] $orig_lines
-        * @param string[] $closing_lines
-        */
-       public function __construct( $orig_lines, $closing_lines ) {
-
-               list( $orig_words, $orig_stripped ) = $this->split( $orig_lines );
-               list( $closing_words, $closing_stripped ) = $this->split( $closing_lines );
-
-               parent::__construct( $orig_words, $closing_words,
-                       $orig_stripped, $closing_stripped );
-       }
-
-       /**
-        * @param string[] $lines
-        *
-        * @return array[]
-        */
-       private function split( $lines ) {
-
-               $words = [];
-               $stripped = [];
-               $first = true;
-               foreach ( $lines as $line ) {
-                       # If the line is too long, just pretend the entire line is one big word
-                       # This prevents resource exhaustion problems
-                       if ( $first ) {
-                               $first = false;
-                       } else {
-                               $words[] = "\n";
-                               $stripped[] = "\n";
-                       }
-                       if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
-                               $words[] = $line;
-                               $stripped[] = $line;
-                       } else {
-                               $m = [];
-                               if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
-                                       $line, $m )
-                               ) {
-                                       foreach ( $m[0] as $word ) {
-                                               $words[] = $word;
-                                       }
-                                       foreach ( $m[1] as $stripped_word ) {
-                                               $stripped[] = $stripped_word;
-                                       }
-                               }
-                       }
-               }
-
-               return [ $words, $stripped ];
-       }
-
-       /**
-        * @return string[]
-        */
-       public function orig() {
-               $orig = new HWLDFWordAccumulator;
-
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit->type == 'copy' ) {
-                               $orig->addWords( $edit->orig );
-                       } elseif ( $edit->orig ) {
-                               $orig->addWords( $edit->orig, 'del' );
-                       }
-               }
-               $lines = $orig->getLines();
-
-               return $lines;
-       }
-
-       /**
-        * @return string[]
-        */
-       public function closing() {
-               $closing = new HWLDFWordAccumulator;
-
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit->type == 'copy' ) {
-                               $closing->addWords( $edit->closing );
-                       } elseif ( $edit->closing ) {
-                               $closing->addWords( $edit->closing, 'ins' );
-                       }
-               }
-               $lines = $closing->getLines();
-
-               return $lines;
-       }
-
+class HWLDFWordAccumulator extends MediaWiki\Diff\WordAccumulator {
 }
diff --git a/includes/diff/DiffEngine.php b/includes/diff/DiffEngine.php
new file mode 100644 (file)
index 0000000..1853b86
--- /dev/null
@@ -0,0 +1,825 @@
+<?php
+/**
+ * New version of the difference engine
+ *
+ * Copyright © 2008 Guy Van den Broeck <guy@guyvdb.eu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * This diff implementation is mainly lifted from the LCS algorithm of the Eclipse project which
+ * in turn is based on Myers' "An O(ND) difference algorithm and its variations"
+ * (http://citeseer.ist.psu.edu/myers86ond.html) with range compression (see Wu et al.'s
+ * "An O(NP) Sequence Comparison Algorithm").
+ *
+ * This implementation supports an upper bound on the execution time.
+ *
+ * Some ideas (and a bit of code) are from analyze.c, from GNU
+ * diffutils-2.7, which can be found at:
+ *     ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
+ *
+ * Complexity: O((M + N)D) worst case time, O(M + N + D^2) expected time, O(M + N) space
+ *
+ * @author Guy Van den Broeck, Geoffrey T. Dairiki, Tim Starling
+ * @ingroup DifferenceEngine
+ */
+class DiffEngine {
+
+       // Input variables
+       private $from;
+       private $to;
+       private $m;
+       private $n;
+
+       private $tooLong;
+       private $powLimit;
+
+       // State variables
+       private $maxDifferences;
+       private $lcsLengthCorrectedForHeuristic = false;
+
+       // Output variables
+       public $length;
+       public $removed;
+       public $added;
+       public $heuristicUsed;
+
+       function __construct( $tooLong = 2000000, $powLimit = 1.45 ) {
+               $this->tooLong = $tooLong;
+               $this->powLimit = $powLimit;
+       }
+
+       /**
+        * Performs diff
+        *
+        * @param string[] $from_lines
+        * @param string[] $to_lines
+        *
+        * @return DiffOp[]
+        */
+       public function diff( $from_lines, $to_lines ) {
+
+               // Diff and store locally
+               $this->diffInternal( $from_lines, $to_lines );
+
+               // Merge edits when possible
+               $this->shiftBoundaries( $from_lines, $this->removed, $this->added );
+               $this->shiftBoundaries( $to_lines, $this->added, $this->removed );
+
+               // Compute the edit operations.
+               $n_from = count( $from_lines );
+               $n_to = count( $to_lines );
+
+               $edits = [];
+               $xi = $yi = 0;
+               while ( $xi < $n_from || $yi < $n_to ) {
+                       assert( $yi < $n_to || $this->removed[$xi] );
+                       assert( $xi < $n_from || $this->added[$yi] );
+
+                       // Skip matching "snake".
+                       $copy = [];
+                       while ( $xi < $n_from && $yi < $n_to
+                                       && !$this->removed[$xi] && !$this->added[$yi]
+                       ) {
+                               $copy[] = $from_lines[$xi++];
+                               ++$yi;
+                       }
+                       if ( $copy ) {
+                               $edits[] = new DiffOpCopy( $copy );
+                       }
+
+                       // Find deletes & adds.
+                       $delete = [];
+                       while ( $xi < $n_from && $this->removed[$xi] ) {
+                               $delete[] = $from_lines[$xi++];
+                       }
+
+                       $add = [];
+                       while ( $yi < $n_to && $this->added[$yi] ) {
+                               $add[] = $to_lines[$yi++];
+                       }
+
+                       if ( $delete && $add ) {
+                               $edits[] = new DiffOpChange( $delete, $add );
+                       } elseif ( $delete ) {
+                               $edits[] = new DiffOpDelete( $delete );
+                       } elseif ( $add ) {
+                               $edits[] = new DiffOpAdd( $add );
+                       }
+               }
+
+               return $edits;
+       }
+
+       /**
+        * Adjust inserts/deletes of identical lines to join changes
+        * as much as possible.
+        *
+        * We do something when a run of changed lines include a
+        * line at one end and has an excluded, identical line at the other.
+        * We are free to choose which identical line is included.
+        * `compareseq' usually chooses the one at the beginning,
+        * but usually it is cleaner to consider the following identical line
+        * to be the "change".
+        *
+        * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
+        *
+        * @param string[] $lines
+        * @param string[] $changed
+        * @param string[] $other_changed
+        */
+       private function shiftBoundaries( array $lines, array &$changed, array $other_changed ) {
+               $i = 0;
+               $j = 0;
+
+               assert( count( $lines ) == count( $changed ) );
+               $len = count( $lines );
+               $other_len = count( $other_changed );
+
+               while ( 1 ) {
+                       /*
+                        * Scan forwards to find beginning of another run of changes.
+                        * Also keep track of the corresponding point in the other file.
+                        *
+                        * Throughout this code, $i and $j are adjusted together so that
+                        * the first $i elements of $changed and the first $j elements
+                        * of $other_changed both contain the same number of zeros
+                        * (unchanged lines).
+                        * Furthermore, $j is always kept so that $j == $other_len or
+                        * $other_changed[$j] == false.
+                        */
+                       while ( $j < $other_len && $other_changed[$j] ) {
+                               $j++;
+                       }
+
+                       while ( $i < $len && !$changed[$i] ) {
+                               assert( $j < $other_len && ! $other_changed[$j] );
+                               $i++;
+                               $j++;
+                               while ( $j < $other_len && $other_changed[$j] ) {
+                                       $j++;
+                               }
+                       }
+
+                       if ( $i == $len ) {
+                               break;
+                       }
+
+                       $start = $i;
+
+                       // Find the end of this run of changes.
+                       while ( ++$i < $len && $changed[$i] ) {
+                               continue;
+                       }
+
+                       do {
+                               /*
+                                * Record the length of this run of changes, so that
+                                * we can later determine whether the run has grown.
+                                */
+                               $runlength = $i - $start;
+
+                               /*
+                                * Move the changed region back, so long as the
+                                * previous unchanged line matches the last changed one.
+                                * This merges with previous changed regions.
+                                */
+                               while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) {
+                                       $changed[--$start] = 1;
+                                       $changed[--$i] = false;
+                                       while ( $start > 0 && $changed[$start - 1] ) {
+                                               $start--;
+                                       }
+                                       assert( $j > 0 );
+                                       while ( $other_changed[--$j] ) {
+                                               continue;
+                                       }
+                                       assert( $j >= 0 && !$other_changed[$j] );
+                               }
+
+                               /*
+                                * Set CORRESPONDING to the end of the changed run, at the last
+                                * point where it corresponds to a changed run in the other file.
+                                * CORRESPONDING == LEN means no such point has been found.
+                                */
+                               $corresponding = $j < $other_len ? $i : $len;
+
+                               /*
+                                * Move the changed region forward, so long as the
+                                * first changed line matches the following unchanged one.
+                                * This merges with following changed regions.
+                                * Do this second, so that if there are no merges,
+                                * the changed region is moved forward as far as possible.
+                                */
+                               while ( $i < $len && $lines[$start] == $lines[$i] ) {
+                                       $changed[$start++] = false;
+                                       $changed[$i++] = 1;
+                                       while ( $i < $len && $changed[$i] ) {
+                                               $i++;
+                                       }
+
+                                       assert( $j < $other_len && ! $other_changed[$j] );
+                                       $j++;
+                                       if ( $j < $other_len && $other_changed[$j] ) {
+                                               $corresponding = $i;
+                                               while ( $j < $other_len && $other_changed[$j] ) {
+                                                       $j++;
+                                               }
+                                       }
+                               }
+                       } while ( $runlength != $i - $start );
+
+                       /*
+                        * If possible, move the fully-merged run of changes
+                        * back to a corresponding run in the other file.
+                        */
+                       while ( $corresponding < $i ) {
+                               $changed[--$start] = 1;
+                               $changed[--$i] = 0;
+                               assert( $j > 0 );
+                               while ( $other_changed[--$j] ) {
+                                       continue;
+                               }
+                               assert( $j >= 0 && !$other_changed[$j] );
+                       }
+               }
+       }
+
+       /**
+        * @param string[] $from
+        * @param string[] $to
+        */
+       protected function diffInternal( array $from, array $to ) {
+               // remember initial lengths
+               $m = count( $from );
+               $n = count( $to );
+
+               $this->heuristicUsed = false;
+
+               // output
+               $removed = $m > 0 ? array_fill( 0, $m, true ) : [];
+               $added = $n > 0 ? array_fill( 0, $n, true ) : [];
+
+               // reduce the complexity for the next step (intentionally done twice)
+               // remove common tokens at the start
+               $i = 0;
+               while ( $i < $m && $i < $n && $from[$i] === $to[$i] ) {
+                       $removed[$i] = $added[$i] = false;
+                       unset( $from[$i], $to[$i] );
+                       ++$i;
+               }
+
+               // remove common tokens at the end
+               $j = 1;
+               while ( $i + $j <= $m && $i + $j <= $n && $from[$m - $j] === $to[$n - $j] ) {
+                       $removed[$m - $j] = $added[$n - $j] = false;
+                       unset( $from[$m - $j], $to[$n - $j] );
+                       ++$j;
+               }
+
+               $this->from = $newFromIndex = $this->to = $newToIndex = [];
+
+               // remove tokens not in both sequences
+               $shared = [];
+               foreach ( $from as $key ) {
+                       $shared[$key] = false;
+               }
+
+               foreach ( $to as $index => &$el ) {
+                       if ( array_key_exists( $el, $shared ) ) {
+                               // keep it
+                               $this->to[] = $el;
+                               $shared[$el] = true;
+                               $newToIndex[] = $index;
+                       }
+               }
+               foreach ( $from as $index => &$el ) {
+                       if ( $shared[$el] ) {
+                               // keep it
+                               $this->from[] = $el;
+                               $newFromIndex[] = $index;
+                       }
+               }
+
+               unset( $shared, $from, $to );
+
+               $this->m = count( $this->from );
+               $this->n = count( $this->to );
+
+               $this->removed = $this->m > 0 ? array_fill( 0, $this->m, true ) : [];
+               $this->added = $this->n > 0 ? array_fill( 0, $this->n, true ) : [];
+
+               if ( $this->m == 0 || $this->n == 0 ) {
+                       $this->length = 0;
+               } else {
+                       $this->maxDifferences = ceil( ( $this->m + $this->n ) / 2.0 );
+                       if ( $this->m * $this->n > $this->tooLong ) {
+                               // limit complexity to D^POW_LIMIT for long sequences
+                               $this->maxDifferences = floor( pow( $this->maxDifferences, $this->powLimit - 1.0 ) );
+                               wfDebug( "Limiting max number of differences to $this->maxDifferences\n" );
+                       }
+
+                       /*
+                        * The common prefixes and suffixes are always part of some LCS, include
+                        * them now to reduce our search space
+                        */
+                       $max = min( $this->m, $this->n );
+                       for ( $forwardBound = 0; $forwardBound < $max
+                               && $this->from[$forwardBound] === $this->to[$forwardBound];
+                               ++$forwardBound
+                       ) {
+                               $this->removed[$forwardBound] = $this->added[$forwardBound] = false;
+                       }
+
+                       $backBoundL1 = $this->m - 1;
+                       $backBoundL2 = $this->n - 1;
+
+                       while ( $backBoundL1 >= $forwardBound && $backBoundL2 >= $forwardBound
+                               && $this->from[$backBoundL1] === $this->to[$backBoundL2]
+                       ) {
+                               $this->removed[$backBoundL1--] = $this->added[$backBoundL2--] = false;
+                       }
+
+                       $temp = array_fill( 0, $this->m + $this->n + 1, 0 );
+                       $V = [ $temp, $temp ];
+                       $snake = [ 0, 0, 0 ];
+
+                       $this->length = $forwardBound + $this->m - $backBoundL1 - 1
+                               + $this->lcs_rec(
+                                       $forwardBound,
+                                       $backBoundL1,
+                                       $forwardBound,
+                                       $backBoundL2,
+                                       $V,
+                                       $snake
+                       );
+               }
+
+               $this->m = $m;
+               $this->n = $n;
+
+               $this->length += $i + $j - 1;
+
+               foreach ( $this->removed as $key => &$removed_elem ) {
+                       if ( !$removed_elem ) {
+                               $removed[$newFromIndex[$key]] = false;
+                       }
+               }
+               foreach ( $this->added as $key => &$added_elem ) {
+                       if ( !$added_elem ) {
+                               $added[$newToIndex[$key]] = false;
+                       }
+               }
+               $this->removed = $removed;
+               $this->added = $added;
+       }
+
+       function diff_range( $from_lines, $to_lines ) {
+               // Diff and store locally
+               $this->diff( $from_lines, $to_lines );
+               unset( $from_lines, $to_lines );
+
+               $ranges = [];
+               $xi = $yi = 0;
+               while ( $xi < $this->m || $yi < $this->n ) {
+                       // Matching "snake".
+                       while ( $xi < $this->m && $yi < $this->n
+                               && !$this->removed[$xi]
+                               && !$this->added[$yi]
+                       ) {
+                               ++$xi;
+                               ++$yi;
+                       }
+                       // Find deletes & adds.
+                       $xstart = $xi;
+                       while ( $xi < $this->m && $this->removed[$xi] ) {
+                               ++$xi;
+                       }
+
+                       $ystart = $yi;
+                       while ( $yi < $this->n && $this->added[$yi] ) {
+                               ++$yi;
+                       }
+
+                       if ( $xi > $xstart || $yi > $ystart ) {
+                               $ranges[] = new RangeDifference( $xstart, $xi, $ystart, $yi );
+                       }
+               }
+
+               return $ranges;
+       }
+
+       private function lcs_rec( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) {
+               // check that both sequences are non-empty
+               if ( $bottoml1 > $topl1 || $bottoml2 > $topl2 ) {
+                       return 0;
+               }
+
+               $d = $this->find_middle_snake( $bottoml1, $topl1, $bottoml2,
+                       $topl2, $V, $snake );
+
+               // need to store these so we don't lose them when they're
+               // overwritten by the recursion
+               $len = $snake[2];
+               $startx = $snake[0];
+               $starty = $snake[1];
+
+               // the middle snake is part of the LCS, store it
+               for ( $i = 0; $i < $len; ++$i ) {
+                       $this->removed[$startx + $i] = $this->added[$starty + $i] = false;
+               }
+
+               if ( $d > 1 ) {
+                       return $len
+                       + $this->lcs_rec( $bottoml1, $startx - 1, $bottoml2,
+                               $starty - 1, $V, $snake )
+                       + $this->lcs_rec( $startx + $len, $topl1, $starty + $len,
+                               $topl2, $V, $snake );
+               } elseif ( $d == 1 ) {
+                       /*
+                        * In this case the sequences differ by exactly 1 line. We have
+                        * already saved all the lines after the difference in the for loop
+                        * above, now we need to save all the lines before the difference.
+                        */
+                       $max = min( $startx - $bottoml1, $starty - $bottoml2 );
+                       for ( $i = 0; $i < $max; ++$i ) {
+                               $this->removed[$bottoml1 + $i] =
+                                       $this->added[$bottoml2 + $i] = false;
+                       }
+
+                       return $max + $len;
+               }
+
+               return $len;
+       }
+
+       private function find_middle_snake( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) {
+               $from = &$this->from;
+               $to = &$this->to;
+               $V0 = &$V[0];
+               $V1 = &$V[1];
+               $snake0 = &$snake[0];
+               $snake1 = &$snake[1];
+               $snake2 = &$snake[2];
+               $bottoml1_min_1 = $bottoml1 - 1;
+               $bottoml2_min_1 = $bottoml2 - 1;
+               $N = $topl1 - $bottoml1_min_1;
+               $M = $topl2 - $bottoml2_min_1;
+               $delta = $N - $M;
+               $maxabsx = $N + $bottoml1;
+               $maxabsy = $M + $bottoml2;
+               $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) );
+
+               // value_to_add_forward: a 0 or 1 that we add to the start
+               // offset to make it odd/even
+               if ( ( $M & 1 ) == 1 ) {
+                       $value_to_add_forward = 1;
+               } else {
+                       $value_to_add_forward = 0;
+               }
+
+               if ( ( $N & 1 ) == 1 ) {
+                       $value_to_add_backward = 1;
+               } else {
+                       $value_to_add_backward = 0;
+               }
+
+               $start_forward = -$M;
+               $end_forward = $N;
+               $start_backward = -$N;
+               $end_backward = $M;
+
+               $limit_min_1 = $limit - 1;
+               $limit_plus_1 = $limit + 1;
+
+               $V0[$limit_plus_1] = 0;
+               $V1[$limit_min_1] = $N;
+               $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) );
+
+               if ( ( $delta & 1 ) == 1 ) {
+                       for ( $d = 0; $d <= $limit; ++$d ) {
+                               $start_diag = max( $value_to_add_forward + $start_forward, -$d );
+                               $end_diag = min( $end_forward, $d );
+                               $value_to_add_forward = 1 - $value_to_add_forward;
+
+                               // compute forward furthest reaching paths
+                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+                                       if ( $k == -$d || ( $k < $d
+                                                       && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] )
+                                       ) {
+                                               $x = $V0[$limit_plus_1 + $k];
+                                       } else {
+                                               $x = $V0[$limit_min_1 + $k] + 1;
+                                       }
+
+                                       $absx = $snake0 = $x + $bottoml1;
+                                       $absy = $snake1 = $x - $k + $bottoml2;
+
+                                       while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) {
+                                               ++$absx;
+                                               ++$absy;
+                                       }
+                                       $x = $absx - $bottoml1;
+
+                                       $snake2 = $absx - $snake0;
+                                       $V0[$limit + $k] = $x;
+                                       if ( $k >= $delta - $d + 1 && $k <= $delta + $d - 1
+                                               && $x >= $V1[$limit + $k - $delta]
+                                       ) {
+                                               return 2 * $d - 1;
+                                       }
+
+                                       // check to see if we can cut down the diagonal range
+                                       if ( $x >= $N && $end_forward > $k - 1 ) {
+                                               $end_forward = $k - 1;
+                                       } elseif ( $absy - $bottoml2 >= $M ) {
+                                               $start_forward = $k + 1;
+                                               $value_to_add_forward = 0;
+                                       }
+                               }
+
+                               $start_diag = max( $value_to_add_backward + $start_backward, -$d );
+                               $end_diag = min( $end_backward, $d );
+                               $value_to_add_backward = 1 - $value_to_add_backward;
+
+                               // compute backward furthest reaching paths
+                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+                                       if ( $k == $d
+                                               || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] )
+                                       ) {
+                                               $x = $V1[$limit_min_1 + $k];
+                                       } else {
+                                               $x = $V1[$limit_plus_1 + $k] - 1;
+                                       }
+
+                                       $y = $x - $k - $delta;
+
+                                       $snake2 = 0;
+                                       while ( $x > 0 && $y > 0
+                                               && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1]
+                                       ) {
+                                               --$x;
+                                               --$y;
+                                               ++$snake2;
+                                       }
+                                       $V1[$limit + $k] = $x;
+
+                                       // check to see if we can cut down our diagonal range
+                                       if ( $x <= 0 ) {
+                                               $start_backward = $k + 1;
+                                               $value_to_add_backward = 0;
+                                       } elseif ( $y <= 0 && $end_backward > $k - 1 ) {
+                                               $end_backward = $k - 1;
+                                       }
+                               }
+                       }
+               } else {
+                       for ( $d = 0; $d <= $limit; ++$d ) {
+                               $start_diag = max( $value_to_add_forward + $start_forward, -$d );
+                               $end_diag = min( $end_forward, $d );
+                               $value_to_add_forward = 1 - $value_to_add_forward;
+
+                               // compute forward furthest reaching paths
+                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+                                       if ( $k == -$d
+                                               || ( $k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] )
+                                       ) {
+                                               $x = $V0[$limit_plus_1 + $k];
+                                       } else {
+                                               $x = $V0[$limit_min_1 + $k] + 1;
+                                       }
+
+                                       $absx = $snake0 = $x + $bottoml1;
+                                       $absy = $snake1 = $x - $k + $bottoml2;
+
+                                       while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) {
+                                               ++$absx;
+                                               ++$absy;
+                                       }
+                                       $x = $absx - $bottoml1;
+                                       $snake2 = $absx - $snake0;
+                                       $V0[$limit + $k] = $x;
+
+                                       // check to see if we can cut down the diagonal range
+                                       if ( $x >= $N && $end_forward > $k - 1 ) {
+                                               $end_forward = $k - 1;
+                                       } elseif ( $absy - $bottoml2 >= $M ) {
+                                               $start_forward = $k + 1;
+                                               $value_to_add_forward = 0;
+                                       }
+                               }
+
+                               $start_diag = max( $value_to_add_backward + $start_backward, -$d );
+                               $end_diag = min( $end_backward, $d );
+                               $value_to_add_backward = 1 - $value_to_add_backward;
+
+                               // compute backward furthest reaching paths
+                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+                                       if ( $k == $d
+                                               || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] )
+                                       ) {
+                                               $x = $V1[$limit_min_1 + $k];
+                                       } else {
+                                               $x = $V1[$limit_plus_1 + $k] - 1;
+                                       }
+
+                                       $y = $x - $k - $delta;
+
+                                       $snake2 = 0;
+                                       while ( $x > 0 && $y > 0
+                                               && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1]
+                                       ) {
+                                               --$x;
+                                               --$y;
+                                               ++$snake2;
+                                       }
+                                       $V1[$limit + $k] = $x;
+
+                                       if ( $k >= -$delta - $d && $k <= $d - $delta
+                                               && $x <= $V0[$limit + $k + $delta]
+                                       ) {
+                                               $snake0 = $bottoml1 + $x;
+                                               $snake1 = $bottoml2 + $y;
+
+                                               return 2 * $d;
+                                       }
+
+                                       // check to see if we can cut down our diagonal range
+                                       if ( $x <= 0 ) {
+                                               $start_backward = $k + 1;
+                                               $value_to_add_backward = 0;
+                                       } elseif ( $y <= 0 && $end_backward > $k - 1 ) {
+                                               $end_backward = $k - 1;
+                                       }
+                               }
+                       }
+               }
+               /*
+                * computing the true LCS is too expensive, instead find the diagonal
+                * with the most progress and pretend a midle snake of length 0 occurs
+                * there.
+                */
+
+               $most_progress = self::findMostProgress( $M, $N, $limit, $V );
+
+               $snake0 = $bottoml1 + $most_progress[0];
+               $snake1 = $bottoml2 + $most_progress[1];
+               $snake2 = 0;
+               wfDebug( "Computing the LCS is too expensive. Using a heuristic.\n" );
+               $this->heuristicUsed = true;
+
+               return 5; /*
+               * HACK: since we didn't really finish the LCS computation
+               * we don't really know the length of the SES. We don't do
+               * anything with the result anyway, unless it's <=1. We know
+               * for a fact SES > 1 so 5 is as good a number as any to
+               * return here
+               */
+       }
+
+       private static function findMostProgress( $M, $N, $limit, $V ) {
+               $delta = $N - $M;
+
+               if ( ( $M & 1 ) == ( $limit & 1 ) ) {
+                       $forward_start_diag = max( -$M, -$limit );
+               } else {
+                       $forward_start_diag = max( 1 - $M, -$limit );
+               }
+
+               $forward_end_diag = min( $N, $limit );
+
+               if ( ( $N & 1 ) == ( $limit & 1 ) ) {
+                       $backward_start_diag = max( -$N, -$limit );
+               } else {
+                       $backward_start_diag = max( 1 - $N, -$limit );
+               }
+
+               $backward_end_diag = -min( $M, $limit );
+
+               $temp = [ 0, 0, 0 ];
+
+               $max_progress = array_fill( 0, ceil( max( $forward_end_diag - $forward_start_diag,
+                               $backward_end_diag - $backward_start_diag ) / 2 ), $temp );
+               $num_progress = 0; // the 1st entry is current, it is initialized
+               // with 0s
+
+               // first search the forward diagonals
+               for ( $k = $forward_start_diag; $k <= $forward_end_diag; $k += 2 ) {
+                       $x = $V[0][$limit + $k];
+                       $y = $x - $k;
+                       if ( $x > $N || $y > $M ) {
+                               continue;
+                       }
+
+                       $progress = $x + $y;
+                       if ( $progress > $max_progress[0][2] ) {
+                               $num_progress = 0;
+                               $max_progress[0][0] = $x;
+                               $max_progress[0][1] = $y;
+                               $max_progress[0][2] = $progress;
+                       } elseif ( $progress == $max_progress[0][2] ) {
+                               ++$num_progress;
+                               $max_progress[$num_progress][0] = $x;
+                               $max_progress[$num_progress][1] = $y;
+                               $max_progress[$num_progress][2] = $progress;
+                       }
+               }
+
+               $max_progress_forward = true; // initially the maximum
+               // progress is in the forward
+               // direction
+
+               // now search the backward diagonals
+               for ( $k = $backward_start_diag; $k <= $backward_end_diag; $k += 2 ) {
+                       $x = $V[1][$limit + $k];
+                       $y = $x - $k - $delta;
+                       if ( $x < 0 || $y < 0 ) {
+                               continue;
+                       }
+
+                       $progress = $N - $x + $M - $y;
+                       if ( $progress > $max_progress[0][2] ) {
+                               $num_progress = 0;
+                               $max_progress_forward = false;
+                               $max_progress[0][0] = $x;
+                               $max_progress[0][1] = $y;
+                               $max_progress[0][2] = $progress;
+                       } elseif ( $progress == $max_progress[0][2] && !$max_progress_forward ) {
+                               ++$num_progress;
+                               $max_progress[$num_progress][0] = $x;
+                               $max_progress[$num_progress][1] = $y;
+                               $max_progress[$num_progress][2] = $progress;
+                       }
+               }
+
+               // return the middle diagonal with maximal progress.
+               return $max_progress[(int)floor( $num_progress / 2 )];
+       }
+
+       /**
+        * @return mixed
+        */
+       public function getLcsLength() {
+               if ( $this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic ) {
+                       $this->lcsLengthCorrectedForHeuristic = true;
+                       $this->length = $this->m - array_sum( $this->added );
+               }
+
+               return $this->length;
+       }
+
+}
+
+/**
+ * Alternative representation of a set of changes, by the index
+ * ranges that are changed.
+ *
+ * @ingroup DifferenceEngine
+ */
+class RangeDifference {
+
+       /** @var int */
+       public $leftstart;
+
+       /** @var int */
+       public $leftend;
+
+       /** @var int */
+       public $leftlength;
+
+       /** @var int */
+       public $rightstart;
+
+       /** @var int */
+       public $rightend;
+
+       /** @var int */
+       public $rightlength;
+
+       function __construct( $leftstart, $leftend, $rightstart, $rightend ) {
+               $this->leftstart = $leftstart;
+               $this->leftend = $leftend;
+               $this->leftlength = $leftend - $leftstart;
+               $this->rightstart = $rightstart;
+               $this->rightend = $rightend;
+               $this->rightlength = $rightend - $rightstart;
+       }
+
+}
diff --git a/includes/diff/WikiDiff3.php b/includes/diff/WikiDiff3.php
deleted file mode 100644 (file)
index f35e30f..0000000
+++ /dev/null
@@ -1,621 +0,0 @@
-<?php
-/**
- * New version of the difference engine
- *
- * Copyright © 2008 Guy Van den Broeck <guy@guyvdb.eu>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup DifferenceEngine
- */
-
-/**
- * This diff implementation is mainly lifted from the LCS algorithm of the Eclipse project which
- * in turn is based on Myers' "An O(ND) difference algorithm and its variations"
- * (http://citeseer.ist.psu.edu/myers86ond.html) with range compression (see Wu et al.'s
- * "An O(NP) Sequence Comparison Algorithm").
- *
- * This implementation supports an upper bound on the execution time.
- *
- * Complexity: O((M + N)D) worst case time, O(M + N + D^2) expected time, O(M + N) space
- *
- * @author Guy Van den Broeck
- * @ingroup DifferenceEngine
- */
-class WikiDiff3 {
-
-       // Input variables
-       private $from;
-       private $to;
-       private $m;
-       private $n;
-
-       private $tooLong;
-       private $powLimit;
-
-       // State variables
-       private $maxDifferences;
-       private $lcsLengthCorrectedForHeuristic = false;
-
-       // Output variables
-       public $length;
-       public $removed;
-       public $added;
-       public $heuristicUsed;
-
-       function __construct( $tooLong = 2000000, $powLimit = 1.45 ) {
-               $this->tooLong = $tooLong;
-               $this->powLimit = $powLimit;
-       }
-
-       public function diff( /*array*/ $from, /*array*/ $to ) {
-               // remember initial lengths
-               $m = count( $from );
-               $n = count( $to );
-
-               $this->heuristicUsed = false;
-
-               // output
-               $removed = $m > 0 ? array_fill( 0, $m, true ) : [];
-               $added = $n > 0 ? array_fill( 0, $n, true ) : [];
-
-               // reduce the complexity for the next step (intentionally done twice)
-               // remove common tokens at the start
-               $i = 0;
-               while ( $i < $m && $i < $n && $from[$i] === $to[$i] ) {
-                       $removed[$i] = $added[$i] = false;
-                       unset( $from[$i], $to[$i] );
-                       ++$i;
-               }
-
-               // remove common tokens at the end
-               $j = 1;
-               while ( $i + $j <= $m && $i + $j <= $n && $from[$m - $j] === $to[$n - $j] ) {
-                       $removed[$m - $j] = $added[$n - $j] = false;
-                       unset( $from[$m - $j], $to[$n - $j] );
-                       ++$j;
-               }
-
-               $this->from = $newFromIndex = $this->to = $newToIndex = [];
-
-               // remove tokens not in both sequences
-               $shared = [];
-               foreach ( $from as $key ) {
-                       $shared[$key] = false;
-               }
-
-               foreach ( $to as $index => &$el ) {
-                       if ( array_key_exists( $el, $shared ) ) {
-                               // keep it
-                               $this->to[] = $el;
-                               $shared[$el] = true;
-                               $newToIndex[] = $index;
-                       }
-               }
-               foreach ( $from as $index => &$el ) {
-                       if ( $shared[$el] ) {
-                               // keep it
-                               $this->from[] = $el;
-                               $newFromIndex[] = $index;
-                       }
-               }
-
-               unset( $shared, $from, $to );
-
-               $this->m = count( $this->from );
-               $this->n = count( $this->to );
-
-               $this->removed = $this->m > 0 ? array_fill( 0, $this->m, true ) : [];
-               $this->added = $this->n > 0 ? array_fill( 0, $this->n, true ) : [];
-
-               if ( $this->m == 0 || $this->n == 0 ) {
-                       $this->length = 0;
-               } else {
-                       $this->maxDifferences = ceil( ( $this->m + $this->n ) / 2.0 );
-                       if ( $this->m * $this->n > $this->tooLong ) {
-                               // limit complexity to D^POW_LIMIT for long sequences
-                               $this->maxDifferences = floor( pow( $this->maxDifferences, $this->powLimit - 1.0 ) );
-                               wfDebug( "Limiting max number of differences to $this->maxDifferences\n" );
-                       }
-
-                       /*
-                        * The common prefixes and suffixes are always part of some LCS, include
-                        * them now to reduce our search space
-                        */
-                       $max = min( $this->m, $this->n );
-                       for ( $forwardBound = 0; $forwardBound < $max
-                               && $this->from[$forwardBound] === $this->to[$forwardBound];
-                               ++$forwardBound
-                       ) {
-                               $this->removed[$forwardBound] = $this->added[$forwardBound] = false;
-                       }
-
-                       $backBoundL1 = $this->m - 1;
-                       $backBoundL2 = $this->n - 1;
-
-                       while ( $backBoundL1 >= $forwardBound && $backBoundL2 >= $forwardBound
-                               && $this->from[$backBoundL1] === $this->to[$backBoundL2]
-                       ) {
-                               $this->removed[$backBoundL1--] = $this->added[$backBoundL2--] = false;
-                       }
-
-                       $temp = array_fill( 0, $this->m + $this->n + 1, 0 );
-                       $V = [ $temp, $temp ];
-                       $snake = [ 0, 0, 0 ];
-
-                       $this->length = $forwardBound + $this->m - $backBoundL1 - 1
-                               + $this->lcs_rec(
-                                       $forwardBound,
-                                       $backBoundL1,
-                                       $forwardBound,
-                                       $backBoundL2,
-                                       $V,
-                                       $snake
-                       );
-               }
-
-               $this->m = $m;
-               $this->n = $n;
-
-               $this->length += $i + $j - 1;
-
-               foreach ( $this->removed as $key => &$removed_elem ) {
-                       if ( !$removed_elem ) {
-                               $removed[$newFromIndex[$key]] = false;
-                       }
-               }
-               foreach ( $this->added as $key => &$added_elem ) {
-                       if ( !$added_elem ) {
-                               $added[$newToIndex[$key]] = false;
-                       }
-               }
-               $this->removed = $removed;
-               $this->added = $added;
-       }
-
-       function diff_range( $from_lines, $to_lines ) {
-               // Diff and store locally
-               $this->diff( $from_lines, $to_lines );
-               unset( $from_lines, $to_lines );
-
-               $ranges = [];
-               $xi = $yi = 0;
-               while ( $xi < $this->m || $yi < $this->n ) {
-                       // Matching "snake".
-                       while ( $xi < $this->m && $yi < $this->n
-                               && !$this->removed[$xi]
-                               && !$this->added[$yi]
-                       ) {
-                               ++$xi;
-                               ++$yi;
-                       }
-                       // Find deletes & adds.
-                       $xstart = $xi;
-                       while ( $xi < $this->m && $this->removed[$xi] ) {
-                               ++$xi;
-                       }
-
-                       $ystart = $yi;
-                       while ( $yi < $this->n && $this->added[$yi] ) {
-                               ++$yi;
-                       }
-
-                       if ( $xi > $xstart || $yi > $ystart ) {
-                               $ranges[] = new RangeDifference( $xstart, $xi, $ystart, $yi );
-                       }
-               }
-
-               return $ranges;
-       }
-
-       private function lcs_rec( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) {
-               // check that both sequences are non-empty
-               if ( $bottoml1 > $topl1 || $bottoml2 > $topl2 ) {
-                       return 0;
-               }
-
-               $d = $this->find_middle_snake( $bottoml1, $topl1, $bottoml2,
-                       $topl2, $V, $snake );
-
-               // need to store these so we don't lose them when they're
-               // overwritten by the recursion
-               $len = $snake[2];
-               $startx = $snake[0];
-               $starty = $snake[1];
-
-               // the middle snake is part of the LCS, store it
-               for ( $i = 0; $i < $len; ++$i ) {
-                       $this->removed[$startx + $i] = $this->added[$starty + $i] = false;
-               }
-
-               if ( $d > 1 ) {
-                       return $len
-                       + $this->lcs_rec( $bottoml1, $startx - 1, $bottoml2,
-                               $starty - 1, $V, $snake )
-                       + $this->lcs_rec( $startx + $len, $topl1, $starty + $len,
-                               $topl2, $V, $snake );
-               } elseif ( $d == 1 ) {
-                       /*
-                        * In this case the sequences differ by exactly 1 line. We have
-                        * already saved all the lines after the difference in the for loop
-                        * above, now we need to save all the lines before the difference.
-                        */
-                       $max = min( $startx - $bottoml1, $starty - $bottoml2 );
-                       for ( $i = 0; $i < $max; ++$i ) {
-                               $this->removed[$bottoml1 + $i] =
-                                       $this->added[$bottoml2 + $i] = false;
-                       }
-
-                       return $max + $len;
-               }
-
-               return $len;
-       }
-
-       private function find_middle_snake( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) {
-               $from = &$this->from;
-               $to = &$this->to;
-               $V0 = &$V[0];
-               $V1 = &$V[1];
-               $snake0 = &$snake[0];
-               $snake1 = &$snake[1];
-               $snake2 = &$snake[2];
-               $bottoml1_min_1 = $bottoml1 - 1;
-               $bottoml2_min_1 = $bottoml2 - 1;
-               $N = $topl1 - $bottoml1_min_1;
-               $M = $topl2 - $bottoml2_min_1;
-               $delta = $N - $M;
-               $maxabsx = $N + $bottoml1;
-               $maxabsy = $M + $bottoml2;
-               $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) );
-
-               // value_to_add_forward: a 0 or 1 that we add to the start
-               // offset to make it odd/even
-               if ( ( $M & 1 ) == 1 ) {
-                       $value_to_add_forward = 1;
-               } else {
-                       $value_to_add_forward = 0;
-               }
-
-               if ( ( $N & 1 ) == 1 ) {
-                       $value_to_add_backward = 1;
-               } else {
-                       $value_to_add_backward = 0;
-               }
-
-               $start_forward = -$M;
-               $end_forward = $N;
-               $start_backward = -$N;
-               $end_backward = $M;
-
-               $limit_min_1 = $limit - 1;
-               $limit_plus_1 = $limit + 1;
-
-               $V0[$limit_plus_1] = 0;
-               $V1[$limit_min_1] = $N;
-               $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) );
-
-               if ( ( $delta & 1 ) == 1 ) {
-                       for ( $d = 0; $d <= $limit; ++$d ) {
-                               $start_diag = max( $value_to_add_forward + $start_forward, -$d );
-                               $end_diag = min( $end_forward, $d );
-                               $value_to_add_forward = 1 - $value_to_add_forward;
-
-                               // compute forward furthest reaching paths
-                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
-                                       if ( $k == -$d || ( $k < $d
-                                                       && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] )
-                                       ) {
-                                               $x = $V0[$limit_plus_1 + $k];
-                                       } else {
-                                               $x = $V0[$limit_min_1 + $k] + 1;
-                                       }
-
-                                       $absx = $snake0 = $x + $bottoml1;
-                                       $absy = $snake1 = $x - $k + $bottoml2;
-
-                                       while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) {
-                                               ++$absx;
-                                               ++$absy;
-                                       }
-                                       $x = $absx - $bottoml1;
-
-                                       $snake2 = $absx - $snake0;
-                                       $V0[$limit + $k] = $x;
-                                       if ( $k >= $delta - $d + 1 && $k <= $delta + $d - 1
-                                               && $x >= $V1[$limit + $k - $delta]
-                                       ) {
-                                               return 2 * $d - 1;
-                                       }
-
-                                       // check to see if we can cut down the diagonal range
-                                       if ( $x >= $N && $end_forward > $k - 1 ) {
-                                               $end_forward = $k - 1;
-                                       } elseif ( $absy - $bottoml2 >= $M ) {
-                                               $start_forward = $k + 1;
-                                               $value_to_add_forward = 0;
-                                       }
-                               }
-
-                               $start_diag = max( $value_to_add_backward + $start_backward, -$d );
-                               $end_diag = min( $end_backward, $d );
-                               $value_to_add_backward = 1 - $value_to_add_backward;
-
-                               // compute backward furthest reaching paths
-                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
-                                       if ( $k == $d
-                                               || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] )
-                                       ) {
-                                               $x = $V1[$limit_min_1 + $k];
-                                       } else {
-                                               $x = $V1[$limit_plus_1 + $k] - 1;
-                                       }
-
-                                       $y = $x - $k - $delta;
-
-                                       $snake2 = 0;
-                                       while ( $x > 0 && $y > 0
-                                               && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1]
-                                       ) {
-                                               --$x;
-                                               --$y;
-                                               ++$snake2;
-                                       }
-                                       $V1[$limit + $k] = $x;
-
-                                       // check to see if we can cut down our diagonal range
-                                       if ( $x <= 0 ) {
-                                               $start_backward = $k + 1;
-                                               $value_to_add_backward = 0;
-                                       } elseif ( $y <= 0 && $end_backward > $k - 1 ) {
-                                               $end_backward = $k - 1;
-                                       }
-                               }
-                       }
-               } else {
-                       for ( $d = 0; $d <= $limit; ++$d ) {
-                               $start_diag = max( $value_to_add_forward + $start_forward, -$d );
-                               $end_diag = min( $end_forward, $d );
-                               $value_to_add_forward = 1 - $value_to_add_forward;
-
-                               // compute forward furthest reaching paths
-                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
-                                       if ( $k == -$d
-                                               || ( $k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] )
-                                       ) {
-                                               $x = $V0[$limit_plus_1 + $k];
-                                       } else {
-                                               $x = $V0[$limit_min_1 + $k] + 1;
-                                       }
-
-                                       $absx = $snake0 = $x + $bottoml1;
-                                       $absy = $snake1 = $x - $k + $bottoml2;
-
-                                       while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) {
-                                               ++$absx;
-                                               ++$absy;
-                                       }
-                                       $x = $absx - $bottoml1;
-                                       $snake2 = $absx - $snake0;
-                                       $V0[$limit + $k] = $x;
-
-                                       // check to see if we can cut down the diagonal range
-                                       if ( $x >= $N && $end_forward > $k - 1 ) {
-                                               $end_forward = $k - 1;
-                                       } elseif ( $absy - $bottoml2 >= $M ) {
-                                               $start_forward = $k + 1;
-                                               $value_to_add_forward = 0;
-                                       }
-                               }
-
-                               $start_diag = max( $value_to_add_backward + $start_backward, -$d );
-                               $end_diag = min( $end_backward, $d );
-                               $value_to_add_backward = 1 - $value_to_add_backward;
-
-                               // compute backward furthest reaching paths
-                               for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
-                                       if ( $k == $d
-                                               || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] )
-                                       ) {
-                                               $x = $V1[$limit_min_1 + $k];
-                                       } else {
-                                               $x = $V1[$limit_plus_1 + $k] - 1;
-                                       }
-
-                                       $y = $x - $k - $delta;
-
-                                       $snake2 = 0;
-                                       while ( $x > 0 && $y > 0
-                                               && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1]
-                                       ) {
-                                               --$x;
-                                               --$y;
-                                               ++$snake2;
-                                       }
-                                       $V1[$limit + $k] = $x;
-
-                                       if ( $k >= -$delta - $d && $k <= $d - $delta
-                                               && $x <= $V0[$limit + $k + $delta]
-                                       ) {
-                                               $snake0 = $bottoml1 + $x;
-                                               $snake1 = $bottoml2 + $y;
-
-                                               return 2 * $d;
-                                       }
-
-                                       // check to see if we can cut down our diagonal range
-                                       if ( $x <= 0 ) {
-                                               $start_backward = $k + 1;
-                                               $value_to_add_backward = 0;
-                                       } elseif ( $y <= 0 && $end_backward > $k - 1 ) {
-                                               $end_backward = $k - 1;
-                                       }
-                               }
-                       }
-               }
-               /*
-                * computing the true LCS is too expensive, instead find the diagonal
-                * with the most progress and pretend a midle snake of length 0 occurs
-                * there.
-                */
-
-               $most_progress = self::findMostProgress( $M, $N, $limit, $V );
-
-               $snake0 = $bottoml1 + $most_progress[0];
-               $snake1 = $bottoml2 + $most_progress[1];
-               $snake2 = 0;
-               wfDebug( "Computing the LCS is too expensive. Using a heuristic.\n" );
-               $this->heuristicUsed = true;
-
-               return 5; /*
-               * HACK: since we didn't really finish the LCS computation
-               * we don't really know the length of the SES. We don't do
-               * anything with the result anyway, unless it's <=1. We know
-               * for a fact SES > 1 so 5 is as good a number as any to
-               * return here
-               */
-       }
-
-       private static function findMostProgress( $M, $N, $limit, $V ) {
-               $delta = $N - $M;
-
-               if ( ( $M & 1 ) == ( $limit & 1 ) ) {
-                       $forward_start_diag = max( -$M, -$limit );
-               } else {
-                       $forward_start_diag = max( 1 - $M, -$limit );
-               }
-
-               $forward_end_diag = min( $N, $limit );
-
-               if ( ( $N & 1 ) == ( $limit & 1 ) ) {
-                       $backward_start_diag = max( -$N, -$limit );
-               } else {
-                       $backward_start_diag = max( 1 - $N, -$limit );
-               }
-
-               $backward_end_diag = -min( $M, $limit );
-
-               $temp = [ 0, 0, 0 ];
-
-               $max_progress = array_fill( 0, ceil( max( $forward_end_diag - $forward_start_diag,
-                               $backward_end_diag - $backward_start_diag ) / 2 ), $temp );
-               $num_progress = 0; // the 1st entry is current, it is initialized
-               // with 0s
-
-               // first search the forward diagonals
-               for ( $k = $forward_start_diag; $k <= $forward_end_diag; $k += 2 ) {
-                       $x = $V[0][$limit + $k];
-                       $y = $x - $k;
-                       if ( $x > $N || $y > $M ) {
-                               continue;
-                       }
-
-                       $progress = $x + $y;
-                       if ( $progress > $max_progress[0][2] ) {
-                               $num_progress = 0;
-                               $max_progress[0][0] = $x;
-                               $max_progress[0][1] = $y;
-                               $max_progress[0][2] = $progress;
-                       } elseif ( $progress == $max_progress[0][2] ) {
-                               ++$num_progress;
-                               $max_progress[$num_progress][0] = $x;
-                               $max_progress[$num_progress][1] = $y;
-                               $max_progress[$num_progress][2] = $progress;
-                       }
-               }
-
-               $max_progress_forward = true; // initially the maximum
-               // progress is in the forward
-               // direction
-
-               // now search the backward diagonals
-               for ( $k = $backward_start_diag; $k <= $backward_end_diag; $k += 2 ) {
-                       $x = $V[1][$limit + $k];
-                       $y = $x - $k - $delta;
-                       if ( $x < 0 || $y < 0 ) {
-                               continue;
-                       }
-
-                       $progress = $N - $x + $M - $y;
-                       if ( $progress > $max_progress[0][2] ) {
-                               $num_progress = 0;
-                               $max_progress_forward = false;
-                               $max_progress[0][0] = $x;
-                               $max_progress[0][1] = $y;
-                               $max_progress[0][2] = $progress;
-                       } elseif ( $progress == $max_progress[0][2] && !$max_progress_forward ) {
-                               ++$num_progress;
-                               $max_progress[$num_progress][0] = $x;
-                               $max_progress[$num_progress][1] = $y;
-                               $max_progress[$num_progress][2] = $progress;
-                       }
-               }
-
-               // return the middle diagonal with maximal progress.
-               return $max_progress[(int)floor( $num_progress / 2 )];
-       }
-
-       /**
-        * @return mixed
-        */
-       public function getLcsLength() {
-               if ( $this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic ) {
-                       $this->lcsLengthCorrectedForHeuristic = true;
-                       $this->length = $this->m - array_sum( $this->added );
-               }
-
-               return $this->length;
-       }
-
-}
-
-/**
- * Alternative representation of a set of changes, by the index
- * ranges that are changed.
- *
- * @ingroup DifferenceEngine
- */
-class RangeDifference {
-
-       /** @var int */
-       public $leftstart;
-
-       /** @var int */
-       public $leftend;
-
-       /** @var int */
-       public $leftlength;
-
-       /** @var int */
-       public $rightstart;
-
-       /** @var int */
-       public $rightend;
-
-       /** @var int */
-       public $rightlength;
-
-       function __construct( $leftstart, $leftend, $rightstart, $rightend ) {
-               $this->leftstart = $leftstart;
-               $this->leftend = $leftend;
-               $this->leftlength = $leftend - $leftstart;
-               $this->rightstart = $rightstart;
-               $this->rightend = $rightend;
-               $this->rightlength = $rightend - $rightstart;
-       }
-
-}
diff --git a/includes/diff/WordAccumulator.php b/includes/diff/WordAccumulator.php
new file mode 100644 (file)
index 0000000..a26775f
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ * @defgroup DifferenceEngine DifferenceEngine
+ */
+
+namespace MediaWiki\Diff;
+
+/**
+ * Stores, escapes and formats the results of word-level diff
+ *
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class WordAccumulator {
+       public $insClass = ' class="diffchange diffchange-inline"';
+       public $delClass = ' class="diffchange diffchange-inline"';
+
+       private $lines = [];
+       private $line = '';
+       private $group = '';
+       private $tag = '';
+
+       /**
+        * @param string $new_tag
+        */
+       private function flushGroup( $new_tag ) {
+               if ( $this->group !== '' ) {
+                       if ( $this->tag == 'ins' ) {
+                               $this->line .= "<ins{$this->insClass}>" .
+                                                          htmlspecialchars( $this->group ) . '</ins>';
+                       } elseif ( $this->tag == 'del' ) {
+                               $this->line .= "<del{$this->delClass}>" .
+                                                          htmlspecialchars( $this->group ) . '</del>';
+                       } else {
+                               $this->line .= htmlspecialchars( $this->group );
+                       }
+               }
+               $this->group = '';
+               $this->tag = $new_tag;
+       }
+
+       /**
+        * @param string $new_tag
+        */
+       private function flushLine( $new_tag ) {
+               $this->flushGroup( $new_tag );
+               if ( $this->line != '' ) {
+                       array_push( $this->lines, $this->line );
+               } else {
+                       # make empty lines visible by inserting an NBSP
+                       array_push( $this->lines, '&#160;' );
+               }
+               $this->line = '';
+       }
+
+       /**
+        * @param string[] $words
+        * @param string $tag
+        */
+       public function addWords( $words, $tag = '' ) {
+               if ( $tag != $this->tag ) {
+                       $this->flushGroup( $tag );
+               }
+
+               foreach ( $words as $word ) {
+                       // new-line should only come as first char of word.
+                       if ( $word == '' ) {
+                               continue;
+                       }
+                       if ( $word[0] == "\n" ) {
+                               $this->flushLine( $tag );
+                               $word = substr( $word, 1 );
+                       }
+                       assert( !strstr( $word, "\n" ) );
+                       $this->group .= $word;
+               }
+       }
+
+       /**
+        * @return string[]
+        */
+       public function getLines() {
+               $this->flushLine( '~done' );
+
+               return $this->lines;
+       }
+}
diff --git a/includes/diff/WordLevelDiff.php b/includes/diff/WordLevelDiff.php
new file mode 100644 (file)
index 0000000..12cf376
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ * @defgroup DifferenceEngine DifferenceEngine
+ */
+
+use MediaWiki\Diff\WordAccumulator;
+
+/**
+ * Performs a word-level diff on several lines
+ *
+ * @ingroup DifferenceEngine
+ */
+class WordLevelDiff extends \Diff {
+       const MAX_LINE_LENGTH = 10000;
+
+       /**
+        * @param string[] $linesBefore
+        * @param string[] $linesAfter
+        */
+       public function __construct( $linesBefore, $linesAfter ) {
+
+               list( $wordsBefore, $wordsBeforeStripped ) = $this->split( $linesBefore );
+               list( $wordsAfter, $wordsAfterStripped ) = $this->split( $linesAfter );
+
+               parent::__construct( $wordsBeforeStripped, $wordsAfterStripped );
+
+               $xi = $yi = 0;
+               $editCount = count( $this->edits );
+               for ( $i = 0; $i < $editCount; $i++ ) {
+                       $orig = &$this->edits[$i]->orig;
+                       if ( is_array( $orig ) ) {
+                               $orig = array_slice( $wordsBefore, $xi, count( $orig ) );
+                               $xi += count( $orig );
+                       }
+
+                       $closing = &$this->edits[$i]->closing;
+                       if ( is_array( $closing ) ) {
+                               $closing = array_slice( $wordsAfter, $yi, count( $closing ) );
+                               $yi += count( $closing );
+                       }
+               }
+
+       }
+
+       /**
+        * @param string[] $lines
+        *
+        * @return array[]
+        */
+       private function split( $lines ) {
+
+               $words = [];
+               $stripped = [];
+               $first = true;
+               foreach ( $lines as $line ) {
+                       # If the line is too long, just pretend the entire line is one big word
+                       # This prevents resource exhaustion problems
+                       if ( $first ) {
+                               $first = false;
+                       } else {
+                               $words[] = "\n";
+                               $stripped[] = "\n";
+                       }
+                       if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
+                               $words[] = $line;
+                               $stripped[] = $line;
+                       } else {
+                               $m = [];
+                               if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
+                                       $line, $m )
+                               ) {
+                                       foreach ( $m[0] as $word ) {
+                                               $words[] = $word;
+                                       }
+                                       foreach ( $m[1] as $stripped_word ) {
+                                               $stripped[] = $stripped_word;
+                                       }
+                               }
+                       }
+               }
+
+               return [ $words, $stripped ];
+       }
+
+       /**
+        * @return string[]
+        */
+       public function orig() {
+               $orig = new WordAccumulator;
+
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit->type == 'copy' ) {
+                               $orig->addWords( $edit->orig );
+                       } elseif ( $edit->orig ) {
+                               $orig->addWords( $edit->orig, 'del' );
+                       }
+               }
+               $lines = $orig->getLines();
+
+               return $lines;
+       }
+
+       /**
+        * @return string[]
+        */
+       public function closing() {
+               $closing = new WordAccumulator;
+
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit->type == 'copy' ) {
+                               $closing->addWords( $edit->closing );
+                       } elseif ( $edit->closing ) {
+                               $closing->addWords( $edit->closing, 'ins' );
+                       }
+               }
+               $lines = $closing->getLines();
+
+               return $lines;
+       }
+
+}
index 916be2d..b7c3489 100644 (file)
@@ -25,7 +25,7 @@
  * 'exception-nologin' as a title and 'exception-nologin-text' for the message.
  *
  * @note In order for this exception to redirect, the error message passed to the
- * constructor has to be explicitly added to LoginForm::validErrorMessages or with
+ * constructor has to be explicitly added to LoginHelper::validErrorMessages or with
  * the LoginFormValidErrorMessages hook. Otherwise, the user will just be shown the message
  * rather than redirected.
  *
@@ -79,7 +79,7 @@ class UserNotLoggedIn extends ErrorPageError {
        public function report() {
                // If an unsupported message is used, don't try redirecting to Special:Userlogin,
                // since the message may not be compatible.
-               if ( !in_array( $this->msg, LoginForm::getValidErrorMessages() ) ) {
+               if ( !in_array( $this->msg, LoginHelper::getValidErrorMessages() ) ) {
                        parent::report();
                }
 
index c432025..3b20048 100644 (file)
@@ -385,6 +385,11 @@ class FileBackendMultiWrite extends FileBackend {
                        }
                }
 
+               if ( !$status->isOK() ) {
+                       wfDebugLog( 'FileOperation', get_class( $this ) .
+                               " failed to resync: " . FormatJson::encode( $paths ) );
+               }
+
                return $status;
        }
 
index 15821ea..9ad2428 100644 (file)
@@ -815,7 +815,6 @@ class FileRepo {
         * @param string $dstZone Destination zone
         * @param string $dstRel Destination relative path
         * @param int $flags Bitwise combination of the following flags:
-        *   self::DELETE_SOURCE     Delete the source file after upload
         *   self::OVERWRITE         Overwrite an existing destination file instead of failing
         *   self::OVERWRITE_SAME    Overwrite the file if the destination exists and has the
         *                           same contents as the source
@@ -838,7 +837,6 @@ class FileRepo {
         *
         * @param array $triplets (src, dest zone, dest rel) triplets as per store()
         * @param int $flags Bitwise combination of the following flags:
-        *   self::DELETE_SOURCE     Delete the source file after upload
         *   self::OVERWRITE         Overwrite an existing destination file instead of failing
         *   self::OVERWRITE_SAME    Overwrite the file if the destination exists and has the
         *                           same contents as the source
@@ -849,11 +847,14 @@ class FileRepo {
        public function storeBatch( array $triplets, $flags = 0 ) {
                $this->assertWritableRepo(); // fail out if read-only
 
+               if ( $flags & self::DELETE_SOURCE ) {
+                       throw new InvalidArgumentException( "DELETE_SOURCE not supported in " . __METHOD__ );
+               }
+
                $status = $this->newGood();
                $backend = $this->backend; // convenience
 
                $operations = [];
-               $sourceFSFilesToDelete = []; // cleanup for disk source files
                // Validate each triplet and get the store operation...
                foreach ( $triplets as $triplet ) {
                        list( $srcPath, $dstZone, $dstRel ) = $triplet;
@@ -881,12 +882,9 @@ class FileRepo {
 
                        // Get the appropriate file operation
                        if ( FileBackend::isStoragePath( $srcPath ) ) {
-                               $opName = ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy';
+                               $opName = 'copy';
                        } else {
                                $opName = 'store';
-                               if ( $flags & self::DELETE_SOURCE ) {
-                                       $sourceFSFilesToDelete[] = $srcPath;
-                               }
                        }
                        $operations[] = [
                                'op' => $opName,
@@ -903,12 +901,6 @@ class FileRepo {
                        $opts['nonLocking'] = true;
                }
                $status->merge( $backend->doOperations( $operations, $opts ) );
-               // Cleanup for disk source files...
-               foreach ( $sourceFSFilesToDelete as $file ) {
-                       MediaWiki\suppressWarnings();
-                       unlink( $file ); // FS cleanup
-                       MediaWiki\restoreWarnings();
-               }
 
                return $status;
        }
index 8248699..eaec151 100644 (file)
@@ -227,7 +227,7 @@ class LocalRepo extends FileRepo {
                                        ? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
                                        : ''; // negative cache
                        },
-                       [ 'pcTTL' => 30 ]
+                       [ 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
                );
 
                // @note: also checks " " for b/c
index c037516..8175b58 100644 (file)
@@ -1065,7 +1065,6 @@ abstract class File implements IDBAccessObject {
                        if ( $this->repo ) {
                                // Defer rendering if a 404 handler is set up...
                                if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
-                                       wfDebug( __METHOD__ . " transformation deferred.\n" );
                                        // XXX: Pass in the storage path even though we are not rendering anything
                                        // and the path is supposed to be an FS path. This is due to getScalerType()
                                        // getting called on the path and clobbering $thumb->getUrl() if it's false.
index aa278aa..2a15fd7 100644 (file)
@@ -559,11 +559,11 @@ class LocalFile extends File {
                        return;
                }
 
+               $upgrade = false;
                if ( is_null( $this->media_type ) ||
                        $this->mime == 'image/svg'
                ) {
-                       $this->upgradeRow();
-                       $this->upgraded = true;
+                       $upgrade = true;
                } else {
                        $handler = $this->getHandler();
                        if ( $handler ) {
@@ -571,11 +571,19 @@ class LocalFile extends File {
                                if ( $validity === MediaHandler::METADATA_BAD
                                        || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
                                ) {
-                                       $this->upgradeRow();
-                                       $this->upgraded = true;
+                                       $upgrade = true;
                                }
                        }
                }
+
+               if ( $upgrade ) {
+                       try {
+                               $this->upgradeRow();
+                       } catch ( LocalFileLockError $e ) {
+                               // let the other process handle it (or do it next time)
+                       }
+                       $this->upgraded = true; // avoid rework/retries
+               }
        }
 
        function getUpgraded() {
@@ -586,7 +594,6 @@ class LocalFile extends File {
         * Fix assorted version-related problems with the image row by reloading it from the file
         */
        function upgradeRow() {
-
                $this->lock(); // begin
 
                $this->loadFromFile();
@@ -1898,29 +1905,35 @@ class LocalFile extends File {
        /**
         * Start a transaction and lock the image for update
         * Increments a reference counter if the lock is already held
-        * @throws MWException Throws an error if the lock was not acquired
+        * @throws LocalFileLockError Throws an error if the lock was not acquired
         * @return bool Whether the file lock owns/spawned the DB transaction
         */
        function lock() {
-               $dbw = $this->repo->getMasterDB();
-
                if ( !$this->locked ) {
+                       $dbw = $this->repo->getMasterDB();
                        if ( !$dbw->trxLevel() ) {
                                $dbw->begin( __METHOD__ );
                                $this->lockedOwnTrx = true;
                        }
-                       $this->locked++;
                        // Bug 54736: use simple lock to handle when the file does not exist.
                        // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
                        // Also, that would cause contention on INSERT of similarly named rows.
                        $backend = $this->getRepo()->getBackend();
                        $lockPaths = [ $this->getPath() ]; // represents all versions of the file
-                       $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 5 );
+                       $start = microtime( true );
+                       $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 10 );
+                       $waited = microtime( true ) - $start;
                        if ( !$status->isGood() ) {
-                               throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
+                               if ( $this->lockedOwnTrx ) {
+                                       $dbw->rollback( __METHOD__ );
+                               }
+                               throw new LocalFileLockError(
+                                       "Could not acquire lock for '{$this->getName()}' ($waited sec)." );
                        }
+                       // Release the lock *after* commit to avoid row-level contention
+                       $this->locked++;
                        $dbw->onTransactionIdle( function () use ( $backend, $lockPaths ) {
-                               $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX ); // release on commit
+                               $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX );
                        } );
                }
 
@@ -3031,3 +3044,7 @@ class LocalFileMoveBatch {
                $this->file->repo->cleanupBatch( $files );
        }
 }
+
+class LocalFileLockError extends Exception {
+
+}
index d671029..e891c9c 100644 (file)
@@ -569,7 +569,7 @@ class HTMLForm extends ContextSource {
 
                # Check for cancelled submission
                foreach ( $this->mFlatFields as $fieldname => $field ) {
-                       if ( !empty( $field->mParams['nodata'] ) ) {
+                       if ( !array_key_exists( $fieldname, $this->mFieldData ) ) {
                                continue;
                        }
                        if ( $field->cancelSubmit( $this->mFieldData[$fieldname], $this->mFieldData ) ) {
@@ -580,7 +580,7 @@ class HTMLForm extends ContextSource {
 
                # Check for validation
                foreach ( $this->mFlatFields as $fieldname => $field ) {
-                       if ( !empty( $field->mParams['nodata'] ) ) {
+                       if ( !array_key_exists( $fieldname, $this->mFieldData ) ) {
                                continue;
                        }
                        if ( $field->isHidden( $this->mFieldData ) ) {
@@ -1117,7 +1117,7 @@ class HTMLForm extends ContextSource {
                        ];
 
                        if ( isset( $button['label-message'] ) ) {
-                               $label = $this->msg( $button['label-message'] )->parse();
+                               $label = $this->getMessage( $button['label-message'] )->parse();
                        } elseif ( isset( $button['label'] ) ) {
                                $label = htmlspecialchars( $button['label'] );
                        } elseif ( isset( $button['label-raw'] ) ) {
@@ -1198,17 +1198,10 @@ class HTMLForm extends ContextSource {
                $errorstr = '';
 
                foreach ( $errors as $error ) {
-                       if ( is_array( $error ) ) {
-                               $msg = array_shift( $error );
-                       } else {
-                               $msg = $error;
-                               $error = [];
-                       }
-
                        $errorstr .= Html::rawElement(
                                'li',
                                [],
-                               $this->msg( $msg, $error )->parse()
+                               $this->getMessage( $error )->parse()
                        );
                }
 
@@ -1233,17 +1226,25 @@ class HTMLForm extends ContextSource {
        /**
         * Identify that the submit button in the form has a destructive action
         * @since 1.24
+        *
+        * @return HTMLForm $this for chaining calls (since 1.28)
         */
        public function setSubmitDestructive() {
                $this->mSubmitFlags = [ 'destructive', 'primary' ];
+
+               return $this;
        }
 
        /**
         * Identify that the submit button in the form has a progressive action
         * @since 1.25
+        *
+        * @return HTMLForm $this for chaining calls (since 1.28)
         */
        public function setSubmitProgressive() {
                $this->mSubmitFlags = [ 'progressive', 'primary' ];
+
+               return $this;
        }
 
        /**
@@ -1725,4 +1726,14 @@ class HTMLForm extends ContextSource {
 
                return $this;
        }
+
+       /**
+        * Turns a *-message parameter (which could be a MessageSpecifier, or a message name, or a
+        * name + parameters array) into a Message.
+        * @param mixed $value
+        * @return Message
+        */
+       protected function getMessage( $value ) {
+               return Message::newFromSpecifier( $value )->setContext( $this );
+       }
 }
index d14fa90..9f5e728 100644 (file)
@@ -28,7 +28,7 @@ abstract class HTMLFormField {
        protected $mShowEmptyLabels = true;
 
        /**
-        * @var HTMLForm
+        * @var HTMLForm|null
         */
        public $mParent;
 
@@ -1095,16 +1095,13 @@ abstract class HTMLFormField {
         * @return Message
         */
        protected function getMessage( $value ) {
-               if ( $value instanceof Message ) {
-                       return $value;
-               } elseif ( $value instanceof MessageSpecifier ) {
-                       return Message::newFromKey( $value );
-               } elseif ( is_array( $value ) ) {
-                       $msg = array_shift( $value );
-                       return $this->msg( $msg, $value );
-               } else {
-                       return $this->msg( $value, [] );
+               $message = Message::newFromSpecifier( $value );
+
+               if ( $this->mParent ) {
+                       $message->setContext( $this->mParent );
                }
+
+               return $message;
        }
 
        /**
index 3f80884..ec1bd84 100644 (file)
@@ -207,7 +207,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
                foreach ( $values as $key => $value ) {
                        $fields = $this->createFieldsForKey( $key );
                        foreach ( $fields as $fieldname => $field ) {
-                               if ( !empty( $field->mParams['nodata'] ) ) {
+                               if ( !array_key_exists( $fieldname, $value ) ) {
                                        continue;
                                }
                                if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
@@ -237,7 +237,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
                foreach ( $values as $key => $value ) {
                        $fields = $this->createFieldsForKey( $key );
                        foreach ( $fields as $fieldname => $field ) {
-                               if ( !empty( $field->mParams['nodata'] ) ) {
+                               if ( !array_key_exists( $fieldname, $value ) ) {
                                        continue;
                                }
                                $ok = $field->validate( $value[$fieldname], $alldata );
index 278d453..711750b 100644 (file)
@@ -100,7 +100,7 @@ class OOUIHTMLForm extends HTMLForm {
                        if ( $isBadIE ) {
                                $label = $button['value'];
                        } elseif ( isset( $button['label-message'] ) ) {
-                               $label = new OOUI\HtmlSnippet( $this->msg( $button['label-message'] )->parse() );
+                               $label = new OOUI\HtmlSnippet( $this->getMessage( $button['label-message'] )->parse() );
                        } elseif ( isset( $button['label'] ) ) {
                                $label = $button['label'];
                        } elseif ( isset( $button['label-raw'] ) ) {
@@ -198,18 +198,7 @@ class OOUIHTMLForm extends HTMLForm {
                }
 
                foreach ( $errors as &$error ) {
-                       if ( is_array( $error ) ) {
-                               $msg = array_shift( $error );
-                       } else {
-                               $msg = $error;
-                               $error = [];
-                       }
-                       // if the error is already a message object, don't use it as a message key
-                       if ( !$msg instanceof Message ) {
-                               $error = $this->msg( $msg, $error )->parse();
-                       } else {
-                               $error = $msg->parse();
-                       }
+                       $error = $this->getMessage( $error )->parse();
                        $error = new OOUI\HtmlSnippet( $error );
                }
 
@@ -232,22 +221,27 @@ class OOUIHTMLForm extends HTMLForm {
                // FIXME This only works for forms with no subsections
                if ( $fieldset instanceof OOUI\FieldsetLayout ) {
                        $classes = [ 'mw-htmlform-ooui-header' ];
-                       if ( !$this->mHeader ) {
-                               $classes[] = 'mw-htmlform-ooui-header-empty';
-                       }
                        if ( $this->oouiErrors ) {
                                $classes[] = 'mw-htmlform-ooui-header-errors';
                        }
-                       $fieldset->addItems( [
-                               new OOUI\FieldLayout(
-                                       new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet( $this->mHeader ) ] ),
-                                       [
-                                               'align' => 'top',
-                                               'errors' => $this->oouiErrors,
-                                               'classes' => $classes,
-                                       ]
-                               )
-                       ], 0 );
+                       if ( $this->mHeader || $this->oouiErrors ) {
+                               // if there's no header, don't create an (empty) LabelWidget, simply use a placeholder
+                               if ( $this->mHeader ) {
+                                       $element = new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet( $this->mHeader ) ] );
+                               } else {
+                                       $element = new OOUI\Widget( [] );
+                               }
+                               $fieldset->addItems( [
+                                       new OOUI\FieldLayout(
+                                               $element,
+                                               [
+                                                       'align' => 'top',
+                                                       'errors' => $this->oouiErrors,
+                                                       'classes' => $classes,
+                                               ]
+                                       )
+                               ], 0 );
+                       }
                }
                return $fieldset;
        }
index 79bd961..701403e 100644 (file)
@@ -287,8 +287,16 @@ abstract class DatabaseInstaller {
                if ( !$status->isOK() ) {
                        throw new MWException( __METHOD__ . ': unexpected DB connection error' );
                }
-               LBFactory::setInstance( new LBFactorySingle( [
-                       'connection' => $status->value ] ) );
+
+               \MediaWiki\MediaWikiServices::resetGlobalInstance();
+               $services = \MediaWiki\MediaWikiServices::getInstance();
+
+               $connection = $status->value;
+               $services->redefineService( 'DBLoadBalancerFactory', function() use ( $connection ) {
+                       return new LBFactorySingle( [
+                               'connection' => $connection ] );
+               } );
+
        }
 
        /**
index 3d1c860..85b1013 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Deployment
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * This documentation group collects source code files with deployment functionality.
@@ -351,37 +352,78 @@ abstract class Installer {
         */
        abstract public function showStatusMessage( Status $status );
 
+       /**
+        * Constructs a Config object that contains configuration settings that should be
+        * overwritten for the installation process.
+        *
+        * @since 1.27
+        *
+        * @param Config $baseConfig
+        *
+        * @return Config The config to use during installation.
+        */
+       public static function getInstallerConfig( Config $baseConfig ) {
+               $configOverrides = new HashConfig();
+
+               // disable (problematic) object cache types explicitly, preserving all other (working) ones
+               // bug T113843
+               $emptyCache = [ 'class' => 'EmptyBagOStuff' ];
+
+               $objectCaches = [
+                               CACHE_NONE => $emptyCache,
+                               CACHE_DB => $emptyCache,
+                               CACHE_ANYTHING => $emptyCache,
+                               CACHE_MEMCACHED => $emptyCache,
+                       ] + $baseConfig->get( 'ObjectCaches' );
+
+               $configOverrides->set( 'ObjectCaches', $objectCaches );
+
+               // Load the installer's i18n.
+               $messageDirs = $baseConfig->get( 'MessagesDirs' );
+               $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
+
+               $configOverrides->set( 'MessagesDirs', $messageDirs );
+
+               $installerConfig = new MultiConfig( [ $configOverrides, $baseConfig ] );
+
+               // make sure we use the installer config as the main config
+               $configRegistry = $baseConfig->get( 'ConfigRegistry' );
+               $configRegistry['main'] = function() use ( $installerConfig ) {
+                       return $installerConfig;
+               };
+
+               $configOverrides->set( 'ConfigRegistry', $configRegistry );
+
+               return $installerConfig;
+       }
+
        /**
         * Constructor, always call this from child classes.
         */
        public function __construct() {
-               global $wgMessagesDirs, $wgUser;
+               global $wgMemc, $wgUser, $wgObjectCaches;
+
+               $defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php
+               $installerConfig = self::getInstallerConfig( $defaultConfig );
+
+               // Reset all services and inject config overrides
+               MediaWiki\MediaWikiServices::resetGlobalInstance( $installerConfig );
 
                // Don't attempt to load user language options (T126177)
                // This will be overridden in the web installer with the user-specified language
                RequestContext::getMain()->setLanguage( 'en' );
 
                // Disable the i18n cache
+               // TODO: manage LocalisationCache singleton in MediaWikiServices
                Language::getLocalisationCache()->disableBackend();
-               // Disable LoadBalancer and wfGetDB etc.
-               LBFactory::disableBackend();
+
+               // Disable all global services, since we don't have any configuration yet!
+               MediaWiki\MediaWikiServices::disableStorageBackend();
 
                // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
                // SqlBagOStuff will then throw since we just disabled wfGetDB)
-               $GLOBALS['wgMemc'] = new EmptyBagOStuff;
-               ObjectCache::clear();
-               $emptyCache = [ 'class' => 'EmptyBagOStuff' ];
-               // disable (problematic) object cache types explicitly, preserving all other (working) ones
-               // bug T113843
-               $GLOBALS['wgObjectCaches'] = [
-                       CACHE_NONE => $emptyCache,
-                       CACHE_DB => $emptyCache,
-                       CACHE_ANYTHING => $emptyCache,
-                       CACHE_MEMCACHED => $emptyCache,
-               ] + $GLOBALS['wgObjectCaches'];
-
-               // Load the installer's i18n.
-               $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
+               $wgObjectCaches = MediaWikiServices::getInstance()->getMainConfig()->get( 'ObjectCaches' );
+               $wgMemc = ObjectCache::getInstance( CACHE_NONE );
 
                // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
                $wgUser = User::newFromId( 0 );
@@ -1697,7 +1739,7 @@ abstract class Installer {
                                wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
                        );
 
-                       $page->doEditContent( $content,
+                       $status = $page->doEditContent( $content,
                                '',
                                EDIT_NEW,
                                false,
index c6b8960..62cd883 100644 (file)
@@ -500,19 +500,19 @@ class MssqlInstaller extends DatabaseInstaller {
                                "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ),
                                __METHOD__
                        );
-                       $conn->selectDB( $dbName );
-                       if ( !$this->schemaExists( $schemaName ) ) {
-                               $conn->query(
-                                       "CREATE SCHEMA " . $conn->addIdentifierQuotes( $schemaName ),
-                                       __METHOD__
-                               );
-                       }
-                       if ( !$this->catalogExists( $schemaName ) ) {
-                               $conn->query(
-                                       "CREATE FULLTEXT CATALOG " . $conn->addIdentifierQuotes( $schemaName ),
-                                       __METHOD__
-                               );
-                       }
+               }
+               $conn->selectDB( $dbName );
+               if ( !$this->schemaExists( $schemaName ) ) {
+                       $conn->query(
+                               "CREATE SCHEMA " . $conn->addIdentifierQuotes( $schemaName ),
+                               __METHOD__
+                       );
+               }
+               if ( !$this->catalogExists( $schemaName ) ) {
+                       $conn->query(
+                               "CREATE FULLTEXT CATALOG " . $conn->addIdentifierQuotes( $schemaName ),
+                               __METHOD__
+                       );
                }
                $this->setupSchemaVars();
 
index bdaf4c8..accc42f 100644 (file)
@@ -41,22 +41,24 @@ class MssqlUpdater extends DatabaseUpdater {
                        [ 'addField', 'mwuser', 'user_password_expires', 'patch-user_password_expires.sql' ],
 
                        // 1.24
-                       [ 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ],
+                       [ 'addField', 'page', 'page_lang', 'patch-page_page_lang.sql' ],
 
                        // 1.25
                        [ 'dropTable', 'hitcounter' ],
                        [ 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ],
                        [ 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ],
-                       // Constraint updates
-                       [ 'updateConstraints', 'category_types', 'categorylinks', 'cl_type' ],
-                       [ 'updateConstraints', 'major_mime', 'filearchive', 'fa_major_mime' ],
-                       [ 'updateConstraints', 'media_type', 'filearchive', 'fa_media_type' ],
-                       [ 'updateConstraints', 'major_mime', 'oldimage', 'oi_major_mime' ],
-                       [ 'updateConstraints', 'media_type', 'oldimage', 'oi_media_type' ],
-                       [ 'updateConstraints', 'major_mime', 'image', 'img_major_mime' ],
-                       [ 'updateConstraints', 'media_type', 'image', 'img_media_type' ],
-                       [ 'updateConstraints', 'media_type', 'uploadstash', 'us_media_type' ],
-                       // END: Constraint updates
+                       // scripts were updated in 1.27 due to SQL errors; retaining old updatekeys so that people
+                       // updating from 1.23->1.25->1.27 do not execute these scripts twice even though the
+                       // updatekeys no longer make sense as they are.
+                       [ 'updateSchema', 'categorylinks', 'cl_type-category_types-ck',
+                               'patch-categorylinks-constraints.sql' ],
+                       [ 'updateSchema', 'filearchive', 'fa_major_mime-major_mime-ck',
+                               'patch-filearchive-constraints.sql' ],
+                       [ 'updateSchema', 'oldimage', 'oi_major_mime-major_mime-ck',
+                               'patch-oldimage-constraints.sql' ],
+                       [ 'updateSchema', 'image', 'img_major_mime-major_mime-ck', 'patch-image-constraints.sql' ],
+                       [ 'updateSchema', 'uploadstash', 'us_media_type-media_type-ck',
+                               'patch-uploadstash-constraints.sql' ],
 
                        [ 'modifyField', 'image', 'img_major_mime',
                                'patch-img_major_mime-chemical.sql' ],
@@ -69,79 +71,53 @@ class MssqlUpdater extends DatabaseUpdater {
                        [ 'dropTable', 'msg_resource_links' ],
                        [ 'dropTable', 'msg_resource' ],
                        [ 'addField', 'watchlist', 'wl_id', 'patch-watchlist-wl_id.sql' ],
+                       [ 'dropField', 'mwuser', 'user_options', 'patch-drop-user_options.sql' ],
+                       [ 'addTable', 'bot_passwords', 'patch-bot_passwords.sql' ],
+                       [ 'addField', 'pagelinks', 'pl_from_namespace', 'patch-pl_from_namespace.sql' ],
+                       [ 'addField', 'templatelinks', 'tl_from_namespace', 'patch-tl_from_namespace.sql' ],
+                       [ 'addField', 'imagelinks', 'il_from_namespace', 'patch-il_from_namespace.sql' ],
+                       [ 'dropIndex', 'categorylinks', 'cl_collation', 'patch-kill-cl_collation_index.sql' ],
+                       [ 'addIndex', 'categorylinks', 'cl_collation_ext',
+                               'patch-add-cl_collation_ext_index.sql' ],
+                       [ 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ],
+                       [ 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ],
+                       [ 'updateSchema', 'oldimage', 'oldimage varchar', 'patch-oldimage-schema.sql' ],
+                       [ 'updateSchema', 'filearchive', 'filearchive varchar', 'patch-filearchive-schema.sql' ],
+                       [ 'updateSchema', 'image', 'image varchar', 'patch-image-schema.sql' ],
+                       [ 'updateSchema', 'recentchanges', 'recentchanges-drop-fks',
+                               'patch-recentchanges-drop-fks.sql' ],
+                       [ 'updateSchema', 'logging', 'logging-drop-fks', 'patch-logging-drop-fks.sql' ],
+                       [ 'updateSchema', 'archive', 'archive-drop-fks', 'patch-archive-drop-fks.sql' ]
                ];
        }
 
+       protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
+               $prevScroll = $this->db->scrollableCursor( false );
+               $prevPrep = $this->db->prepareStatements( false );
+               parent::applyPatch( $path, $isFullPath, $msg );
+               $this->db->scrollableCursor( $prevScroll );
+               $this->db->prepareStatements( $prevPrep );
+       }
+
        /**
-        * Drops unnamed and creates named constraints following the pattern
-        * <column>_ckc
+        * General schema update for a table that touches more than one field or requires
+        * destructive actions (such as dropping and recreating the table).
         *
-        * @param string $constraintType
-        * @param string $table Name of the table to which the field belongs
-        * @param string $field Name of the field to modify
-        * @return bool False if patch is skipped.
+        * @param string $table
+        * @param string $updatekey
+        * @param string $patch
+        * @param bool $fullpath
         */
-       protected function updateConstraints( $constraintType, $table, $field ) {
-               global $wgDBname, $wgDBmwschema;
-
-               if ( !$this->doTable( $table ) ) {
-                       return true;
-               }
-
-               $this->output( "...updating constraints on [$table].[$field] ..." );
-               $updateKey = "$field-$constraintType-ck";
+       protected function updateSchema( $table, $updatekey, $patch, $fullpath = false ) {
                if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
-                       $this->output( "...$table table does not exist, skipping modify field patch.\n" );
-                       return true;
-               } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
-                       $this->output( "...$field field does not exist in $table table, " .
-                               "skipping modify field patch.\n" );
-                       return true;
-               } elseif ( $this->updateRowExists( $updateKey ) ) {
-                       $this->output( "...$field in table $table already patched.\n" );
-                       return true;
-               }
-
-               # After all checks passed, start the update
-               $this->insertUpdateRow( $updateKey );
-               $path = 'named_constraints.sql';
-               $constraintMap = [
-                       'category_types' =>
-                               "($field in('page', 'subcat', 'file'))",
-                       'major_mime'     =>
-                               "($field in('unknown', 'application', 'audio', 'image', 'text', 'video'," .
-                               " 'message', 'model', 'multipart'))",
-                       'media_type'     =>
-                               "($field in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA'," .
-                               "'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'))"
-               ];
-               $constraint = $constraintMap[$constraintType];
-
-               # and hack-in those variables that should be replaced
-               # in our template file right now
-               $this->db->setSchemaVars( [
-                       'tableName'       => $table,
-                       'fieldName'       => $field,
-                       'checkConstraint' => $constraint,
-                       'wgDBname'        => $wgDBname,
-                       'wgDBmwschema'    => $wgDBmwschema,
-               ] );
-
-               # Full path from file name
-               $path = $this->db->patchPath( $path );
-
-               # No need for a cursor allowing result-iteration; just apply a patch
-               # store old value for re-setting later
-               $wasScrollable = $this->db->scrollableCursor( false );
+                       $this->output( "...$table table does not exist, skipping schema update patch.\n" );
+               } elseif ( $this->updateRowExists( $updatekey ) ) {
+                       $this->output( "...$table already had schema updated by $patch.\n" );
+               } else {
+                       $this->insertUpdateRow( $updatekey );
 
-               # Apply patch
-               $this->db->sourceFile( $path );
-
-               # Reset DB instance to have original state
-               $this->db->setSchemaVars( false );
-               $this->db->scrollableCursor( $wasScrollable );
-
-               $this->output( "done.\n" );
+                       return $this->applyPatch( $patch, $fullpath, "Updating schema of table $table" );
+               }
 
                return true;
        }
index b4c8aa8..f8dc8ee 100644 (file)
@@ -115,7 +115,10 @@ class WebInstallerOutput {
 
        public function output() {
                $this->flush();
-               $this->outputFooter();
+
+               if ( !$this->redirectTarget ) {
+                       $this->outputFooter();
+               }
        }
 
        /**
index daa429a..2ab0554 100644 (file)
@@ -98,7 +98,7 @@ abstract class WebInstallerPage {
                                wfMessage( "config-$continue" )->text(),
                                [
                                        'name' => "enter-$continue",
-                                       'style' => 'visibility:hidden;overflow:hidden;width:1px;margin:0'
+                                       'style' => 'width:0;border:0;height:0;padding:0'
                                ]
                        ) . "\n";
                }
index d33e03b..45a17c1 100644 (file)
@@ -1,9 +1,11 @@
 {
        "@metadata": {
                "authors": [
-                       "Anggoro"
+                       "Anggoro",
+                       "NoiX180"
                ]
        },
+       "config-install-mainpage-failed": "Ora bisa nglebokaké tepas: $1",
        "mainpagetext": "'''Prangkat empuk wiki wis suksès dipasang.'''",
        "mainpagedocfooter": "Mangga maca [//meta.wikimedia.org/wiki/Help:Contents User's Guide] kanggo katrangan luwih langkung prakara panggunan prangkat empuk wiki\n== Miwiti panggunan  ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Daftar pangaturan préférènsi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
 }
index f621fe4..62afeee 100644 (file)
@@ -10,7 +10,8 @@
                        "Alex00728",
                        "Hwangjy9",
                        "Macofe",
-                       "Mooozi"
+                       "Mooozi",
+                       "Ykhwong"
                ]
        },
        "config-desc": "미디어위키를 위한 설치 관리자",
@@ -71,6 +72,7 @@
        "config-ctype": "<strong>치명</strong>: PHP는 [http://www.php.net/manual/en/ctype.installation.php Ctype 확장 기능]을 지원하도록 하여 컴파일해야 합니다.",
        "config-iconv": "<strong>치명</strong>: PHP는 [http://www.php.net/manual/en/iconv.installation.php iconv 확장 기능]을 지원하도록 하여 컴파일해야 합니다.",
        "config-json": "<strong>치명:</strong> PHP가 JSON 지원이 없이 컴파일되었습니다.\n미디어위키를 설치하기 전에 PHP JSON 확장 기능이나 [http://pecl.php.net/package/jsonc PECL jsonc] 확장 기능 중 하나를 설치해야 합니다.\n* PHP 확장 기능은 Red Hat Enterprise Linux (CentOS) 5와 6에 포함되어 있지만, <code>/etc/php.ini</code>나 <code>/etc/php.d/json.ini</code>에서 활성화해야 합니다.\n* 2013년 5월 이후에 출시된 일부 리눅스 배포판은 PHP 확장 기능이 생략된 대신, <code>php5-json</code>이나 <code>php-pecl-jsonc</code>로 PECL 확장 기능이 포장되어 있습니다.",
+       "config-mbstring-absent": "<strong>치명적 오류:</strong> PHP는 [http://www.php.net/manual/en/mbstring.setup.php mbstring 확장]을 지원하도록 컴파일되어야 합니다.",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache]가 설치되었습니다",
        "config-apc": "[http://www.php.net/apc APC]가 설치되었습니다",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache]가 설치되었습니다",
        "config-ns-site-name": "위키 이름과 같은 이름: $1",
        "config-ns-other": "기타 (지정)",
        "config-ns-other-default": "내위키",
-       "config-project-namespace-help": "위키백과의 예에 따르면, 많은 위키는 정책 문서를 일반 문서와는 별도로 \"'''프로젝트 이름공간'''\"에 보관합니다.\n이 이름공간에 있는 모든 문서의 제목은 여기서 지정할 수 있는 특정 접두어로 시작합니다.\n보통 이 접두어는 위키의 이름에서 파생되지만, \"#\" 또는 \":\"와 같은 특수 문자를 포함할 수 없습니다.",
+       "config-project-namespace-help": "위키백과의 예에 따르면, 많은 위키는 정책 문서를 일반 문서와는 별도로 '''프로젝트 이름공간'''에 보관합니다.\n이 이름공간에 있는 모든 문서의 제목은 여기서 지정할 수 있는 특정 접두어로 시작합니다.\n보통 이 접두어는 위키의 이름에서 파생되지만, \"#\" 또는 \":\"와 같은 특수 문자를 포함할 수 없습니다.",
        "config-ns-invalid": "특정 \"<nowiki>$1</nowiki>\" 이름공간이 잘못되었습니다.\n다른 프로젝트 이름공간을 지정하세요.",
        "config-ns-conflict": "특정 \"<nowiki>$1</nowiki>\" 이름공간이 기본 미디어위키 이름공간과 충돌합니다.\n다른 프로젝트 이름공간을 지정하세요.",
        "config-admin-box": "관리자 계정",
index 2644f3f..aabf541 100644 (file)
@@ -67,6 +67,7 @@
        "config-ctype": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/ctype.installation.php phần mở rộng Ctype].",
        "config-iconv": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/iconv.installation.php phần mở rộng iconv].",
        "config-json": "<strong>Lỗi chí tử:</strong> PHP được biên dịch mà không có hỗ trợ cho JSON.\nBạn phải cài đặt hoặc phần mở rộng JSON PHP hoặc phần mở rộng [http://pecl.php.net/package/jsonc PECL jsonc] trước khi cài đặt MediaWiki.\n* Phần mở rộng PHP có sẵn trong Red Hat Enterprise Linux (CentOS) 5 và 6 nhưng phải được kích hoạt trong <code>/etc/php.ini</code> hoặc <code>/etc/php.d/json.ini</code>.\n* Một số phiên bản Linux được phát hành sau tháng 5 năm 2013 bỏ qua phần mở rộng PHP và gói lại phần mở rộng PECL là <code>php5-json</code> hoặc <code>php-pecl-jsonc</code> thay thế.",
+       "config-mbstring-absent": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/mbstring.setup.php phần mở rộng mbstring].",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] đã được cài đặt",
        "config-apc": "[http://www.php.net/apc APC] đã được cài đặt",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] đã được cài đặt",
index 7d2926c..59968e2 100644 (file)
        "config-email-watchlist": "启用监视列表通知",
        "config-email-watchlist-help": "允许用户收到与其监视列表有关的通知,假若他们启用了该功能。",
        "config-email-auth": "启用电子邮件身份验证",
-       "config-email-auth-help": "如果启用此选项,在用户设置或修改电子邮件地址时,就会收到一封邮件,内含确认电子地址的链接。只有经过身份验证的电子邮件地址,才能收到来自其他用户的电子邮件,或任何修改通知的邮件。'''建议'''公开wiki启用本选项,以防对电子邮件功能的滥用。",
+       "config-email-auth-help": "如果启用此选项,在用户设置或修改电子邮件地址时,就会收到一封邮件,内含确认电子地址的链接。只有经过身份验证的电子邮件地址,才能收到来自其他用户的电子邮件,或任何修改通知的邮件。<strong>建议</strong>公开wiki启用本选项,以防对电子邮件功能的滥用。",
        "config-email-sender": "回复电子邮件地址:",
        "config-email-sender-help": "输入要用来发送出站电子邮件的地址,该地址将会收到被拒收的邮件。许多邮件服务器要求域名部分必须有效。",
        "config-upload-settings": "图像和文件上传",
        "config-upload-deleted": "已删除文件的目录:",
        "config-upload-deleted-help": "指定用于存放被删除文件的目录。理想情况下,该目录不应能通过web访问。",
        "config-logo": "标志URL:",
-       "config-logo-help": "在MediaWiki的默认外观中,左侧栏菜单之上有一块135x160像素的标志区。请上传一幅相应大小的图像,并在此输入URL。\n\n可以用<code>$wgStylePath</code>或<code>$wgScriptPath</code>来表示相对于这些位置的路径。\n\n如果您不希望使用标志,请将本处留空。",
+       "config-logo-help": "在MediaWiki的默认外观中,左侧栏菜单之上有一块135x160像素的标志区。请上传一幅相应大小的图像,并在此输入URL。\n\n可以用<code>$wgStylePath</code>或<code>$wgScriptPath</code>来表示相对于这些位置的路径。\n\n如果您不希望使用标志,请将本处留空。",
        "config-instantcommons": "启用即时共享资源",
        "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons 即时共享资源]可以让wiki使用来自[//commons.wikimedia.org/ 维基共享资源]网站的图像、音频和其他媒体文件。要启用该功能,MediaWiki必须能够访问互联网。\n\n有关此功能的详细信息,包括如何将其他wiki网站设为具有类似共享功能的方法,请参考[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos 手册]。",
        "config-cc-error": "知识共享许可证挑选器无法找到结果,请手动输入许可证的名称。",
        "config-cc-again": "重新挑选……",
-       "config-cc-not-chosen": "选择想要的知识共享许可协议并单击“proceed”。",
+       "config-cc-not-chosen": "选择想要的知识共享许可协议并单击“proceed”。",
        "config-advanced-settings": "高级设置",
        "config-cache-options": "对象缓存设置:",
        "config-cache-help": "对象缓存可通过缓存频繁使用的数据来提高MediaWiki的速度。高度推荐中到大型的网站启用该功能,小型网站亦能从其中受益。",
        "config-skins-missing": "没有找到皮肤;MediaWiki将使用备选皮肤直到您自行安装一个后。",
        "config-skins-must-enable-some": "您必须选择至少一个皮肤以起用。",
        "config-skins-must-enable-default": "默认选择的皮肤必须启用。",
-       "config-install-alreadydone": "'''警告:'''您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
+       "config-install-alreadydone": "<strong>警告:</strong>您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
        "config-install-begin": "点击“{{int:config-continue}}”后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击“{{int:config-back}}”。",
        "config-install-step-done": "完成",
        "config-install-step-failed": "失败",
        "config-install-user-missing": "指定的用户“$1”不存在。",
        "config-install-user-missing-create": "指定的用户“$1”不存在。如果您想要创建一名,请点选“创建帐户”下面的复选框。",
        "config-install-tables": "正在创建数据表",
-       "config-install-tables-exist": "'''警告''':MediaWiki的数据表似乎已经存在,跳过创建。",
-       "config-install-tables-failed": "'''错误''':创建数据表出错,下为错误信息:$1",
+       "config-install-tables-exist": "<strong>警告:</strong>MediaWiki的数据表似乎已经存在,跳过创建。",
+       "config-install-tables-failed": "<strong>错误:</strong>创建数据表出错,下为错误信息:$1",
        "config-install-interwiki": "正在填充默认的跨wiki数据表",
-       "config-install-interwiki-list": "æ\89¾ä¸\8då\88°文件<code>interwiki.list</code>。",
-       "config-install-interwiki-exists": "'''警告''':跨wiki数据表似乎已有内容,跳过默认列表。",
+       "config-install-interwiki-list": "æ\97 æ³\95读å\8f\96文件<code>interwiki.list</code>。",
+       "config-install-interwiki-exists": "<strong>警告:</strong>跨wiki数据表似乎已有内容,跳过默认列表。",
        "config-install-stats": "初始化统计",
        "config-install-keys": "生成密钥中",
-       "config-insecure-keys": "'''警告''':在安装过程中生成的{{PLURAL:$2|安全密钥|安全密钥}}($1){{PLURAL:$2|并|并}}不一定安全。请考虑手动更改{{PLURAL:$2|它|它们}}。",
+       "config-insecure-keys": "<strong>警告:</strong>在安装过程中生成的{{PLURAL:$2|安全密钥}}($1){{PLURAL:$2|并}}不一定安全。请考虑手动更改{{PLURAL:$2|它|它们}}。",
        "config-install-updates": "防止运行不需要的更新",
        "config-install-updates-failed": "<strong>错误:</strong>表格中插入更新关键字失败并出现如下错误:$1",
        "config-install-sysop": "正在创建管理员用户帐号",
        "config-help": "帮助",
        "config-help-tooltip": "单击展开",
        "config-nofile": "找不到文件“$1”。它是否已被删除?",
-       "config-extension-link": "æ\82¨æ\98¯å\90¦ç\9f¥é\81\93æ\82¨ç\9a\84wikiæ\94¯æ\8c\81[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions æ\8b\93å±\95]ï¼\9f\næ\82¨å\8f¯æµ\8fè§\88[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category æ\8b\93å±\95å\88\86ç±»]。",
+       "config-extension-link": "æ\82¨æ\98¯å\90¦ç\9f¥é\81\93æ\82¨ç\9a\84wikiæ\94¯æ\8c\81[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions æ\89©å±\95]ï¼\9f\n\næ\82¨å\8f¯ä»¥æµ\8fè§\88[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category æ\89©å±\95å\88\86ç±»]æ\88\96[//www.mediawiki.org/wiki/Extension_Matrix æ\89©å±\95ç\9f©é\98µ]以æ\9f¥ç\9c\8bå®\8cæ\95´ç\9a\84æ\89©å±\95å\88\97表。",
        "mainpagetext": "<strong>已安装MediaWiki。</strong>",
        "mainpagedocfooter": "请查阅[//meta.wikimedia.org/wiki/Help:Contents 用户指南]以获取使用本wiki软件的信息!\n\n== 入门 ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki配置设置列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh-hans MediaWiki常见问题]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 本地化MediaWiki到您的语言]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 了解如何在您的wiki上打击破坏]"
 }
index 4f57b1f..4f96f4b 100644 (file)
@@ -14,7 +14,8 @@
                        "S8321414",
                        "LNDDYL",
                        "NigelSoft",
-                       "Macofe"
+                       "Macofe",
+                       "Reke"
                ]
        },
        "config-desc": "MediaWiki 安裝程式",
@@ -23,7 +24,7 @@
        "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-localsettings-key": "升級金鑰:",
-       "config-localsettings-badkey": "你提供的金鑰不正確。",
+       "config-localsettings-badkey": "你提供的升級金鑰不正確。",
        "config-upgrade-key-missing": "已偵測到先前安裝的 MediaWiki。\n要升級目前安裝的版本,請將下列文字附加到 <code>LocalSettings.php</code> 的檔案最下方:\n\n$1",
        "config-localsettings-incomplete": "目前的 <code>LocalSettings.php</code> 檔案不完整。\n未設定參數 $1。\n請將此參數設定至 <code>LocalSettings.php</code> 中,並點選 \"{{int:Config-continue}}\"。",
        "config-localsettings-connection-error": "使用 <code>LocalSettings.php</code> 中所指定的資料庫設定連線發生錯誤。 請修復相關設定並再試一次。\n\n$1",
diff --git a/includes/interwiki/ClassicInterwikiLookup.php b/includes/interwiki/ClassicInterwikiLookup.php
new file mode 100644 (file)
index 0000000..6ac165a
--- /dev/null
@@ -0,0 +1,453 @@
+<?php
+namespace MediaWiki\Interwiki;
+
+/**
+ * InterwikiLookup implementing the "classic" interwiki storage (hardcoded up to MW 1.26).
+ *
+ * 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 \Cdb\Exception as CdbException;
+use \Cdb\Reader as CdbReader;
+use Database;
+use Hooks;
+use Interwiki;
+use Language;
+use MapCacheLRU;
+use WANObjectCache;
+
+/**
+ * InterwikiLookup implementing the "classic" interwiki storage (hardcoded up to MW 1.26).
+ *
+ * This implements two levels of caching (in-process array and a WANObjectCache)
+ * and tree storage backends (SQL, CDB, and plain PHP arrays).
+ *
+ * All information is loaded on creation when called by $this->fetch( $prefix ).
+ * All work is done on slave, because this should *never* change (except during
+ * schema updates etc, which aren't wiki-related)
+ *
+ * @since 1.28
+ */
+class ClassicInterwikiLookup implements InterwikiLookup {
+
+       /**
+        * @var MapCacheLRU
+        */
+       private $localCache;
+
+       /**
+        * @var Language
+        */
+       private $contentLanguage;
+
+       /**
+        * @var WANObjectCache
+        */
+       private $objectCache;
+
+       /**
+        * @var int
+        */
+       private $objectCacheExpiry;
+
+       /**
+        * @var bool|array|string
+        */
+       private $cdbData;
+
+       /**
+        * @var int
+        */
+       private $interwikiScopes;
+
+       /**
+        * @var string
+        */
+       private $fallbackSite;
+
+       /**
+        * @var CdbReader|null
+        */
+       private $cdbReader = null;
+
+       /**
+        * @var string|null
+        */
+       private $thisSite = null;
+
+       /**
+        * @param Language $contentLanguage Language object used to convert prefixes to lower case
+        * @param WANObjectCache $objectCache Cache for interwiki info retrieved from the database
+        * @param int $objectCacheExpiry Expiry time for $objectCache, in seconds
+        * @param bool|array|string $cdbData The path of a CDB file, or
+        *        an array resembling the contents of a CDB file,
+        *        or false to use the database.
+        * @param int $interwikiScopes Specify number of domains to check for messages:
+        *    - 1: Just local wiki level
+        *    - 2: wiki and global levels
+        *    - 3: site level as well as wiki and global levels
+        * @param string $fallbackSite The code to assume for the local site,
+        */
+       function __construct(
+               Language $contentLanguage,
+               WANObjectCache $objectCache,
+               $objectCacheExpiry,
+               $cdbData,
+               $interwikiScopes,
+               $fallbackSite
+       ) {
+               $this->localCache = new MapCacheLRU( 100 );
+
+               $this->contentLanguage = $contentLanguage;
+               $this->objectCache = $objectCache;
+               $this->objectCacheExpiry = $objectCacheExpiry;
+               $this->cdbData = $cdbData;
+               $this->interwikiScopes = $interwikiScopes;
+               $this->fallbackSite = $fallbackSite;
+       }
+
+       /**
+        * Check whether an interwiki prefix exists
+        *
+        * @param string $prefix Interwiki prefix to use
+        * @return bool Whether it exists
+        */
+       public function isValidInterwiki( $prefix ) {
+               $result = $this->fetch( $prefix );
+
+               return (bool)$result;
+       }
+
+       /**
+        * Fetch an Interwiki object
+        *
+        * @param string $prefix Interwiki prefix to use
+        * @return Interwiki|null|bool
+        */
+       public function fetch( $prefix ) {
+               if ( $prefix == '' ) {
+                       return null;
+               }
+
+               $prefix = $this->contentLanguage->lc( $prefix );
+               if ( $this->localCache->has( $prefix ) ) {
+                       return $this->localCache->get( $prefix );
+               }
+
+               if ( $this->cdbData ) {
+                       $iw = $this->getInterwikiCached( $prefix );
+               } else {
+                       $iw = $this->load( $prefix );
+                       if ( !$iw ) {
+                               $iw = false;
+                       }
+               }
+               $this->localCache->set( $prefix, $iw );
+
+               return $iw;
+       }
+
+       /**
+        * Resets locally cached Interwiki objects. This is intended for use during testing only.
+        * This does not invalidate entries in the persistent cache, as invalidateCache() does.
+        * @since 1.27
+        */
+       public function resetLocalCache() {
+               $this->localCache->clear();
+       }
+
+       /**
+        * Purge the in-process and object cache for an interwiki prefix
+        * @param string $prefix
+        */
+       public function invalidateCache( $prefix ) {
+               $this->localCache->clear( $prefix );
+
+               $key = $this->objectCache->makeKey( 'interwiki', $prefix );
+               $this->objectCache->delete( $key );
+       }
+
+       /**
+        * Fetch interwiki prefix data from local cache in constant database.
+        *
+        * @note More logic is explained in DefaultSettings.
+        *
+        * @param string $prefix Interwiki prefix
+        * @return Interwiki
+        */
+       private function getInterwikiCached( $prefix ) {
+               $value = $this->getInterwikiCacheEntry( $prefix );
+
+               if ( $value ) {
+                       // Split values
+                       list( $local, $url ) = explode( ' ', $value, 2 );
+                       return new Interwiki( $prefix, $url, '', '', (int)$local );
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Get entry from interwiki cache
+        *
+        * @note More logic is explained in DefaultSettings.
+        *
+        * @param string $prefix Database key
+        * @return bool|string The interwiki entry or false if not found
+        */
+       private function getInterwikiCacheEntry( $prefix ) {
+               wfDebug( __METHOD__ . "( $prefix )\n" );
+               $value = false;
+               try {
+                       // Resolve site name
+                       if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
+                               $this->thisSite = $this->getCacheValue( '__sites:' . wfWikiID() );
+                               if ( $this->thisSite == '' ) {
+                                       $this->thisSite = $this->fallbackSite;
+                               }
+                       }
+
+                       $value = $this->getCacheValue( wfMemcKey( $prefix ) );
+                       // Site level
+                       if ( $value == '' && $this->interwikiScopes >= 3 ) {
+                               $value = $this->getCacheValue( "_{$this->thisSite}:{$prefix}" );
+                       }
+                       // Global Level
+                       if ( $value == '' && $this->interwikiScopes >= 2 ) {
+                               $value = $this->getCacheValue( "__global:{$prefix}" );
+                       }
+                       if ( $value == 'undef' ) {
+                               $value = '';
+                       }
+               } catch ( CdbException $e ) {
+                       wfDebug( __METHOD__ . ": CdbException caught, error message was "
+                               . $e->getMessage() );
+               }
+
+               return $value;
+       }
+
+       private function getCacheValue( $key ) {
+               if ( $this->cdbReader === null ) {
+                       if ( is_string( $this->cdbData ) ) {
+                               $this->cdbReader = \Cdb\Reader::open( $this->cdbData );
+                       } elseif ( is_array( $this->cdbData ) ) {
+                               $this->cdbReader = new \Cdb\Reader\Hash( $this->cdbData );
+                       } else {
+                               $this->cdbReader = false;
+                       }
+               }
+
+               if ( $this->cdbReader ) {
+                       return $this->cdbReader->get( $key );
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Load the interwiki, trying first memcached then the DB
+        *
+        * @param string $prefix The interwiki prefix
+        * @return Interwiki|bool Interwiki if $prefix is valid, otherwise false
+        */
+       private function load( $prefix ) {
+               $iwData = [];
+               if ( !Hooks::run( 'InterwikiLoadPrefix', [ $prefix, &$iwData ] ) ) {
+                       return $this->loadFromArray( $iwData );
+               }
+
+               if ( is_array( $iwData ) ) {
+                       $iw = $this->loadFromArray( $iwData );
+                       if ( $iw ) {
+                               return $iw; // handled by hook
+                       }
+               }
+
+               $iwData = $this->objectCache->getWithSetCallback(
+                       $this->objectCache->makeKey( 'interwiki', $prefix ),
+                       $this->objectCacheExpiry,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
+                               $dbr = wfGetDB( DB_SLAVE ); // TODO: inject LoadBalancer
+
+                               $setOpts += Database::getCacheSetOptions( $dbr );
+
+                               $row = $dbr->selectRow(
+                                       'interwiki',
+                                       ClassicInterwikiLookup::selectFields(),
+                                       [ 'iw_prefix' => $prefix ],
+                                       __METHOD__
+                               );
+
+                               return $row ? (array)$row : '!NONEXISTENT';
+                       }
+               );
+
+               if ( is_array( $iwData ) ) {
+                       return $this->loadFromArray( $iwData ) ?: false;
+               }
+
+               return false;
+       }
+
+       /**
+        * Fill in member variables from an array (e.g. memcached result, Database::fetchRow, etc)
+        *
+        * @param array $mc Associative array: row from the interwiki table
+        * @return Interwiki|bool Interwiki object or false if $mc['iw_url'] is not set
+        */
+       private function loadFromArray( $mc ) {
+               if ( isset( $mc['iw_url'] ) ) {
+                       $url = $mc['iw_url'];
+                       $local = isset( $mc['iw_local'] ) ? $mc['iw_local'] : 0;
+                       $trans = isset( $mc['iw_trans'] ) ? $mc['iw_trans'] : 0;
+                       $api = isset( $mc['iw_api'] ) ? $mc['iw_api'] : '';
+                       $wikiId = isset( $mc['iw_wikiid'] ) ? $mc['iw_wikiid'] : '';
+
+                       return new Interwiki( null, $url, $api, $wikiId, $local, $trans );
+               }
+
+               return false;
+       }
+
+       /**
+        * Fetch all interwiki prefixes from interwiki cache
+        *
+        * @param null|string $local If not null, limits output to local/non-local interwikis
+        * @return array List of prefixes, where each row is an associative array
+        */
+       private function getAllPrefixesCached( $local ) {
+               wfDebug( __METHOD__ . "()\n" );
+               $data = [];
+               try {
+                       /* Resolve site name */
+                       if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
+                               $site = $this->getCacheValue( '__sites:' . wfWikiID() );
+
+                               if ( $site == '' ) {
+                                       $this->thisSite = $this->fallbackSite;
+                               } else {
+                                       $this->thisSite = $site;
+                               }
+                       }
+
+                       // List of interwiki sources
+                       $sources = [];
+                       // Global Level
+                       if ( $this->interwikiScopes >= 2 ) {
+                               $sources[] = '__global';
+                       }
+                       // Site level
+                       if ( $this->interwikiScopes >= 3 ) {
+                               $sources[] = '_' . $this->thisSite;
+                       }
+                       $sources[] = wfWikiID();
+
+                       foreach ( $sources as $source ) {
+                               $list = $this->getCacheValue( '__list:' . $source );
+                               foreach ( explode( ' ', $list ) as $iw_prefix ) {
+                                       $row = $this->getCacheValue( "{$source}:{$iw_prefix}" );
+                                       if ( !$row ) {
+                                               continue;
+                                       }
+
+                                       list( $iw_local, $iw_url ) = explode( ' ', $row );
+
+                                       if ( $local !== null && $local != $iw_local ) {
+                                               continue;
+                                       }
+
+                                       $data[$iw_prefix] = [
+                                               'iw_prefix' => $iw_prefix,
+                                               'iw_url' => $iw_url,
+                                               'iw_local' => $iw_local,
+                                       ];
+                               }
+                       }
+               } catch ( CdbException $e ) {
+                       wfDebug( __METHOD__ . ": CdbException caught, error message was "
+                               . $e->getMessage() );
+               }
+
+               ksort( $data );
+
+               return array_values( $data );
+       }
+
+       /**
+        * Fetch all interwiki prefixes from DB
+        *
+        * @param string|null $local If not null, limits output to local/non-local interwikis
+        * @return array[] Interwiki rows
+        */
+       private function getAllPrefixesDB( $local ) {
+               $db = wfGetDB( DB_SLAVE ); // TODO: inject DB LoadBalancer
+
+               $where = [];
+
+               if ( $local !== null ) {
+                       if ( $local == 1 ) {
+                               $where['iw_local'] = 1;
+                       } elseif ( $local == 0 ) {
+                               $where['iw_local'] = 0;
+                       }
+               }
+
+               $res = $db->select( 'interwiki',
+                       $this->selectFields(),
+                       $where, __METHOD__, [ 'ORDER BY' => 'iw_prefix' ]
+               );
+
+               $retval = [];
+               foreach ( $res as $row ) {
+                       $retval[] = (array)$row;
+               }
+
+               return $retval;
+       }
+
+       /**
+        * Returns all interwiki prefixes
+        *
+        * @param string|null $local If set, limits output to local/non-local interwikis
+        * @return array[] Interwiki rows, where each row is an associative array
+        */
+       public function getAllPrefixes( $local = null ) {
+               if ( $this->cdbData ) {
+                       return $this->getAllPrefixesCached( $local );
+               }
+
+               return $this->getAllPrefixesDB( $local );
+       }
+
+       /**
+        * Return the list of interwiki fields that should be selected to create
+        * a new Interwiki object.
+        * @return string[]
+        */
+       private static function selectFields() {
+               return [
+                       'iw_prefix',
+                       'iw_url',
+                       'iw_api',
+                       'iw_wikiid',
+                       'iw_local',
+                       'iw_trans'
+               ];
+       }
+
+}
index f68651b..558e32c 100644 (file)
  *
  * @file
  */
-use \Cdb\Exception as CdbException;
-use \Cdb\Reader as CdbReader;
+use MediaWiki\MediaWikiServices;
 
 /**
- * The interwiki class
- * All information is loaded on creation when called by Interwiki::fetch( $prefix ).
- * All work is done on slave, because this should *never* change (except during
- * schema updates etc, which aren't wiki-related)
+ * Value object for representing interwiki records.
  */
 class Interwiki {
-       // Cache - removes oldest entry when it hits limit
-       protected static $smCache = [];
-       const CACHE_LIMIT = 100; // 0 means unlimited, any other value is max number of entries.
 
        /** @var string The interwiki prefix, (e.g. "Meatball", or the language prefix "de") */
        protected $mPrefix;
@@ -67,336 +60,48 @@ class Interwiki {
        /**
         * Check whether an interwiki prefix exists
         *
+        * @deprecated since 1.28, use InterwikiLookup instead
+        *
         * @param string $prefix Interwiki prefix to use
         * @return bool Whether it exists
         */
        public static function isValidInterwiki( $prefix ) {
-               $result = self::fetch( $prefix );
-
-               return (bool)$result;
+               return MediaWikiServices::getInstance()->getInterwikiLookup()->isValidInterwiki( $prefix );
        }
 
        /**
         * Fetch an Interwiki object
         *
+        * @deprecated since 1.28, use InterwikiLookup instead
+        *
         * @param string $prefix Interwiki prefix to use
         * @return Interwiki|null|bool
         */
        public static function fetch( $prefix ) {
-               global $wgContLang;
-
-               if ( $prefix == '' ) {
-                       return null;
-               }
-
-               $prefix = $wgContLang->lc( $prefix );
-               if ( isset( self::$smCache[$prefix] ) ) {
-                       return self::$smCache[$prefix];
-               }
-
-               global $wgInterwikiCache;
-               if ( $wgInterwikiCache ) {
-                       $iw = Interwiki::getInterwikiCached( $prefix );
-               } else {
-                       $iw = Interwiki::load( $prefix );
-                       if ( !$iw ) {
-                               $iw = false;
-                       }
-               }
-
-               if ( self::CACHE_LIMIT && count( self::$smCache ) >= self::CACHE_LIMIT ) {
-                       reset( self::$smCache );
-                       unset( self::$smCache[key( self::$smCache )] );
-               }
-
-               self::$smCache[$prefix] = $iw;
-
-               return $iw;
-       }
-
-       /**
-        * Resets locally cached Interwiki objects. This is intended for use during testing only.
-        * This does not invalidate entries in the persistent cache, as invalidateCache() does.
-        * @since 1.27
-        */
-       public static function resetLocalCache() {
-               static::$smCache = [];
+               return MediaWikiServices::getInstance()->getInterwikiLookup()->fetch( $prefix );
        }
 
        /**
         * Purge the cache (local and persistent) for an interwiki prefix.
+        *
         * @param string $prefix
         * @since 1.26
         */
        public static function invalidateCache( $prefix ) {
-               $cache = ObjectCache::getMainWANInstance();
-               $key = wfMemcKey( 'interwiki', $prefix );
-               $cache->delete( $key );
-               unset( static::$smCache[$prefix] );
-       }
-
-       /**
-        * Fetch interwiki prefix data from local cache in constant database.
-        *
-        * @note More logic is explained in DefaultSettings.
-        *
-        * @param string $prefix Interwiki prefix
-        * @return Interwiki
-        */
-       protected static function getInterwikiCached( $prefix ) {
-               $value = self::getInterwikiCacheEntry( $prefix );
-
-               $s = new Interwiki( $prefix );
-               if ( $value ) {
-                       // Split values
-                       list( $local, $url ) = explode( ' ', $value, 2 );
-                       $s->mURL = $url;
-                       $s->mLocal = (bool)$local;
-               } else {
-                       $s = false;
-               }
-
-               return $s;
-       }
-
-       /**
-        * Get entry from interwiki cache
-        *
-        * @note More logic is explained in DefaultSettings.
-        *
-        * @param string $prefix Database key
-        * @return bool|string The interwiki entry or false if not found
-        */
-       protected static function getInterwikiCacheEntry( $prefix ) {
-               global $wgInterwikiScopes, $wgInterwikiFallbackSite;
-               static $site;
-
-               wfDebug( __METHOD__ . "( $prefix )\n" );
-               $value = false;
-               try {
-                       // Resolve site name
-                       if ( $wgInterwikiScopes >= 3 && !$site ) {
-                               $site = self::getCacheValue( '__sites:' . wfWikiID() );
-                               if ( $site == '' ) {
-                                       $site = $wgInterwikiFallbackSite;
-                               }
-                       }
-
-                       $value = self::getCacheValue( wfMemcKey( $prefix ) );
-                       // Site level
-                       if ( $value == '' && $wgInterwikiScopes >= 3 ) {
-                               $value = self::getCacheValue( "_{$site}:{$prefix}" );
-                       }
-                       // Global Level
-                       if ( $value == '' && $wgInterwikiScopes >= 2 ) {
-                               $value = self::getCacheValue( "__global:{$prefix}" );
-                       }
-                       if ( $value == 'undef' ) {
-                               $value = '';
-                       }
-               } catch ( CdbException $e ) {
-                       wfDebug( __METHOD__ . ": CdbException caught, error message was "
-                               . $e->getMessage() );
-               }
-
-               return $value;
-       }
-
-       private static function getCacheValue( $key ) {
-               global $wgInterwikiCache;
-               static $reader;
-               if ( $reader === null ) {
-                       $reader = is_array( $wgInterwikiCache ) ? false : CdbReader::open( $wgInterwikiCache );
-               }
-               if ( $reader ) {
-                       return $reader->get( $key );
-               } else {
-                       return isset( $wgInterwikiCache[$key] ) ? $wgInterwikiCache[$key] : false;
-               }
-       }
-
-       /**
-        * Load the interwiki, trying first memcached then the DB
-        *
-        * @param string $prefix The interwiki prefix
-        * @return Interwiki|bool Interwiki if $prefix is valid, otherwise false
-        */
-       protected static function load( $prefix ) {
-               global $wgInterwikiExpiry;
-
-               $iwData = [];
-               if ( !Hooks::run( 'InterwikiLoadPrefix', [ $prefix, &$iwData ] ) ) {
-                       return Interwiki::loadFromArray( $iwData );
-               }
-
-               if ( is_array( $iwData ) ) {
-                       $iw = Interwiki::loadFromArray( $iwData );
-                       if ( $iw ) {
-                               return $iw; // handled by hook
-                       }
-               }
-
-               $iwData = ObjectCache::getMainWANInstance()->getWithSetCallback(
-                       wfMemcKey( 'interwiki', $prefix ),
-                       $wgInterwikiExpiry,
-                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
-                               $dbr = wfGetDB( DB_SLAVE );
-
-                               $setOpts += Database::getCacheSetOptions( $dbr );
-
-                               $row = $dbr->selectRow(
-                                       'interwiki',
-                                       Interwiki::selectFields(),
-                                       [ 'iw_prefix' => $prefix ],
-                                       __METHOD__
-                               );
-
-                               return $row ? (array)$row : '!NONEXISTENT';
-                       }
-               );
-
-               if ( is_array( $iwData ) ) {
-                       return Interwiki::loadFromArray( $iwData ) ?: false;
-               }
-
-               return false;
-       }
-
-       /**
-        * Fill in member variables from an array (e.g. memcached result, Database::fetchRow, etc)
-        *
-        * @param array $mc Associative array: row from the interwiki table
-        * @return Interwiki|bool Interwiki object or false if $mc['iw_url'] is not set
-        */
-       protected static function loadFromArray( $mc ) {
-               if ( isset( $mc['iw_url'] ) ) {
-                       $iw = new Interwiki();
-                       $iw->mURL = $mc['iw_url'];
-                       $iw->mLocal = isset( $mc['iw_local'] ) ? (bool)$mc['iw_local'] : false;
-                       $iw->mTrans = isset( $mc['iw_trans'] ) ? (bool)$mc['iw_trans'] : false;
-                       $iw->mAPI = isset( $mc['iw_api'] ) ? $mc['iw_api'] : '';
-                       $iw->mWikiID = isset( $mc['iw_wikiid'] ) ? $mc['iw_wikiid'] : '';
-
-                       return $iw;
-               }
-
-               return false;
-       }
-
-       /**
-        * Fetch all interwiki prefixes from interwiki cache
-        *
-        * @param null|string $local If not null, limits output to local/non-local interwikis
-        * @return array List of prefixes
-        * @since 1.19
-        */
-       protected static function getAllPrefixesCached( $local ) {
-               global $wgInterwikiScopes, $wgInterwikiFallbackSite;
-               static $site;
-
-               wfDebug( __METHOD__ . "()\n" );
-               $data = [];
-               try {
-                       /* Resolve site name */
-                       if ( $wgInterwikiScopes >= 3 && !$site ) {
-                               $site = self::getCacheValue( '__sites:' . wfWikiID() );
-
-                               if ( $site == '' ) {
-                                       $site = $wgInterwikiFallbackSite;
-                               }
-                       }
-
-                       // List of interwiki sources
-                       $sources = [];
-                       // Global Level
-                       if ( $wgInterwikiScopes >= 2 ) {
-                               $sources[] = '__global';
-                       }
-                       // Site level
-                       if ( $wgInterwikiScopes >= 3 ) {
-                               $sources[] = '_' . $site;
-                       }
-                       $sources[] = wfWikiID();
-
-                       foreach ( $sources as $source ) {
-                               $list = self::getCacheValue( '__list:' . $source );
-                               foreach ( explode( ' ', $list ) as $iw_prefix ) {
-                                       $row = self::getCacheValue( "{$source}:{$iw_prefix}" );
-                                       if ( !$row ) {
-                                               continue;
-                                       }
-
-                                       list( $iw_local, $iw_url ) = explode( ' ', $row );
-
-                                       if ( $local !== null && $local != $iw_local ) {
-                                               continue;
-                                       }
-
-                                       $data[$iw_prefix] = [
-                                               'iw_prefix' => $iw_prefix,
-                                               'iw_url' => $iw_url,
-                                               'iw_local' => $iw_local,
-                                       ];
-                               }
-                       }
-               } catch ( CdbException $e ) {
-                       wfDebug( __METHOD__ . ": CdbException caught, error message was "
-                               . $e->getMessage() );
-               }
-
-               ksort( $data );
-
-               return array_values( $data );
-       }
-
-       /**
-        * Fetch all interwiki prefixes from DB
-        *
-        * @param string|null $local If not null, limits output to local/non-local interwikis
-        * @return array List of prefixes
-        * @since 1.19
-        */
-       protected static function getAllPrefixesDB( $local ) {
-               $db = wfGetDB( DB_SLAVE );
-
-               $where = [];
-
-               if ( $local !== null ) {
-                       if ( $local == 1 ) {
-                               $where['iw_local'] = 1;
-                       } elseif ( $local == 0 ) {
-                               $where['iw_local'] = 0;
-                       }
-               }
-
-               $res = $db->select( 'interwiki',
-                       self::selectFields(),
-                       $where, __METHOD__, [ 'ORDER BY' => 'iw_prefix' ]
-               );
-
-               $retval = [];
-               foreach ( $res as $row ) {
-                       $retval[] = (array)$row;
-               }
-
-               return $retval;
+               return MediaWikiServices::getInstance()->getInterwikiLookup()->invalidateCache( $prefix );
        }
 
        /**
         * Returns all interwiki prefixes
         *
+        * @deprecated since 1.28, unused. Use InterwikiLookup instead.
+        *
         * @param string|null $local If set, limits output to local/non-local interwikis
         * @return array List of prefixes
         * @since 1.19
         */
        public static function getAllPrefixes( $local = null ) {
-               global $wgInterwikiCache;
-
-               if ( $wgInterwikiCache ) {
-                       return self::getAllPrefixesCached( $local );
-               }
-
-               return self::getAllPrefixesDB( $local );
+               return MediaWikiServices::getInstance()->getInterwikiLookup()->getAllPrefixes( $local );
        }
 
        /**
@@ -477,19 +182,4 @@ class Interwiki {
                return !$msg->exists() ? '' : $msg->text();
        }
 
-       /**
-        * Return the list of interwiki fields that should be selected to create
-        * a new Interwiki object.
-        * @return string[]
-        */
-       public static function selectFields() {
-               return [
-                       'iw_prefix',
-                       'iw_url',
-                       'iw_api',
-                       'iw_wikiid',
-                       'iw_local',
-                       'iw_trans'
-               ];
-       }
 }
diff --git a/includes/interwiki/InterwikiLookup.php b/includes/interwiki/InterwikiLookup.php
new file mode 100644 (file)
index 0000000..459910a
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace MediaWiki\Interwiki;
+
+/**
+ * Service interface for looking up Interwiki records.
+ *
+ * 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 Interwiki;
+
+/**
+ * Service interface for looking up Interwiki records.
+ *
+ * @singe 1.28
+ */
+interface InterwikiLookup {
+
+       /**
+        * Check whether an interwiki prefix exists
+        *
+        * @param string $prefix Interwiki prefix to use
+        * @return bool Whether it exists
+        */
+       public function isValidInterwiki( $prefix );
+
+       /**
+        * Fetch an Interwiki object
+        *
+        * @param string $prefix Interwiki prefix to use
+        * @return Interwiki|null|bool
+        */
+       public function fetch( $prefix );
+
+       /**
+        * Returns all interwiki prefixes
+        *
+        * @param string|null $local If set, limits output to local/non-local interwikis
+        * @return string[] List of prefixes
+        */
+       public function getAllPrefixes( $local = null );
+
+       /**
+        * Purge the in-process and persistent object cache for an interwiki prefix
+        * @param string $prefix
+        */
+       public function invalidateCache( $prefix );
+
+}
index 2dd0615..a4b3241 100644 (file)
@@ -407,7 +407,7 @@ class JobQueueGroup {
 
                                        return [ 'v' => $wgConf->getConfig( $wiki, $name ) ];
                                },
-                               [ 'pcTTL' => 30 ]
+                               [ 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
                        );
 
                        return $value['v'];
index a2f55b9..1350958 100644 (file)
@@ -268,13 +268,19 @@ class JobRunner implements LoggerAwareInterface {
 
                        DeferredUpdates::doUpdates();
                        $this->commitMasterChanges( $job );
-                       $job->teardown();
                } catch ( Exception $e ) {
                        MWExceptionHandler::rollbackMasterChangesAndLog( $e );
                        $status = false;
                        $error = get_class( $e ) . ': ' . $e->getMessage();
                        MWExceptionHandler::logException( $e );
                }
+               // Always attempt to call teardown() even if Job throws exception.
+               try {
+                       $job->teardown();
+               } catch ( Exception $e ) {
+                       MWExceptionHandler::logException( $e );
+               }
+
                // Commit all outstanding connections that are in a transaction
                // to get a fresh repeatable read snapshot on every connection.
                // Note that jobs are still responsible for handling slave lag.
index 16e35f1..1e804c4 100644 (file)
@@ -33,7 +33,6 @@ class AssembleUploadChunksJob extends Job {
        }
 
        public function run() {
-               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = RequestContext::importScopedSession( $this->params['session'] );
                $this->addTeardownCallback( function () use ( &$scope ) {
                        ScopedCallback::consume( $scope ); // T126450
index d2825a8..78531dc 100644 (file)
@@ -35,7 +35,6 @@ class PublishStashedFileJob extends Job {
        }
 
        public function run() {
-               /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = RequestContext::importScopedSession( $this->params['session'] );
                $this->addTeardownCallback( function () use ( &$scope ) {
                        ScopedCallback::consume( $scope ); // T126450
index 22afead..fbc1572 100644 (file)
@@ -70,7 +70,7 @@ class RecentChangesUpdateJob extends Job {
        }
 
        protected function purgeExpiredRows() {
-               global $wgRCMaxAge;
+               global $wgRCMaxAge, $wgUpdateRowsPerQuery;
 
                $lockKey = wfWikiID() . ':recentchanges-prune';
 
@@ -81,14 +81,13 @@ class RecentChangesUpdateJob extends Job {
                        return; // already in progress
                }
 
-               $batchSize = 100; // avoid slave lag
                $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
                do {
                        $rcIds = $dbw->selectFieldValues( 'recentchanges',
                                'rc_id',
                                [ 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ],
                                __METHOD__,
-                               [ 'LIMIT' => $batchSize ]
+                               [ 'LIMIT' => $wgUpdateRowsPerQuery ]
                        );
                        if ( $rcIds ) {
                                $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
@@ -96,7 +95,7 @@ class RecentChangesUpdateJob extends Job {
                        // Commit in chunks to avoid slave lag
                        $dbw->commit( __METHOD__, 'flush' );
 
-                       if ( count( $rcIds ) === $batchSize ) {
+                       if ( count( $rcIds ) === $wgUpdateRowsPerQuery ) {
                                // There might be more, so try waiting for slaves
                                try {
                                        wfGetLBFactory()->waitForReplication( [ 'timeout' => 3 ] );
index 15ee980..b7653be 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup JobQueue
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Job to update link tables for pages
@@ -149,9 +150,18 @@ class RefreshLinksJob extends Job {
                        $revision = Revision::newFromTitle( $title, false, Revision::READ_LATEST );
                }
 
+               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+
                if ( !$revision ) {
+                       $stats->increment( 'refreshlinks.rev_not_found' );
                        $this->setLastError( "Revision not found for {$title->getPrefixedDBkey()}" );
                        return false; // just deleted?
+               } elseif ( !$revision->isCurrent() ) {
+                       // If the revision isn't current, there's no point in doing a bunch
+                       // of work just to fail at the lockAndGetLatest() check later.
+                       $stats->increment( 'refreshlinks.rev_not_current' );
+                       $this->setLastError( "Revision {$revision->getId()} is not current" );
+                       return false;
                }
 
                $content = $revision->getContent( Revision::RAW );
@@ -181,21 +191,27 @@ class RefreshLinksJob extends Job {
 
                        if ( $page->getLinksTimestamp() > $skewedTimestamp ) {
                                // Something already updated the backlinks since this job was made
+                               $stats->increment( 'refreshlinks.update_skipped' );
                                return true;
                        }
 
-                       if ( $page->getTouched() >= $skewedTimestamp || $opportunistic ) {
-                               // Something bumped page_touched since this job was made
-                               // or the cache is otherwise suspected to be up-to-date
+                       if ( $page->getTouched() >= $this->params['rootJobTimestamp'] || $opportunistic ) {
+                               // Cache is suspected to be up-to-date. As long as the cache rev ID matches
+                               // and it reflects the job's triggering change, then it is usable.
                                $parserOutput = ParserCache::singleton()->getDirty( $page, $parserOptions );
-                               if ( $parserOutput && $parserOutput->getCacheTime() < $skewedTimestamp ) {
+                               if ( !$parserOutput
+                                       || $parserOutput->getCacheRevisionId() != $revision->getId()
+                                       || $parserOutput->getCacheTime() < $skewedTimestamp
+                               ) {
                                        $parserOutput = false; // too stale
                                }
                        }
                }
 
                // Fetch the current revision and parse it if necessary...
-               if ( $parserOutput == false ) {
+               if ( $parserOutput ) {
+                       $stats->increment( 'refreshlinks.parser_cached' );
+               } else {
                        $start = microtime( true );
                        // Revision ID must be passed to the parser output to get revision variables correct
                        $parserOutput = $content->getParserOutput(
@@ -213,6 +229,7 @@ class RefreshLinksJob extends Job {
                                        $parserOutput, $page, $parserOptions, $ctime, $revision->getId()
                                );
                        }
+                       $stats->increment( 'refreshlinks.parser_uncached' );
                }
 
                $updates = $content->getSecondaryDataUpdates(
@@ -246,6 +263,7 @@ class RefreshLinksJob extends Job {
                        // serialized, it would be OK to update links based on older revisions since it
                        // would eventually get to the latest. Since that is not the case (by design),
                        // only update the link tables to a state matching the current revision's output.
+                       $stats->increment( 'refreshlinks.rev_cas_failure' );
                        $this->setLastError( "page_latest changed from {$revision->getId()} to $latestNow" );
                        return false;
                }
index 2f2faed..6e40f4c 100644 (file)
@@ -172,7 +172,7 @@ class CSSMin {
        }
 
        /**
-        * @param $file string
+        * @param string $file
         * @return bool|string
         */
        public static function getMimeType( $file ) {
index d0f067f..9c1ec8e 100644 (file)
  * @file
  */
 
-use RunningStat\RunningStat;
-
 /**
  * Convenience class for working with XHProf
  * <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.
  *
- * @author Bryan Davis <bd808@wikimedia.org>
- * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
- * @since 1.25
+ * @since 1.28
  */
 class Xhprof {
-
-       /**
-        * @var array $config
-        */
-       protected $config;
-
-       /**
-        * Hierarchical profiling data returned by xhprof.
-        * @var array $hieraData
-        */
-       protected $hieraData;
-
        /**
-        * Per-function inclusive data.
-        * @var array $inclusive
+        * @var bool $enabled Whether XHProf is currently running.
         */
-       protected $inclusive;
+       protected static $enabled;
 
        /**
-        * Per-function inclusive and exclusive data.
-        * @var array $complete
+        * Start xhprof profiler
         */
-       protected $complete;
-
-       /**
-        * Configuration data can contain:
-        * - flags:   Optional flags to add additional information to the
-        *            profiling data collected.
-        *            (XHPROF_FLAGS_NO_BUILTINS, XHPROF_FLAGS_CPU,
-        *            XHPROF_FLAGS_MEMORY)
-        * - exclude: Array of function names to exclude from profiling.
-        * - include: Array of function names to include in profiling.
-        * - sort:    Key to sort per-function reports on.
-        *
-        * Note: When running under HHVM, xhprof will always behave as though the
-        * XHPROF_FLAGS_NO_BUILTINS flag has been used unless the
-        * Eval.JitEnableRenameFunction option is enabled for the HHVM process.
-        *
-        * @param array $config
-        */
-       public function __construct( array $config = [] ) {
-               $this->config = array_merge(
-                       [
-                               'flags' => 0,
-                               'exclude' => [],
-                               'include' => null,
-                               'sort' => 'wt',
-                       ],
-                       $config
-               );
-
-               xhprof_enable( $this->config['flags'], [
-                       'ignored_functions' => $this->config['exclude']
-               ] );
+       public static function isEnabled() {
+               return self::$enabled;
        }
 
        /**
-        * Stop collecting profiling data.
-        *
-        * Only the first invocation of this method will effect the internal
-        * object state. Subsequent calls will return the data collected by the
-        * initial call.
-        *
-        * @return array Collected profiling data (possibly cached)
+        * Start xhprof profiler
         */
-       public function stop() {
-               if ( $this->hieraData === null ) {
-                       $this->hieraData = $this->pruneData( xhprof_disable() );
+       public static function enable( $flags = 0, $options = [] ) {
+               if ( self::isEnabled() ) {
+                       throw new Exception( 'Xhprof profiling is already enabled.' );
                }
-               return $this->hieraData;
-       }
-
-       /**
-        * Load raw data from a prior run for analysis.
-        * Stops any existing data collection and clears internal caches.
-        *
-        * Any 'include' filters configured for this Xhprof instance will be
-        * enforced on the data as it is loaded. 'exclude' filters will however
-        * not be enforced as they are an XHProf intrinsic behavior.
-        *
-        * @param array $data
-        * @see getRawData()
-        */
-       public function loadRawData( array $data ) {
-               $this->stop();
-               $this->inclusive = null;
-               $this->complete = null;
-               $this->hieraData = $this->pruneData( $data );
-       }
-
-       /**
-        * Get raw data collected by xhprof.
-        *
-        * If data collection has not been stopped yet this method will halt
-        * collection to gather the profiling data.
-        *
-        * Each key in the returned array is an edge label for the call graph in
-        * the form "caller==>callee". There is once special case edge labled
-        * simply "main()" which represents the global scope entry point of the
-        * application.
-        *
-        * XHProf will collect different data depending on the flags that are used:
-        * - ct:    Number of matching events seen.
-        * - wt:    Inclusive elapsed wall time for this event in microseconds.
-        * - cpu:   Inclusive elapsed cpu time for this event in microseconds.
-        *          (XHPROF_FLAGS_CPU)
-        * - mu:    Delta of memory usage from start to end of callee in bytes.
-        *          (XHPROF_FLAGS_MEMORY)
-        * - pmu:   Delta of peak memory usage from start to end of callee in
-        *          bytes. (XHPROF_FLAGS_MEMORY)
-        * - alloc: Delta of amount memory requested from malloc() by the callee,
-        *          in bytes. (XHPROF_FLAGS_MALLOC)
-        * - free:  Delta of amount of memory passed to free() by the callee, in
-        *          bytes. (XHPROF_FLAGS_MALLOC)
-        *
-        * @return array
-        * @see stop()
-        * @see getInclusiveMetrics()
-        * @see getCompleteMetrics()
-        */
-       public function getRawData() {
-               return $this->stop();
+               self::$enabled = true;
+               xhprof_enable( $flags, $options );
        }
 
        /**
-        * Convert an xhprof data key into an array of ['parent', 'child']
-        * function names.
-        *
-        * The resulting array is left padded with nulls, so a key
-        * with no parent (eg 'main()') will return [null, 'function'].
+        * Stop xhprof profiler
         *
-        * @return array
+        * @return array|null xhprof data from the run, or null if xhprof was not running.
         */
-       public static function splitKey( $key ) {
-               return array_pad( explode( '==>', $key, 2 ), -2, null );
-       }
-
-       /**
-        * Remove data for functions that are not included in the 'include'
-        * configuration array.
-        *
-        * @param array $data Raw xhprof data
-        * @return array
-        */
-       protected function pruneData( $data ) {
-               if ( !$this->config['include'] ) {
-                       return $data;
-               }
-
-               $want = array_fill_keys( $this->config['include'], true );
-               $want['main()'] = true;
-
-               $keep = [];
-               foreach ( $data as $key => $stats ) {
-                       list( $parent, $child ) = self::splitKey( $key );
-                       if ( isset( $want[$parent] ) || isset( $want[$child] ) ) {
-                               $keep[$key] = $stats;
-                       }
+       public static function disable() {
+               if ( self::isEnabled() ) {
+                       self::$enabled = false;
+                       return xhprof_disable();
                }
-               return $keep;
-       }
-
-       /**
-        * Get the inclusive metrics for each function call. Inclusive metrics
-        * for given function include the metrics for all functions that were
-        * called from that function during the measurement period.
-        *
-        * If data collection has not been stopped yet this method will halt
-        * collection to gather the profiling data.
-        *
-        * See getRawData() for a description of the metric that are returned for
-        * each funcition call. The values for the wt, cpu, mu and pmu metrics are
-        * arrays with these values:
-        * - total: Cumulative value
-        * - min: Minimum value
-        * - mean: Mean (average) value
-        * - max: Maximum value
-        * - variance: Variance (spread) of the values
-        *
-        * @return array
-        * @see getRawData()
-        * @see getCompleteMetrics()
-        */
-       public function getInclusiveMetrics() {
-               if ( $this->inclusive === null ) {
-                       // Make sure we have data to work with
-                       $this->stop();
-
-                       $main = $this->hieraData['main()'];
-                       $hasCpu = isset( $main['cpu'] );
-                       $hasMu = isset( $main['mu'] );
-                       $hasAlloc = isset( $main['alloc'] );
-
-                       $this->inclusive = [];
-                       foreach ( $this->hieraData as $key => $stats ) {
-                               list( $parent, $child ) = self::splitKey( $key );
-                               if ( !isset( $this->inclusive[$child] ) ) {
-                                       $this->inclusive[$child] = [
-                                               'ct' => 0,
-                                               'wt' => new RunningStat(),
-                                       ];
-                                       if ( $hasCpu ) {
-                                               $this->inclusive[$child]['cpu'] = new RunningStat();
-                                       }
-                                       if ( $hasMu ) {
-                                               $this->inclusive[$child]['mu'] = new RunningStat();
-                                               $this->inclusive[$child]['pmu'] = new RunningStat();
-                                       }
-                                       if ( $hasAlloc ) {
-                                               $this->inclusive[$child]['alloc'] = new RunningStat();
-                                               $this->inclusive[$child]['free'] = new RunningStat();
-                                       }
-                               }
-
-                               $this->inclusive[$child]['ct'] += $stats['ct'];
-                               foreach ( $stats as $stat => $value ) {
-                                       if ( $stat === 'ct' ) {
-                                               continue;
-                                       }
-
-                                       if ( !isset( $this->inclusive[$child][$stat] ) ) {
-                                               // Ignore unknown stats
-                                               continue;
-                                       }
-
-                                       for ( $i = 0; $i < $stats['ct']; $i++ ) {
-                                               $this->inclusive[$child][$stat]->addObservation(
-                                                       $value / $stats['ct']
-                                               );
-                                       }
-                               }
-                       }
-
-                       // Convert RunningStat instances to static arrays and add
-                       // percentage stats.
-                       foreach ( $this->inclusive as $func => $stats ) {
-                               foreach ( $stats as $name => $value ) {
-                                       if ( $value instanceof RunningStat ) {
-                                               $total = $value->m1 * $value->n;
-                                               $percent = ( isset( $main[$name] ) && $main[$name] )
-                                                       ? 100 * $total / $main[$name]
-                                                       : 0;
-                                               $this->inclusive[$func][$name] = [
-                                                       'total' => $total,
-                                                       'min' => $value->min,
-                                                       'mean' => $value->m1,
-                                                       'max' => $value->max,
-                                                       'variance' => $value->m2,
-                                                       'percent' => $percent,
-                                               ];
-                                       }
-                               }
-                       }
-
-                       uasort( $this->inclusive, self::makeSortFunction(
-                               $this->config['sort'], 'total'
-                       ) );
-               }
-               return $this->inclusive;
-       }
-
-       /**
-        * Get the inclusive and exclusive metrics for each function call.
-        *
-        * If data collection has not been stopped yet this method will halt
-        * collection to gather the profiling data.
-        *
-        * In addition to the normal data contained in the inclusive metrics, the
-        * metrics have an additional 'exclusive' measurement which is the total
-        * minus the totals of all child function calls.
-        *
-        * @return array
-        * @see getRawData()
-        * @see getInclusiveMetrics()
-        */
-       public function getCompleteMetrics() {
-               if ( $this->complete === null ) {
-                       // Start with inclusive data
-                       $this->complete = $this->getInclusiveMetrics();
-
-                       foreach ( $this->complete as $func => $stats ) {
-                               foreach ( $stats as $stat => $value ) {
-                                       if ( $stat === 'ct' ) {
-                                               continue;
-                                       }
-                                       // Initialize exclusive data with inclusive totals
-                                       $this->complete[$func][$stat]['exclusive'] = $value['total'];
-                               }
-                               // Add sapce for call tree information to be filled in later
-                               $this->complete[$func]['calls'] = [];
-                               $this->complete[$func]['subcalls'] = [];
-                       }
-
-                       foreach ( $this->hieraData as $key => $stats ) {
-                               list( $parent, $child ) = self::splitKey( $key );
-                               if ( $parent !== null ) {
-                                       // Track call tree information
-                                       $this->complete[$child]['calls'][$parent] = $stats;
-                                       $this->complete[$parent]['subcalls'][$child] = $stats;
-                               }
-
-                               if ( isset( $this->complete[$parent] ) ) {
-                                       // Deduct child inclusive data from exclusive data
-                                       foreach ( $stats as $stat => $value ) {
-                                               if ( $stat === 'ct' ) {
-                                                       continue;
-                                               }
-
-                                               if ( !isset( $this->complete[$parent][$stat] ) ) {
-                                                       // Ignore unknown stats
-                                                       continue;
-                                               }
-
-                                               $this->complete[$parent][$stat]['exclusive'] -= $value;
-                                       }
-                               }
-                       }
-
-                       uasort( $this->complete, self::makeSortFunction(
-                               $this->config['sort'], 'exclusive'
-                       ) );
-               }
-               return $this->complete;
-       }
-
-       /**
-        * Get a list of all callers of a given function.
-        *
-        * @param string $function Function name
-        * @return array
-        * @see getEdges()
-        */
-       public function getCallers( $function ) {
-               $edges = $this->getCompleteMetrics();
-               if ( isset( $edges[$function]['calls'] ) ) {
-                       return array_keys( $edges[$function]['calls'] );
-               } else {
-                       return [];
-               }
-       }
-
-       /**
-        * Get a list of all callees from a given function.
-        *
-        * @param string $function Function name
-        * @return array
-        * @see getEdges()
-        */
-       public function getCallees( $function ) {
-               $edges = $this->getCompleteMetrics();
-               if ( isset( $edges[$function]['subcalls'] ) ) {
-                       return array_keys( $edges[$function]['subcalls'] );
-               } else {
-                       return [];
-               }
-       }
-
-       /**
-        * Find the critical path for the given metric.
-        *
-        * @param string $metric Metric to find critical path for
-        * @return array
-        */
-       public function getCriticalPath( $metric = 'wt' ) {
-               $this->stop();
-               $func = 'main()';
-               $path = [
-                       $func => $this->hieraData[$func],
-               ];
-               while ( $func ) {
-                       $callees = $this->getCallees( $func );
-                       $maxCallee = null;
-                       $maxCall = null;
-                       foreach ( $callees as $callee ) {
-                               $call = "{$func}==>{$callee}";
-                               if ( $maxCall === null ||
-                                       $this->hieraData[$call][$metric] >
-                                               $this->hieraData[$maxCall][$metric]
-                               ) {
-                                       $maxCallee = $callee;
-                                       $maxCall = $call;
-                               }
-                       }
-                       if ( $maxCall !== null ) {
-                               $path[$maxCall] = $this->hieraData[$maxCall];
-                       }
-                       $func = $maxCallee;
-               }
-               return $path;
-       }
-
-       /**
-        * Make a closure to use as a sort function. The resulting function will
-        * sort by descending numeric values (largest value first).
-        *
-        * @param string $key Data key to sort on
-        * @param string $sub Sub key to sort array values on
-        * @return Closure
-        */
-       public static function makeSortFunction( $key, $sub ) {
-               return function ( $a, $b ) use ( $key, $sub ) {
-                       if ( isset( $a[$key] ) && isset( $b[$key] ) ) {
-                               // Descending sort: larger values will be first in result.
-                               // Assumes all values are numeric.
-                               // Values for 'main()' will not have sub keys
-                               $valA = is_array( $a[$key] ) ? $a[$key][$sub] : $a[$key];
-                               $valB = is_array( $b[$key] ) ? $b[$key][$sub] : $b[$key];
-                               return $valB - $valA;
-                       } else {
-                               // Sort datum with the key before those without
-                               return isset( $a[$key] ) ? -1 : 1;
-                       }
-               };
        }
 }
diff --git a/includes/libs/XhprofData.php b/includes/libs/XhprofData.php
new file mode 100644 (file)
index 0000000..c6da432
--- /dev/null
@@ -0,0 +1,384 @@
+<?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 RunningStat\RunningStat;
+
+/**
+ * Convenience class for working with XHProf profiling data
+ * <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.
+ *
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ * @since 1.28
+ */
+class XhprofData {
+
+       /**
+        * @var array $config
+        */
+       protected $config;
+
+       /**
+        * Hierarchical profiling data returned by xhprof.
+        * @var array $hieraData
+        */
+       protected $hieraData;
+
+       /**
+        * Per-function inclusive data.
+        * @var array $inclusive
+        */
+       protected $inclusive;
+
+       /**
+        * Per-function inclusive and exclusive data.
+        * @var array $complete
+        */
+       protected $complete;
+
+       /**
+        * Configuration data can contain:
+        * - include: Array of function names to include in profiling.
+        * - sort:    Key to sort per-function reports on.
+        *
+        * @param array $data Xhprof profiling data, as returned by xhprof_disable()
+        * @param array $config
+        */
+       public function __construct( array $data, array $config = [] ) {
+               $this->config = array_merge( [
+                       'include' => null,
+                       'sort' => 'wt',
+               ], $config );
+
+               $this->hieraData = $this->pruneData( $data );
+       }
+
+       /**
+        * Get raw data collected by xhprof.
+        *
+        * Each key in the returned array is an edge label for the call graph in
+        * the form "caller==>callee". There is once special case edge labled
+        * simply "main()" which represents the global scope entry point of the
+        * application.
+        *
+        * XHProf will collect different data depending on the flags that are used:
+        * - ct:    Number of matching events seen.
+        * - wt:    Inclusive elapsed wall time for this event in microseconds.
+        * - cpu:   Inclusive elapsed cpu time for this event in microseconds.
+        *          (XHPROF_FLAGS_CPU)
+        * - mu:    Delta of memory usage from start to end of callee in bytes.
+        *          (XHPROF_FLAGS_MEMORY)
+        * - pmu:   Delta of peak memory usage from start to end of callee in
+        *          bytes. (XHPROF_FLAGS_MEMORY)
+        * - alloc: Delta of amount memory requested from malloc() by the callee,
+        *          in bytes. (XHPROF_FLAGS_MALLOC)
+        * - free:  Delta of amount of memory passed to free() by the callee, in
+        *          bytes. (XHPROF_FLAGS_MALLOC)
+        *
+        * @return array
+        * @see getInclusiveMetrics()
+        * @see getCompleteMetrics()
+        */
+       public function getRawData() {
+               return $this->hieraData;
+       }
+
+       /**
+        * Convert an xhprof data key into an array of ['parent', 'child']
+        * function names.
+        *
+        * The resulting array is left padded with nulls, so a key
+        * with no parent (eg 'main()') will return [null, 'function'].
+        *
+        * @return array
+        */
+       public static function splitKey( $key ) {
+               return array_pad( explode( '==>', $key, 2 ), -2, null );
+       }
+
+       /**
+        * Remove data for functions that are not included in the 'include'
+        * configuration array.
+        *
+        * @param array $data Raw xhprof data
+        * @return array
+        */
+       protected function pruneData( $data ) {
+               if ( !$this->config['include'] ) {
+                       return $data;
+               }
+
+               $want = array_fill_keys( $this->config['include'], true );
+               $want['main()'] = true;
+
+               $keep = [];
+               foreach ( $data as $key => $stats ) {
+                       list( $parent, $child ) = self::splitKey( $key );
+                       if ( isset( $want[$parent] ) || isset( $want[$child] ) ) {
+                               $keep[$key] = $stats;
+                       }
+               }
+               return $keep;
+       }
+
+       /**
+        * Get the inclusive metrics for each function call. Inclusive metrics
+        * for given function include the metrics for all functions that were
+        * called from that function during the measurement period.
+        *
+        * See getRawData() for a description of the metric that are returned for
+        * each funcition call. The values for the wt, cpu, mu and pmu metrics are
+        * arrays with these values:
+        * - total: Cumulative value
+        * - min: Minimum value
+        * - mean: Mean (average) value
+        * - max: Maximum value
+        * - variance: Variance (spread) of the values
+        *
+        * @return array
+        * @see getRawData()
+        * @see getCompleteMetrics()
+        */
+       public function getInclusiveMetrics() {
+               if ( $this->inclusive === null ) {
+                       $main = $this->hieraData['main()'];
+                       $hasCpu = isset( $main['cpu'] );
+                       $hasMu = isset( $main['mu'] );
+                       $hasAlloc = isset( $main['alloc'] );
+
+                       $this->inclusive = [];
+                       foreach ( $this->hieraData as $key => $stats ) {
+                               list( $parent, $child ) = self::splitKey( $key );
+                               if ( !isset( $this->inclusive[$child] ) ) {
+                                       $this->inclusive[$child] = [
+                                               'ct' => 0,
+                                               'wt' => new RunningStat(),
+                                       ];
+                                       if ( $hasCpu ) {
+                                               $this->inclusive[$child]['cpu'] = new RunningStat();
+                                       }
+                                       if ( $hasMu ) {
+                                               $this->inclusive[$child]['mu'] = new RunningStat();
+                                               $this->inclusive[$child]['pmu'] = new RunningStat();
+                                       }
+                                       if ( $hasAlloc ) {
+                                               $this->inclusive[$child]['alloc'] = new RunningStat();
+                                               $this->inclusive[$child]['free'] = new RunningStat();
+                                       }
+                               }
+
+                               $this->inclusive[$child]['ct'] += $stats['ct'];
+                               foreach ( $stats as $stat => $value ) {
+                                       if ( $stat === 'ct' ) {
+                                               continue;
+                                       }
+
+                                       if ( !isset( $this->inclusive[$child][$stat] ) ) {
+                                               // Ignore unknown stats
+                                               continue;
+                                       }
+
+                                       for ( $i = 0; $i < $stats['ct']; $i++ ) {
+                                               $this->inclusive[$child][$stat]->addObservation(
+                                                       $value / $stats['ct']
+                                               );
+                                       }
+                               }
+                       }
+
+                       // Convert RunningStat instances to static arrays and add
+                       // percentage stats.
+                       foreach ( $this->inclusive as $func => $stats ) {
+                               foreach ( $stats as $name => $value ) {
+                                       if ( $value instanceof RunningStat ) {
+                                               $total = $value->m1 * $value->n;
+                                               $percent = ( isset( $main[$name] ) && $main[$name] )
+                                                       ? 100 * $total / $main[$name]
+                                                       : 0;
+                                               $this->inclusive[$func][$name] = [
+                                                       'total' => $total,
+                                                       'min' => $value->min,
+                                                       'mean' => $value->m1,
+                                                       'max' => $value->max,
+                                                       'variance' => $value->m2,
+                                                       'percent' => $percent,
+                                               ];
+                                       }
+                               }
+                       }
+
+                       uasort( $this->inclusive, self::makeSortFunction(
+                               $this->config['sort'], 'total'
+                       ) );
+               }
+               return $this->inclusive;
+       }
+
+       /**
+        * Get the inclusive and exclusive metrics for each function call.
+        *
+        * In addition to the normal data contained in the inclusive metrics, the
+        * metrics have an additional 'exclusive' measurement which is the total
+        * minus the totals of all child function calls.
+        *
+        * @return array
+        * @see getRawData()
+        * @see getInclusiveMetrics()
+        */
+       public function getCompleteMetrics() {
+               if ( $this->complete === null ) {
+                       // Start with inclusive data
+                       $this->complete = $this->getInclusiveMetrics();
+
+                       foreach ( $this->complete as $func => $stats ) {
+                               foreach ( $stats as $stat => $value ) {
+                                       if ( $stat === 'ct' ) {
+                                               continue;
+                                       }
+                                       // Initialize exclusive data with inclusive totals
+                                       $this->complete[$func][$stat]['exclusive'] = $value['total'];
+                               }
+                               // Add sapce for call tree information to be filled in later
+                               $this->complete[$func]['calls'] = [];
+                               $this->complete[$func]['subcalls'] = [];
+                       }
+
+                       foreach ( $this->hieraData as $key => $stats ) {
+                               list( $parent, $child ) = self::splitKey( $key );
+                               if ( $parent !== null ) {
+                                       // Track call tree information
+                                       $this->complete[$child]['calls'][$parent] = $stats;
+                                       $this->complete[$parent]['subcalls'][$child] = $stats;
+                               }
+
+                               if ( isset( $this->complete[$parent] ) ) {
+                                       // Deduct child inclusive data from exclusive data
+                                       foreach ( $stats as $stat => $value ) {
+                                               if ( $stat === 'ct' ) {
+                                                       continue;
+                                               }
+
+                                               if ( !isset( $this->complete[$parent][$stat] ) ) {
+                                                       // Ignore unknown stats
+                                                       continue;
+                                               }
+
+                                               $this->complete[$parent][$stat]['exclusive'] -= $value;
+                                       }
+                               }
+                       }
+
+                       uasort( $this->complete, self::makeSortFunction(
+                               $this->config['sort'], 'exclusive'
+                       ) );
+               }
+               return $this->complete;
+       }
+
+       /**
+        * Get a list of all callers of a given function.
+        *
+        * @param string $function Function name
+        * @return array
+        * @see getEdges()
+        */
+       public function getCallers( $function ) {
+               $edges = $this->getCompleteMetrics();
+               if ( isset( $edges[$function]['calls'] ) ) {
+                       return array_keys( $edges[$function]['calls'] );
+               } else {
+                       return [];
+               }
+       }
+
+       /**
+        * Get a list of all callees from a given function.
+        *
+        * @param string $function Function name
+        * @return array
+        * @see getEdges()
+        */
+       public function getCallees( $function ) {
+               $edges = $this->getCompleteMetrics();
+               if ( isset( $edges[$function]['subcalls'] ) ) {
+                       return array_keys( $edges[$function]['subcalls'] );
+               } else {
+                       return [];
+               }
+       }
+
+       /**
+        * Find the critical path for the given metric.
+        *
+        * @param string $metric Metric to find critical path for
+        * @return array
+        */
+       public function getCriticalPath( $metric = 'wt' ) {
+               $func = 'main()';
+               $path = [
+                       $func => $this->hieraData[$func],
+               ];
+               while ( $func ) {
+                       $callees = $this->getCallees( $func );
+                       $maxCallee = null;
+                       $maxCall = null;
+                       foreach ( $callees as $callee ) {
+                               $call = "{$func}==>{$callee}";
+                               if ( $maxCall === null ||
+                                       $this->hieraData[$call][$metric] >
+                                               $this->hieraData[$maxCall][$metric]
+                               ) {
+                                       $maxCallee = $callee;
+                                       $maxCall = $call;
+                               }
+                       }
+                       if ( $maxCall !== null ) {
+                               $path[$maxCall] = $this->hieraData[$maxCall];
+                       }
+                       $func = $maxCallee;
+               }
+               return $path;
+       }
+
+       /**
+        * Make a closure to use as a sort function. The resulting function will
+        * sort by descending numeric values (largest value first).
+        *
+        * @param string $key Data key to sort on
+        * @param string $sub Sub key to sort array values on
+        * @return Closure
+        */
+       public static function makeSortFunction( $key, $sub ) {
+               return function ( $a, $b ) use ( $key, $sub ) {
+                       if ( isset( $a[$key] ) && isset( $b[$key] ) ) {
+                               // Descending sort: larger values will be first in result.
+                               // Assumes all values are numeric.
+                               // Values for 'main()' will not have sub keys
+                               $valA = is_array( $a[$key] ) ? $a[$key][$sub] : $a[$key];
+                               $valB = is_array( $b[$key] ) ? $b[$key][$sub] : $b[$key];
+                               return $valB - $valA;
+                       } else {
+                               // Sort datum with the key before those without
+                               return isset( $a[$key] ) ? -1 : 1;
+                       }
+               };
+       }
+}
index 8e3c0a5..dd22d91 100644 (file)
@@ -252,10 +252,12 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
        abstract public function delete( $key );
 
        /**
-        * Merge changes into the existing cache value (possibly creating a new one).
+        * Merge changes into the existing cache value (possibly creating a new one)
+        *
         * The callback function returns the new value given the current value
         * (which will be false if not present), and takes the arguments:
-        * (this BagOStuff, cache key, current value).
+        * (this BagOStuff, cache key, current value, TTL).
+        * The TTL parameter is reference set to $exptime. It can be overriden in the callback.
         *
         * @param string $key
         * @param callable $callback Callback method to be executed
@@ -285,14 +287,18 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
        protected function mergeViaCas( $key, $callback, $exptime = 0, $attempts = 10 ) {
                do {
                        $this->clearLastError();
+                       $reportDupes = $this->reportDupes;
+                       $this->reportDupes = false;
                        $casToken = null; // passed by reference
                        $currentValue = $this->getWithToken( $key, $casToken, self::READ_LATEST );
+                       $this->reportDupes = $reportDupes;
+
                        if ( $this->getLastError() ) {
                                return false; // don't spam retries (retry only on races)
                        }
 
                        // Derive the new value from the old value
-                       $value = call_user_func( $callback, $this, $key, $currentValue );
+                       $value = call_user_func( $callback, $this, $key, $currentValue, $exptime );
 
                        $this->clearLastError();
                        if ( $value === false ) {
@@ -342,12 +348,16 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
                }
 
                $this->clearLastError();
+               $reportDupes = $this->reportDupes;
+               $this->reportDupes = false;
                $currentValue = $this->get( $key, self::READ_LATEST );
+               $this->reportDupes = $reportDupes;
+
                if ( $this->getLastError() ) {
                        $success = false;
                } else {
                        // Derive the new value from the old value
-                       $value = call_user_func( $callback, $this, $key, $currentValue );
+                       $value = call_user_func( $callback, $this, $key, $currentValue, $exptime );
                        if ( $value === false ) {
                                $success = true; // do nothing
                        } else {
index fa465c7..91e7934 100644 (file)
@@ -29,7 +29,6 @@
  * @since 1.27
  */
 interface IExpiringStore {
-
        // Constants for TTL values, in seconds
        const TTL_MINUTE = 60;
        const TTL_HOUR = 3600;
@@ -38,5 +37,9 @@ interface IExpiringStore {
        const TTL_MONTH = 2592000; // 30 * 24 * 3600
        const TTL_YEAR = 31536000; // 365 * 24 * 3600
 
+       // Shorthand process cache TTLs (useful for web requests and CLI mode)
+       const TTL_PROC_SHORT = 3; // reasonably strict cache time that last the life of quick requests
+       const TTL_PROC_LONG = 30; // loose cache time that can survive slow web requests
+
        const TTL_INDEFINITE = 0;
 }
index 59322b6..668135d 100644 (file)
@@ -487,7 +487,7 @@ class MemcachedClient {
         */
        public function get_multi( $keys ) {
                if ( !$this->_active ) {
-                       return false;
+                       return array();
                }
 
                if ( isset( $this->stats['get_multi'] ) ) {
index 18cc10e..470a38c 100644 (file)
@@ -110,12 +110,12 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        /** Cache format version number */
        const VERSION = 1;
 
-       const FLD_VERSION = 0;
-       const FLD_VALUE = 1;
-       const FLD_TTL = 2;
-       const FLD_TIME = 3;
-       const FLD_FLAGS = 4;
-       const FLD_HOLDOFF = 5;
+       const FLD_VERSION = 0; // key to cache version number
+       const FLD_VALUE = 1; // key to the cached value
+       const FLD_TTL = 2; // key to the original TTL
+       const FLD_TIME = 3; // key to the cache time
+       const FLD_FLAGS = 4; // key to the flags bitfield
+       const FLD_HOLDOFF = 5; // key to any hold-off TTL
 
        /** @var integer Treat this value as expired-on-arrival */
        const FLG_STALE = 1;
@@ -377,8 +377,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @return bool Success
         */
        final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
+               $now = microtime( true );
                $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
-               $age = isset( $opts['since'] ) ? max( 0, microtime( true ) - $opts['since'] ) : 0;
+               $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
                $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
 
                // Do not cache potentially uncommitted data as it might get rolled back
@@ -413,7 +414,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                }
 
                // Wrap that value with time/TTL/version metadata
-               $wrapped = $this->wrap( $value, $ttl ) + $wrapExtra;
+               $wrapped = $this->wrap( $value, $ttl, $now ) + $wrapExtra;
 
                $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
                        return ( is_string( $cWrapped ) )
@@ -1009,14 +1010,15 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *
         * @param mixed $value
         * @param integer $ttl [0=forever]
+        * @param float $now Unix Current timestamp just before calling set()
         * @return array
         */
-       protected function wrap( $value, $ttl ) {
+       protected function wrap( $value, $ttl, $now ) {
                return [
                        self::FLD_VERSION => self::VERSION,
                        self::FLD_VALUE => $value,
                        self::FLD_TTL => $ttl,
-                       self::FLD_TIME => microtime( true )
+                       self::FLD_TIME => $now
                ];
        }
 
@@ -1024,7 +1026,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * Do not use this method outside WANObjectCache
         *
         * @param array|string|bool $wrapped
-        * @param float $now Unix Current timestamp (preferrable pre-query)
+        * @param float $now Unix Current timestamp (preferrably pre-query)
         * @return array (mixed; false if absent/invalid, current time left)
         */
        protected function unwrap( $wrapped, $now ) {
index 7b59751..da48e00 100644 (file)
@@ -33,6 +33,14 @@ interface LinkTarget {
         */
        public function getNamespace();
 
+       /**
+        * Convenience function to test if it is in the namespace
+        *
+        * @param int $ns
+        * @return bool
+        */
+       public function inNamespace( $ns );
+
        /**
         * Get the link fragment (i.e. the bit after the #) in text form.
         *
index 664c111..1d0bdf6 100644 (file)
@@ -25,6 +25,8 @@
  */
 use MediaWiki\Linker\LinkTarget;
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * This module processes the email notifications when the current page is
  * changed. It looks up the table watchlist to find out which users are watching
@@ -92,7 +94,7 @@ class EmailNotification {
                if ( !$config->get( 'EnotifWatchlist' ) && !$config->get( 'ShowUpdatedMarker' ) ) {
                        return [];
                }
-               return WatchedItemStore::getDefaultInstance()->updateNotificationTimestamp(
+               return MediaWikiServices::getInstance()->getWatchedItemStore()->updateNotificationTimestamp(
                        $editor,
                        $linkTarget,
                        $timestamp
@@ -125,7 +127,7 @@ class EmailNotification {
                $config = RequestContext::getMain()->getConfig();
                $watchers = [];
                if ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) ) {
-                       $watchers = WatchedItemStore::getDefaultInstance()->updateNotificationTimestamp(
+                       $watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->updateNotificationTimestamp(
                                $editor,
                                $title,
                                $timestamp
index 8ec7298..5f23855 100644 (file)
@@ -193,7 +193,7 @@ class GIFHandler extends BitmapHandler {
         *
         * Shown in the &query=imageinfo&iiprop=size api query.
         *
-        * @param $file File
+        * @param File $file
         * @return float The duration of the file.
         */
        public function getLength( $file ) {
index f9af101..8a3e001 100644 (file)
@@ -180,7 +180,7 @@ class PNGHandler extends BitmapHandler {
         *
         * Shown in the &query=imageinfo&iiprop=size api query.
         *
-        * @param $file File
+        * @param File $file
         * @return float The duration of the file.
         */
        public function getLength( $file ) {
index f1f2161..3287fac 100644 (file)
@@ -288,9 +288,9 @@ abstract class TransformationalImageHandler extends ImageHandler {
        /**
         * Get the source file for the transform
         *
-        * @param $file File
-        * @param $params Array
-        * @return Array Array with keys  width, height and path.
+        * @param File $file
+        * @param array $params
+        * @return array Array with keys  width, height and path.
         */
        protected function getThumbnailSource( $file, $params ) {
                return $file->getThumbnailSource( $params );
index 1ef4d26..b4f515a 100644 (file)
@@ -184,3 +184,5 @@ chemical/x-mdl-sdfile sdf
 chemical/x-mdl-rxnfile rxn
 chemical/x-mdl-rdfile rd
 chemical/x-mdl-rgfile rg
+application/x-amf amf
+application/sla stl
index 24846e6..e1bb2db 100644 (file)
@@ -23,6 +23,7 @@
 
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Services\ServiceDisabledException;
 
 /**
  * Functions to get cache objects
@@ -226,7 +227,18 @@ class ObjectCache {
                                return self::getInstance( $candidate );
                        }
                }
-               return self::getInstance( CACHE_DB );
+
+               try {
+                       // Make sure we actually have a DB backend before falling back to CACHE_DB
+                       MediaWikiServices::getInstance()->getDBLoadBalancer();
+                       $candidate = CACHE_DB;
+               } catch ( ServiceDisabledException $e ) {
+                       // The LoadBalancer is disabled, probably because
+                       // MediaWikiServices::disableStorageBackend was called.
+                       $candidate = CACHE_NONE;
+               }
+
+               return self::getInstance( $candidate );
        }
 
        /**
index 61e6926..90508da 100644 (file)
@@ -310,7 +310,8 @@ class RedisBagOStuff extends BagOStuff {
         * @return mixed
         */
        protected function unserialize( $data ) {
-               return ctype_digit( $data ) ? intval( $data ) : unserialize( $data );
+               $int = intval( $data );
+               return $data === (string)$int ? $int : unserialize( $data );
        }
 
        /**
index 6c42e34..4c9eaed 100644 (file)
@@ -2204,7 +2204,7 @@ class Article implements Page {
 
        /**
         * Call to WikiPage function for backwards compatibility.
-        * @see WikiPage::getActionOverrides
+        * @see ContentHandler::getActionOverrides
         */
        public function getActionOverrides() {
                return $this->mPage->getActionOverrides();
index a6b9915..0dc28bd 100644 (file)
  * @ingroup Media
  */
 class WikiFilePage extends WikiPage {
-       /**
-        * @var File
-        */
+       /** @var File */
        protected $mFile = false;
+       /** @var LocalRepo */
        protected $mRepo = null;
+       /** @var bool */
        protected $mFileLoaded = false;
+       /** @var array */
        protected $mDupes = null;
 
        public function __construct( $title ) {
@@ -170,7 +171,6 @@ class WikiFilePage extends WikiPage {
                if ( $this->mFile->exists() ) {
                        wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
                        DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->mTitle, 'imagelinks' ) );
-                       $this->mFile->upgradeRow();
                        $this->mFile->purgeCache( [ 'forThumbRefresh' => true ] );
                } else {
                        wfDebug( 'ImagePage::doPurge no image for '
index 06785b7..8702156 100644 (file)
@@ -183,18 +183,12 @@ class WikiPage implements Page, IDBAccessObject {
        }
 
        /**
-        * Returns overrides for action handlers.
-        * Classes listed here will be used instead of the default one when
-        * (and only when) $wgActions[$action] === true. This allows subclasses
-        * to override the default behavior.
-        *
         * @todo Move this UI stuff somewhere else
         *
-        * @return array
+        * @see ContentHandler::getActionOverrides
         */
        public function getActionOverrides() {
-               $content_handler = $this->getContentHandler();
-               return $content_handler->getActionOverrides();
+               return $this->getContentHandler()->getActionOverrides();
        }
 
        /**
@@ -2115,7 +2109,13 @@ class WikiPage implements Page, IDBAccessObject {
                        : '';
                $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
 
+               if ( $edit->output ) {
+                       $edit->output->setCacheTime( wfTimestampNow() );
+               }
+
+               // Process cache the result
                $this->mPreparedEdit = $edit;
+
                return $edit;
        }
 
diff --git a/includes/parser/BlockLevelPass.php b/includes/parser/BlockLevelPass.php
new file mode 100644 (file)
index 0000000..cbacd34
--- /dev/null
@@ -0,0 +1,535 @@
+<?php
+
+/**
+ * This is the part of the wikitext parser which handles automatic paragraphs
+ * and conversion of start-of-line prefixes to HTML lists.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+class BlockLevelPass {
+       private $DTopen = false;
+       private $inPre = false;
+       private $lastSection = '';
+       private $linestart;
+       private $text;
+
+       # State constants for the definition list colon extraction
+       const COLON_STATE_TEXT = 0;
+       const COLON_STATE_TAG = 1;
+       const COLON_STATE_TAGSTART = 2;
+       const COLON_STATE_CLOSETAG = 3;
+       const COLON_STATE_TAGSLASH = 4;
+       const COLON_STATE_COMMENT = 5;
+       const COLON_STATE_COMMENTDASH = 6;
+       const COLON_STATE_COMMENTDASHDASH = 7;
+
+       /**
+        * Make lists from lines starting with ':', '*', '#', etc.
+        *
+        * @param string $text
+        * @param bool $lineStart Whether or not this is at the start of a line.
+        * @return string The lists rendered as HTML
+        */
+       public static function doBlockLevels( $text, $lineStart ) {
+               $pass = new self( $text, $lineStart );
+               return $pass->execute();
+       }
+
+       /**
+        * Private constructor
+        */
+       private function __construct( $text, $lineStart ) {
+               $this->text = $text;
+               $this->lineStart = $lineStart;
+       }
+
+       /**
+        * If a pre or p is open, return the corresponding close tag and update
+        * the state. If no tag is open, return an empty string.
+        * @return string
+        */
+       private function closeParagraph() {
+               $result = '';
+               if ( $this->lastSection !== '' ) {
+                       $result = '</' . $this->lastSection . ">\n";
+               }
+               $this->inPre = false;
+               $this->lastSection = '';
+               return $result;
+       }
+
+       /**
+        * getCommon() returns the length of the longest common substring
+        * of both arguments, starting at the beginning of both.
+        *
+        * @param string $st1
+        * @param string $st2
+        *
+        * @return int
+        */
+       private function getCommon( $st1, $st2 ) {
+               $shorter = min( strlen( $st1 ), strlen( $st2 ) );
+
+               for ( $i = 0; $i < $shorter; ++$i ) {
+                       if ( $st1[$i] !== $st2[$i] ) {
+                               break;
+                       }
+               }
+               return $i;
+       }
+
+       /**
+        * Open the list item element identified by the prefix character.
+        *
+        * @param string $char
+        *
+        * @return string
+        */
+       private function openList( $char ) {
+               $result = $this->closeParagraph();
+
+               if ( '*' === $char ) {
+                       $result .= "<ul><li>";
+               } elseif ( '#' === $char ) {
+                       $result .= "<ol><li>";
+               } elseif ( ':' === $char ) {
+                       $result .= "<dl><dd>";
+               } elseif ( ';' === $char ) {
+                       $result .= "<dl><dt>";
+                       $this->DTopen = true;
+               } else {
+                       $result = '<!-- ERR 1 -->';
+               }
+
+               return $result;
+       }
+
+       /**
+        * Close the current list item and open the next one.
+        * @param string $char
+        *
+        * @return string
+        */
+       private function nextItem( $char ) {
+               if ( '*' === $char || '#' === $char ) {
+                       return "</li>\n<li>";
+               } elseif ( ':' === $char || ';' === $char ) {
+                       $close = "</dd>\n";
+                       if ( $this->DTopen ) {
+                               $close = "</dt>\n";
+                       }
+                       if ( ';' === $char ) {
+                               $this->DTopen = true;
+                               return $close . '<dt>';
+                       } else {
+                               $this->DTopen = false;
+                               return $close . '<dd>';
+                       }
+               }
+               return '<!-- ERR 2 -->';
+       }
+
+       /**
+        * Close the current list item identified by the prefix character.
+        * @param string $char
+        *
+        * @return string
+        */
+       private function closeList( $char ) {
+               if ( '*' === $char ) {
+                       $text = "</li></ul>";
+               } elseif ( '#' === $char ) {
+                       $text = "</li></ol>";
+               } elseif ( ':' === $char ) {
+                       if ( $this->DTopen ) {
+                               $this->DTopen = false;
+                               $text = "</dt></dl>";
+                       } else {
+                               $text = "</dd></dl>";
+                       }
+               } else {
+                       return '<!-- ERR 3 -->';
+               }
+               return $text;
+       }
+
+       /**
+        * Execute the pass.
+        * @return string
+        */
+       private function execute() {
+               $text = $this->text;
+               # Parsing through the text line by line.  The main thing
+               # happening here is handling of block-level elements p, pre,
+               # and making lists from lines starting with * # : etc.
+               $textLines = StringUtils::explode( "\n", $text );
+
+               $lastPrefix = $output = '';
+               $this->DTopen = $inBlockElem = false;
+               $prefixLength = 0;
+               $pendingPTag = false;
+               $inBlockquote = false;
+
+               foreach ( $textLines as $inputLine ) {
+                       # Fix up $lineStart
+                       if ( !$this->lineStart ) {
+                               $output .= $inputLine;
+                               $this->lineStart = true;
+                               continue;
+                       }
+                       # * = ul
+                       # # = ol
+                       # ; = dt
+                       # : = dd
+
+                       $lastPrefixLength = strlen( $lastPrefix );
+                       $preCloseMatch = preg_match( '/<\\/pre/i', $inputLine );
+                       $preOpenMatch = preg_match( '/<pre/i', $inputLine );
+                       # If not in a <pre> element, scan for and figure out what prefixes are there.
+                       if ( !$this->inPre ) {
+                               # Multiple prefixes may abut each other for nested lists.
+                               $prefixLength = strspn( $inputLine, '*#:;' );
+                               $prefix = substr( $inputLine, 0, $prefixLength );
+
+                               # eh?
+                               # ; and : are both from definition-lists, so they're equivalent
+                               #  for the purposes of determining whether or not we need to open/close
+                               #  elements.
+                               $prefix2 = str_replace( ';', ':', $prefix );
+                               $t = substr( $inputLine, $prefixLength );
+                               $this->inPre = (bool)$preOpenMatch;
+                       } else {
+                               # Don't interpret any other prefixes in preformatted text
+                               $prefixLength = 0;
+                               $prefix = $prefix2 = '';
+                               $t = $inputLine;
+                       }
+
+                       # List generation
+                       if ( $prefixLength && $lastPrefix === $prefix2 ) {
+                               # Same as the last item, so no need to deal with nesting or opening stuff
+                               $output .= $this->nextItem( substr( $prefix, -1 ) );
+                               $pendingPTag = false;
+
+                               if ( substr( $prefix, -1 ) === ';' ) {
+                                       # The one nasty exception: definition lists work like this:
+                                       # ; title : definition text
+                                       # So we check for : in the remainder text to split up the
+                                       # title and definition, without b0rking links.
+                                       $term = $t2 = '';
+                                       if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
+                                               $t = $t2;
+                                               $output .= $term . $this->nextItem( ':' );
+                                       }
+                               }
+                       } elseif ( $prefixLength || $lastPrefixLength ) {
+                               # We need to open or close prefixes, or both.
+
+                               # Either open or close a level...
+                               $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
+                               $pendingPTag = false;
+
+                               # Close all the prefixes which aren't shared.
+                               while ( $commonPrefixLength < $lastPrefixLength ) {
+                                       $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
+                                       --$lastPrefixLength;
+                               }
+
+                               # Continue the current prefix if appropriate.
+                               if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
+                                       $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
+                               }
+
+                               # Open prefixes where appropriate.
+                               if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
+                                       $output .= "\n";
+                               }
+                               while ( $prefixLength > $commonPrefixLength ) {
+                                       $char = substr( $prefix, $commonPrefixLength, 1 );
+                                       $output .= $this->openList( $char );
+
+                                       if ( ';' === $char ) {
+                                               # @todo FIXME: This is dupe of code above
+                                               if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
+                                                       $t = $t2;
+                                                       $output .= $term . $this->nextItem( ':' );
+                                               }
+                                       }
+                                       ++$commonPrefixLength;
+                               }
+                               if ( !$prefixLength && $lastPrefix ) {
+                                       $output .= "\n";
+                               }
+                               $lastPrefix = $prefix2;
+                       }
+
+                       # If we have no prefixes, go to paragraph mode.
+                       if ( 0 == $prefixLength ) {
+                               # No prefix (not in list)--go to paragraph mode
+                               # @todo consider using a stack for nestable elements like span, table and div
+                               $openMatch = preg_match(
+                                       '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
+                                               . '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS',
+                                       $t
+                               );
+                               $closeMatch = preg_match(
+                                       '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
+                                               . '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
+                                               . Parser::MARKER_PREFIX
+                                               . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS',
+                                       $t
+                               );
+
+                               if ( $openMatch || $closeMatch ) {
+                                       $pendingPTag = false;
+                                       # @todo bug 5718: paragraph closed
+                                       $output .= $this->closeParagraph();
+                                       if ( $preOpenMatch && !$preCloseMatch ) {
+                                               $this->inPre = true;
+                                       }
+                                       $bqOffset = 0;
+                                       while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t,
+                                               $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset )
+                                       ) {
+                                               $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
+                                               $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
+                                       }
+                                       $inBlockElem = !$closeMatch;
+                               } elseif ( !$inBlockElem && !$this->inPre ) {
+                                       if ( ' ' == substr( $t, 0, 1 )
+                                               && ( $this->lastSection === 'pre' || trim( $t ) != '' )
+                                               && !$inBlockquote
+                                       ) {
+                                               # pre
+                                               if ( $this->lastSection !== 'pre' ) {
+                                                       $pendingPTag = false;
+                                                       $output .= $this->closeParagraph() . '<pre>';
+                                                       $this->lastSection = 'pre';
+                                               }
+                                               $t = substr( $t, 1 );
+                                       } else {
+                                               # paragraph
+                                               if ( trim( $t ) === '' ) {
+                                                       if ( $pendingPTag ) {
+                                                               $output .= $pendingPTag . '<br />';
+                                                               $pendingPTag = false;
+                                                               $this->lastSection = 'p';
+                                                       } else {
+                                                               if ( $this->lastSection !== 'p' ) {
+                                                                       $output .= $this->closeParagraph();
+                                                                       $this->lastSection = '';
+                                                                       $pendingPTag = '<p>';
+                                                               } else {
+                                                                       $pendingPTag = '</p><p>';
+                                                               }
+                                                       }
+                                               } else {
+                                                       if ( $pendingPTag ) {
+                                                               $output .= $pendingPTag;
+                                                               $pendingPTag = false;
+                                                               $this->lastSection = 'p';
+                                                       } elseif ( $this->lastSection !== 'p' ) {
+                                                               $output .= $this->closeParagraph() . '<p>';
+                                                               $this->lastSection = 'p';
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       # somewhere above we forget to get out of pre block (bug 785)
+                       if ( $preCloseMatch && $this->inPre ) {
+                               $this->inPre = false;
+                       }
+                       if ( $pendingPTag === false ) {
+                               $output .= $t;
+                               if ( $prefixLength === 0 ) {
+                                       $output .= "\n";
+                               }
+                       }
+               }
+               while ( $prefixLength ) {
+                       $output .= $this->closeList( $prefix2[$prefixLength - 1] );
+                       --$prefixLength;
+                       if ( !$prefixLength ) {
+                               $output .= "\n";
+                       }
+               }
+               if ( $this->lastSection !== '' ) {
+                       $output .= '</' . $this->lastSection . '>';
+                       $this->lastSection = '';
+               }
+
+               return $output;
+       }
+
+       /**
+        * Split up a string on ':', ignoring any occurrences inside tags
+        * to prevent illegal overlapping.
+        *
+        * @param string $str The string to split
+        * @param string &$before Set to everything before the ':'
+        * @param string &$after Set to everything after the ':'
+        * @throws MWException
+        * @return string The position of the ':', or false if none found
+        */
+       private function findColonNoLinks( $str, &$before, &$after ) {
+               $colonPos = strpos( $str, ':' );
+               if ( $colonPos === false ) {
+                       # Nothing to find!
+                       return false;
+               }
+
+               $ltPos = strpos( $str, '<' );
+               if ( $ltPos === false || $ltPos > $colonPos ) {
+                       # Easy; no tag nesting to worry about
+                       $before = substr( $str, 0, $colonPos );
+                       $after = substr( $str, $colonPos + 1 );
+                       return $colonPos;
+               }
+
+               # Ugly state machine to walk through avoiding tags.
+               $state = self::COLON_STATE_TEXT;
+               $level = 0;
+               $len = strlen( $str );
+               for ( $i = 0; $i < $len; $i++ ) {
+                       $c = $str[$i];
+
+                       switch ( $state ) {
+                       case self::COLON_STATE_TEXT:
+                               switch ( $c ) {
+                               case "<":
+                                       # Could be either a <start> tag or an </end> tag
+                                       $state = self::COLON_STATE_TAGSTART;
+                                       break;
+                               case ":":
+                                       if ( $level === 0 ) {
+                                               # We found it!
+                                               $before = substr( $str, 0, $i );
+                                               $after = substr( $str, $i + 1 );
+                                               return $i;
+                                       }
+                                       # Embedded in a tag; don't break it.
+                                       break;
+                               default:
+                                       # Skip ahead looking for something interesting
+                                       $colonPos = strpos( $str, ':', $i );
+                                       if ( $colonPos === false ) {
+                                               # Nothing else interesting
+                                               return false;
+                                       }
+                                       $ltPos = strpos( $str, '<', $i );
+                                       if ( $level === 0 ) {
+                                               if ( $ltPos === false || $colonPos < $ltPos ) {
+                                                       # We found it!
+                                                       $before = substr( $str, 0, $colonPos );
+                                                       $after = substr( $str, $colonPos + 1 );
+                                                       return $i;
+                                               }
+                                       }
+                                       if ( $ltPos === false ) {
+                                               # Nothing else interesting to find; abort!
+                                               # We're nested, but there's no close tags left. Abort!
+                                               break 2;
+                                       }
+                                       # Skip ahead to next tag start
+                                       $i = $ltPos;
+                                       $state = self::COLON_STATE_TAGSTART;
+                               }
+                               break;
+                       case self::COLON_STATE_TAG:
+                               # In a <tag>
+                               switch ( $c ) {
+                               case ">":
+                                       $level++;
+                                       $state = self::COLON_STATE_TEXT;
+                                       break;
+                               case "/":
+                                       # Slash may be followed by >?
+                                       $state = self::COLON_STATE_TAGSLASH;
+                                       break;
+                               default:
+                                       # ignore
+                               }
+                               break;
+                       case self::COLON_STATE_TAGSTART:
+                               switch ( $c ) {
+                               case "/":
+                                       $state = self::COLON_STATE_CLOSETAG;
+                                       break;
+                               case "!":
+                                       $state = self::COLON_STATE_COMMENT;
+                                       break;
+                               case ">":
+                                       # Illegal early close? This shouldn't happen D:
+                                       $state = self::COLON_STATE_TEXT;
+                                       break;
+                               default:
+                                       $state = self::COLON_STATE_TAG;
+                               }
+                               break;
+                       case self::COLON_STATE_CLOSETAG:
+                               # In a </tag>
+                               if ( $c === ">" ) {
+                                       $level--;
+                                       if ( $level < 0 ) {
+                                               wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
+                                               return false;
+                                       }
+                                       $state = self::COLON_STATE_TEXT;
+                               }
+                               break;
+                       case self::COLON_STATE_TAGSLASH:
+                               if ( $c === ">" ) {
+                                       # Yes, a self-closed tag <blah/>
+                                       $state = self::COLON_STATE_TEXT;
+                               } else {
+                                       # Probably we're jumping the gun, and this is an attribute
+                                       $state = self::COLON_STATE_TAG;
+                               }
+                               break;
+                       case self::COLON_STATE_COMMENT:
+                               if ( $c === "-" ) {
+                                       $state = self::COLON_STATE_COMMENTDASH;
+                               }
+                               break;
+                       case self::COLON_STATE_COMMENTDASH:
+                               if ( $c === "-" ) {
+                                       $state = self::COLON_STATE_COMMENTDASHDASH;
+                               } else {
+                                       $state = self::COLON_STATE_COMMENT;
+                               }
+                               break;
+                       case self::COLON_STATE_COMMENTDASHDASH:
+                               if ( $c === ">" ) {
+                                       $state = self::COLON_STATE_TEXT;
+                               } else {
+                                       $state = self::COLON_STATE_COMMENT;
+                               }
+                               break;
+                       default:
+                               throw new MWException( "State machine error in " . __METHOD__ );
+                       }
+               }
+               if ( $level > 0 ) {
+                       wfDebug( __METHOD__ . ": Invalid input; not enough close tags (level $level, state $state)\n" );
+                       return false;
+               }
+               return false;
+       }
+}
index a55ddf3..3b8b513 100644 (file)
@@ -456,10 +456,18 @@ class CoreParserFunctions {
                                                $converter->markNoConversion( wfEscapeWikiText( $text ) )
                                        )->inContentLanguage()->text() .
                                        '</span>';
+                       } else {
+                               return '';
                        }
+               } else {
+                       $converter = $parser->getConverterLanguage()->getConverter();
+                       return '<span class="error">' .
+                               wfMessage( 'restricted-displaytitle',
+                                       // Message should be parsed, but this param should only be escaped.
+                                       $converter->markNoConversion( wfEscapeWikiText( $text ) )
+                               )->inContentLanguage()->text() .
+                               '</span>';
                }
-
-               return '';
        }
 
        /**
index 04b5614..8575e69 100644 (file)
@@ -282,7 +282,7 @@ class LinkHolderArray {
                        return;
                }
 
-               global $wgContLang, $wgContentHandlerUseDB, $wgPageLanguageUseDB;
+               global $wgContLang;
 
                $colours = [];
                $linkCache = LinkCache::singleton();
@@ -297,7 +297,9 @@ class LinkHolderArray {
                $linkcolour_ids = [];
 
                # Generate query
-               $queries = [];
+               $lb = new LinkBatch();
+               $lb->setCaller( __METHOD__ );
+
                foreach ( $this->internals as $ns => $entries ) {
                        foreach ( $entries as $entry ) {
                                /** @var Title $title */
@@ -325,37 +327,21 @@ class LinkHolderArray {
                                                $colours[$pdbk] = 'new';
                                        } else {
                                                # Not in the link cache, add it to the query
-                                               $queries[$ns][] = $title->getDBkey();
+                                               $lb->addObj( $title );
                                        }
                                }
                        }
                }
-               if ( $queries ) {
-                       $where = [];
-                       foreach ( $queries as $ns => $pages ) {
-                               $where[] = $dbr->makeList(
-                                       [
-                                               'page_namespace' => $ns,
-                                               'page_title' => array_unique( $pages ),
-                                       ],
-                                       LIST_AND
-                               );
-                       }
-
-                       $fields = [ 'page_id', 'page_namespace', 'page_title',
-                               'page_is_redirect', 'page_len', 'page_latest' ];
-
-                       if ( $wgContentHandlerUseDB ) {
-                               $fields[] = 'page_content_model';
-                       }
-                       if ( $wgPageLanguageUseDB ) {
-                               $fields[] = 'page_lang';
-                       }
+               if ( !$lb->isEmpty() ) {
+                       $fields = array_merge(
+                               LinkCache::getSelectFields(),
+                               [ 'page_namespace', 'page_title' ]
+                       );
 
                        $res = $dbr->select(
                                'page',
                                $fields,
-                               $dbr->makeList( $where, LIST_OR ),
+                               $lb->constructSet( 'page', $dbr ),
                                __METHOD__
                        );
 
@@ -463,7 +449,7 @@ class LinkHolderArray {
         * @param array $colours
         */
        protected function doVariants( &$colours ) {
-               global $wgContLang, $wgContentHandlerUseDB, $wgPageLanguageUseDB;
+               global $wgContLang;
                $linkBatch = new LinkBatch();
                $variantMap = []; // maps $pdbkey_Variant => $keys (of link holders)
                $output = $this->parent->getOutput();
@@ -513,9 +499,6 @@ class LinkHolderArray {
                                }
 
                                $variantTitle = Title::makeTitle( $ns, $textVariant );
-                               if ( is_null( $variantTitle ) ) {
-                                       continue;
-                               }
 
                                // Self-link checking for mixed/different variant titles. At this point, we
                                // already know the exact title does not exist, so the link cannot be to a
@@ -552,15 +535,10 @@ class LinkHolderArray {
                if ( !$linkBatch->isEmpty() ) {
                        // construct query
                        $dbr = wfGetDB( DB_SLAVE );
-                       $fields = [ 'page_id', 'page_namespace', 'page_title',
-                               'page_is_redirect', 'page_len', 'page_latest' ];
-
-                       if ( $wgContentHandlerUseDB ) {
-                               $fields[] = 'page_content_model';
-                       }
-                       if ( $wgPageLanguageUseDB ) {
-                               $fields[] = 'page_lang';
-                       }
+                       $fields = array_merge(
+                               LinkCache::getSelectFields(),
+                               [ 'page_namespace', 'page_title' ]
+                       );
 
                        $varRes = $dbr->select( 'page',
                                $fields,
index a1d62e5..96674be 100644 (file)
@@ -99,16 +99,6 @@ class Parser {
        # Regular expression for a non-newline space
        const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
 
-       # State constants for the definition list colon extraction
-       const COLON_STATE_TEXT = 0;
-       const COLON_STATE_TAG = 1;
-       const COLON_STATE_TAGSTART = 2;
-       const COLON_STATE_CLOSETAG = 3;
-       const COLON_STATE_TAGSLASH = 4;
-       const COLON_STATE_COMMENT = 5;
-       const COLON_STATE_COMMENTDASH = 6;
-       const COLON_STATE_COMMENTDASHDASH = 7;
-
        # Flags for preprocessToDom
        const PTD_FOR_INCLUSION = 1;
 
@@ -176,14 +166,14 @@ class Parser {
         * @var ParserOutput
         */
        public $mOutput;
-       public $mAutonumber, $mDTopen;
+       public $mAutonumber;
 
        /**
         * @var StripState
         */
        public $mStripState;
 
-       public $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
+       public $mIncludeCount;
        /**
         * @var LinkHolderArray
         */
@@ -342,11 +332,7 @@ class Parser {
                $this->mOutput = new ParserOutput;
                $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
                $this->mAutonumber = 0;
-               $this->mLastSection = '';
-               $this->mDTopen = false;
                $this->mIncludeCount = [];
-               $this->mArgStack = false;
-               $this->mInPre = false;
                $this->mLinkHolders = new LinkHolderArray( $this );
                $this->mLinkID = 0;
                $this->mRevisionObject = $this->mRevisionTimestamp =
@@ -2401,127 +2387,6 @@ class Parser {
                return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
        }
 
-       /**#@+
-        * Used by doBlockLevels()
-        * @private
-        *
-        * @return string
-        */
-       public function closeParagraph() {
-               $result = '';
-               if ( $this->mLastSection != '' ) {
-                       $result = '</' . $this->mLastSection . ">\n";
-               }
-               $this->mInPre = false;
-               $this->mLastSection = '';
-               return $result;
-       }
-
-       /**
-        * getCommon() returns the length of the longest common substring
-        * of both arguments, starting at the beginning of both.
-        * @private
-        *
-        * @param string $st1
-        * @param string $st2
-        *
-        * @return int
-        */
-       public function getCommon( $st1, $st2 ) {
-               $fl = strlen( $st1 );
-               $shorter = strlen( $st2 );
-               if ( $fl < $shorter ) {
-                       $shorter = $fl;
-               }
-
-               for ( $i = 0; $i < $shorter; ++$i ) {
-                       if ( $st1[$i] != $st2[$i] ) {
-                               break;
-                       }
-               }
-               return $i;
-       }
-
-       /**
-        * These next three functions open, continue, and close the list
-        * element appropriate to the prefix character passed into them.
-        * @private
-        *
-        * @param string $char
-        *
-        * @return string
-        */
-       public function openList( $char ) {
-               $result = $this->closeParagraph();
-
-               if ( '*' === $char ) {
-                       $result .= "<ul><li>";
-               } elseif ( '#' === $char ) {
-                       $result .= "<ol><li>";
-               } elseif ( ':' === $char ) {
-                       $result .= "<dl><dd>";
-               } elseif ( ';' === $char ) {
-                       $result .= "<dl><dt>";
-                       $this->mDTopen = true;
-               } else {
-                       $result = '<!-- ERR 1 -->';
-               }
-
-               return $result;
-       }
-
-       /**
-        * TODO: document
-        * @param string $char
-        * @private
-        *
-        * @return string
-        */
-       public function nextItem( $char ) {
-               if ( '*' === $char || '#' === $char ) {
-                       return "</li>\n<li>";
-               } elseif ( ':' === $char || ';' === $char ) {
-                       $close = "</dd>\n";
-                       if ( $this->mDTopen ) {
-                               $close = "</dt>\n";
-                       }
-                       if ( ';' === $char ) {
-                               $this->mDTopen = true;
-                               return $close . '<dt>';
-                       } else {
-                               $this->mDTopen = false;
-                               return $close . '<dd>';
-                       }
-               }
-               return '<!-- ERR 2 -->';
-       }
-
-       /**
-        * @todo Document
-        * @param string $char
-        * @private
-        *
-        * @return string
-        */
-       public function closeList( $char ) {
-               if ( '*' === $char ) {
-                       $text = "</li></ul>";
-               } elseif ( '#' === $char ) {
-                       $text = "</li></ol>";
-               } elseif ( ':' === $char ) {
-                       if ( $this->mDTopen ) {
-                               $this->mDTopen = false;
-                               $text = "</dt></dl>";
-                       } else {
-                               $text = "</dd></dl>";
-                       }
-               } else {
-                       return '<!-- ERR 3 -->';
-               }
-               return $text;
-       }
-       /**#@-*/
-
        /**
         * Make lists from lines starting with ':', '*', '#', etc. (DBL)
         *
@@ -2531,365 +2396,7 @@ class Parser {
         * @return string The lists rendered as HTML
         */
        public function doBlockLevels( $text, $linestart ) {
-
-               # Parsing through the text line by line.  The main thing
-               # happening here is handling of block-level elements p, pre,
-               # and making lists from lines starting with * # : etc.
-               $textLines = StringUtils::explode( "\n", $text );
-
-               $lastPrefix = $output = '';
-               $this->mDTopen = $inBlockElem = false;
-               $prefixLength = 0;
-               $paragraphStack = false;
-               $inBlockquote = false;
-
-               foreach ( $textLines as $oLine ) {
-                       # Fix up $linestart
-                       if ( !$linestart ) {
-                               $output .= $oLine;
-                               $linestart = true;
-                               continue;
-                       }
-                       # * = ul
-                       # # = ol
-                       # ; = dt
-                       # : = dd
-
-                       $lastPrefixLength = strlen( $lastPrefix );
-                       $preCloseMatch = preg_match( '/<\\/pre/i', $oLine );
-                       $preOpenMatch = preg_match( '/<pre/i', $oLine );
-                       # If not in a <pre> element, scan for and figure out what prefixes are there.
-                       if ( !$this->mInPre ) {
-                               # Multiple prefixes may abut each other for nested lists.
-                               $prefixLength = strspn( $oLine, '*#:;' );
-                               $prefix = substr( $oLine, 0, $prefixLength );
-
-                               # eh?
-                               # ; and : are both from definition-lists, so they're equivalent
-                               #  for the purposes of determining whether or not we need to open/close
-                               #  elements.
-                               $prefix2 = str_replace( ';', ':', $prefix );
-                               $t = substr( $oLine, $prefixLength );
-                               $this->mInPre = (bool)$preOpenMatch;
-                       } else {
-                               # Don't interpret any other prefixes in preformatted text
-                               $prefixLength = 0;
-                               $prefix = $prefix2 = '';
-                               $t = $oLine;
-                       }
-
-                       # List generation
-                       if ( $prefixLength && $lastPrefix === $prefix2 ) {
-                               # Same as the last item, so no need to deal with nesting or opening stuff
-                               $output .= $this->nextItem( substr( $prefix, -1 ) );
-                               $paragraphStack = false;
-
-                               if ( substr( $prefix, -1 ) === ';' ) {
-                                       # The one nasty exception: definition lists work like this:
-                                       # ; title : definition text
-                                       # So we check for : in the remainder text to split up the
-                                       # title and definition, without b0rking links.
-                                       $term = $t2 = '';
-                                       if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
-                                               $t = $t2;
-                                               $output .= $term . $this->nextItem( ':' );
-                                       }
-                               }
-                       } elseif ( $prefixLength || $lastPrefixLength ) {
-                               # We need to open or close prefixes, or both.
-
-                               # Either open or close a level...
-                               $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
-                               $paragraphStack = false;
-
-                               # Close all the prefixes which aren't shared.
-                               while ( $commonPrefixLength < $lastPrefixLength ) {
-                                       $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
-                                       --$lastPrefixLength;
-                               }
-
-                               # Continue the current prefix if appropriate.
-                               if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
-                                       $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
-                               }
-
-                               # Open prefixes where appropriate.
-                               if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
-                                       $output .= "\n";
-                               }
-                               while ( $prefixLength > $commonPrefixLength ) {
-                                       $char = substr( $prefix, $commonPrefixLength, 1 );
-                                       $output .= $this->openList( $char );
-
-                                       if ( ';' === $char ) {
-                                               # @todo FIXME: This is dupe of code above
-                                               if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
-                                                       $t = $t2;
-                                                       $output .= $term . $this->nextItem( ':' );
-                                               }
-                                       }
-                                       ++$commonPrefixLength;
-                               }
-                               if ( !$prefixLength && $lastPrefix ) {
-                                       $output .= "\n";
-                               }
-                               $lastPrefix = $prefix2;
-                       }
-
-                       # If we have no prefixes, go to paragraph mode.
-                       if ( 0 == $prefixLength ) {
-                               # No prefix (not in list)--go to paragraph mode
-                               # XXX: use a stack for nestable elements like span, table and div
-                               $openmatch = preg_match(
-                                       '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
-                                               . '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS',
-                                       $t
-                               );
-                               $closematch = preg_match(
-                                       '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
-                                               . '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
-                                               . self::MARKER_PREFIX
-                                               . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS',
-                                       $t
-                               );
-
-                               if ( $openmatch || $closematch ) {
-                                       $paragraphStack = false;
-                                       # @todo bug 5718: paragraph closed
-                                       $output .= $this->closeParagraph();
-                                       if ( $preOpenMatch && !$preCloseMatch ) {
-                                               $this->mInPre = true;
-                                       }
-                                       $bqOffset = 0;
-                                       while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t,
-                                               $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset )
-                                       ) {
-                                               $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
-                                               $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
-                                       }
-                                       $inBlockElem = !$closematch;
-                               } elseif ( !$inBlockElem && !$this->mInPre ) {
-                                       if ( ' ' == substr( $t, 0, 1 )
-                                               && ( $this->mLastSection === 'pre' || trim( $t ) != '' )
-                                               && !$inBlockquote
-                                       ) {
-                                               # pre
-                                               if ( $this->mLastSection !== 'pre' ) {
-                                                       $paragraphStack = false;
-                                                       $output .= $this->closeParagraph() . '<pre>';
-                                                       $this->mLastSection = 'pre';
-                                               }
-                                               $t = substr( $t, 1 );
-                                       } else {
-                                               # paragraph
-                                               if ( trim( $t ) === '' ) {
-                                                       if ( $paragraphStack ) {
-                                                               $output .= $paragraphStack . '<br />';
-                                                               $paragraphStack = false;
-                                                               $this->mLastSection = 'p';
-                                                       } else {
-                                                               if ( $this->mLastSection !== 'p' ) {
-                                                                       $output .= $this->closeParagraph();
-                                                                       $this->mLastSection = '';
-                                                                       $paragraphStack = '<p>';
-                                                               } else {
-                                                                       $paragraphStack = '</p><p>';
-                                                               }
-                                                       }
-                                               } else {
-                                                       if ( $paragraphStack ) {
-                                                               $output .= $paragraphStack;
-                                                               $paragraphStack = false;
-                                                               $this->mLastSection = 'p';
-                                                       } elseif ( $this->mLastSection !== 'p' ) {
-                                                               $output .= $this->closeParagraph() . '<p>';
-                                                               $this->mLastSection = 'p';
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-                       # somewhere above we forget to get out of pre block (bug 785)
-                       if ( $preCloseMatch && $this->mInPre ) {
-                               $this->mInPre = false;
-                       }
-                       if ( $paragraphStack === false ) {
-                               $output .= $t;
-                               if ( $prefixLength === 0 ) {
-                                       $output .= "\n";
-                               }
-                       }
-               }
-               while ( $prefixLength ) {
-                       $output .= $this->closeList( $prefix2[$prefixLength - 1] );
-                       --$prefixLength;
-                       if ( !$prefixLength ) {
-                               $output .= "\n";
-                       }
-               }
-               if ( $this->mLastSection != '' ) {
-                       $output .= '</' . $this->mLastSection . '>';
-                       $this->mLastSection = '';
-               }
-
-               return $output;
-       }
-
-       /**
-        * Split up a string on ':', ignoring any occurrences inside tags
-        * to prevent illegal overlapping.
-        *
-        * @param string $str The string to split
-        * @param string &$before Set to everything before the ':'
-        * @param string &$after Set to everything after the ':'
-        * @throws MWException
-        * @return string The position of the ':', or false if none found
-        */
-       public function findColonNoLinks( $str, &$before, &$after ) {
-
-               $pos = strpos( $str, ':' );
-               if ( $pos === false ) {
-                       # Nothing to find!
-                       return false;
-               }
-
-               $lt = strpos( $str, '<' );
-               if ( $lt === false || $lt > $pos ) {
-                       # Easy; no tag nesting to worry about
-                       $before = substr( $str, 0, $pos );
-                       $after = substr( $str, $pos + 1 );
-                       return $pos;
-               }
-
-               # Ugly state machine to walk through avoiding tags.
-               $state = self::COLON_STATE_TEXT;
-               $stack = 0;
-               $len = strlen( $str );
-               for ( $i = 0; $i < $len; $i++ ) {
-                       $c = $str[$i];
-
-                       switch ( $state ) {
-                       # (Using the number is a performance hack for common cases)
-                       case 0: # self::COLON_STATE_TEXT:
-                               switch ( $c ) {
-                               case "<":
-                                       # Could be either a <start> tag or an </end> tag
-                                       $state = self::COLON_STATE_TAGSTART;
-                                       break;
-                               case ":":
-                                       if ( $stack == 0 ) {
-                                               # We found it!
-                                               $before = substr( $str, 0, $i );
-                                               $after = substr( $str, $i + 1 );
-                                               return $i;
-                                       }
-                                       # Embedded in a tag; don't break it.
-                                       break;
-                               default:
-                                       # Skip ahead looking for something interesting
-                                       $colon = strpos( $str, ':', $i );
-                                       if ( $colon === false ) {
-                                               # Nothing else interesting
-                                               return false;
-                                       }
-                                       $lt = strpos( $str, '<', $i );
-                                       if ( $stack === 0 ) {
-                                               if ( $lt === false || $colon < $lt ) {
-                                                       # We found it!
-                                                       $before = substr( $str, 0, $colon );
-                                                       $after = substr( $str, $colon + 1 );
-                                                       return $i;
-                                               }
-                                       }
-                                       if ( $lt === false ) {
-                                               # Nothing else interesting to find; abort!
-                                               # We're nested, but there's no close tags left. Abort!
-                                               break 2;
-                                       }
-                                       # Skip ahead to next tag start
-                                       $i = $lt;
-                                       $state = self::COLON_STATE_TAGSTART;
-                               }
-                               break;
-                       case 1: # self::COLON_STATE_TAG:
-                               # In a <tag>
-                               switch ( $c ) {
-                               case ">":
-                                       $stack++;
-                                       $state = self::COLON_STATE_TEXT;
-                                       break;
-                               case "/":
-                                       # Slash may be followed by >?
-                                       $state = self::COLON_STATE_TAGSLASH;
-                                       break;
-                               default:
-                                       # ignore
-                               }
-                               break;
-                       case 2: # self::COLON_STATE_TAGSTART:
-                               switch ( $c ) {
-                               case "/":
-                                       $state = self::COLON_STATE_CLOSETAG;
-                                       break;
-                               case "!":
-                                       $state = self::COLON_STATE_COMMENT;
-                                       break;
-                               case ">":
-                                       # Illegal early close? This shouldn't happen D:
-                                       $state = self::COLON_STATE_TEXT;
-                                       break;
-                               default:
-                                       $state = self::COLON_STATE_TAG;
-                               }
-                               break;
-                       case 3: # self::COLON_STATE_CLOSETAG:
-                               # In a </tag>
-                               if ( $c === ">" ) {
-                                       $stack--;
-                                       if ( $stack < 0 ) {
-                                               wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
-                                               return false;
-                                       }
-                                       $state = self::COLON_STATE_TEXT;
-                               }
-                               break;
-                       case self::COLON_STATE_TAGSLASH:
-                               if ( $c === ">" ) {
-                                       # Yes, a self-closed tag <blah/>
-                                       $state = self::COLON_STATE_TEXT;
-                               } else {
-                                       # Probably we're jumping the gun, and this is an attribute
-                                       $state = self::COLON_STATE_TAG;
-                               }
-                               break;
-                       case 5: # self::COLON_STATE_COMMENT:
-                               if ( $c === "-" ) {
-                                       $state = self::COLON_STATE_COMMENTDASH;
-                               }
-                               break;
-                       case self::COLON_STATE_COMMENTDASH:
-                               if ( $c === "-" ) {
-                                       $state = self::COLON_STATE_COMMENTDASHDASH;
-                               } else {
-                                       $state = self::COLON_STATE_COMMENT;
-                               }
-                               break;
-                       case self::COLON_STATE_COMMENTDASHDASH:
-                               if ( $c === ">" ) {
-                                       $state = self::COLON_STATE_TEXT;
-                               } else {
-                                       $state = self::COLON_STATE_COMMENT;
-                               }
-                               break;
-                       default:
-                               throw new MWException( "State machine error in " . __METHOD__ );
-                       }
-               }
-               if ( $stack > 0 ) {
-                       wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
-                       return false;
-               }
-               return false;
+               return BlockLevelPass::doBlockLevels( $text, $linestart );
        }
 
        /**
index 731d4a0..c6265a7 100644 (file)
@@ -150,7 +150,7 @@ class ParserCache {
                                        "Parser options key expired, touched " . $article->getTouched()
                                        . ", epoch $wgCacheEpoch, cached $cacheTime\n" );
                                return false;
-                       } elseif ( $optionsKey->isDifferentRevision( $article->getLatest() ) ) {
+                       } elseif ( !$useOutdated && $optionsKey->isDifferentRevision( $article->getLatest() ) ) {
                                wfIncrStats( "pcache.miss.revid" );
                                $revId = $article->getLatest();
                                $cachedRevId = $optionsKey->getCacheRevisionId();
@@ -204,6 +204,7 @@ class ParserCache {
                }
 
                $casToken = null;
+               /** @var ParserOutput $value */
                $value = $this->mMemc->get( $parserOutputKey, $casToken, BagOStuff::READ_VERIFIED );
                if ( !$value ) {
                        wfDebug( "ParserOutput cache miss.\n" );
@@ -229,7 +230,7 @@ class ParserCache {
                                "ParserOutput key expired, touched $touched, "
                                . "epoch $wgCacheEpoch, cached $cacheTime\n" );
                        $value = false;
-               } elseif ( $value->isDifferentRevision( $article->getLatest() ) ) {
+               } elseif ( !$useOutdated && $value->isDifferentRevision( $article->getLatest() ) ) {
                        wfIncrStats( "pcache.miss.revid" );
                        $revId = $article->getLatest();
                        $cachedRevId = $value->getCacheRevisionId();
@@ -261,7 +262,7 @@ class ParserCache {
         */
        public function save( $parserOutput, $page, $popts, $cacheTime = null, $revId = null ) {
                $expire = $parserOutput->getCacheExpiry();
-               if ( $expire > 0 ) {
+               if ( $expire > 0 && !$this->mMemc instanceof EmptyBagOStuff ) {
                        $cacheTime = $cacheTime ?: wfTimestampNow();
                        if ( !$revId ) {
                                $revision = $page->getRevision();
@@ -301,7 +302,7 @@ class ParserCache {
                                'ParserCacheSaveComplete',
                                [ $this, $parserOutput, $page->getTitle(), $popts, $revId ]
                        );
-               } else {
+               } elseif ( $expire <= 0 ) {
                        wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
                }
        }
index 40c67dd..6c7ad4e 100644 (file)
@@ -381,6 +381,9 @@ class ParserOutput extends CacheTime {
                return $this->mTOCHTML;
        }
 
+       /**
+        * @return string|null TS_MW timestamp of the revision content
+        */
        public function getTimestamp() {
                return $this->mTimestamp;
        }
index c168aa6..4ed176c 100644 (file)
@@ -50,7 +50,7 @@ class StripState {
                        'nowiki' => [],
                        'general' => []
                ];
-               $this->regex = '/' . Parser::MARKER_PREFIX . "([^\x7f]+)" . Parser::MARKER_SUFFIX . '/';
+               $this->regex = '/' . Parser::MARKER_PREFIX . "([^\x7f<>&'\"]+)" . Parser::MARKER_SUFFIX . '/';
                $this->circularRefGuard = [];
        }
 
index 7ea87c3..b3776bd 100644 (file)
@@ -105,11 +105,15 @@ class PasswordPolicyChecks {
 
                $status = Status::newGood();
                $username = $user->getName();
-               if ( $policyVal
-                       && isset( $blockedLogins[$username] )
-                       && $password == $blockedLogins[$username]
-               ) {
-                       $status->error( 'password-login-forbidden' );
+               if ( $policyVal ) {
+                       if ( isset( $blockedLogins[$username] ) && $password == $blockedLogins[$username] ) {
+                               $status->error( 'password-login-forbidden' );
+                       }
+
+                       // Example from ApiChangeAuthenticationRequest
+                       if ( $password === 'ExamplePassword' ) {
+                               $status->error( 'password-login-forbidden' );
+                       }
                }
                return $status;
        }
@@ -117,11 +121,11 @@ class PasswordPolicyChecks {
        /**
         * Ensure that password isn't in top X most popular passwords
         *
-        * @param $policyVal int Cut off to use. Will automatically shrink to the max
+        * @param int $policyVal Cut off to use. Will automatically shrink to the max
         *   supported for error messages if set to more than max number of passwords on file,
         *   so you can use the PHP_INT_MAX constant here safely.
-        * @param $user User
-        * @param $password String
+        * @param User $user
+        * @param string $password
         * @since 1.27
         * @return Status
         */
index acdbd81..bd7072a 100644 (file)
@@ -81,7 +81,7 @@ abstract class PoolCounter {
 
        /**
         * @param array $conf
-        * @param string $type
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key
         */
        protected function __construct( $conf, $type, $key ) {
@@ -93,8 +93,9 @@ abstract class PoolCounter {
                }
 
                if ( $this->slots ) {
-                       $key = $this->hashKeyIntoSlots( $key, $this->slots );
+                       $key = $this->hashKeyIntoSlots( $type, $key, $this->slots );
                }
+
                $this->key = $key;
                $this->isMightWaitKey = !preg_match( '/^nowait:/', $this->key );
        }
@@ -102,7 +103,7 @@ abstract class PoolCounter {
        /**
         * Create a Pool counter. This should only be called from the PoolWorks.
         *
-        * @param string $type
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key
         *
         * @return PoolCounter
@@ -192,18 +193,19 @@ abstract class PoolCounter {
        }
 
        /**
-        * Given a key (any string) and the number of lots, returns a slot number (an integer from
-        * the [0..($slots-1)] range). This is used for a global limit on the number of instances of
-        * a given type that can acquire a lock. The hashing is deterministic so that
+        * Given a key (any string) and the number of lots, returns a slot key (a prefix with a suffix
+        * integer from the [0..($slots-1)] range). This is used for a global limit on the number of
+        * instances of a given type that can acquire a lock. The hashing is deterministic so that
         * PoolCounter::$workers is always an upper limit of how many instances with the same key
         * can acquire a lock.
         *
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key PoolCounter instance key (any string)
         * @param int $slots The number of slots (max allowed value is 65536)
-        * @return int
+        * @return string Slot key with the type and slot number
         */
-       protected function hashKeyIntoSlots( $key, $slots ) {
-               return hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots;
+       protected function hashKeyIntoSlots( $type, $key, $slots ) {
+               return $type . ':' . ( hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots );
        }
 }
 
index e61b65a..a570d78 100644 (file)
@@ -31,7 +31,7 @@ abstract class PoolCounterWork {
        protected $cacheable = false; // does this override getCachedWork() ?
 
        /**
-        * @param string $type The type of PoolCounter to use
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key Key that identifies the queue this work is placed on
         */
        public function __construct( $type, $key ) {
index 85a7cef..834b8b1 100644 (file)
@@ -44,7 +44,7 @@ class PoolCounterWorkViaCallback extends PoolCounterWork {
         * If a 'doCachedWork' callback is provided, then execute() may wait for any prior
         * process in the pool to finish and reuse its cached result.
         *
-        * @param string $type
+        * @param string $type The class of actions to limit concurrency for
         * @param string $key
         * @param array $callbacks Map of callbacks
         * @throws MWException
index 7ddedb9..29016a8 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 class PoolWorkArticleView extends PoolCounterWork {
-       /** @var Page */
+       /** @var WikiPage */
        private $page;
 
        /** @var string */
@@ -44,7 +44,7 @@ class PoolWorkArticleView extends PoolCounterWork {
        private $error = false;
 
        /**
-        * @param Page $page
+        * @param WikiPage $page
         * @param ParserOptions $parserOptions ParserOptions to use for the parse
         * @param int $revid ID of the revision being parsed.
         * @param bool $useParserCache Whether to use the parser cache.
@@ -52,7 +52,7 @@ class PoolWorkArticleView extends PoolCounterWork {
         * @param Content|string $content Content to parse or null to load it; may
         *   also be given as a wikitext string, for BC.
         */
-       public function __construct( Page $page, ParserOptions $parserOptions,
+       public function __construct( WikiPage $page, ParserOptions $parserOptions,
                $revid, $useParserCache, $content = null
        ) {
                if ( is_string( $content ) ) { // BC: old style call
index 7c4fde4..8fc0b77 100644 (file)
@@ -52,9 +52,9 @@
  */
 class ProfilerXhprof extends Profiler {
        /**
-        * @var Xhprof $xhprof
+        * @var XhprofData|null $xhprofData
         */
-       protected $xhprof;
+       protected $xhprofData;
 
        /**
         * Profiler for explicit, arbitrary, frame labels
@@ -68,10 +68,24 @@ class ProfilerXhprof extends Profiler {
         */
        public function __construct( array $params = [] ) {
                parent::__construct( $params );
-               $this->xhprof = new Xhprof( $params );
+
+               $flags = isset( $params['flags'] ) ? $params['flags'] : 0;
+               $options = isset( $params['exclude'] )
+                       ? [ 'ignored_functions' => $params['exclude'] ] : [];
+               Xhprof::enable( $flags, $options );
                $this->sprofiler = new SectionProfiler();
        }
 
+       /**
+        * @return XhprofData
+        */
+       public function getXhprofData() {
+               if ( !$this->xhprofData ) {
+                       $this->xhprofData = new XhprofData( Xhprof::disable(), $this->params );
+               }
+               return $this->xhprofData;
+       }
+
        public function scopedProfileIn( $section ) {
                $key = 'section.' . ltrim( $section, '.' );
                return $this->sprofiler->scopedProfileIn( $key );
@@ -112,7 +126,7 @@ class ProfilerXhprof extends Profiler {
        }
 
        public function getFunctionStats() {
-               $metrics = $this->xhprof->getCompleteMetrics();
+               $metrics = $this->getXhprofData()->getCompleteMetrics();
                $profile = [];
 
                $main = null; // units in ms
@@ -216,6 +230,6 @@ class ProfilerXhprof extends Profiler {
         * @return array
         */
        public function getRawData() {
-               return $this->xhprof->getRawData();
+               return $this->getXhprofData()->getRawData();
        }
 }
index f977124..78f9370 100644 (file)
@@ -23,6 +23,8 @@ class ExtensionProcessor implements Processor {
                'AvailableRights',
                'ContentHandlers',
                'ConfigRegistry',
+               'SessionProviders',
+               'AuthManagerAutoConfig',
                'CentralIdLookupProviders',
                'RateLimits',
                'RecentChangesFlags',
@@ -67,6 +69,7 @@ class ExtensionProcessor implements Processor {
                'wgNamespaceProtection' => 'array_plus',
                'wgCapitalLinkOverrides' => 'array_plus',
                'wgRateLimits' => 'array_plus_2d',
+               'wgAuthManagerAutoConfig' => 'array_plus_2d',
        ];
 
        /**
@@ -209,8 +212,12 @@ class ExtensionProcessor implements Processor {
        protected function extractHooks( array $info ) {
                if ( isset( $info['Hooks'] ) ) {
                        foreach ( $info['Hooks'] as $name => $value ) {
-                               foreach ( (array)$value as $callback ) {
-                                       $this->globals['wgHooks'][$name][] = $callback;
+                               if ( is_array( $value ) ) {
+                                       foreach ( $value as $callback ) {
+                                               $this->globals['wgHooks'][$name][] = $callback;
+                                       }
+                               } else {
+                                       $this->globals['wgHooks'][$name][] = $value;
                                }
                        }
                }
index 8e0239a..85fc53d 100644 (file)
@@ -227,15 +227,17 @@ class ResourceLoaderContext {
         * Get the possibly-cached User object for the specified username
         *
         * @since 1.25
-        * @return User|bool false if a valid object cannot be created
+        * @return User
         */
        public function getUserObj() {
                if ( $this->userObj === null ) {
                        $username = $this->getUser();
                        if ( $username ) {
-                               $this->userObj = User::newFromName( $username );
+                               // Use provided username if valid, fallback to anonymous user
+                               $this->userObj = User::newFromName( $username ) ?: new User;
                        } else {
-                               $this->userObj = new User; // Anonymous user
+                               // Anonymous user
+                               $this->userObj = new User;
                        }
                }
 
index 13f13e6..121a6c9 100644 (file)
@@ -114,16 +114,6 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                return $this->origin;
        }
 
-       /**
-        * Set this module's origin. This is called by ResourceLoader::register()
-        * when registering the module. Other code should not call this.
-        *
-        * @param int $origin Origin
-        */
-       public function setOrigin( $origin ) {
-               $this->origin = $origin;
-       }
-
        /**
         * @param ResourceLoaderContext $context
         * @return bool
index 490a4ab..91e63e7 100644 (file)
@@ -26,7 +26,7 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
        /* Methods */
 
        /**
-        * @param $context ResourceLoaderContext
+        * @param ResourceLoaderContext $context
         * @return array
         */
        public function getStyles( ResourceLoaderContext $context ) {
@@ -68,7 +68,7 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
        }
 
        /**
-        * @param $context ResourceLoaderContext
+        * @param ResourceLoaderContext $context
         * @return bool
         */
        public function isKnownEmpty( ResourceLoaderContext $context ) {
@@ -78,7 +78,7 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
        }
 
        /**
-        * @param $context ResourceLoaderContext
+        * @param ResourceLoaderContext $context
         * @return string: Hash
         */
        public function getModifiedHash( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderUploadDialogModule.php b/includes/resourceloader/ResourceLoaderUploadDialogModule.php
new file mode 100644 (file)
index 0000000..52e2210
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * ResourceLoader module for the upload dialog configuration data.
+ *
+ * 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
+ */
+
+/**
+ * ResourceLoader module for the upload dialog configuration data.
+ *
+ * @since 1.27
+ */
+class ResourceLoaderUploadDialogModule extends ResourceLoaderModule {
+
+       protected $targets = [ 'desktop', 'mobile' ];
+
+       public function getScript( ResourceLoaderContext $context ) {
+               $config = $context->getResourceLoader()->getConfig();
+               return ResourceLoader::makeConfigSetScript( [
+                       'wgUploadDialog' => $config->get( 'UploadDialog' ),
+               ] );
+       }
+
+       public function enableModuleContentVersion() {
+               return true;
+       }
+}
index e2a8e41..b225185 100644 (file)
@@ -40,7 +40,7 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
                }
 
                $user = $context->getUserObj();
-               if ( !$user || $user->isAnon() ) {
+               if ( $user->isAnon() ) {
                        return [];
                }
 
index d584165..c38f8d8 100644 (file)
@@ -43,7 +43,7 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
                }
 
                $user = $context->getUserObj();
-               if ( !$user || $user->isAnon() ) {
+               if ( $user->isAnon() ) {
                        return [];
                }
 
index 796dc20..a3f8825 100644 (file)
@@ -75,6 +75,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                                case 'styles':
                                case 'scripts':
                                case 'group':
+                               case 'targets':
                                        $this->{$member} = $option;
                                        break;
                        }
index be5ab92..dcef95c 100644 (file)
@@ -380,7 +380,7 @@ abstract class SearchEngine {
         * Makes search simple string if it was namespaced.
         * Sets namespaces of the search to namespaces extracted from string.
         * @param string $search
-        * @return $string Simplified search string
+        * @return string Simplified search string
         */
        protected function normalizeNamespaces( $search ) {
                // Find a Title which is not an interwiki and is in NS_MAIN
@@ -607,7 +607,7 @@ abstract class SearchEngine {
         * @return array
         */
        public static function namespacesAsText( $namespaces ) {
-               return MediaWikiServices::getInstance()->getSearchEngineConfig()->namespacesAsText();
+               return MediaWikiServices::getInstance()->getSearchEngineConfig()->namespacesAsText( $namespaces );
        }
 
        /**
index 8ce3174..3df0dae 100644 (file)
@@ -217,19 +217,13 @@ class CookieSessionProvider extends SessionProvider {
                        [ 'prefix' => '' ] + $options
                );
 
-               $extendedCookies = $this->config->get( 'ExtendedLoginCookies' );
-               $extendedExpiry = $this->config->get( 'ExtendedLoginCookieExpiration' );
-
                foreach ( $cookies as $key => $value ) {
                        if ( $value === false ) {
                                $response->clearCookie( $key, $options );
                        } else {
-                               if ( $extendedExpiry !== null && in_array( $key, $extendedCookies ) ) {
-                                       $expiry = time() + (int)$extendedExpiry;
-                               } else {
-                                       $expiry = 0; // Default cookie expiration
-                               }
-                               $response->setCookie( $key, (string)$value, $expiry, $options );
+                               $expirationDuration = $this->getLoginCookieExpiration( $key );
+                               $expiration = $expirationDuration ? $expirationDuration + time() : null;
+                               $response->setCookie( $key, (string)$value, $expiration, $options );
                        }
                }
 
@@ -276,7 +270,13 @@ class CookieSessionProvider extends SessionProvider {
        ) {
                $response = $request->response();
                if ( $set ) {
-                       $response->setCookie( 'forceHTTPS', 'true', $backend->shouldRememberUser() ? 0 : null,
+                       if ( $backend->shouldRememberUser() ) {
+                               $expirationDuration = $this->getLoginCookieExpiration( 'forceHTTPS' );
+                               $expiration = $expirationDuration ? $expirationDuration + time() : null;
+                       } else {
+                               $expiration = null;
+                       }
+                       $response->setCookie( 'forceHTTPS', 'true', $expiration,
                                [ 'prefix' => '', 'secure' => false ] + $this->cookieOptions );
                } else {
                        $response->clearCookie( 'forceHTTPS',
@@ -396,4 +396,24 @@ class CookieSessionProvider extends SessionProvider {
                return wfMessage( 'sessionprovider-nocookies' );
        }
 
+       public function getRememberUserDuration() {
+               return min( $this->getLoginCookieExpiration( 'UserID' ),
+                       $this->getLoginCookieExpiration( 'Token' ) ) ?: null;
+       }
+
+       /**
+        * Returns the lifespan of the login cookies, in seconds. 0 means until the end of the session.
+        * @param string $cookieName
+        * @return int Cookie expiration time in seconds; 0 for session cookies
+        */
+       protected function getLoginCookieExpiration( $cookieName ) {
+               $normalExpiration = $this->config->get( 'CookieExpiration' );
+               $extendedExpiration = $this->config->get( 'ExtendedLoginCookieExpiration' );
+               $extendedCookies = $this->config->get( 'ExtendedLoginCookies' );
+
+               if ( !in_array( $cookieName, $extendedCookies, true ) ) {
+                       return (int)$normalExpiration;
+               }
+               return ( $extendedExpiration !== null ) ? (int)$extendedExpiration : (int)$normalExpiration;
+       }
 }
index 2f6e133..1cab3d3 100644 (file)
@@ -113,7 +113,7 @@ abstract class ImmutableSessionProviderWithCookie extends SessionProvider {
 
                $options = $this->sessionCookieOptions;
                if ( $session->shouldForceHTTPS() || $session->getUser()->requiresHTTPS() ) {
-                       $response->setCookie( 'forceHTTPS', 'true', $session->shouldRememberUser() ? 0 : null,
+                       $response->setCookie( 'forceHTTPS', 'true', null,
                                [ 'prefix' => '', 'secure' => false ] + $options );
                        $options['secure'] = true;
                }
index 4188f4f..29878d4 100644 (file)
@@ -379,6 +379,156 @@ final class Session implements \Countable, \Iterator, \ArrayAccess {
                $this->remove( 'wsTokenSecrets' );
        }
 
+       /**
+        * Fetch the secret keys for self::setSecret() and self::getSecret().
+        * @return string[] Encryption key, HMAC key
+        */
+       private function getSecretKeys() {
+               global $wgSessionSecret, $wgSecretKey;
+
+               $wikiSecret = $wgSessionSecret ?: $wgSecretKey;
+               $userSecret = $this->get( 'wsSessionSecret', null );
+               if ( $userSecret === null ) {
+                       $userSecret = \MWCryptRand::generateHex( 32 );
+                       $this->set( 'wsSessionSecret', $userSecret );
+               }
+
+               $keymats = hash_pbkdf2( 'sha256', $wikiSecret, $userSecret, 10001, 64, true );
+               return [
+                       substr( $keymats, 0, 32 ),
+                       substr( $keymats, 32, 32 ),
+               ];
+       }
+
+       /**
+        * Set a value in the session, encrypted
+        *
+        * This relies on the secrecy of $wgSecretKey (by default), or $wgSessionSecret.
+        *
+        * @param string|int $key
+        * @param mixed $value
+        */
+       public function setSecret( $key, $value ) {
+               global $wgSessionInsecureSecrets;
+
+               list( $encKey, $hmacKey ) = $this->getSecretKeys();
+               $serialized = serialize( $value );
+
+               // The code for encryption (with OpenSSL) and sealing is taken from
+               // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
+
+               // Encrypt
+               // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
+               $iv = \MWCryptRand::generate( 16, true );
+               if ( function_exists( 'openssl_encrypt' ) ) {
+                       $ciphertext = openssl_encrypt( $serialized, 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv );
+                       if ( $ciphertext === false ) {
+                               throw new UnexpectedValueException( 'Encryption failed: ' . openssl_error_string() );
+                       }
+               } elseif ( function_exists( 'mcrypt_encrypt' ) ) {
+                       $ciphertext = mcrypt_encrypt( 'rijndael-128', $encKey, $serialized, 'ctr', $iv );
+                       if ( $ciphertext === false ) {
+                               throw new UnexpectedValueException( 'Encryption failed' );
+                       }
+               } elseif ( $wgSessionInsecureSecrets ) {
+                       $ex = new \Exception( 'No encryption is available, storing data as plain text' );
+                       $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
+                       $ciphertext = $serialized;
+               } else {
+                       throw new \BadMethodCallException(
+                               'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
+                               'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
+                               'to accept insecure storage of sensitive session data, set ' .
+                               '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
+                       );
+               }
+
+               // Seal
+               $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
+               $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
+               $encrypted = base64_encode( $hmac ) . '.' . $sealed;
+
+               // Store
+               $this->set( $key, $encrypted );
+       }
+
+       /**
+        * Fetch a value from the session that was set with self::setSecret()
+        * @param string|int $key
+        * @param mixed $default Returned if $this->exists( $key ) would be false or decryption fails
+        * @return mixed
+        */
+       public function getSecret( $key, $default = null ) {
+               global $wgSessionInsecureSecrets;
+
+               // Fetch
+               $encrypted = $this->get( $key, null );
+               if ( $encrypted === null ) {
+                       return $default;
+               }
+
+               // The code for unsealing, checking, and decrypting (with OpenSSL) is
+               // taken from Chris Steipp's OATHAuthUtils class in
+               // Extension::OATHAuth.
+
+               // Unseal and check
+               $pieces = explode( '.', $encrypted );
+               if ( count( $pieces ) !== 3 ) {
+                       $ex = new \Exception( 'Invalid sealed-secret format' );
+                       $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
+                       return $default;
+               }
+               list( $hmac, $iv, $ciphertext ) = $pieces;
+               list( $encKey, $hmacKey ) = $this->getSecretKeys();
+               $integCalc = hash_hmac( 'sha256', $iv . '.' . $ciphertext, $hmacKey, true );
+               if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
+                       $ex = new \Exception( 'Sealed secret has been tampered with, aborting.' );
+                       $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
+                       return $default;
+               }
+
+               // Decrypt
+               // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
+               if ( function_exists( 'openssl_decrypt' ) ) {
+                       $serialized = openssl_decrypt(
+                               base64_decode( $ciphertext ), 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, base64_decode( $iv )
+                       );
+                       if ( $serialized === false ) {
+                               $ex = new \Exception( 'Decyption failed: ' . openssl_error_string() );
+                               $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
+                               return $default;
+                       }
+               } elseif ( function_exists( 'mcrypt_decrypt' ) ) {
+                       $serialized = mcrypt_decrypt(
+                               'rijndael-128', $encKey, base64_decode( $ciphertext ), 'ctr', base64_decode( $iv )
+                       );
+                       if ( $serialized === false ) {
+                               $ex = new \Exception( 'Decyption failed' );
+                               $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
+                               return $default;
+                       }
+               } elseif ( $wgSessionInsecureSecrets ) {
+                       $ex = new \Exception(
+                               'No encryption is available, retrieving data that was stored as plain text'
+                       );
+                       $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
+                       $serialized = base64_decode( $ciphertext );
+               } else {
+                       throw new \BadMethodCallException(
+                               'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
+                               'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
+                               'to accept insecure storage of sensitive session data, set ' .
+                               '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
+                       );
+               }
+
+               $value = unserialize( $serialized );
+               if ( $value === false && $serialized !== serialize( false ) ) {
+                       $value = $default;
+               }
+               return $value;
+       }
+
        /**
         * Delay automatic saving while multiple updates are being made
         *
index 1b5a834..c235861 100644 (file)
@@ -54,6 +54,7 @@ class SessionInfo {
        private $remembered = false;
        private $forceHTTPS = false;
        private $idIsSafe = false;
+       private $forceUse = false;
 
        /** @var array|null */
        private $providerMetadata = null;
@@ -76,6 +77,10 @@ class SessionInfo {
         *  - idIsSafe: (bool) Set true if the 'id' did not come from the user.
         *    Generally you'll use this from SessionProvider::newEmptySession(),
         *    and not from any other method.
+        *  - forceUse: (bool) Set true if the 'id' is from
+        *    SessionProvider::hashToSessionId() to delete conflicting session
+        *    store data instead of discarding this SessionInfo. Ignored unless
+        *    both 'provider' and 'id' are given.
         *  - copyFrom: (SessionInfo) SessionInfo to copy other data items from.
         */
        public function __construct( $priority, array $data ) {
@@ -97,6 +102,7 @@ class SessionInfo {
                                'forceHTTPS' => $from->forceHTTPS,
                                'metadata' => $from->providerMetadata,
                                'idIsSafe' => $from->idIsSafe,
+                               'forceUse' => $from->forceUse,
                                // @codeCoverageIgnoreStart
                        ];
                        // @codeCoverageIgnoreEnd
@@ -110,6 +116,7 @@ class SessionInfo {
                                'forceHTTPS' => false,
                                'metadata' => null,
                                'idIsSafe' => false,
+                               'forceUse' => false,
                                // @codeCoverageIgnoreStart
                        ];
                        // @codeCoverageIgnoreEnd
@@ -137,9 +144,11 @@ class SessionInfo {
                if ( $data['id'] !== null ) {
                        $this->id = $data['id'];
                        $this->idIsSafe = $data['idIsSafe'];
+                       $this->forceUse = $data['forceUse'] && $this->provider;
                } else {
                        $this->id = $this->provider->getManager()->generateSessionId();
                        $this->idIsSafe = true;
+                       $this->forceUse = false;
                }
                $this->priority = (int)$priority;
                $this->userInfo = $data['userInfo'];
@@ -185,6 +194,20 @@ class SessionInfo {
                return $this->idIsSafe;
        }
 
+       /**
+        * Force use of this SessionInfo if validation fails
+        *
+        * The normal behavior is to discard the SessionInfo if validation against
+        * the data stored in the session store fails. If this returns true,
+        * SessionManager will instead delete the session store data so this
+        * SessionInfo may still be used.
+        *
+        * @return bool
+        */
+       final public function forceUse() {
+               return $this->forceUse;
+       }
+
        /**
         * Return the priority
         * @return int
index a364045..c3481e8 100644 (file)
@@ -301,6 +301,20 @@ final class SessionManager implements SessionManagerInterface {
                return $this->getSessionFromInfo( $infos[0], $request );
        }
 
+       public function invalidateSessionsForUser( User $user ) {
+               $user->setToken();
+               $user->saveSettings();
+
+               $authUser = \MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ] );
+               if ( $authUser ) {
+                       $authUser->resetAuthToken();
+               }
+
+               foreach ( $this->getProviders() as $provider ) {
+                       $provider->invalidateSessionsForUser( $user );
+               }
+       }
+
        public function getVaryHeaders() {
                // @codeCoverageIgnoreStart
                if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
@@ -357,14 +371,23 @@ final class SessionManager implements SessionManagerInterface {
        /**
         * Auto-create the given user, if necessary
         * @private Don't call this yourself. Let Setup.php do it for you at the right time.
-        * @note This more properly belongs in AuthManager, but we need it now.
-        *  When AuthManager comes, this will be deprecated and will pass-through
-        *  to the corresponding AuthManager method.
+        * @deprecated since 1.27, use MediaWiki\Auth\AuthManager::autoCreateUser instead
         * @param User $user User to auto-create
         * @return bool Success
         */
        public static function autoCreateUser( User $user ) {
-               global $wgAuth;
+               global $wgAuth, $wgDisableAuthManager;
+
+               // @codeCoverageIgnoreStart
+               if ( !$wgDisableAuthManager ) {
+                       wfDeprecated( __METHOD__, '1.27' );
+                       return \MediaWiki\Auth\AuthManager::singleton()->autoCreateUser(
+                               $user,
+                               \MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSSION,
+                               false
+                       )->isGood();
+               }
+               // @codeCoverageIgnoreEnd
 
                $logger = self::singleton()->logger;
 
@@ -704,6 +727,20 @@ final class SessionManager implements SessionManagerInterface {
                $key = wfMemcKey( 'MWSession', $info->getId() );
                $blob = $this->store->get( $key );
 
+               // If we got data from the store and the SessionInfo says to force use,
+               // "fail" means to delete the data from the store and retry. Otherwise,
+               // "fail" is just return false.
+               if ( $info->forceUse() && $blob !== false ) {
+                       $failHandler = function () use ( $key, &$info, $request ) {
+                               $this->store->delete( $key );
+                               return $this->loadSessionInfoFromStore( $info, $request );
+                       };
+               } else {
+                       $failHandler = function () {
+                               return false;
+                       };
+               }
+
                $newParams = [];
 
                if ( $blob !== false ) {
@@ -713,7 +750,7 @@ final class SessionManager implements SessionManagerInterface {
                                        'session' => $info,
                                ] );
                                $this->store->delete( $key );
-                               return false;
+                               return $failHandler();
                        }
 
                        // Sanity check: blob has data and metadata arrays
@@ -724,7 +761,7 @@ final class SessionManager implements SessionManagerInterface {
                                        'session' => $info,
                                ] );
                                $this->store->delete( $key );
-                               return false;
+                               return $failHandler();
                        }
 
                        $data = $blob['data'];
@@ -741,7 +778,7 @@ final class SessionManager implements SessionManagerInterface {
                                        'session' => $info,
                                ] );
                                $this->store->delete( $key );
-                               return false;
+                               return $failHandler();
                        }
 
                        // First, load the provider from metadata, or validate it against the metadata.
@@ -756,7 +793,7 @@ final class SessionManager implements SessionManagerInterface {
                                                ]
                                        );
                                        $this->store->delete( $key );
-                                       return false;
+                                       return $failHandler();
                                }
                        } elseif ( $metadata['provider'] !== (string)$provider ) {
                                $this->logger->warning( 'Session "{session}": Wrong provider ' .
@@ -764,7 +801,7 @@ final class SessionManager implements SessionManagerInterface {
                                        [
                                                'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
 
                        // Load provider metadata from metadata, or validate it against the metadata
@@ -788,7 +825,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'exception' => $ex,
                                                        ] + $ex->getContext()
                                                );
-                                               return false;
+                                               return $failHandler();
                                        }
                                }
                        }
@@ -810,7 +847,7 @@ final class SessionManager implements SessionManagerInterface {
                                                'session' => $info,
                                                'exception' => $ex,
                                        ] );
-                                       return false;
+                                       return $failHandler();
                                }
                                $newParams['userInfo'] = $userInfo;
                        } else {
@@ -825,7 +862,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'uid_a' => $metadata['userId'],
                                                                'uid_b' => $userInfo->getId(),
                                                ] );
-                                               return false;
+                                               return $failHandler();
                                        }
 
                                        // If the user was renamed, probably best to fail here.
@@ -839,7 +876,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'uname_a' => $metadata['userName'],
                                                                'uname_b' => $userInfo->getName(),
                                                ] );
-                                               return false;
+                                               return $failHandler();
                                        }
 
                                } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
@@ -851,7 +888,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'uname_a' => $metadata['userName'],
                                                                'uname_b' => $userInfo->getName(),
                                                ] );
-                                               return false;
+                                               return $failHandler();
                                        }
                                } elseif ( !$userInfo->isAnon() ) {
                                        // Metadata specifies an anonymous user, but the passed-in
@@ -861,7 +898,7 @@ final class SessionManager implements SessionManagerInterface {
                                                [
                                                        'session' => $info,
                                        ] );
-                                       return false;
+                                       return $failHandler();
                                }
                        }
 
@@ -872,7 +909,7 @@ final class SessionManager implements SessionManagerInterface {
                                $this->logger->warning( 'Session "{session}": User token mismatch', [
                                        'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
                        if ( !$userInfo->isVerified() ) {
                                $newParams['userInfo'] = $userInfo->verified();
@@ -899,7 +936,7 @@ final class SessionManager implements SessionManagerInterface {
                                        [
                                                'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
 
                        // If no user was provided and no metadata, it must be anon.
@@ -912,7 +949,7 @@ final class SessionManager implements SessionManagerInterface {
                                                [
                                                        'session' => $info,
                                        ] );
-                                       return false;
+                                       return $failHandler();
                                }
                        } elseif ( !$info->getUserInfo()->isVerified() ) {
                                $this->logger->warning(
@@ -920,7 +957,7 @@ final class SessionManager implements SessionManagerInterface {
                                        [
                                                'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
 
                        $data = false;
@@ -942,7 +979,7 @@ final class SessionManager implements SessionManagerInterface {
                // Allow the provider to check the loaded SessionInfo
                $providerMetadata = $info->getProviderMetadata();
                if ( !$info->getProvider()->refreshSessionInfo( $info, $request, $providerMetadata ) ) {
-                       return false;
+                       return $failHandler();
                }
                if ( $providerMetadata !== $info->getProviderMetadata() ) {
                        $info = new SessionInfo( $info->getPriority(), [
@@ -962,7 +999,7 @@ final class SessionManager implements SessionManagerInterface {
                        $this->logger->warning( 'Session "{session}": ' . $reason, [
                                'session' => $info,
                        ] );
-                       return false;
+                       return $failHandler();
                }
 
                return true;
@@ -1074,7 +1111,7 @@ final class SessionManager implements SessionManagerInterface {
         */
        public function generateSessionId() {
                do {
-                       $id = wfBaseConvert( \MWCryptRand::generateHex( 40 ), 16, 32, 32 );
+                       $id = \Wikimedia\base_convert( \MWCryptRand::generateHex( 40 ), 16, 32, 32 );
                        $key = wfMemcKey( 'MWSession', $id );
                } while ( isset( $this->allSessionIds[$id] ) || is_array( $this->store->get( $key ) ) );
                return $id;
index b3e28fe..d4e52c7 100644 (file)
@@ -24,6 +24,7 @@
 namespace MediaWiki\Session;
 
 use Psr\Log\LoggerAwareInterface;
+use User;
 use WebRequest;
 
 /**
@@ -72,6 +73,17 @@ interface SessionManagerInterface extends LoggerAwareInterface {
         */
        public function getEmptySession( WebRequest $request = null );
 
+       /**
+        * Invalidate sessions for a user
+        *
+        * After calling this, existing sessions should be invalid. For mutable
+        * session providers, this generally means the user has to log in again;
+        * for immutable providers, it generally means the loss of session data.
+        *
+        * @param User $user
+        */
+       public function invalidateSessionsForUser( User $user );
+
        /**
         * Return the HTTP headers that need varying on.
         *
index 8ee1272..50794d0 100644 (file)
@@ -27,6 +27,7 @@ use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 use Config;
 use Language;
+use User;
 use WebRequest;
 
 /**
@@ -274,6 +275,16 @@ abstract class SessionProvider implements SessionProviderInterface, LoggerAwareI
         */
        abstract public function canChangeUser();
 
+       /**
+        * Returns the duration (in seconds) for which users will be remembered when
+        * Session::setRememberUser() is set. Null means setting the remember flag will
+        * have no effect (and endpoints should not offer that option).
+        * @return int|null
+        */
+       public function getRememberUserDuration() {
+               return null;
+       }
+
        /**
         * Notification that the session ID was reset
         *
@@ -358,6 +369,19 @@ abstract class SessionProvider implements SessionProviderInterface, LoggerAwareI
                }
        }
 
+       /**
+        * Invalidate existing sessions for a user
+        *
+        * If the provider has its own equivalent of CookieSessionProvider's Token
+        * cookie (and doesn't use User::getToken() to implement it), it should
+        * reset whatever token it does use here.
+        *
+        * @protected For use by \MediaWiki\Session\SessionManager only
+        * @param User $user;
+        */
+       public function invalidateSessionsForUser( User $user ) {
+       }
+
        /**
         * Return the HTTP headers that need varying on.
         *
@@ -458,7 +482,9 @@ abstract class SessionProvider implements SessionProviderInterface, LoggerAwareI
         *
         * Generally this will only be used when self::persistsSessionId() is false and
         * the provider has to base the session ID on the verified user's identity
-        * or other static data.
+        * or other static data. The SessionInfo should then typically have the
+        * 'forceUse' flag set to avoid persistent session failure if validation of
+        * the stored data fails.
         *
         * @param string $data
         * @param string|null $key Defaults to $this->config->get( 'SecretKey' )
@@ -484,7 +510,7 @@ abstract class SessionProvider implements SessionProviderInterface, LoggerAwareI
                        // @codeCoverageIgnoreEnd
                }
                if ( strlen( $hash ) >= 40 ) {
-                       $hash = wfBaseConvert( $hash, 16, 32, 32 );
+                       $hash = \Wikimedia\base_convert( $hash, 16, 32, 32 );
                }
                return substr( $hash, -32 );
        }
index 97fffda..d70a6b9 100644 (file)
@@ -1589,8 +1589,8 @@ abstract class Skin extends ContextSource {
        public function linkKnown(
                $target,
                $html = null,
-               $customAttribs = [ ],
-               $query = [ ],
+               $customAttribs = [],
+               $query = [],
                $options = [ 'known', 'noclasses' ]
        ) {
                wfDeprecated( __METHOD__, '1.21' );
index e5dc59f..cefc5bc 100644 (file)
@@ -663,27 +663,38 @@ class SkinTemplate extends Skin {
                        $loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink
                                ? 'nav-login-createaccount'
                                : 'pt-login';
-                       $is_signup = $request->getText( 'type' ) == 'signup';
 
-                       $login_url = [
-                               'text' => $this->msg( $loginlink )->text(),
-                               'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
-                               'active' => $title->isSpecial( 'Userlogin' )
-                                       && ( $loginlink == 'nav-login-createaccount' || !$is_signup ),
-                       ];
-                       $createaccount_url = [
-                               'text' => $this->msg( 'pt-createaccount' )->text(),
-                               'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
-                               'active' => $title->isSpecial( 'Userlogin' ) && $is_signup,
-                       ];
+                       // TODO remove this after AuthManager is stable
+                       global $wgDisableAuthManager;
+                       if ( $wgDisableAuthManager ) {
+                               $is_signup = $request->getText( 'type' ) == 'signup';
+                               $login_url = [
+                                       'text' => $this->msg( $loginlink )->text(),
+                                       'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
+                                       'active' => $title->isSpecial( 'Userlogin' )
+                                               && ( $loginlink == 'nav-login-createaccount' || !$is_signup ),
+                               ];
+                               $createaccount_url = [
+                                       'text' => $this->msg( 'pt-createaccount' )->text(),
+                                       'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
+                                       'active' => $title->isSpecial( 'Userlogin' ) && $is_signup,
+                               ];
+                       } else {
+                               $login_url = [
+                                       'text' => $this->msg( $loginlink )->text(),
+                                       'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
+                                       'active' => $title->isSpecial( 'Userlogin' ) ||
+                                               $title->isSpecial( 'CreateAccount' ) && $useCombinedLoginLink,
+                               ];
+                               $createaccount_url = [
+                                       'text' => $this->msg( 'pt-createaccount' )->text(),
+                                       'href' => self::makeSpecialUrl( 'CreateAccount', $returnto ),
+                                       'active' => $title->isSpecial( 'CreateAccount' ),
+                               ];
+                       }
 
                        // No need to show Talk and Contributions to anons if they can't contribute!
                        if ( User::groupHasPermission( '*', 'edit' ) ) {
-                               // Show the text "Not logged in"
-                               $personal_urls['anonuserpage'] = [
-                                       'text' => $this->msg( 'notloggedin' )->text()
-                               ];
-
                                // Because of caching, we can't link directly to the IP talk and
                                // contributions pages. Instead we use the special page shortcuts
                                // (which work correctly regardless of caching). This means we can't
diff --git a/includes/specialpage/AuthManagerSpecialPage.php b/includes/specialpage/AuthManagerSpecialPage.php
new file mode 100644 (file)
index 0000000..7866c12
--- /dev/null
@@ -0,0 +1,744 @@
+<?php
+
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\Session\SessionManager;
+use MediaWiki\Session\Token;
+
+/**
+ * A special page subclass for authentication-related special pages. It generates a form from
+ * a set of AuthenticationRequest objects, submits the result to AuthManager and
+ * partially handles the response.
+ */
+abstract class AuthManagerSpecialPage extends SpecialPage {
+       /** @var string[] The list of actions this special page deals with. Subclasses should override
+        * this. */
+       protected static $allowedActions = [
+               AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
+               AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
+               AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
+               AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
+       ];
+
+       /** @var array Customized messages */
+       protected static $messages = [];
+
+       /** @var string one of the AuthManager::ACTION_* constants. */
+       protected $authAction;
+
+       /** @var AuthenticationRequest[] */
+       protected $authRequests;
+
+       /** @var string Subpage of the special page. */
+       protected $subPage;
+
+       /** @var bool True if the current request is a result of returning from a redirect flow. */
+       protected $isReturn;
+
+       /** @var WebRequest|null If set, will be used instead of the real request. Used for redirection. */
+       protected $savedRequest;
+
+       /**
+        * Change the form descriptor that determines how a field will look in the authentication form.
+        * Called from fieldInfoToFormDescriptor().
+        * @param AuthenticationRequest[] $requests
+        * @param string $fieldInfo Field information array (union of all
+        *    AuthenticationRequest::getFieldInfo() responses).
+        * @param array $formDescriptor HTMLForm descriptor. The special key 'weight' can be set to
+        *    change the order of the fields.
+        * @param string $action Authentication type (one of the AuthManager::ACTION_* constants)
+        * @return bool
+        */
+       public function onAuthChangeFormFields(
+               array $requests, array $fieldInfo, array &$formDescriptor, $action
+       ) {
+               return true;
+       }
+
+       protected function getLoginSecurityLevel() {
+               return $this->getName();
+       }
+
+       public function getRequest() {
+               return $this->savedRequest ?: $this->getContext()->getRequest();
+       }
+
+       /**
+        * Override the POST data, GET data from the real request is preserved.
+        *
+        * Used to preserve POST data over a HTTP redirect.
+        *
+        * @param array $data
+        * @param bool $wasPosted
+        */
+       protected function setRequest( array $data, $wasPosted = null ) {
+               $request = $this->getContext()->getRequest();
+               if ( $wasPosted === null ) {
+                       $wasPosted = $request->wasPosted();
+               }
+               $this->savedRequest = new DerivativeRequest( $request, $data + $request->getQueryValues(),
+                       $wasPosted );
+       }
+
+       protected function beforeExecute( $subPage ) {
+               $this->getOutput()->disallowUserJs();
+
+               return $this->handleReturnBeforeExecute( $subPage )
+                       && $this->handleReauthBeforeExecute( $subPage );
+       }
+
+       /**
+        * Handle redirection from the /return subpage.
+        *
+        * This is used in the redirect flow where we need
+        * to be able to process data that was sent via a GET request. We set the /return subpage as
+        * the reentry point so we know we need to treat GET as POST, but we don't want to handle all
+        * future GETs as POSTs so we need to normalize the URL. (Also we don't want to show any
+        * received parameters around in the URL; they are ugly and might be sensitive.)
+        *
+        * Thus when on the /return subpage, we stash the request data in the session, redirect, then
+        * use the session to detect that we have been redirected, recover the data and replace the
+        * real WebRequest with a fake one that contains the saved data.
+        *
+        * @param string $subPage
+        * @return bool False if execution should be stopped.
+        */
+       protected function handleReturnBeforeExecute( $subPage ) {
+               $authManager = AuthManager::singleton();
+               $key = 'AuthManagerSpecialPage:return:' . $this->getName();
+
+               if ( $subPage === 'return' ) {
+                       $this->loadAuth( $subPage );
+                       $preservedParams = $this->getPreservedParams( false );
+
+                       // FIXME save POST values only from request
+                       $authData = array_diff_key( $this->getRequest()->getValues(),
+                               $preservedParams, [ 'title' => 1 ] );
+                       $authManager->setAuthenticationSessionData( $key, $authData );
+
+                       $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
+                       $this->getOutput()->redirect( $url );
+                       return false;
+               }
+
+               $authData = $authManager->getAuthenticationSessionData( $key );
+               if ( $authData ) {
+                       $authManager->removeAuthenticationSessionData( $key );
+                       $this->isReturn = true;
+                       $this->setRequest( $authData, true );
+               }
+
+               return true;
+       }
+
+       /**
+        * Handle redirection when the user needs to (re)authenticate.
+        *
+        * Send the user to the login form if needed; in case the request was a POST, stash in the
+        * session and simulate it once the user gets back.
+        *
+        * @param string $subPage
+        * @return bool False if execution should be stopped.
+        * @throws ErrorPageError When the user is not allowed to use this page.
+        */
+       protected function handleReauthBeforeExecute( $subPage ) {
+               $authManager = AuthManager::singleton();
+               $request = $this->getRequest();
+               $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
+
+               $securityLevel = $this->getLoginSecurityLevel();
+               if ( $securityLevel ) {
+                       $securityStatus = AuthManager::singleton()
+                               ->securitySensitiveOperationStatus( $securityLevel );
+                       if ( $securityStatus === AuthManager::SEC_REAUTH ) {
+                               $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
+
+                               if ( $request->wasPosted() ) {
+                                       // unique ID in case the same special page is open in multiple browser tabs
+                                       $uniqueId = MWCryptRand::generateHex( 6 );
+                                       $key = $key . ':' . $uniqueId;
+
+                                       $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
+                                       $authData = array_diff_key( $request->getValues(),
+                                                       $this->getPreservedParams( false ), [ 'title' => 1 ] );
+                                       $authManager->setAuthenticationSessionData( $key, $authData );
+                               }
+
+                               $title = SpecialPage::getTitleFor( 'Userlogin' );
+                               $url = $title->getFullURL( [
+                                       'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
+                                       'returntoquery' => wfArrayToCgi( $queryParams ),
+                                       'force' => $securityLevel,
+                               ], false, PROTO_HTTPS );
+
+                               $this->getOutput()->redirect( $url );
+                               return false;
+                       } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
+                               throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
+                       }
+               }
+
+               $uniqueId = $request->getVal( 'authUniqueId' );
+               if ( $uniqueId ) {
+                       $key = $key . ':' . $uniqueId;
+                       $authData = $authManager->getAuthenticationSessionData( $key );
+                       if ( $authData ) {
+                               $authManager->removeAuthenticationSessionData( $key );
+                               $this->setRequest( $authData, true );
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * Get the default action for this special page, if none is given via URL/POST data.
+        * Subclasses should override this (or override loadAuth() so this is never called).
+        * @param string $subPage Subpage of the special page.
+        * @return string an AuthManager::ACTION_* constant.
+        */
+       protected function getDefaultAction( $subPage ) {
+               throw new BadMethodCallException( 'Subclass did not implement getDefaultAction' );
+       }
+
+       /**
+        * Return custom message key.
+        * Allows subclasses to customize messages.
+        * @return string
+        */
+       protected function messageKey( $defaultKey ) {
+               return array_key_exists( $defaultKey, static::$messages )
+                       ? static::$messages[$defaultKey] : $defaultKey;
+       }
+
+       /**
+        * Allows blacklisting certain request types.
+        * @return array A list of AuthenticationRequest subclass names
+        */
+       protected function getRequestBlacklist() {
+               return [];
+       }
+
+       /**
+        * Load or initialize $authAction, $authRequests and $subPage.
+        * Subclasses should call this from execute() or otherwise ensure the variables are initialized.
+        * @param string $subPage Subpage of the special page.
+        * @param string $authAction Override auth action specified in request (this is useful
+        *    when the form needs to be changed from <action> to <action>_CONTINUE after a successful
+        *    authentication step)
+        * @param bool $reset Regenerate the requests even if a cached version is available
+        */
+       protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
+               // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
+               // calls. This is important for requests which have hidden information so any
+               // getAuthenticationRequests call would mean putting data into some cache.
+               if (
+                       !$reset && $this->subPage === $subPage && $this->authAction
+                       && ( !$authAction || $authAction === $this->authAction )
+               ) {
+                       return;
+               }
+
+               $request = $this->getRequest();
+               $this->subPage = $subPage;
+               $this->authAction = $authAction ?: $request->getText( 'authAction' );
+               if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
+                       $this->authAction = $this->getDefaultAction( $subPage );
+                       if ( $request->wasPosted() ) {
+                               $continueAction = $this->getContinueAction( $this->authAction );
+                               if ( in_array( $continueAction, static::$allowedActions, true ) ) {
+                                       $this->authAction = $continueAction;
+                               }
+                       }
+               }
+
+               $allReqs = AuthManager::singleton()->getAuthenticationRequests(
+                       $this->authAction, $this->getUser() );
+               $this->authRequests = array_filter( $allReqs, function ( $req ) use ( $subPage ) {
+                       return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
+               } );
+       }
+
+       /**
+        * Returns true if this is not the first step of the authentication.
+        * @return bool
+        */
+       protected function isContinued() {
+               return in_array( $this->authAction, [
+                       AuthManager::ACTION_LOGIN_CONTINUE,
+                       AuthManager::ACTION_CREATE_CONTINUE,
+                       AuthManager::ACTION_LINK_CONTINUE,
+               ], true );
+       }
+
+       /**
+        * Gets the _CONTINUE version of an action.
+        * @param string $action An AuthManager::ACTION_* constant.
+        * @return string An AuthManager::ACTION_*_CONTINUE constant.
+        */
+       protected function getContinueAction( $action ) {
+               switch ( $action ) {
+                       case AuthManager::ACTION_LOGIN:
+                               $action = AuthManager::ACTION_LOGIN_CONTINUE;
+                               break;
+                       case AuthManager::ACTION_CREATE:
+                               $action = AuthManager::ACTION_CREATE_CONTINUE;
+                               break;
+                       case AuthManager::ACTION_LINK:
+                               $action = AuthManager::ACTION_LINK_CONTINUE;
+                               break;
+               }
+               return $action;
+       }
+
+       /**
+        * Checks whether AuthManager is ready to perform the action.
+        * ACTION_CHANGE needs special verification (AuthManager::allowsAuthenticationData*) which is
+        * the caller's responsibility.
+        * @param string $action One of the AuthManager::ACTION_* constants in static::$allowedActions
+        * @return bool
+        * @throws LogicException if $action is invalid
+        */
+       protected function isActionAllowed( $action ) {
+               $authManager = AuthManager::singleton();
+               if ( !in_array( $action, static::$allowedActions, true ) ) {
+                       throw new InvalidArgumentException( 'invalid action: ' . $action );
+               }
+
+               // calling getAuthenticationRequests can be expensive, avoid if possible
+               $requests = ( $action === $this->authAction ) ? $this->authRequests
+                       : $authManager->getAuthenticationRequests( $action );
+               if ( !$requests ) {
+                       // no provider supports this action in the current state
+                       return false;
+               }
+
+               switch ( $action ) {
+                       case AuthManager::ACTION_LOGIN:
+                       case AuthManager::ACTION_LOGIN_CONTINUE:
+                               return $authManager->canAuthenticateNow();
+                       case AuthManager::ACTION_CREATE:
+                       case AuthManager::ACTION_CREATE_CONTINUE:
+                               return $authManager->canCreateAccounts();
+                       case AuthManager::ACTION_LINK:
+                       case AuthManager::ACTION_LINK_CONTINUE:
+                               return $authManager->canLinkAccounts();
+                       case AuthManager::ACTION_CHANGE:
+                       case AuthManager::ACTION_REMOVE:
+                       case AuthManager::ACTION_UNLINK:
+                               return true;
+                       default:
+                               // should never reach here but makes static code analyzers happy
+                               throw new InvalidArgumentException( 'invalid action: ' . $action );
+               }
+       }
+
+       /**
+        * @param string $action One of the AuthManager::ACTION_* constants
+        * @param AuthenticationRequest[] $requests
+        * @return AuthenticationResponse
+        * @throws LogicException if $action is invalid
+        */
+       protected function performAuthenticationStep( $action, array $requests ) {
+               if ( !in_array( $action, static::$allowedActions, true ) ) {
+                       throw new InvalidArgumentException( 'invalid action: ' . $action );
+               }
+
+               $authManager = AuthManager::singleton();
+               $returnToUrl = $this->getPageTitle( 'return' )
+                       ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
+
+               switch ( $action ) {
+                       case AuthManager::ACTION_LOGIN:
+                               return $authManager->beginAuthentication( $requests, $returnToUrl );
+                       case AuthManager::ACTION_LOGIN_CONTINUE:
+                               return $authManager->continueAuthentication( $requests );
+                       case AuthManager::ACTION_CREATE:
+                               return $authManager->beginAccountCreation( $this->getUser(), $requests,
+                                       $returnToUrl );
+                       case AuthManager::ACTION_CREATE_CONTINUE:
+                               return $authManager->continueAccountCreation( $requests );
+                       case AuthManager::ACTION_LINK:
+                               return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
+                       case AuthManager::ACTION_LINK_CONTINUE:
+                               return $authManager->continueAccountLink( $requests );
+                       case AuthManager::ACTION_CHANGE:
+                       case AuthManager::ACTION_REMOVE:
+                       case AuthManager::ACTION_UNLINK:
+                               if ( count( $requests ) > 1 ) {
+                                       throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
+                               } elseif ( !$requests ) {
+                                       throw new InvalidArgumentException( 'no auth request' );
+                               }
+                               $req = reset( $requests );
+                               $status = $authManager->allowsAuthenticationDataChange( $req );
+                               Hooks::run( 'ChangeAuthenticationDataAudit', [ $req, $status ] );
+                               if ( !$status->isOK() ) {
+                                       return AuthenticationResponse::newFail( $status->getMessage() );
+                               }
+                               $authManager->changeAuthenticationData( $req );
+                               return AuthenticationResponse::newPass();
+                       default:
+                               // should never reach here but makes static code analyzers happy
+                               throw new InvalidArgumentException( 'invalid action: ' . $action );
+               }
+       }
+
+       /**
+        * Attempts to do an authentication step with the submitted data.
+        * Subclasses should probably call this from execute().
+        * @return false|Status
+        *    - false if there was no submit at all
+        *    - a good Status wrapping an AuthenticationResponse if the form submit was successful.
+        *      This does not necessarily mean that the authentication itself was successful; see the
+        *      response for that.
+        *    - a bad Status for form errors.
+        */
+       protected function trySubmit() {
+               $status = false;
+
+               $form = $this->getAuthForm( $this->authRequests, $this->authAction );
+               $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
+
+               if ( $this->getRequest()->wasPosted() ) {
+                       // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
+                       $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
+                       $sessionToken = $this->getToken();
+                       if ( $sessionToken->wasNew() ) {
+                               return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
+                       } elseif ( !$requestTokenValue ) {
+                               return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
+                       } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
+                               return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
+                       }
+
+                       $form->prepareForm();
+                       $status = $form->trySubmit();
+
+                       // HTMLForm submit return values are a mess; let's ensure it is false or a Status
+                       // FIXME this probably should be in HTMLForm
+                       if ( $status === true ) {
+                               // not supposed to happen since our submit handler should always return a Status
+                               throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
+                       } elseif ( $status === false ) {
+                               // form was not submitted; nothing to do
+                       } elseif ( $status instanceof Status ) {
+                               // already handled by the form; nothing to do
+                       } elseif ( $status instanceof StatusValue ) {
+                               // in theory not an allowed return type but nothing stops the submit handler from
+                               // accidentally returning it so best check and fix
+                               $status = Status::wrap( $status );
+                       } elseif ( is_string( $status ) ) {
+                               $status = Status::newFatal( new RawMessage( '$1', $status ) );
+                       } elseif ( is_array( $status ) ) {
+                               if ( is_string( reset( $status ) ) ) {
+                                       $status = call_user_func_array( 'Status::newFatal', $status );
+                               } elseif ( is_array( reset( $status ) ) ) {
+                                       $status = Status::newGood();
+                                       foreach ( $status as $message ) {
+                                               call_user_func_array( [ $status, 'fatal' ], $message );
+                                       }
+                               } else {
+                                       throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
+                                               . 'first element of array is ' . gettype( reset( $status ) ) );
+                               }
+                       } else {
+                               // not supposed to happen but HTMLForm does not actually verify the return type
+                               // from the submit callback; better safe then sorry
+                               throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
+                                       . gettype( $status ) );
+                       }
+
+                       if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
+                               // This is awkward. There was a form validation error, which means the data was not
+                               // passed to AuthManager. Normally we would display the form with an error message,
+                               // but for the data we received via the redirect flow that would not be helpful at all.
+                               // Let's just submit the data to AuthManager directly instead.
+                               LoggerFactory::getInstance( 'authmanager' )
+                                       ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
+                                               'status' => $status->getWikiText() ] );
+                               $status = $this->handleFormSubmit( $form->mFieldData );
+                       }
+               }
+
+               $changeActions = [
+                       AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
+               ];
+               if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
+                       Hooks::run( 'ChangeAuthenticationDataAudit', [ reset( $this->authRequests ), $status ] );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Submit handler callback for HTMLForm
+        * @private
+        * @param $data array Submitted data
+        * @return Status
+        */
+       public function handleFormSubmit( $data ) {
+               $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
+               $response = $this->performAuthenticationStep( $this->authAction, $requests );
+
+               // we can't handle FAIL or similar as failure here since it might require changing the form
+               return Status::newGood( $response );
+       }
+
+       /**
+        * Returns URL query parameters which can be used to reload the page (or leave and return) while
+        * preserving all information that is necessary for authentication to continue. These parameters
+        * will be preserved in the action URL of the form and in the return URL for redirect flow.
+        * @param bool $withToken Include CSRF token
+        * @return array
+        */
+       protected function getPreservedParams( $withToken = false ) {
+               $params = [];
+               if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
+                       $params['authAction'] = $this->getContinueAction( $this->authAction );
+               }
+               if ( $withToken ) {
+                       $params[$this->getTokenName()] = $this->getToken()->toString();
+               }
+               return $params;
+       }
+
+       /**
+        * Generates a HTMLForm descriptor array from a set of authentication requests.
+        * @param AuthenticationRequest[] $requests
+        * @param string $action AuthManager action name (one of the AuthManager::ACTION_* constants)
+        * @return array
+        */
+       protected function getAuthFormDescriptor( $requests, $action ) {
+               $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
+               $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
+
+               $this->addTabIndex( $formDescriptor );
+
+               return $formDescriptor;
+       }
+
+       /**
+        * @param AuthenticationRequest[] $requests
+        * @param string $action AuthManager action name (one of the AuthManager::ACTION_* constants)
+        * @return HTMLForm
+        */
+       protected function getAuthForm( array $requests, $action ) {
+               $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
+               $context = $this->getContext();
+               if ( $context->getRequest() !== $this->getRequest() ) {
+                       // We have overridden the request, need to make sure the form uses that too.
+                       $context = new DerivativeContext( $this->getContext() );
+                       $context->setRequest( $this->getRequest() );
+               }
+               $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
+               $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
+               $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
+               $form->addHiddenField( 'authAction', $this->authAction );
+               $form->suppressDefaultSubmit( !$this->needsSubmitButton( $formDescriptor ) );
+
+               return $form;
+       }
+
+       /**
+        * Display the form.
+        * @param false|Status|StatusValue $status A form submit status, as in HTMLForm::trySubmit()
+        */
+       protected function displayForm( $status ) {
+               if ( $status instanceof StatusValue ) {
+                       $status = Status::wrap( $status );
+               }
+               $form = $this->getAuthForm( $this->authRequests, $this->authAction );
+               $form->prepareForm()->displayForm( $status );
+       }
+
+       /**
+        * Returns true if the form has fields which take values. If all available providers use the
+        * redirect flow, the form might contain nothing but submit buttons, in which case we should
+        * not add an extra submit button which does nothing.
+        *
+        * @param array $formDescriptor A HTMLForm descriptor
+        * @return bool
+        */
+       protected function needsSubmitButton( $formDescriptor ) {
+               return (bool)array_filter( $formDescriptor, function ( $item ) {
+                       $class = false;
+                       if ( array_key_exists( 'class', $item ) ) {
+                               $class = $item['class'];
+                       } elseif ( array_key_exists( 'type', $item ) ) {
+                               $class = HTMLForm::$typeMappings[$item['type']];
+                       }
+                       return !in_array( $class, [ 'HTMLInfoField', 'HTMLSubmitField' ], true );
+               } );
+       }
+
+       /**
+        * Adds a sequential tabindex starting from 1 to all form elements. This way the user can
+        * use the tab key to traverse the form without having to step through all links and such.
+        * @param $formDescriptor
+        */
+       protected function addTabIndex( &$formDescriptor ) {
+               $i = 1;
+               foreach ( $formDescriptor as $field => &$definition ) {
+                       $class = false;
+                       if ( array_key_exists( 'class', $definition ) ) {
+                               $class = $definition['class'];
+                       } elseif ( array_key_exists( 'type', $definition ) ) {
+                               $class = HTMLForm::$typeMappings[$definition['type']];
+                       }
+                       if ( $class !== 'HTMLInfoField' ) {
+                               $definition['tabindex'] = $i;
+                               $i++;
+                       }
+               }
+       }
+
+       /**
+        * Returns the CSRF token.
+        * @return Token
+        */
+       protected function getToken() {
+               return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
+                       . $this->getName() );
+       }
+
+       /**
+        * Returns the name of the CSRF token (under which it should be found in the POST or GET data).
+        * @return string
+        */
+       protected function getTokenName() {
+               return 'wpAuthToken';
+       }
+
+       /**
+        * Turns a field info array into a form descriptor. Behavior can be modified by the
+        * AuthChangeFormFields hook.
+        * @param AuthenticationRequest[] $requests
+        * @param array $fieldInfo Field information, in the format used by
+        *   AuthenticationRequest::getFieldInfo()
+        * @param string $action One of the AuthManager::ACTION_* constants
+        * @return array A form descriptor that can be passed to HTMLForm
+        */
+       protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
+               $formDescriptor = [];
+               foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
+                       $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
+               }
+
+               $requestSnapshot = serialize( $requests );
+               $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
+               \Hooks::run( 'AuthChangeFormFields', [ $requests, $fieldInfo, &$formDescriptor, $action ] );
+               if ( $requestSnapshot !== serialize( $requests ) ) {
+                       LoggerFactory::getInstance( 'authentication' )->warning(
+                               'AuthChangeFormFields hook changed auth requests' );
+               }
+
+               // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
+               // subscribers (who only see one field at a time) to influence ordering.
+               self::sortFormDescriptorFields( $formDescriptor );
+
+               return $formDescriptor;
+       }
+
+       /**
+        * Maps an authentication field configuration for a single field (as returned by
+        * AuthenticationRequest::getFieldInfo()) to a HTMLForm field descriptor.
+        * @param array $singleFieldInfo
+        * @return array
+        */
+       protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
+               $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
+               $descriptor = [
+                       'type' => $type,
+                       // Do not prefix input name with 'wp'. This is important for the redirect flow.
+                       'name' => $fieldName,
+               ];
+
+               if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
+                       $descriptor['default'] = wfMessage( $singleFieldInfo['label'] )->plain();
+               } elseif ( $type !== 'submit' ) {
+                       $descriptor += array_filter( [
+                               // help-message is omitted as it is usually not really useful for a web interface
+                               'label-message' => self::getField( $singleFieldInfo, 'label' ),
+                       ] );
+
+                       if ( isset( $singleFieldInfo['options'] ) ) {
+                               $descriptor['options'] = array_flip( array_map( function ( $message ) {
+                                       /** @var $message Message */
+                                       return $message->parse();
+                               }, $singleFieldInfo['options'] ) );
+                       }
+
+                       if ( isset( $singleFieldInfo['value'] ) ) {
+                               $descriptor['default'] = $singleFieldInfo['value'];
+                       }
+
+                       if ( empty( $singleFieldInfo['optional'] ) ) {
+                               $descriptor['required'] = true;
+                       }
+               }
+
+               return $descriptor;
+       }
+
+       /**
+        * Sort the fields of a form descriptor by their 'weight' property. (Fields with higher weight
+        * are shown closer to the bottom; weight defaults to 0. Negative weight is allowed.)
+        * Keep order if weights are equal.
+        * @param array $formDescriptor
+        * @return array
+        */
+       protected static function sortFormDescriptorFields( array &$formDescriptor ) {
+               $i = 0;
+               foreach ( $formDescriptor as &$field ) {
+                       $field['__index'] = $i++;
+               }
+               uasort( $formDescriptor, function ( $first, $second ) {
+                       return self::getField( $first, 'weight', 0 ) - self::getField( $second, 'weight', 0 )
+                               ?: $first['__index'] - $second['__index'];
+               } );
+               foreach ( $formDescriptor as &$field ) {
+                       unset( $field['__index'] );
+               }
+       }
+
+       /**
+        * Get an array value, or a default if it does not exist.
+        * @param array $array
+        * @param string $fieldName
+        * @param mixed $default
+        * @return mixed
+        */
+       protected static function getField( array $array, $fieldName, $default = null ) {
+               if ( array_key_exists( $fieldName, $array ) ) {
+                       return $array[$fieldName];
+               } else {
+                       return $default;
+               }
+       }
+
+       /**
+        * Maps AuthenticationRequest::getFieldInfo() types to HTMLForm types
+        * @param string $type
+        * @return string
+        * @throws \LogicException
+        */
+       protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
+               $map = [
+                       'string' => 'text',
+                       'password' => 'password',
+                       'select' => 'select',
+                       'checkbox' => 'check',
+                       'multiselect' => 'multiselect',
+                       'button' => 'submit',
+                       'hidden' => 'hidden',
+                       'null' => 'info',
+               ];
+               if ( !array_key_exists( $type, $map ) ) {
+                       throw new \LogicException( 'invalid field type: ' . $type );
+               }
+               return $map[$type];
+       }
+}
diff --git a/includes/specialpage/LoginSignupSpecialPage.php b/includes/specialpage/LoginSignupSpecialPage.php
new file mode 100644 (file)
index 0000000..0e4252c
--- /dev/null
@@ -0,0 +1,1636 @@
+<?php
+/**
+ * Holds shared logic for login and account creation pages.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\Throttler;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\Session\SessionManager;
+use Psr\Log\LogLevel;
+
+/**
+ * Holds shared logic for login and account creation pages.
+ *
+ * @ingroup SpecialPage
+ */
+abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
+       protected $mReturnTo;
+       protected $mPosted;
+       protected $mAction;
+       protected $mLanguage;
+       protected $mReturnToQuery;
+       protected $mToken;
+       protected $mStickHTTPS;
+       protected $mFromHTTP;
+       protected $mEntryError = '';
+       protected $mEntryErrorType = 'error';
+
+       protected $mLoaded = false;
+       protected $mSecureLoginUrl;
+
+       /** @var string */
+       protected $securityLevel;
+
+       /** @var bool True if the user if creating an account for someone else. Flag used for internal
+        * communication, only set at the very end. */
+       protected $proxyAccountCreation;
+       /** @var User FIXME another flag for passing data. */
+       protected $targetUser;
+
+       /** @var HTMLForm */
+       protected $authForm;
+
+       /** @var FakeAuthTemplate */
+       protected $fakeTemplate;
+
+       abstract protected function isSignup();
+
+       /**
+        * @param bool $direct True if the action was successful just now; false if that happened
+        *    pre-redirection (so this handler was called already)
+        * @param StatusValue|null $extraMessages
+        * @return void
+        */
+       abstract protected function successfulAction( $direct = false, $extraMessages = null );
+
+       /**
+        * Logs to the authmanager-stats channel.
+        * @param bool $success
+        * @param string|null $status Error message key
+        */
+       abstract protected function logAuthResult( $success, $status = null );
+
+       public function __construct( $name ) {
+               global $wgUseMediaWikiUIEverywhere;
+               parent::__construct( $name );
+
+               // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
+               $wgUseMediaWikiUIEverywhere = true;
+       }
+
+       /**
+        * Load data from request.
+        * @private
+        * @param string $subPage Subpage of Special:Userlogin
+        */
+       protected function load( $subPage ) {
+               global $wgSecureLogin;
+
+               if ( $this->mLoaded ) {
+                       return;
+               }
+               $this->mLoaded = true;
+
+               $request = $this->getRequest();
+
+               $this->mPosted = $request->wasPosted();
+               $this->mIsReturn = $subPage === 'return';
+               $this->mAction = $request->getVal( 'action' );
+               $this->mFromHTTP = $request->getBool( 'fromhttp', false )
+                       || $request->getBool( 'wpFromhttp', false );
+               $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
+                       || $request->getBool( 'wpForceHttps', false );
+               $this->mLanguage = $request->getText( 'uselang' );
+               $this->mReturnTo = $request->getVal( 'returnto', '' );
+               $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
+
+               $securityLevel = $this->getRequest()->getText( 'force' );
+               if (
+                       $securityLevel && AuthManager::singleton()->securitySensitiveOperationStatus(
+                               $securityLevel ) === AuthManager::SEC_REAUTH
+               ) {
+                       $this->securityLevel = $securityLevel;
+               }
+
+               $this->loadAuth( $subPage );
+
+               $this->mToken = $request->getVal( $this->getTokenName() );
+
+               // Show an error or warning passed on from a previous page
+               $entryError = $this->msg( $request->getVal( 'error', '' ) );
+               $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
+               // bc: provide login link as a parameter for messages where the translation
+               // was not updated
+               $loginreqlink = Linker::linkKnown(
+                       $this->getPageTitle(),
+                       $this->msg( 'loginreqlink' )->escaped(),
+                       [],
+                       [
+                               'returnto' => $this->mReturnTo,
+                               'returntoquery' => $this->mReturnToQuery,
+                               'uselang' => $this->mLanguage,
+                               'fromhttp' => $wgSecureLogin && $this->mFromHTTP ? '1' : null,
+                       ]
+               );
+
+               // Only show valid error or warning messages.
+               if ( $entryError->exists()
+                       && in_array( $entryError->getKey(), LoginHelper::getValidErrorMessages(), true )
+               ) {
+                       $this->mEntryErrorType = 'error';
+                       $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
+
+               } elseif ( $entryWarning->exists()
+                       && in_array( $entryWarning->getKey(), LoginHelper::getValidErrorMessages(), true )
+               ) {
+                       $this->mEntryErrorType = 'warning';
+                       $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
+               }
+
+               # 1. When switching accounts, it sucks to get automatically logged out
+               # 2. Do not return to PasswordReset after a successful password change
+               #    but goto Wiki start page (Main_Page) instead ( bug 33997 )
+               $returnToTitle = Title::newFromText( $this->mReturnTo );
+               if ( is_object( $returnToTitle )
+                       && ( $returnToTitle->isSpecial( 'Userlogout' )
+                               || $returnToTitle->isSpecial( 'PasswordReset' ) )
+               ) {
+                       $this->mReturnTo = '';
+                       $this->mReturnToQuery = '';
+               }
+       }
+
+       protected function getPreservedParams( $withToken = false ) {
+               global $wgSecureLogin;
+
+               $params = parent::getPreservedParams( $withToken );
+               $params += [
+                       'returnto' => $this->mReturnTo ?: null,
+                       'returntoquery' => $this->mReturnToQuery ?: null,
+               ];
+               if ( $wgSecureLogin && !$this->isSignup() ) {
+                       $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
+               }
+               return $params;
+       }
+
+       /**
+        * @param string|null $subPage
+        */
+       public function execute( $subPage ) {
+               $authManager = AuthManager::singleton();
+               $session = SessionManager::getGlobalSession();
+
+               // Session data is used for various things in the authentication process, so we must make
+               // sure a session cookie or some equivalent mechanism is set.
+               $session->persist();
+
+               $this->load( $subPage );
+               $this->setHeaders();
+               $this->checkPermissions();
+
+               // Make sure it's possible to log in
+               if ( !$this->isSignup() && !$session->canSetUser() ) {
+                       throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
+                                       $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
+                               ] );
+               }
+
+               /*
+                * In the case where the user is already logged in, and was redirected to
+                * the login form from a page that requires login, do not show the login
+                * page. The use case scenario for this is when a user opens a large number
+                * of tabs, is redirected to the login page on all of them, and then logs
+                * in on one, expecting all the others to work properly.
+                *
+                * However, do show the form if it was visited intentionally (no 'returnto'
+                * is present). People who often switch between several accounts have grown
+                * accustomed to this behavior.
+                *
+                * Also make an exception when force=<level> is set in the URL, which means the user must
+                * reauthenticate for security reasons.
+                */
+               if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
+                        ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
+                        $this->getUser()->isLoggedIn()
+               ) {
+                       $this->successfulAction();
+               }
+
+               // If logging in and not on HTTPS, either redirect to it or offer a link.
+               global $wgSecureLogin;
+               if ( $this->getRequest()->getProtocol() !== 'https' ) {
+                       $title = $this->getFullTitle();
+                       $query = $this->getPreservedParams( false ) + [
+                                       'title' => null,
+                                       ( $this->mEntryErrorType === 'error' ? 'error'
+                                               : 'warning' ) => $this->mEntryError,
+                               ] + $this->getRequest()->getQueryValues();
+                       $url = $title->getFullURL( $query, false, PROTO_HTTPS );
+                       if ( $wgSecureLogin && !$this->mFromHTTP &&
+                                wfCanIPUseHTTPS( $this->getRequest()->getIP() )
+                       ) {
+                               // Avoid infinite redirect
+                               $url = wfAppendQuery( $url, 'fromhttp=1' );
+                               $this->getOutput()->redirect( $url );
+                               // Since we only do this redir to change proto, always vary
+                               $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
+
+                               return;
+                       } else {
+                               // A wiki without HTTPS login support should set $wgServer to
+                               // http://somehost, in which case the secure URL generated
+                               // above won't actually start with https://
+                               if ( substr( $url, 0, 8 ) === 'https://' ) {
+                                       $this->mSecureLoginUrl = $url;
+                               }
+                       }
+               }
+
+               if ( !$this->isActionAllowed( $this->authAction ) ) {
+                       // FIXME how do we explain this to the user? can we handle session loss better?
+                       // messages used: authpage-cannot-login, authpage-cannot-login-continue,
+                       // authpage-cannot-create, authpage-cannot-create-continue
+                       $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
+                       return;
+               }
+
+               $status = $this->trySubmit();
+
+               if ( !$status || !$status->isGood() ) {
+                       $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
+                       return;
+               }
+
+               /** @var AuthenticationResponse $response */
+               $response = $status->getValue();
+
+               $returnToUrl = $this->getPageTitle( 'return' )
+                       ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
+               switch ( $response->status ) {
+                       case AuthenticationResponse::PASS:
+                               $this->logAuthResult( true );
+                               $this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon();
+                               $this->targetUser = User::newFromName( $response->username );
+
+                               if (
+                                       !$this->proxyAccountCreation
+                                       && $response->loginRequest
+                                       && $authManager->canAuthenticateNow()
+                               ) {
+                                       // successful registration; log the user in instantly
+                                       $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
+                                               $returnToUrl );
+                                       if ( $response2->status !== AuthenticationResponse::PASS ) {
+                                               LoggerFactory::getInstance( 'login' )
+                                                       ->error( 'Could not log in after account creation' );
+                                               $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
+                                               break;
+                                       }
+                               }
+
+                               if ( !$this->proxyAccountCreation ) {
+                                       // Ensure that the context user is the same as the session user.
+                                       $this->setSessionUserForCurrentRequest();
+                               }
+
+                               $this->successfulAction( true );
+                               break;
+                       case AuthenticationResponse::FAIL:
+                               // fall through
+                       case AuthenticationResponse::RESTART:
+                               unset( $this->authForm );
+                               if ( $response->status === AuthenticationResponse::FAIL ) {
+                                       $action = $this->getDefaultAction( $subPage );
+                                       $messageType = 'error';
+                               } else {
+                                       $action = $this->getContinueAction( $this->authAction );
+                                       $messageType = 'warning';
+                               }
+                               $this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
+                               $this->loadAuth( $subPage, $action, true );
+                               $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
+                               break;
+                       case AuthenticationResponse::REDIRECT:
+                               unset( $this->authForm );
+                               $this->getOutput()->redirect( $response->redirectTarget );
+                               break;
+                       case AuthenticationResponse::UI:
+                               unset( $this->authForm );
+                               $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
+                                       : AuthManager::ACTION_LOGIN_CONTINUE;
+                               $this->authRequests = $response->neededRequests;
+                               $this->mainLoginForm( $response->neededRequests, $response->message, 'warning' );
+                               break;
+                       default:
+                               throw new LogicException( 'invalid AuthenticationResponse' );
+               }
+       }
+
+       /**
+        * Show the success page.
+        *
+        * @param string $type Condition of return to; see `executeReturnTo`
+        * @param string|Message $title Page's title
+        * @param string $msgname
+        * @param string $injected_html
+        * @param StatusValue|null $extraMessages
+        */
+       protected function showSuccessPage(
+               $type, $title, $msgname, $injected_html, $extraMessages
+       ) {
+               $out = $this->getOutput();
+               $out->setPageTitle( $title );
+               if ( $msgname ) {
+                       $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
+               }
+               if ( $extraMessages ) {
+                       $extraMessages = Status::wrap( $extraMessages );
+                       $out->addWikiText( $extraMessages->getWikiText() );
+               }
+
+               $out->addHTML( $injected_html );
+
+               $helper = new LoginHelper( $this->getContext() );
+               $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS );
+       }
+
+       /**
+        * Add a "return to" link or redirect to it.
+        * Extensions can use this to reuse the "return to" logic after
+        * inject steps (such as redirection) into the login process.
+        *
+        * @param string $type One of the following:
+        *    - error: display a return to link ignoring $wgRedirectOnLogin
+        *    - signup: display a return to link using $wgRedirectOnLogin if needed
+        *    - success: display a return to link using $wgRedirectOnLogin if needed
+        *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
+        * @param string $returnTo
+        * @param array|string $returnToQuery
+        * @param bool $stickHTTPS Keep redirect link on HTTPS
+        * @since 1.22
+        */
+       public function showReturnToPage(
+               $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
+       ) {
+               $helper = new LoginHelper( $this->getContext() );
+               $helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS );
+       }
+
+       /**
+        * Replace some globals to make sure the fact that the user has just been logged in is
+        * reflected in the current request.
+        * @param User $user
+        */
+       protected function setSessionUserForCurrentRequest() {
+               global $wgUser, $wgLang;
+
+               $context = RequestContext::getMain();
+               $localContext = $this->getContext();
+               if ( $context !== $localContext ) {
+                       // remove AuthManagerSpecialPage context hack
+                       $this->setContext( $context );
+               }
+
+               $user = $context->getRequest()->getSession()->getUser();
+
+               $wgUser = $user;
+               $context->setUser( $user );
+
+               $code = $this->getRequest()->getVal( 'uselang', $user->getOption( 'language' ) );
+               $userLang = Language::factory( $code );
+               $wgLang = $userLang;
+               $context->setLanguage( $userLang );
+       }
+
+       /**
+        * @param AuthenticationRequest[] $requests A list of AuthorizationRequest objects,
+        *   used to generate the form fields. An empty array means a fatal error
+        *   (authentication cannot continue).
+        * @param string|Message $msg
+        * @param string $msgtype
+        * @throws ErrorPageError
+        * @throws Exception
+        * @throws FatalError
+        * @throws MWException
+        * @throws PermissionsError
+        * @throws ReadOnlyError
+        * @private
+        */
+       protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
+               $titleObj = $this->getPageTitle();
+               $user = $this->getUser();
+               $out = $this->getOutput();
+
+               // FIXME how to handle empty $requests - restart, or no form, just an error message?
+               // no form would be better for no session type errors, restart is better when can* fails.
+               if ( !$requests ) {
+                       $this->authAction = $this->getDefaultAction( $this->subPage );
+                       $this->authForm = null;
+                       $requests = AuthManager::singleton()->getAuthenticationRequests( $this->authAction, $user );
+               }
+
+               // Generic styles and scripts for both login and signup form
+               $out->addModuleStyles( [
+                       'mediawiki.ui',
+                       'mediawiki.ui.button',
+                       'mediawiki.ui.checkbox',
+                       'mediawiki.ui.input',
+                       'mediawiki.special.userlogin.common.styles'
+               ] );
+               if ( $this->isSignup() ) {
+                       // XXX hack pending RL or JS parse() support for complex content messages T27349
+                       $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
+                               $this->msg( 'createacct-imgcaptcha-help' )->parse() );
+
+                       // Additional styles and scripts for signup form
+                       $out->addModules( [
+                               'mediawiki.special.userlogin.signup.js'
+                       ] );
+                       $out->addModuleStyles( [
+                               'mediawiki.special.userlogin.signup.styles'
+                       ] );
+               } else {
+                       // Additional styles for login form
+                       $out->addModuleStyles( [
+                               'mediawiki.special.userlogin.login.styles'
+                       ] );
+               }
+               $out->disallowUserJs(); // just in case...
+
+               $form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
+               $form->prepareForm();
+               $formHtml = $form->getHTML( $msg ? Status::newFatal( $msg ) : false );
+
+               $out->addHTML( $this->getPageHtml( $formHtml ) );
+       }
+
+       /**
+        * Add page elements which are outside the form.
+        * FIXME this should probably be a template, but use a sane language (handlebars?)
+        * @param string $formHtml
+        * @return string
+        */
+       protected function getPageHtml( $formHtml ) {
+               global $wgLoginLanguageSelector;
+
+               $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
+                       [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
+               $languageLinks = $wgLoginLanguageSelector ? $this->makeLanguageSelector() : '';
+               $signupStartMsg = $this->msg( 'signupstart' );
+               $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
+                       ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
+               if ( $languageLinks ) {
+                       $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
+                               Html::rawElement( 'p', [], $languageLinks )
+                       );
+               }
+
+               $benefitsContainer = '';
+               if ( $this->isSignup() && $this->showExtraInformation() ) {
+                       // messages used:
+                       // createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
+                       // createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
+                       // createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
+                       $benefitCount = 3;
+                       $benefitList = '';
+                       for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
+                               $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
+                               $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->escaped();
+                               $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
+                                       Html::rawElement( 'h3', [],
+                                               $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
+                                       )
+                                       . Html::rawElement( 'p', [],
+                                               $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
+                                       )
+                               );
+                       }
+                       $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
+                               Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() )
+                               . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ],
+                                       $benefitList
+                               )
+                       );
+               }
+
+               $html = Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
+                       $loginPrompt
+                       . $languageLinks
+                       . $signupStart
+                       . Html::rawElement( 'div', [ 'id' => 'userloginForm' ],
+                               $formHtml
+                       )
+                       . $benefitsContainer
+               );
+
+               return $html;
+       }
+
+       /**
+        * Generates a form from the given request.
+        * @param AuthenticationRequest[] $requests
+        * @param string $action AuthManager action name
+        * @param string|Message $msg
+        * @param string $msgType
+        * @return HTMLForm
+        */
+       protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) {
+               global $wgSecureLogin, $wgLoginLanguageSelector;
+               // FIXME merge this with parent
+
+               if ( isset( $this->authForm ) ) {
+                       return $this->authForm;
+               }
+
+               $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
+
+               // get basic form description from the auth logic
+               $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
+               $fakeTemplate = $this->getFakeTemplate( $msg, $msgType );
+               $this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
+               // this will call onAuthChangeFormFields()
+               $formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
+               $this->postProcessFormDescriptor( $formDescriptor );
+
+               $context = $this->getContext();
+               if ( $context->getRequest() !== $this->getRequest() ) {
+                       // We have overridden the request, need to make sure the form uses that too.
+                       $context = new DerivativeContext( $this->getContext() );
+                       $context->setRequest( $this->getRequest() );
+               }
+               $form = HTMLForm::factory( 'vform', $formDescriptor, $context );
+
+               $form->addHiddenField( 'authAction', $this->authAction );
+               if ( $wgLoginLanguageSelector ) {
+                       $form->addHiddenField( 'uselang', $this->mLanguage );
+               }
+               $form->addHiddenField( 'force', $this->securityLevel );
+               $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
+               if ( $wgSecureLogin ) {
+                       // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
+                       if ( !$this->isSignup() ) {
+                               $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
+                               $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
+                       }
+               }
+
+               // set properties of the form itself
+               $form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) );
+               $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
+               if ( $this->isSignup() ) {
+                       $form->setId( 'userlogin2' );
+               }
+
+               // add pre/post text
+               // header used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
+               // should be above the error message but HTMLForm doesn't support that
+               $form->addHeaderText( $fakeTemplate->html( 'header' ) );
+
+               // FIXME the old form used this for error/warning messages which does not play well with
+               // HTMLForm (maybe it could with a subclass?); for now only display it for signups
+               // (where the JS username validation needs it) and alway empty
+               if ( $this->isSignup() ) {
+                       // used by the mediawiki.special.userlogin.signup.js module
+                       $statusAreaAttribs = [ 'id' => 'mw-createacct-status-area' ];
+                       // $statusAreaAttribs += $msg ? [ 'class' => "{$msgType}box" ] : [ 'style' => 'display: none;' ];
+                       $form->addHeaderText( Html::element( 'div', $statusAreaAttribs ) );
+               }
+
+               // header used by MobileFrontend
+               $form->addHeaderText( $fakeTemplate->html( 'formheader' ) );
+
+               // blank signup footer for site customization
+               if ( $this->isSignup() && $this->showExtraInformation() ) {
+                       // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
+                       $signupendMsg = $this->msg( 'signupend' );
+                       $signupendHttpsMsg = $this->msg( 'signupend-https' );
+                       if ( !$signupendMsg->isDisabled() ) {
+                               $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
+                                       ? $signupendHttpsMsg ->parse() : $signupendMsg->parse();
+                               $form->addPostText( Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ) );
+                       }
+               }
+
+               // warning header for non-standard workflows (e.g. security reauthentication)
+               if ( !$this->isSignup() && $this->getUser()->isLoggedIn() ) {
+                       $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
+                       $form->addHeaderText( Html::rawElement( 'div', [ 'class' => 'warningbox' ],
+                               $this->msg( $reauthMessage )->params( $this->getUser()->getName() )->parse() ) );
+               }
+
+               if ( !$this->isSignup() && $this->showExtraInformation() ) {
+                       $passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
+                       if ( $passwordReset->isAllowed( $this->getUser() ) ) {
+                               $form->addFooterText( Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'mw-ui-vform-field mw-form-related-link-container' ],
+                                       Linker::link(
+                                               SpecialPage::getTitleFor( 'PasswordReset' ),
+                                               $this->msg( 'userlogin-resetpassword-link' )->escaped()
+                                       )
+                               ) );
+                       }
+
+                       // Don't show a "create account" link if the user can't.
+                       if ( $this->showCreateAccountLink() ) {
+                               // link to the other action
+                               $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' :'CreateAccount' );
+                               $linkq = $this->getReturnToQueryStringFragment();
+                               // Pass any language selection on to the mode switch link
+                               if ( $wgLoginLanguageSelector && $this->mLanguage ) {
+                                       $linkq .= '&uselang=' . $this->mLanguage;
+                               }
+                               $createOrLoginHref = $linkTitle->getLocalURL( $linkq );
+
+                               if ( $this->getUser()->isLoggedIn() ) {
+                                       $createOrLoginHtml = Html::rawElement( 'div',
+                                               [ 'class' => 'mw-ui-vform-field' ],
+                                               Html::element( 'a',
+                                                       [
+                                                               'id' => 'mw-createaccount-join',
+                                                               'href' => $createOrLoginHref,
+                                                               // put right after all auth inputs in the tab order
+                                                               'tabindex' => 100,
+                                                       ],
+                                                       $this->msg( 'userlogin-createanother' )->escaped()
+                                               )
+                                       );
+                               } else {
+                                       $createOrLoginHtml = Html::rawElement( 'div',
+                                               [ 'id' => 'mw-createaccount-cta',
+                                                       'class' => 'mw-ui-vform-field' ],
+                                               $this->msg( 'userlogin-noaccount' )->escaped()
+                                               . Html::element( 'a',
+                                                       [
+                                                               'id' => 'mw-createaccount-join',
+                                                               'href' => $createOrLoginHref,
+                                                               'class' => 'mw-ui-button',
+                                                               'tabindex' => 100,
+                                                       ],
+                                                       $this->msg( 'userlogin-joinproject' )->escaped()
+                                               )
+                                       );
+                               }
+                               $form->addFooterText( $createOrLoginHtml );
+                       }
+               }
+
+               $form->suppressDefaultSubmit();
+
+               $this->authForm = $form;
+
+               return $form;
+       }
+
+       /**
+        * Temporary B/C method to handle extensions using the UserLoginForm/UserCreateForm hooks.
+        * @param string|Message $msg
+        * @param string $msgType
+        * @return FakeAuthTemplate
+        */
+       protected function getFakeTemplate( $msg, $msgType ) {
+               global $wgAuth, $wgEnableEmail, $wgHiddenPrefs, $wgEmailConfirmToEdit, $wgEnableUserEmail,
+                          $wgSecureLogin, $wgLoginLanguageSelector, $wgPasswordResetRoutes;
+
+               // make a best effort to get the value of fields which used to be fixed in the old login
+               // template but now might or might not exist depending on what providers are used
+               $request = $this->getRequest();
+               $data = (object) [
+                       'mUsername' => $request->getText( 'wpName' ),
+                       'mPassword' => $request->getText( 'wpPassword' ),
+                       'mRetype' => $request->getText( 'wpRetype' ),
+                       'mEmail' => $request->getText( 'wpEmail' ),
+                       'mRealName' => $request->getText( 'wpRealName' ),
+                       'mDomain' => $request->getText( 'wpDomain' ),
+                       'mReason' => $request->getText( 'wpReason' ),
+                       'mRemember' => $request->getCheck( 'wpRemember' ),
+               ];
+
+               // Preserves a bunch of logic from the old code that was rewritten in getAuthForm().
+               // There is no code reuse to make this easier to remove .
+               // If an extension tries to change any of these values, they are out of luck - we only
+               // actually use the domain/usedomain/domainnames, extraInput and extrafields keys.
+
+               $titleObj = $this->getPageTitle();
+               $user = $this->getUser();
+               $template = new FakeAuthTemplate();
+
+               // Pre-fill username (if not creating an account, bug 44775).
+               if ( $data->mUsername == '' && $this->isSignup() ) {
+                       if ( $user->isLoggedIn() ) {
+                               $data->mUsername = $user->getName();
+                       } else {
+                               $data->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
+                       }
+               }
+
+               if ( $this->isSignup() ) {
+                       // Must match number of benefits defined in messages
+                       $template->set( 'benefitCount', 3 );
+
+                       $q = 'action=submitlogin&type=signup';
+                       $linkq = 'type=login';
+               } else {
+                       $q = 'action=submitlogin&type=login';
+                       $linkq = 'type=signup';
+               }
+
+               if ( $this->mReturnTo !== '' ) {
+                       $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
+                       if ( $this->mReturnToQuery !== '' ) {
+                               $returnto .= '&returntoquery=' .
+                                                        wfUrlencode( $this->mReturnToQuery );
+                       }
+                       $q .= $returnto;
+                       $linkq .= $returnto;
+               }
+
+               # Don't show a "create account" link if the user can't.
+               if ( $this->showCreateAccountLink() ) {
+                       # Pass any language selection on to the mode switch link
+                       if ( $wgLoginLanguageSelector && $this->mLanguage ) {
+                               $linkq .= '&uselang=' . $this->mLanguage;
+                       }
+                       // Supply URL, login template creates the button.
+                       $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
+               } else {
+                       $template->set( 'link', '' );
+               }
+
+               $resetLink = $this->isSignup()
+                       ? null
+                       : is_array( $wgPasswordResetRoutes )
+                         && in_array( true, array_values( $wgPasswordResetRoutes ), true );
+
+               $template->set( 'header', '' );
+               $template->set( 'formheader', '' );
+               $template->set( 'skin', $this->getSkin() );
+
+               $template->set( 'name', $data->mUsername );
+               $template->set( 'password', $data->mPassword );
+               $template->set( 'retype', $data->mRetype );
+               $template->set( 'createemailset', false ); // no easy way to get that from AuthManager
+               $template->set( 'email', $data->mEmail );
+               $template->set( 'realname', $data->mRealName );
+               $template->set( 'domain', $data->mDomain );
+               $template->set( 'reason', $data->mReason );
+               $template->set( 'remember', $data->mRemember );
+
+               $template->set( 'action', $titleObj->getLocalURL( $q ) );
+               $template->set( 'message', $msg );
+               $template->set( 'messagetype', $msgType );
+               $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
+               $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs, true ) );
+               $template->set( 'useemail', $wgEnableEmail );
+               $template->set( 'emailrequired', $wgEmailConfirmToEdit );
+               $template->set( 'emailothers', $wgEnableUserEmail );
+               $template->set( 'canreset', $wgAuth->allowPasswordChange() );
+               $template->set( 'resetlink', $resetLink );
+               $template->set( 'canremember', $request->getSession()->getProvider()
+                       ->getRememberUserDuration() !== null );
+               $template->set( 'usereason', $user->isLoggedIn() );
+               $template->set( 'cansecurelogin', ( $wgSecureLogin ) );
+               $template->set( 'stickhttps', (int)$this->mStickHTTPS );
+               $template->set( 'loggedin', $user->isLoggedIn() );
+               $template->set( 'loggedinuser', $user->getName() );
+               $template->set( 'token', $this->getToken()->toString() );
+
+               $action = $this->isSignup() ? 'signup' : 'login';
+               $wgAuth->modifyUITemplate( $template, $action );
+
+               $oldTemplate = $template;
+               $hookName = $this->isSignup() ? 'UserCreateForm' : 'UserLoginForm';
+               Hooks::run( $hookName, [ &$template ] );
+               if ( $oldTemplate !== $template ) {
+                       wfDeprecated( "reference in $hookName hook", '1.27' );
+               }
+
+               return $template;
+
+       }
+
+       public function onAuthChangeFormFields(
+               array $requests, array $fieldInfo, array &$formDescriptor, $action
+       ) {
+               $coreFieldDescriptors = $this->getFieldDefinitions( $this->fakeTemplate );
+               $specialFields = array_merge( [ 'extraInput', 'linkcontainer' ],
+                       array_keys( $this->fakeTemplate->getExtraInputDefinitions() ) );
+
+               // keep the ordering from getCoreFieldDescriptors() where there is no explicit weight
+               foreach ( $coreFieldDescriptors as $fieldName => $coreField ) {
+                       $requestField = isset( $formDescriptor[$fieldName] ) ?
+                               $formDescriptor[$fieldName] : [];
+
+                       // remove everything that is not in the fieldinfo, is not marked as a supplemental field
+                       // to something in the fieldinfo, and is not a generic or B/C field or a submit button
+                       if (
+                               !isset( $fieldInfo[$fieldName] )
+                               && (
+                                       !isset( $coreField['baseField'] )
+                                       || !isset( $fieldInfo[$coreField['baseField']] )
+                               ) && !in_array( $fieldName, $specialFields, true )
+                               && $coreField['type'] !== 'submit'
+                       ) {
+                               $coreFieldDescriptors[$fieldName] = null;
+                               continue;
+                       }
+
+                       // core message labels should always take priority
+                       if (
+                               isset( $coreField['label'] )
+                               || isset( $coreField['label-message'] )
+                               || isset( $coreField['label-raw'] )
+                       ) {
+                               unset( $requestField['label'], $requestField['label-message'], $coreField['label-raw'] );
+                       }
+
+                       $coreFieldDescriptors[$fieldName] += $requestField;
+               }
+
+               $formDescriptor = array_filter( $coreFieldDescriptors + $formDescriptor );
+               return true;
+       }
+
+       /**
+        * Show extra information such as password recovery information, link from login to signup,
+        * CTA etc? Such information should only be shown on the "landing page", ie. when the user
+        * is at the first step of the authentication process.
+        * @return bool
+        */
+       protected function showExtraInformation() {
+               return $this->authAction !== $this->getContinueAction( $this->authAction )
+                       && !$this->securityLevel;
+       }
+
+       /**
+        * Create a HTMLForm descriptor for the core login fields.
+        * @param FakeAuthTemplate $template B/C data (not used but needed by getBCFieldDefinitions)
+        * @return array
+        */
+       protected function getFieldDefinitions( $template ) {
+               global $wgEmailConfirmToEdit;
+
+               $isLoggedIn = $this->getUser()->isLoggedIn();
+               $continuePart = $this->isContinued() ? 'continue-' : '';
+               $anotherPart = $isLoggedIn ? 'another-' : '';
+               $expiration = $this->getRequest()->getSession()->getProvider()
+                       ->getRememberUserDuration();
+               $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
+               $secureLoginLink = '';
+               if ( $this->mSecureLoginUrl ) {
+                       $secureLoginLink = Html::element( 'a', [
+                               'href' => $this->mSecureLoginUrl,
+                               'class' => 'mw-ui-flush-right mw-secure',
+                       ], $this->msg( 'userlogin-signwithsecure' )->text() );
+               }
+
+               if ( $this->isSignup() ) {
+                       $fieldDefinitions = [
+                               'username' => [
+                                       'label-message' => 'userlogin-yourname',
+                                       // FIXME help-message does not match old formatting
+                                       'help-message' => 'createacct-helpusername',
+                                       'id' => 'wpName2',
+                                       'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
+                                               : 'userlogin-yourname-ph',
+                               ],
+                               'mailpassword' => [
+                                       // create account without providing password, a temporary one will be mailed
+                                       'type' => 'check',
+                                       'label-message' => 'createaccountmail',
+                                       'name' => 'wpCreateaccountMail',
+                                       'id' => 'wpCreateaccountMail',
+                               ],
+                               'password' => [
+                                       'id' => 'wpPassword2',
+                                       'placeholder-message' => 'createacct-yourpassword-ph',
+                                       'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
+                               ],
+                               'domain' => [],
+                               'retype' => [
+                                       'baseField' => 'password',
+                                       'type' => 'password',
+                                       'label-message' => 'createacct-yourpasswordagain',
+                                       'id' => 'wpRetype',
+                                       'cssclass' => 'loginPassword',
+                                       'size' => 20,
+                                       'validation-callback' => function ( $value, $alldata ) {
+                                               if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
+                                                       if ( !$value ) {
+                                                               return $this->msg( 'htmlform-required' );
+                                                       } elseif ( $value !== $alldata['password'] ) {
+                                                               return $this->msg( 'badretype' );
+                                                       }
+                                               }
+                                               return true;
+                                       },
+                                       'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
+                                       'placeholder-message' => 'createacct-yourpasswordagain-ph',
+                               ],
+                               'email' => [
+                                       'type' => 'email',
+                                       'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired'
+                                               : 'createacct-emailoptional',
+                                       'id' => 'wpEmail',
+                                       'cssclass' => 'loginText',
+                                       'size' => '20',
+                                       // FIXME will break non-standard providers
+                                       'required' => $wgEmailConfirmToEdit,
+                                       'validation-callback' => function ( $value, $alldata ) {
+                                               global $wgEmailConfirmToEdit;
+
+                                               // AuthManager will check most of these, but that will make the auth
+                                               // session fail and this won't, so nicer to do it this way
+                                               if ( !$value && $wgEmailConfirmToEdit ) {
+                                                       // no point in allowing registration without email when email is
+                                                       // required to edit
+                                                       return $this->msg( 'noemailtitle' );
+                                               } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
+                                                       // cannot send password via email when there is no email address
+                                                       return $this->msg( 'noemailcreate' );
+                                               } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
+                                                       return $this->msg( 'invalidemailaddress' );
+                                               }
+                                               return true;
+                                       },
+                                       'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
+                               ],
+                               'realname' => [
+                                       'type' => 'text',
+                                       'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
+                                               : 'prefs-help-realname',
+                                       'label-message' => 'createacct-realname',
+                                       'cssclass' => 'loginText',
+                                       'size' => 20,
+                                       'id' => 'wpRealName',
+                               ],
+                               'reason' => [
+                                       // comment for the user creation log
+                                       'type' => 'text',
+                                       'label-message' => 'createacct-reason',
+                                       'cssclass' => 'loginText',
+                                       'id' => 'wpReason',
+                                       'size' => '20',
+                                       'placeholder-message' => 'createacct-reason-ph',
+                               ],
+                               'extrainput' => [], // placeholder for fields coming from the template
+                               'createaccount' => [
+                                       // submit button
+                                       'type' => 'submit',
+                                       'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
+                                               'submit' )->text(),
+                                       'name' => 'wpCreateaccount',
+                                       'id' => 'wpCreateaccount',
+                                       'weight' => 100,
+                               ],
+                       ];
+               } else {
+                       $fieldDefinitions = [
+                               'username' => [
+                                       'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
+                                       'id' => 'wpName1',
+                                       'placeholder-message' => 'userlogin-yourname-ph',
+                               ],
+                               'password' => [
+                                       'id' => 'wpPassword1',
+                                       'placeholder-message' => 'userlogin-yourpassword-ph',
+                               ],
+                               'domain' => [],
+                               'extrainput' => [],
+                               'rememberMe' => [
+                                       // option for saving the user token to a cookie
+                                       'type' => 'check',
+                                       'label-message' => $this->msg( 'userlogin-remembermypassword' )
+                                               ->numParams( $expirationDays ),
+                                       'id' => 'wpRemember',
+                               ],
+                               'loginattempt' => [
+                                       // submit button
+                                       'type' => 'submit',
+                                       'name' => 'wpRemember',
+                                       'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
+                                       'id' => 'wpLoginAttempt',
+                                       'weight' => 100,
+                               ],
+                               'linkcontainer' => [
+                                       // help link
+                                       'type' => 'info',
+                                       'cssclass' => 'mw-form-related-link-container',
+                                       'id' => 'mw-userlogin-help',
+                                       'raw' => true,
+                                       'default' => Html::element( 'a', [
+                                               'href' => Skin::makeInternalOrExternalUrl( wfMessage( 'helplogin-url' )
+                                                       ->inContentLanguage()
+                                                       ->text() ),
+                                       ], $this->msg( 'userlogin-helplink2' )->text() ),
+                                       'weight' => 200,
+                               ],
+                       ];
+               }
+               $fieldDefinitions['username'] += [
+                       'type' => 'text',
+                       'name' => 'wpName',
+                       'cssclass' => 'loginText',
+                       'size' => 20,
+                       // 'required' => true,
+               ];
+               $fieldDefinitions['password'] += [
+                       'type' => 'password',
+                       // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
+                       'name' => 'wpPassword',
+                       'cssclass' => 'loginPassword',
+                       'size' => 20,
+                       // 'required' => true,
+               ];
+
+               if ( !$this->showExtraInformation() ) {
+                       unset( $fieldDefinitions['linkcontainer'] );
+               }
+
+               $fieldDefinitions = $this->getBCFieldDefinitions( $fieldDefinitions, $template );
+               $fieldDefinitions = array_filter( $fieldDefinitions );
+
+               return $fieldDefinitions;
+       }
+
+       /**
+        * Adds fields provided via the deprecated UserLoginForm / UserCreateForm hooks
+        * @param $fieldDefinitions array
+        * @param FakeAuthTemplate $template
+        * @return array
+        */
+       protected function getBCFieldDefinitions( $fieldDefinitions, $template ) {
+               if ( $template->get( 'usedomain', false ) ) {
+                       // TODO probably should be translated to the new domain notation in AuthManager
+                       $fieldDefinitions['domain'] = [
+                               'type' => 'select',
+                               'label-message' => 'yourdomainname',
+                               'options' => array_combine( $template->get( 'domainnames', [] ),
+                                       $template->get( 'domainnames', [] ) ),
+                               'default' => $template->get( 'domain', '' ),
+                               'name' => 'wpDomain',
+                               // FIXME id => 'mw-user-domain-section' on the parent div
+                       ];
+               }
+
+               // poor man's associative array_splice
+               $extraInputPos = array_search( 'extrainput', array_keys( $fieldDefinitions ), true );
+               $fieldDefinitions = array_slice( $fieldDefinitions, 0, $extraInputPos, true )
+                                                       + $template->getExtraInputDefinitions()
+                                                       + array_slice( $fieldDefinitions, $extraInputPos + 1, null, true );
+
+               return $fieldDefinitions;
+       }
+
+       /**
+        * Check if a session cookie is present.
+        *
+        * This will not pick up a cookie set during _this_ request, but is meant
+        * to ensure that the client is returning the cookie which was set on a
+        * previous pass through the system.
+        *
+        * @return bool
+        */
+       protected function hasSessionCookie() {
+               global $wgDisableCookieCheck, $wgInitialSessionId;
+
+               return $wgDisableCookieCheck || (
+                       $wgInitialSessionId &&
+                       $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId
+               );
+       }
+
+       /**
+        * Returns a string that can be appended to the URL (without encoding) to preserve the
+        * return target. Does not include leading '?'/'&'.
+        */
+       protected function getReturnToQueryStringFragment() {
+               $returnto = '';
+               if ( $this->mReturnTo !== '' ) {
+                       $returnto = 'returnto=' . wfUrlencode( $this->mReturnTo );
+                       if ( $this->mReturnToQuery !== '' ) {
+                               $returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery );
+                       }
+               }
+               return $returnto;
+       }
+
+       /**
+        * Whether the login/create account form should display a link to the
+        * other form (in addition to whatever the skin provides).
+        * @return bool
+        */
+       private function showCreateAccountLink() {
+               if ( $this->isSignup() ) {
+                       return true;
+               } elseif ( $this->getUser()->isAllowed( 'createaccount' ) ) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       protected function getTokenName() {
+               return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
+       }
+
+       /**
+        * Produce a bar of links which allow the user to select another language
+        * during login/registration but retain "returnto"
+        *
+        * @return string
+        */
+       protected function makeLanguageSelector() {
+               $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
+               if ( $msg->isBlank() ) {
+                       return '';
+               }
+               $langs = explode( "\n", $msg->text() );
+               $links = [];
+               foreach ( $langs as $lang ) {
+                       $lang = trim( $lang, '* ' );
+                       $parts = explode( '|', $lang );
+                       if ( count( $parts ) >= 2 ) {
+                               $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
+                       }
+               }
+
+               return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
+                       $this->getLanguage()->pipeList( $links ) )->escaped() : '';
+       }
+
+       /**
+        * Create a language selector link for a particular language
+        * Links back to this page preserving type and returnto
+        *
+        * @param string $text Link text
+        * @param string $lang Language code
+        * @return string
+        */
+       protected function makeLanguageSelectorLink( $text, $lang ) {
+               if ( $this->getLanguage()->getCode() == $lang ) {
+                       // no link for currently used language
+                       return htmlspecialchars( $text );
+               }
+               $query = [ 'uselang' => $lang ];
+               if ( $this->mReturnTo !== '' ) {
+                       $query['returnto'] = $this->mReturnTo;
+                       $query['returntoquery'] = $this->mReturnToQuery;
+               }
+
+               $attr = [];
+               $targetLanguage = Language::factory( $lang );
+               $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
+
+               return Linker::linkKnown(
+                       $this->getPageTitle(),
+                       htmlspecialchars( $text ),
+                       $attr,
+                       $query
+               );
+       }
+
+       protected function getGroupName() {
+               return 'login';
+       }
+
+       /**
+        * @param array $formDescriptor
+        */
+       protected function postProcessFormDescriptor( &$formDescriptor ) {
+               // Pre-fill username (if not creating an account, T46775).
+               if (
+                       isset( $formDescriptor['username'] ) &&
+                       !isset( $formDescriptor['username']['default'] ) &&
+                       !$this->isSignup()
+               ) {
+                       $user = $this->getUser();
+                       if ( $user->isLoggedIn() ) {
+                               $formDescriptor['username']['default'] = $user->getName();
+                       } else {
+                               $formDescriptor['username']['default'] =
+                                       $this->getRequest()->getSession()->suggestLoginUsername();
+                       }
+               }
+
+               // don't show a submit button if there is nothing to submit (i.e. the only form content
+               // is other submit buttons, for redirect flows)
+               if ( !$this->needsSubmitButton( $formDescriptor ) ) {
+                       unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
+               }
+
+               if ( !$this->isSignup() ) {
+                       // FIXME HACK don't focus on non-empty field
+                       // maybe there should be an autofocus-if similar to hide-if?
+                       if (
+                               isset( $formDescriptor['username'] )
+                               && empty( $formDescriptor['username']['default'] )
+                               && !$this->getRequest()->getCheck( 'wpName' )
+                       ) {
+                               $formDescriptor['username']['autofocus'] = true;
+                       } elseif ( isset( $formDescriptor['password'] ) ) {
+                               $formDescriptor['password']['autofocus'] = true;
+                       }
+               }
+
+               $this->addTabIndex( $formDescriptor );
+       }
+}
+
+/**
+ * B/C class to try handling login/signup template modifications even though login/signup does not
+ * actually happen through a template anymore. Just collects extra field definitions and allows
+ * some other class to do decide what to do with threm..
+ * TODO find the right place for adding extra fields and kill this
+ */
+class FakeAuthTemplate extends BaseTemplate {
+       public function execute() {
+               throw new LogicException( 'not used' );
+       }
+
+       /**
+        * Extensions (AntiSpoof and TitleBlacklist) call this in response to
+        * UserCreateForm hook to add checkboxes to the create account form.
+        */
+       public function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
+               // use the same indexes as UserCreateForm just in case someone adds an item manually
+               $this->data['extrainput'][] = [
+                       'name' => $name,
+                       'value' => $value,
+                       'type' => $type,
+                       'msg' => $msg,
+                       'helptext' => $helptext,
+               ];
+       }
+
+       /**
+        * Turns addInputItem-style field definitions into HTMLForm field definitions.
+        * @return array
+        */
+       public function getExtraInputDefinitions() {
+               $definitions = [];
+
+               foreach ( $this->get( 'extrainput', [] ) as $field ) {
+                       $definition = [
+                               'type' => $field['type'] === 'checkbox' ? 'check' : $field['type'],
+                               'name' => $field['name'],
+                               'value' => $field['value'],
+                               'id' => $field['name'],
+                       ];
+                       if ( $field['msg'] ) {
+                               $definition['label-message'] = $this->getMsg( $field['msg'] );
+                       }
+                       if ( $field['helptext'] ) {
+                               $definition['help'] = $this->msgWiki( $field['helptext'] );
+                       }
+
+                       // the array key doesn't matter much when name is defined explicitly but
+                       // let's try and follow HTMLForm conventions
+                       $name = preg_replace( '/^wp(?=[A-Z])/', '', $field['name'] );
+                       $definitions[$name] = $definition;
+               }
+
+               if ( $this->haveData( 'extrafields' ) ) {
+                       $definitions['extrafields'] = [
+                               'type' => 'info',
+                               'raw' => true,
+                               'default' => $this->get( 'extrafields' ),
+                       ];
+               }
+
+               return $definitions;
+       }
+}
+
+/**
+ * A horrible hack to handle AuthManager's feature flag. For other special pages this is done in
+ * SpecialPageFactory, but LoginForm is used directly by some extensions. Will be killed as soon
+ * as AuthManager is stable.
+ */
+class LoginForm extends SpecialPage {
+       private $realLoginForm;
+
+       public function __construct( $request = null ) {
+               global $wgDisableAuthManager;
+               if ( $wgDisableAuthManager ) {
+                       $this->realLoginForm = new LoginFormPreAuthManager( $request );
+               } else {
+                       $this->realLoginForm = new LoginFormAuthManager( $request );
+               }
+       }
+
+       // proxy everything
+
+       public function __get( $name ) {
+               return $this->realLoginForm->$name;
+       }
+
+       public function __set( $name, $value ) {
+               $this->realLoginForm->$name = $value;
+       }
+
+       public function __call( $name, $args ) {
+               return call_user_func_array( [ $this->realLoginForm, $name ], $args );
+       }
+
+       public static function __callStatic( $name, $args ) {
+               global $wgDisableAuthManager;
+               return call_user_func_array( [ $wgDisableAuthManager ? LoginFormPreAuthManager::class
+                       : LoginFormAuthManager::class, $name ], $args );
+       }
+
+       // all public SpecialPage methods need to be proxied explicitly
+
+       public function getName() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getRestriction() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function isListed() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function setListed( $listed ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function listed( $x = null ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function isIncludable() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function including( $x = null ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getLocalName() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function isExpensive() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function isCached() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function isRestricted() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function userCanExecute( User $user ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function displayRestrictionError() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function checkPermissions() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function checkReadOnly() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function requireLogin(
+               $reasonMsg = 'exception-nologin-text', $titleMsg = 'exception-nologin'
+       ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function execute( $subPage ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getDescription() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       function getTitle( $subpage = false ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       function getPageTitle( $subpage = false ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function setContext( $context ) {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getContext() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getRequest() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getOutput() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getUser() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getSkin() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getLanguage() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getConfig() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getFullTitle() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function getFinalGroupName() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+       public function doesWrites() {
+               return call_user_func_array( [ $this->realLoginForm, __FUNCTION__ ], func_get_args() );
+       }
+
+       // no way to proxy constants and static properties
+
+       const SUCCESS = 0;
+       const NO_NAME = 1;
+       const ILLEGAL = 2;
+       const WRONG_PLUGIN_PASS = 3;
+       const NOT_EXISTS = 4;
+       const WRONG_PASS = 5;
+       const EMPTY_PASS = 6;
+       const RESET_PASS = 7;
+       const ABORTED = 8;
+       const CREATE_BLOCKED = 9;
+       const THROTTLED = 10;
+       const USER_BLOCKED = 11;
+       const NEED_TOKEN = 12;
+       const WRONG_TOKEN = 13;
+       const USER_MIGRATED = 14;
+
+       public static $statusCodes = [
+               self::SUCCESS => 'success',
+               self::NO_NAME => 'no_name',
+               self::ILLEGAL => 'illegal',
+               self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
+               self::NOT_EXISTS => 'not_exists',
+               self::WRONG_PASS => 'wrong_pass',
+               self::EMPTY_PASS => 'empty_pass',
+               self::RESET_PASS => 'reset_pass',
+               self::ABORTED => 'aborted',
+               self::CREATE_BLOCKED => 'create_blocked',
+               self::THROTTLED => 'throttled',
+               self::USER_BLOCKED => 'user_blocked',
+               self::NEED_TOKEN => 'need_token',
+               self::WRONG_TOKEN => 'wrong_token',
+               self::USER_MIGRATED => 'user_migrated',
+       ];
+
+       public static $validErrorMessages = [
+               'exception-nologin-text',
+               'watchlistanontext',
+               'changeemail-no-info',
+               'resetpass-no-info',
+               'confirmemail_needlogin',
+               'prefsnologintext2',
+       ];
+}
+
+/**
+ * LoginForm as a special page has been replaced by SpecialUserLogin and SpecialCreateAccount,
+ * but some extensions called its public methods directly, so the class is retained as a
+ * B/C wrapper. Anything that used it before should use AuthManager instead.
+ */
+class LoginFormAuthManager extends SpecialPage {
+       const SUCCESS = 0;
+       const NO_NAME = 1;
+       const ILLEGAL = 2;
+       const WRONG_PLUGIN_PASS = 3;
+       const NOT_EXISTS = 4;
+       const WRONG_PASS = 5;
+       const EMPTY_PASS = 6;
+       const RESET_PASS = 7;
+       const ABORTED = 8;
+       const CREATE_BLOCKED = 9;
+       const THROTTLED = 10;
+       const USER_BLOCKED = 11;
+       const NEED_TOKEN = 12;
+       const WRONG_TOKEN = 13;
+       const USER_MIGRATED = 14;
+
+       public static $statusCodes = [
+               self::SUCCESS => 'success',
+               self::NO_NAME => 'no_name',
+               self::ILLEGAL => 'illegal',
+               self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
+               self::NOT_EXISTS => 'not_exists',
+               self::WRONG_PASS => 'wrong_pass',
+               self::EMPTY_PASS => 'empty_pass',
+               self::RESET_PASS => 'reset_pass',
+               self::ABORTED => 'aborted',
+               self::CREATE_BLOCKED => 'create_blocked',
+               self::THROTTLED => 'throttled',
+               self::USER_BLOCKED => 'user_blocked',
+               self::NEED_TOKEN => 'need_token',
+               self::WRONG_TOKEN => 'wrong_token',
+               self::USER_MIGRATED => 'user_migrated',
+       ];
+
+       /**
+        * @param WebRequest $request
+        */
+       public function __construct( $request = null ) {
+               wfDeprecated( 'LoginForm', '1.27' );
+               parent::__construct();
+       }
+
+       /**
+        * @deprecated since 1.27 - call LoginHelper::getValidErrorMessages instead.
+        */
+       public static function getValidErrorMessages() {
+               return LoginHelper::getValidErrorMessages();
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function incrementLoginThrottle( $username ) {
+               wfDeprecated( __METHOD__, "1.27" );
+               global $wgRequest;
+               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
+               $throttler = new Throttler();
+               return $throttler->increase( $username, $wgRequest->getIP(), __METHOD__ );
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function incLoginThrottle( $username ) {
+               wfDeprecated( __METHOD__, "1.27" );
+               $res = self::incrementLoginThrottle( $username );
+               return is_array( $res ) ? true : 0;
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function clearLoginThrottle( $username ) {
+               wfDeprecated( __METHOD__, "1.27" );
+               global $wgRequest;
+               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
+               $throttler = new Throttler();
+               return $throttler->clear( $username, $wgRequest->getIP() );
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function getLoginToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+               global $wgRequest;
+               return $wgRequest->getSession()->getToken( '', 'login' )->toString();
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function setLoginToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function clearLoginToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+               global $wgRequest;
+               $wgRequest->getSession()->resetToken( 'login' );
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function getCreateaccountToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+               global $wgRequest;
+               return $wgRequest->getSession()->getToken( '', 'createaccount' )->toString();
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function setCreateaccountToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+       }
+
+       /**
+        * @deprecated since 1.27 - don't use LoginForm, use AuthManager instead
+        */
+       public static function clearCreateaccountToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+               global $wgRequest;
+               $wgRequest->getSession()->resetToken( 'createaccount' );
+       }
+}
index 2523810..1beac43 100644 (file)
@@ -513,8 +513,8 @@ abstract class QueryPage extends SpecialPage {
         * What is limit to fetch from DB
         *
         * Used to make it appear the DB stores less results then it actually does
-        * @param $uiLimit int Limit from UI
-        * @param $uiOffset int Offset from UI
+        * @param int $uiLimit Limit from UI
+        * @param int $uiOffset Offset from UI
         * @return int Limit to use for DB (not including extra row to see if at end)
         */
        protected function getDBLimit( $uiLimit, $uiOffset ) {
index 6ca7a13..408c726 100644 (file)
@@ -23,6 +23,8 @@ use MediaWiki\MediaWikiServices;
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Auth\AuthManager;
+
 /**
  * Parent class for all special pages.
  *
@@ -296,6 +298,66 @@ class SpecialPage {
                }
        }
 
+       /**
+        * Tells if the special page does something security-sensitive and needs extra defense against
+        * a stolen account (e.g. a reauthentication). What exactly that will mean is decided by the
+        * authentication framework.
+        * @return bool|string False or the argument for AuthManager::securitySensitiveOperationStatus().
+        *   Typically a special page needing elevated security would return its name here.
+        */
+       protected function getLoginSecurityLevel() {
+               return false;
+       }
+
+       /**
+        * Verifies that the user meets the security level, possibly reauthenticating them in the process.
+        *
+        * This should be used when the page does something security-sensitive and needs extra defense
+        * against a stolen account (e.g. a reauthentication). The authentication framework will make
+        * an extra effort to make sure the user account is not compromised. What that exactly means
+        * will depend on the system and user settings; e.g. the user might be required to log in again
+        * unless their last login happened recently, or they might be given a second-factor challenge.
+        *
+        * Calling this method will result in one if these actions:
+        * - return true: all good.
+        * - return false and set a redirect: caller should abort; the redirect will take the user
+        *   to the login page for reauthentication, and back.
+        * - throw an exception if there is no way for the user to meet the requirements without using
+        *   a different access method (e.g. this functionality is only available from a specific IP).
+        *
+        * Note that this does not in any way check that the user is authorized to use this special page
+        * (use checkPermissions() for that).
+        *
+        * @param string $level A security level. Can be an arbitrary string, defaults to the page name.
+        * @return bool False means a redirect to the reauthentication page has been set and processing
+        *   of the special page should be aborted.
+        * @throws ErrorPageError If the security level cannot be met, even with reauthentication.
+        */
+       protected function checkLoginSecurityLevel( $level = null ) {
+               $level = $level ?: $this->getName();
+               $securityStatus = AuthManager::singleton()->securitySensitiveOperationStatus( $level );
+               if ( $securityStatus === AuthManager::SEC_OK ) {
+                       return true;
+               } elseif ( $securityStatus === AuthManager::SEC_REAUTH ) {
+                       $request = $this->getRequest();
+                       $title = SpecialPage::getTitleFor( 'Userlogin' );
+                       $query = [
+                               'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
+                               'returntoquery' => wfArrayToCgi( array_diff_key( $request->getQueryValues(),
+                                       [ 'title' => true ] ) ),
+                               'force' => $level,
+                       ];
+                       $url = $title->getFullURL( $query, false, PROTO_HTTPS );
+
+                       $this->getOutput()->redirect( $url );
+                       return false;
+               }
+
+               $titleMessage = wfMessage( 'specialpage-securitylevel-not-allowed-title' );
+               $errorMessage = wfMessage( 'specialpage-securitylevel-not-allowed' );
+               throw new ErrorPageError( $titleMessage, $errorMessage );
+       }
+
        /**
         * Return an array of subpages beginning with $search that this special page will accept.
         *
@@ -463,6 +525,7 @@ class SpecialPage {
        public function execute( $subPage ) {
                $this->setHeaders();
                $this->checkPermissions();
+               $this->checkLoginSecurityLevel( $this->getLoginSecurityLevel() );
                $this->outputHeader();
        }
 
@@ -633,6 +696,7 @@ class SpecialPage {
        /**
         * Wrapper around wfMessage that sets the current context.
         *
+        * @since 1.16
         * @return Message
         * @see wfMessage
         */
index 725c4fc..73efa4e 100644 (file)
@@ -81,17 +81,23 @@ class SpecialPageFactory {
                'PagesWithProp' => 'SpecialPagesWithProp',
                'TrackingCategories' => 'SpecialTrackingCategories',
 
-               // Login/create account
-               'Userlogin' => 'LoginForm',
-               'CreateAccount' => 'SpecialCreateAccount',
+               // Authentication
+               'Userlogin' => 'SpecialUserLogin',
+               'Userlogout' => 'SpecialUserlogoutPreAuthManager',
+               'CreateAccount' => 'SpecialCreateAccountPreAuthManager',
+               'LinkAccounts' => 'SpecialLinkAccounts',
+               'UnlinkAccounts' => 'SpecialUnlinkAccounts',
+               'ChangeCredentials' => 'SpecialChangeCredentials',
+               'RemoveCredentials' => 'SpecialRemoveCredentials',
 
                // Users and rights
+               'Activeusers' => 'SpecialActiveUsers',
                'Block' => 'SpecialBlock',
                'Unblock' => 'SpecialUnblock',
                'BlockList' => 'SpecialBlockList',
-               'ChangePassword' => 'SpecialChangePassword',
+               'ChangePassword' => 'SpecialChangePasswordPreAuthManager',
                'BotPasswords' => 'SpecialBotPasswords',
-               'PasswordReset' => 'SpecialPasswordReset',
+               'PasswordReset' => 'SpecialPasswordResetPreAuthManager',
                'DeletedContributions' => 'DeletedContributionsPage',
                'Preferences' => 'SpecialPreferences',
                'ResetTokens' => 'SpecialResetTokens',
@@ -177,7 +183,6 @@ class SpecialPageFactory {
                'Revisiondelete' => 'SpecialRevisionDelete',
                'RunJobs' => 'SpecialRunJobs',
                'Specialpages' => 'SpecialSpecialpages',
-               'Userlogout' => 'SpecialUserlogout',
        ];
 
        private static $list;
@@ -225,6 +230,7 @@ class SpecialPageFactory {
                global $wgDisableInternalSearch, $wgEmailAuthentication;
                global $wgEnableEmail, $wgEnableJavaScriptTest;
                global $wgPageLanguageUseDB, $wgContentHandlerUseDB;
+               global $wgDisableAuthManager;
 
                if ( !is_array( self::$list ) ) {
 
@@ -240,7 +246,7 @@ class SpecialPageFactory {
                        }
 
                        if ( $wgEnableEmail ) {
-                               self::$list['ChangeEmail'] = 'SpecialChangeEmail';
+                               self::$list['ChangeEmail'] = 'SpecialChangeEmailPreAuthManager';
                        }
 
                        if ( $wgEnableJavaScriptTest ) {
@@ -254,7 +260,19 @@ class SpecialPageFactory {
                                self::$list['ChangeContentModel'] = 'SpecialChangeContentModel';
                        }
 
-                       self::$list['Activeusers'] = 'SpecialActiveUsers';
+                       // horrible hack to allow selection between old and new classes via a feature flag - T110756
+                       // will be removed once AuthManager is stable
+                       if ( !$wgDisableAuthManager ) {
+                               self::$list = array_map( function ( $class ) {
+                                       return preg_replace( '/PreAuthManager$/', '', $class );
+                               }, self::$list );
+                               self::$list['Userlogout'] = 'SpecialUserLogout'; // case matters
+                       } else {
+                               self::$list['Userlogin'] = 'LoginForm';
+                               self::$list = array_diff_key( self::$list, array_fill_keys( [
+                                       'LinkAccounts', 'UnlinkAccounts', 'ChangeCredentials', 'RemoveCredentials',
+                               ], true ) );
+                       }
 
                        // Add extension special pages
                        self::$list = array_merge( self::$list, $wgSpecialPages );
@@ -539,6 +557,7 @@ class SpecialPageFactory {
                        $trxProfiler = Profiler::instance()->getTransactionProfiler();
                        if ( $context->getRequest()->wasPosted() && !$page->doesWrites() ) {
                                $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
+                               $context->getRequest()->markAsSafeRequest();
                        }
                }
 
index d6d4500..c697ca7 100644 (file)
@@ -24,6 +24,8 @@
  */
 
 /**
+ * Implements Special:Activeusers
+ *
  * @ingroup SpecialPage
  */
 class SpecialActiveUsers extends SpecialPage {
@@ -41,16 +43,25 @@ class SpecialActiveUsers extends SpecialPage {
         * @param string $par Parameter passed to the page or null
         */
        public function execute( $par ) {
-               $days = $this->getConfig()->get( 'ActiveUserDays' );
+               $out = $this->getOutput();
 
                $this->setHeaders();
                $this->outputHeader();
 
-               $out = $this->getOutput();
-               $out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>",
-                       [ 'activeusers-intro', $this->getLanguage()->formatNum( $days ) ] );
+               $opts = new FormOptions();
+
+               $opts->add( 'username', '' );
+               $opts->add( 'hidebots', false, FormOptions::BOOL );
+               $opts->add( 'hidesysops', false, FormOptions::BOOL );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+
+               if ( $par !== null ) {
+                       $opts->setValue( 'username', $par );
+               }
 
                // Mention the level of cache staleness...
+               $cacheText = '';
                $dbr = wfGetDB( DB_SLAVE, 'recentchanges' );
                $rcMax = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
                if ( $rcMax ) {
@@ -66,22 +77,51 @@ class SpecialActiveUsers extends SpecialPage {
                                $secondsOld = time() - wfTimestamp( TS_UNIX, $rcMin );
                        }
                        if ( $secondsOld > 0 ) {
-                               $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl',
-                               $this->getLanguage()->formatDuration( $secondsOld ) );
+                               $cacheTxt = '<br>' . $this->msg( 'cachedspecial-viewing-cached-ttl' )
+                                       ->durationParams( $secondsOld );
                        }
                }
 
-               $up = new ActiveUsersPager( $this->getContext(), null, $par );
+               $pager = new ActiveUsersPager( $this->getContext(), $opts );
+               $usersBody = $pager->getBody();
+
+               $days = $this->getConfig()->get( 'ActiveUserDays' );
+
+               $formDescriptor = [
+                       'username' => [
+                               'type' => 'user',
+                               'name' => 'username',
+                               'label-message' => 'activeusers-from',
+                       ],
+
+                       'hidebots' => [
+                               'type' => 'check',
+                               'name' => 'hidebots',
+                               'label-message' => 'activeusers-hidebots',
+                               'default' => false,
+                       ],
+
+                       'hidesysops' => [
+                               'type' => 'check',
+                               'name' => 'hidesysops',
+                               'label-message' => 'activeusers-hidesysops',
+                               'default' => false,
+                       ],
+               ];
 
-               # getBody() first to check, if empty
-               $usersbody = $up->getBody();
+               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setIntro( $this->msg( 'activeusers-intro' )->numParams( $days ) . $cacheText )
+                       ->setWrapperLegendMsg( 'activeusers' )
+                       ->setSubmitTextMsg( 'activeusers-submit' )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
 
-               $out->addHTML( $up->getPageHeader() );
-               if ( $usersbody ) {
+               if ( $usersBody ) {
                        $out->addHTML(
-                               $up->getNavigationBar() .
-                               Html::rawElement( 'ul', [], $usersbody ) .
-                               $up->getNavigationBar()
+                               $pager->getNavigationBar() .
+                               Html::rawElement( 'ul', [], $usersBody ) .
+                               $pager->getNavigationBar()
                        );
                } else {
                        $out->addWikiMsg( 'activeusers-noresult' );
index 625e4aa..fcadede 100644 (file)
@@ -28,7 +28,7 @@
  * @ingroup SpecialPage
  */
 class SpecialBlock extends FormSpecialPage {
-       /** @var User User to be blocked, as passed either by parameter (url?wpTarget=Foo)
+       /** @var User|string|null User to be blocked, as passed either by parameter (url?wpTarget=Foo)
         * or as subpage (Special:Block/Foo) */
        protected $target;
 
@@ -330,8 +330,12 @@ class SpecialBlock extends FormSpecialPage {
 
                $otherBlockMessages = [];
                if ( $this->target !== null ) {
+                       $targetName = $this->target;
+                       if ( $this->target instanceof User ) {
+                               $targetName = $this->target->getName();
+                       }
                        # Get other blocks, i.e. from GlobalBlocking or TorBlock extension
-                       Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $this->target ] );
+                       Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $targetName ] );
 
                        if ( count( $otherBlockMessages ) ) {
                                $s = Html::rawElement(
index fe90a4f..11faa28 100644 (file)
@@ -32,7 +32,7 @@ class SpecialBookSources extends SpecialPage {
        /**
         * ISBN passed to the page, if any
         */
-       private $isbn = '';
+       protected $isbn = '';
 
        public function __construct() {
                parent::__construct( 'Booksources' );
@@ -44,17 +44,23 @@ class SpecialBookSources extends SpecialPage {
         * @param string $isbn ISBN passed as a subpage parameter
         */
        public function execute( $isbn ) {
+               $out = $this->getOutput();
+
                $this->setHeaders();
                $this->outputHeader();
+
                $this->isbn = self::cleanIsbn( $isbn ?: $this->getRequest()->getText( 'isbn' ) );
-               $this->getOutput()->addHTML( $this->makeForm() );
+
+               $this->buildForm();
+
                if ( $this->isbn !== '' ) {
                        if ( !self::isValidISBN( $this->isbn ) ) {
-                               $this->getOutput()->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        "<div class=\"error\">\n$1\n</div>",
                                        'booksources-invalid-isbn'
                                );
                        }
+
                        $this->showList();
                }
        }
@@ -115,36 +121,25 @@ class SpecialBookSources extends SpecialPage {
 
        /**
         * Generate a form to allow users to enter an ISBN
-        *
-        * @return string
         */
-       private function makeForm() {
-               $form = Html::openElement( 'fieldset' ) . "\n";
-               $form .= Html::element(
-                       'legend',
-                       [],
-                       $this->msg( 'booksources-search-legend' )->text()
-               ) . "\n";
-               $form .= Html::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] ) . "\n";
-               $form .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . "\n";
-               $form .= '<p>' . Xml::inputLabel(
-                       $this->msg( 'booksources-isbn' )->text(),
-                       'isbn',
-                       'isbn',
-                       20,
-                       $this->isbn,
-                       [ 'autofocus' => '', 'class' => 'mw-ui-input-inline' ]
-               );
-
-               $form .= '&#160;' . Html::submitButton(
-                       $this->msg( 'booksources-search' )->text(),
-                       [], [ 'mw-ui-progressive' ]
-               ) . "</p>\n";
-
-               $form .= Html::closeElement( 'form' ) . "\n";
-               $form .= Html::closeElement( 'fieldset' ) . "\n";
-
-               return $form;
+       private function buildForm() {
+               $formDescriptor = [
+                       'isbn' => [
+                               'type' => 'text',
+                               'name' => 'isbn',
+                               'label-message' => 'booksources-isbn',
+                               'default' => $this->isbn,
+                               'autofocus' => true,
+                               'required' => true,
+                       ],
+               ];
+
+               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setWrapperLegendMsg( 'booksources-search-legend' )
+                       ->setSubmitTextMsg( 'booksources-search' )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
        }
 
        /**
@@ -155,11 +150,13 @@ class SpecialBookSources extends SpecialPage {
         * @return bool
         */
        private function showList() {
+               $out = $this->getOutput();
+
                global $wgContLang;
 
                # Hook to allow extensions to insert additional HTML,
                # e.g. for API-interacting plugins and so on
-               Hooks::run( 'BookInformation', [ $this->isbn, $this->getOutput() ] );
+               Hooks::run( 'BookInformation', [ $this->isbn, $out ] );
 
                # Check for a local page such as Project:Book_sources and use that if available
                $page = $this->msg( 'booksources' )->inContentLanguage()->text();
@@ -172,7 +169,7 @@ class SpecialBookSources extends SpecialPage {
                                // XXX: in the future, this could be stored as structured data, defining a list of book sources
 
                                $text = $content->getNativeData();
-                               $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $text ) );
+                               $out->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $text ) );
 
                                return true;
                        } else {
@@ -181,13 +178,13 @@ class SpecialBookSources extends SpecialPage {
                }
 
                # Fall back to the defaults given in the language file
-               $this->getOutput()->addWikiMsg( 'booksources-text' );
-               $this->getOutput()->addHTML( '<ul>' );
+               $out->addWikiMsg( 'booksources-text' );
+               $out->addHTML( '<ul>' );
                $items = $wgContLang->getBookstoreList();
                foreach ( $items as $label => $url ) {
-                       $this->getOutput()->addHTML( $this->makeListItem( $label, $url ) );
+                       $out->addHTML( $this->makeListItem( $label, $url ) );
                }
-               $this->getOutput()->addHTML( '</ul>' );
+               $out->addHTML( '</ul>' );
 
                return true;
        }
index ee9f665..c7a650c 100644 (file)
@@ -84,12 +84,20 @@ class SpecialChangeContentModel extends FormSpecialPage {
                        ],
                ];
                if ( $this->title ) {
+                       $options = $this->getOptionsForTitle( $this->title );
+                       if ( empty( $options ) ) {
+                               throw new ErrorPageError(
+                                       'changecontentmodel-emptymodels-title',
+                                       'changecontentmodel-emptymodels-text',
+                                       $this->title->getPrefixedText()
+                               );
+                       }
                        $fields['pagetitle']['readonly'] = true;
                        $fields += [
                                'model' => [
                                        'type' => 'select',
                                        'name' => 'model',
-                                       'options' => $this->getOptionsForTitle( $this->title ),
+                                       'options' => $options,
                                        'label-message' => 'changecontentmodel-model-label'
                                ],
                                'reason' => [
diff --git a/includes/specials/SpecialChangeCredentials.php b/includes/specials/SpecialChangeCredentials.php
new file mode 100644 (file)
index 0000000..382dac7
--- /dev/null
@@ -0,0 +1,252 @@
+<?php
+
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Session\SessionManager;
+
+/**
+ * Special change to change credentials (such as the password).
+ *
+ * Also does most of the work for SpecialRemoveCredentials.
+ */
+class SpecialChangeCredentials extends AuthManagerSpecialPage {
+       protected static $allowedActions = [ AuthManager::ACTION_CHANGE ];
+
+       protected static $messagePrefix = 'changecredentials';
+
+       /** Change action needs user data; remove action does not */
+       protected static $loadUserData = true;
+
+       public function __construct( $name = 'ChangeCredentials' ) {
+               parent::__construct( $name, 'editmyprivateinfo' );
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+
+       public function isListed() {
+               $this->loadAuth( '' );
+               return (bool)$this->authRequests;
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       protected function getDefaultAction( $subPage ) {
+               return AuthManager::ACTION_CHANGE;
+       }
+
+       protected function getPreservedParams( $withToken = false ) {
+               $request = $this->getRequest();
+               $params = parent::getPreservedParams( $withToken );
+               $params += [
+                       'returnto' => $request->getVal( 'returnto' ),
+                       'returntoquery' => $request->getVal( 'returntoquery' ),
+               ];
+               return $params;
+       }
+
+       public function onAuthChangeFormFields(
+               array $requests, array $fieldInfo, array &$formDescriptor, $action
+       ) {
+               // This method is never called for remove actions.
+
+               $extraFields = [];
+               Hooks::run( 'ChangePasswordForm', [ &$extraFields ], '1.27' );
+               foreach ( $extraFields as $extra ) {
+                       list( $name, $label, $type, $default ) = $extra;
+                       $formDescriptor[$name] = [
+                               'type' => $type,
+                               'name' => $name,
+                               'label-message' => $label,
+                               'default' => $default,
+                       ];
+
+               }
+
+               return parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
+       }
+
+       public function execute( $subPage ) {
+               $this->setHeaders();
+               $this->outputHeader();
+
+               $this->loadAuth( $subPage );
+
+               if ( !$subPage ) {
+                       $this->showSubpageList();
+                       return;
+               }
+
+               if ( $this->getRequest()->getCheck( 'wpCancel' ) ) {
+                       $returnUrl = $this->getReturnUrl() ?: Title::newMainPage()->getFullURL();
+                       $this->getOutput()->redirect( $returnUrl );
+                       return;
+               }
+
+               if ( !$this->authRequests ) {
+                       // messages used: changecredentials-invalidsubpage, removecredentials-invalidsubpage
+                       $this->showSubpageList( $this->msg( static::$messagePrefix . '-invalidsubpage', $subPage ) );
+                       return;
+               }
+
+               $status = $this->trySubmit();
+
+               if ( $status === false || !$status->isOK() ) {
+                       $this->displayForm( $status );
+                       return;
+               }
+
+               $response = $status->getValue();
+
+               switch ( $response->status ) {
+                       case AuthenticationResponse::PASS:
+                               $this->success();
+                               break;
+                       case AuthenticationResponse::FAIL:
+                               $this->displayForm( Status::newFatal( $response->message ) );
+                               break;
+                       default:
+                               throw new LogicException( 'invalid AuthenticationResponse' );
+               }
+       }
+
+       protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
+               parent::loadAuth( $subPage, $authAction );
+               if ( $subPage ) {
+                       $this->authRequests = array_filter( $this->authRequests, function ( $req ) use ( $subPage ) {
+                               return $req->getUniqueId() === $subPage;
+                       } );
+                       if ( count( $this->authRequests ) > 1 ) {
+                               throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
+                       }
+               }
+       }
+
+       protected function getAuthFormDescriptor( $requests, $action ) {
+               if ( !static::$loadUserData ) {
+                       return [];
+               } else {
+                       return parent::getAuthFormDescriptor( $requests, $action );
+               }
+       }
+
+       protected function getAuthForm( array $requests, $action ) {
+               $form = parent::getAuthForm( $requests, $action );
+               $req = reset( $requests );
+               $info = $req->describeCredentials();
+
+               $form->addPreText(
+                       Html::openElement( 'dl' )
+                       . Html::element( 'dt', [], wfMessage( 'credentialsform-provider' ) )
+                       . Html::element( 'dd', [], $info['provider'] )
+                       . Html::element( 'dt', [], wfMessage( 'credentialsform-account' ) )
+                       . Html::element( 'dd', [], $info['account'] )
+                       . Html::closeElement( 'dl' )
+               );
+
+               // messages used: changecredentials-submit removecredentials-submit
+               // changecredentials-submit-cancel removecredentials-submit-cancel
+               $form->setSubmitTextMsg( static::$messagePrefix . '-submit' );
+               $form->addButton( [
+                       'name' => 'wpCancel',
+                       'value' => $this->msg( static::$messagePrefix . '-submit-cancel' )->text()
+               ] );
+
+               return $form;
+       }
+
+       protected function needsSubmitButton( $formDescriptor ) {
+               // Change/remove forms show are built from a single AuthenticationRequest and do not allow
+               // for redirect flow; they always need a submit button.
+               return true;
+       }
+
+       public function handleFormSubmit( $data ) {
+               // remove requests do not accept user input
+               $requests = $this->authRequests;
+               if ( static::$loadUserData ) {
+                       $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
+               }
+
+               $response = $this->performAuthenticationStep( $this->authAction, $requests );
+
+               // we can't handle FAIL or similar as failure here since it might require changing the form
+               return Status::newGood( $response );
+       }
+
+       /**
+        * @param Message|null $error
+        */
+       protected function showSubpageList( $error = null ) {
+               $out = $this->getOutput();
+
+               if ( $error ) {
+                       $out->addHTML( $error->parse() );
+               }
+
+               $groupedRequests = [];
+               foreach ( $this->authRequests as $req ) {
+                       $info = $req->describeCredentials();
+                       $groupedRequests[(string)$info['provider']][] = $req;
+               }
+
+               $out->addHTML( Html::openElement( 'dl' ) );
+               foreach ( $groupedRequests as $group => $members ) {
+                       $out->addHTML( Html::element( 'dt', [], $group ) );
+                       foreach ( $members as $req ) {
+                               /** @var AuthenticationRequest $req */
+                               $info = $req->describeCredentials();
+                               $out->addHTML( Html::rawElement( 'dd', [],
+                                       Linker::link( $this->getPageTitle( $req->getUniqueId() ),
+                                               htmlspecialchars( $info['account'], ENT_QUOTES ) )
+                               ) );
+                       }
+               }
+               $out->addHTML( Html::closeElement( 'dl' ) );
+       }
+
+       protected function success() {
+               $session = $this->getRequest()->getSession();
+               $user = $this->getUser();
+               $out = $this->getOutput();
+               $returnUrl = $this->getReturnUrl();
+
+               // change user token and update the session
+               SessionManager::singleton()->invalidateSessionsForUser( $user );
+               $session->setUser( $user );
+               $session->resetId();
+
+               if ( $returnUrl ) {
+                       $out->redirect( $returnUrl );
+               } else {
+                       // messages used: changecredentials-success removecredentials-success
+                       $out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", static::$messagePrefix
+                               . '-success' );
+                       $out->returnToMain();
+               }
+       }
+
+       /**
+        * @return string|null
+        */
+       protected function getReturnUrl() {
+               $request = $this->getRequest();
+               $returnTo = $request->getText( 'returnto' );
+               $returnToQuery = $request->getText( 'returntoquery', '' );
+
+               if ( !$returnTo ) {
+                       return null;
+               }
+
+               $title = Title::newFromText( $returnTo );
+               return $title->getFullURL( $returnToQuery );
+       }
+
+       protected function getRequestBlacklist() {
+               return $this->getConfig()->get( 'ChangeCredentialsBlacklist' );
+       }
+}
index b35446d..785447f 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Auth\AuthManager;
+
 /**
  * Let users change their email address.
  *
@@ -44,9 +46,7 @@ class SpecialChangeEmail extends FormSpecialPage {
         * @return bool
         */
        public function isListed() {
-               global $wgAuth;
-
-               return $wgAuth->allowPropChange( 'emailaddress' );
+               return AuthManager::singleton()->allowsPropertyChange( 'emailaddress' );
        }
 
        /**
@@ -54,6 +54,8 @@ class SpecialChangeEmail extends FormSpecialPage {
         * @param string $par
         */
        function execute( $par ) {
+               $this->checkLoginSecurityLevel();
+
                $out = $this->getOutput();
                $out->disallowUserJs();
 
@@ -61,9 +63,8 @@ class SpecialChangeEmail extends FormSpecialPage {
        }
 
        protected function checkExecutePermissions( User $user ) {
-               global $wgAuth;
 
-               if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
+               if ( !AuthManager::singleton()->allowsPropertyChange( 'emailaddress' ) ) {
                        throw new ErrorPageError( 'changeemail', 'cannotchangeemail' );
                }
 
@@ -100,13 +101,6 @@ class SpecialChangeEmail extends FormSpecialPage {
                        ],
                ];
 
-               if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
-                       $fields['Password'] = [
-                               'type' => 'password',
-                               'label-message' => 'changeemail-password'
-                       ];
-               }
-
                return $fields;
        }
 
@@ -121,14 +115,10 @@ class SpecialChangeEmail extends FormSpecialPage {
                $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
 
                $form->addHeaderText( $this->msg( 'changeemail-header' )->parseAsBlock() );
-               if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
-                       $form->addHeaderText( $this->msg( 'changeemail-passwordrequired' )->parseAsBlock() );
-               }
        }
 
        public function onSubmit( array $data ) {
-               $password = isset( $data['Password'] ) ? $data['Password'] : null;
-               $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] );
+               $status = $this->attemptChange( $this->getUser(), $data['NewEmail'] );
 
                $this->status = $status;
 
@@ -158,12 +148,11 @@ class SpecialChangeEmail extends FormSpecialPage {
 
        /**
         * @param User $user
-        * @param string $pass
         * @param string $newaddr
         * @return Status
         */
-       private function attemptChange( User $user, $pass, $newaddr ) {
-               global $wgAuth;
+       private function attemptChange( User $user, $newaddr ) {
+               $authManager = AuthManager::singleton();
 
                if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
                        return Status::newFatal( 'invalidemailaddress' );
@@ -173,24 +162,6 @@ class SpecialChangeEmail extends FormSpecialPage {
                        return Status::newFatal( 'changeemail-nochange' );
                }
 
-               $throttleInfo = LoginForm::incrementLoginThrottle( $user->getName() );
-               if ( $throttleInfo ) {
-                       $lang = $this->getLanguage();
-                       return Status::newFatal(
-                               'changeemail-throttled',
-                               $lang->formatDuration( $throttleInfo['wait'] )
-                       );
-               }
-
-               if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' )
-                       && !$user->checkTemporaryPassword( $pass )
-                       && !$user->checkPassword( $pass )
-               ) {
-                       return Status::newFatal( 'wrongpassword' );
-               }
-
-               LoginForm::clearLoginThrottle( $user->getName() );
-
                $oldaddr = $user->getEmail();
                $status = $user->setEmailWithConfirmation( $newaddr );
                if ( !$status->isGood() ) {
@@ -200,8 +171,7 @@ class SpecialChangeEmail extends FormSpecialPage {
                Hooks::run( 'PrefsEmailAudit', [ $user, $oldaddr, $newaddr ] );
 
                $user->saveSettings();
-
-               $wgAuth->updateExternalDB( $user );
+               MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] );
 
                return $status;
        }
index 5adc315..ce769bf 100644 (file)
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Auth\PasswordAuthenticationRequest;
+
 /**
  * Let users recover their password.
  *
  * @ingroup SpecialPage
  */
-class SpecialChangePassword extends FormSpecialPage {
-       protected $mUserName;
-       protected $mDomain;
-
-       // Optional Wikitext Message to show above the password change form
-       protected $mPreTextMessage = null;
-
-       // label for old password input
-       protected $mOldPassMsg = null;
-
+class SpecialChangePassword extends SpecialRedirectToSpecial {
        public function __construct() {
-               parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
-               $this->listed( false );
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-
-       /**
-        * Main execution point
-        * @param string|null $par
-        */
-       function execute( $par ) {
-               $this->getOutput()->disallowUserJs();
-
-               parent::execute( $par );
-       }
-
-       protected function checkExecutePermissions( User $user ) {
-               parent::checkExecutePermissions( $user );
-
-               if ( !$this->getRequest()->wasPosted() ) {
-                       $this->requireLogin( 'resetpass-no-info' );
-               }
-       }
-
-       /**
-        * Set a message at the top of the Change Password form
-        * @since 1.23
-        * @param Message $msg Message to parse and add to the form header
-        */
-       public function setChangeMessage( Message $msg ) {
-               $this->mPreTextMessage = $msg;
-       }
-
-       /**
-        * Set a message at the top of the Change Password form
-        * @since 1.23
-        * @param string $msg Message label for old/temp password field
-        */
-       public function setOldPasswordMessage( $msg ) {
-               $this->mOldPassMsg = $msg;
-       }
-
-       protected function getFormFields() {
-               $user = $this->getUser();
-               $request = $this->getRequest();
-
-               $oldpassMsg = $this->mOldPassMsg;
-               if ( $oldpassMsg === null ) {
-                       $oldpassMsg = $user->isLoggedIn() ? 'oldpassword' : 'resetpass-temp-password';
-               }
-
-               $fields = [
-                       'Name' => [
-                               'type' => 'info',
-                               'label-message' => 'username',
-                               'default' => $request->getVal( 'wpName', $user->getName() ),
-                       ],
-                       'Password' => [
-                               'type' => 'password',
-                               'label-message' => $oldpassMsg,
-                       ],
-                       'NewPassword' => [
-                               'type' => 'password',
-                               'label-message' => 'newpassword',
-                       ],
-                       'Retype' => [
-                               'type' => 'password',
-                               'label-message' => 'retypenew',
-                       ],
-               ];
-
-               if ( !$this->getUser()->isLoggedIn() ) {
-                       $fields['LoginOnChangeToken'] = [
-                               'type' => 'hidden',
-                               'label' => 'Change Password Token',
-                               'default' => LoginForm::getLoginToken()->toString(),
-                       ];
-               }
-
-               $extraFields = [];
-               Hooks::run( 'ChangePasswordForm', [ &$extraFields ] );
-               foreach ( $extraFields as $extra ) {
-                       list( $name, $label, $type, $default ) = $extra;
-                       $fields[$name] = [
-                               'type' => $type,
-                               'name' => $name,
-                               'label-message' => $label,
-                               'default' => $default,
-                       ];
-               }
-
-               if ( !$user->isLoggedIn() ) {
-                       $fields['Remember'] = [
-                               'type' => 'check',
-                               'label' => $this->msg( 'remembermypassword' )
-                                               ->numParams(
-                                                       ceil( $this->getConfig()->get( 'CookieExpiration' ) / ( 3600 * 24 ) )
-                                               )->text(),
-                               'default' => $request->getVal( 'wpRemember' ),
-                       ];
-               }
-
-               return $fields;
-       }
-
-       protected function alterForm( HTMLForm $form ) {
-               $form->setId( 'mw-resetpass-form' );
-               $form->setTableId( 'mw-resetpass-table' );
-               $form->setWrapperLegendMsg( 'resetpass_header' );
-               $form->setSubmitTextMsg(
-                       $this->getUser()->isLoggedIn()
-                               ? 'resetpass-submit-loggedin'
-                               : 'resetpass_submit'
-               );
-               $form->addButton( [
-                       'name' => 'wpCancel',
-                       'value' => $this->msg( 'resetpass-submit-cancel' )->text()
-               ] );
-               $form->setHeaderText( $this->msg( 'resetpass_text' )->parseAsBlock() );
-               if ( $this->mPreTextMessage instanceof Message ) {
-                       $form->addPreText( $this->mPreTextMessage->parseAsBlock() );
-               }
-               $form->addHiddenFields(
-                       $this->getRequest()->getValues( 'wpName', 'wpDomain', 'returnto', 'returntoquery' ) );
-       }
-
-       public function onSubmit( array $data ) {
-               global $wgAuth;
-
-               $request = $this->getRequest();
-
-               if ( $request->getCheck( 'wpLoginToken' ) ) {
-                       // This comes from Special:Userlogin when logging in with a temporary password
-                       return false;
-               }
-
-               if ( !$this->getUser()->isLoggedIn()
-                       && !LoginForm::getLoginToken()->match( $request->getVal( 'wpLoginOnChangeToken' ) )
-               ) {
-                       // Potential CSRF (bug 62497)
-                       return false;
-               }
-
-               if ( $request->getCheck( 'wpCancel' ) ) {
-                       $returnto = $request->getVal( 'returnto' );
-                       $titleObj = $returnto !== null ? Title::newFromText( $returnto ) : null;
-                       if ( !$titleObj instanceof Title ) {
-                               $titleObj = Title::newMainPage();
-                       }
-                       $query = $request->getVal( 'returntoquery' );
-                       $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
-
-                       return true;
-               }
-
-               $this->mUserName = $request->getVal( 'wpName', $this->getUser()->getName() );
-               $this->mDomain = $wgAuth->getDomain();
-
-               if ( !$wgAuth->allowPasswordChange() ) {
-                       throw new ErrorPageError( 'changepassword', 'resetpass_forbidden' );
-               }
-
-               $status = $this->attemptReset( $data['Password'], $data['NewPassword'], $data['Retype'] );
-
-               return $status;
-       }
-
-       public function onSuccess() {
-               if ( $this->getUser()->isLoggedIn() ) {
-                       $this->getOutput()->wrapWikiMsg(
-                               "<div class=\"successbox\">\n$1\n</div>",
-                               'changepassword-success'
-                       );
-                       $this->getOutput()->returnToMain();
-               } else {
-                       $request = $this->getRequest();
-                       LoginForm::clearLoginToken();
-                       $token = LoginForm::getLoginToken()->toString();
-                       $data = [
-                               'action' => 'submitlogin',
-                               'wpName' => $this->mUserName,
-                               'wpDomain' => $this->mDomain,
-                               'wpLoginToken' => $token,
-                               'wpPassword' => $request->getVal( 'wpNewPassword' ),
-                       ] + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
-                       $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
-                       $login->setContext( $this->getContext() );
-                       $login->execute( null );
-               }
-       }
-
-       /**
-        * Checks the new password if it meets the requirements for passwords and set
-        * it as a current password, otherwise set the passed Status object to fatal
-        * and doesn't change anything
-        *
-        * @param string $oldpass The current (temporary) password.
-        * @param string $newpass The password to set.
-        * @param string $retype The string of the retype password field to check with newpass
-        * @return Status
-        */
-       protected function attemptReset( $oldpass, $newpass, $retype ) {
-               $isSelf = ( $this->mUserName === $this->getUser()->getName() );
-               if ( $isSelf ) {
-                       $user = $this->getUser();
-               } else {
-                       $user = User::newFromName( $this->mUserName );
-               }
-
-               if ( !$user || $user->isAnon() ) {
-                       return Status::newFatal( $this->msg( 'nosuchusershort', $this->mUserName ) );
-               }
-
-               if ( $newpass !== $retype ) {
-                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'badretype' ] );
-                       return Status::newFatal( $this->msg( 'badretype' ) );
-               }
-
-               $throttleInfo = LoginForm::incrementLoginThrottle( $this->mUserName );
-               if ( $throttleInfo ) {
-                       return Status::newFatal( $this->msg( 'changepassword-throttled' )
-                               ->durationParams( $throttleInfo['wait'] )
-                       );
-               }
-
-               // @todo Make these separate messages, since the message is written for both cases
-               if ( !$user->checkTemporaryPassword( $oldpass ) && !$user->checkPassword( $oldpass ) ) {
-                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'wrongpassword' ] );
-                       return Status::newFatal( $this->msg( 'resetpass-wrong-oldpass' ) );
-               }
-
-               // User is resetting their password to their old password
-               if ( $oldpass === $newpass ) {
-                       return Status::newFatal( $this->msg( 'resetpass-recycled' ) );
-               }
-
-               // Do AbortChangePassword after checking mOldpass, so we don't leak information
-               // by possibly aborting a new password before verifying the old password.
-               $abortMsg = 'resetpass-abort-generic';
-               if ( !Hooks::run( 'AbortChangePassword', [ $user, $oldpass, $newpass, &$abortMsg ] ) ) {
-                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'abortreset' ] );
-                       return Status::newFatal( $this->msg( $abortMsg ) );
-               }
-
-               // Please reset throttle for successful logins, thanks!
-               LoginForm::clearLoginThrottle( $this->mUserName );
-
-               try {
-                       $user->setPassword( $newpass );
-                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'success' ] );
-               } catch ( PasswordError $e ) {
-                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'error' ] );
-                       return Status::newFatal( new RawMessage( $e->getMessage() ) );
-               }
-
-               if ( $isSelf ) {
-                       // This is needed to keep the user connected since
-                       // changing the password also modifies the user's token.
-                       $remember = $this->getRequest()->getCookie( 'Token' ) !== null;
-                       $user->setCookies( null, null, $remember );
-               }
-               $user->saveSettings();
-               $this->resetPasswordExpiration( $user );
-               return Status::newGood();
-       }
-
-       public function requiresUnblock() {
-               return false;
-       }
-
-       protected function getGroupName() {
-               return 'users';
-       }
-
-       /**
-        * For resetting user password expiration, until AuthManager comes along
-        * @param User $user
-        */
-       private function resetPasswordExpiration( User $user ) {
-               global $wgPasswordExpirationDays;
-               $newExpire = null;
-               if ( $wgPasswordExpirationDays ) {
-                       $newExpire = wfTimestamp(
-                               TS_MW,
-                               time() + ( $wgPasswordExpirationDays * 24 * 3600 )
-                       );
-               }
-               // Give extensions a chance to force an expiration
-               Hooks::run( 'ResetPasswordExpiration', [ $this, &$newExpire ] );
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->update(
-                       'user',
-                       [ 'user_password_expires' => $dbw->timestampOrNull( $newExpire ) ],
-                       [ 'user_id' => $user->getId() ],
-                       __METHOD__
-               );
-       }
-
-       protected function getDisplayFormat() {
-               return 'ooui';
+               parent::__construct( 'ChangePassword', 'ChangeCredentials',
+                       PasswordAuthenticationRequest::class, [ 'returnto', 'returntoquery' ] );
        }
 }
index 69ddcf9..b046bf9 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ * Implements Special:CreateAccount
  *
  * 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
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LogLevel;
+
 /**
- * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
- * @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
+ * Implements Special:CreateAccount
  *
  * @ingroup SpecialPage
  */
-class SpecialCreateAccount extends SpecialRedirectToSpecial {
-       function __construct() {
-               parent::__construct(
-                       'CreateAccount',
-                       'Userlogin',
-                       'signup',
-                       [ 'returnto', 'returntoquery', 'uselang' ]
-               );
-       }
+class SpecialCreateAccount extends LoginSignupSpecialPage {
+       protected static $allowedActions = [
+               AuthManager::ACTION_CREATE,
+               AuthManager::ACTION_CREATE_CONTINUE
+       ];
 
-       public function doesWrites() {
-               return true;
+       protected static $messages = [
+               'authform-newtoken' => 'nocookiesfornew',
+               'authform-notoken' => 'sessionfailure',
+               'authform-wrongtoken' => 'sessionfailure',
+       ];
+
+       public function __construct() {
+               parent::__construct( 'CreateAccount' );
        }
 
-       // No reason to hide this link on Special:Specialpages
-       public function isListed() {
+       public function doesWrites() {
                return true;
        }
 
@@ -54,7 +58,112 @@ class SpecialCreateAccount extends SpecialRedirectToSpecial {
                return $user->isAllowed( 'createaccount' );
        }
 
+       public function checkPermissions() {
+               parent::checkPermissions();
+
+               $user = $this->getUser();
+               $status = AuthManager::singleton()->checkAccountCreatePermissions( $user );
+               if ( !$status->isGood() ) {
+                       throw new ErrorPageError( 'createacct-error', $status->getMessage() );
+               }
+       }
+
+       protected function getLoginSecurityLevel() {
+               return false;
+       }
+
+       protected function getDefaultAction( $subPage ) {
+               return AuthManager::ACTION_CREATE;
+       }
+
+       public function getDescription() {
+               return $this->msg( 'createaccount' )->text();
+       }
+
+       protected function isSignup() {
+               return true;
+       }
+
+       /**
+        * Run any hooks registered for logins, then display a message welcoming
+        * the user.
+        * @param bool $direct True if the action was successful just now; false if that happened
+        *    pre-redirection (so this handler was called already)
+        * @param StatusValue|null $extraMessages
+        */
+       protected function successfulAction( $direct = false, $extraMessages = null ) {
+               $session = $this->getRequest()->getSession();
+               $user = $this->targetUser ?: $this->getUser();
+
+               if ( $direct ) {
+                       # Only save preferences if the user is not creating an account for someone else.
+                       if ( !$this->proxyAccountCreation ) {
+                               Hooks::run( 'AddNewAccount', [ $user, false ] );
+
+                               // If the user does not have a session cookie at this point, they probably need to
+                               // do something to their browser.
+                               if ( !$this->hasSessionCookie() ) {
+                                       $this->mainLoginForm( [ /*?*/ ], $session->getProvider()->whyNoSession() );
+                                       // TODO something more specific? This used to use nocookiesnew
+                                       // FIXME should redirect to login page instead?
+                                       return;
+                               }
+                       } else {
+                               $byEmail = false; // FIXME no way to set this
+
+                               Hooks::run( 'AddNewAccount', [ $user, $byEmail ] );
+
+                               $out = $this->getOutput();
+                               $out->setPageTitle( $this->msg( $byEmail ? 'accmailtitle' : 'accountcreated' ) );
+                               if ( $byEmail ) {
+                                       $out->addWikiMsg( 'accmailtext', $user->getName(), $user->getEmail() );
+                               } else {
+                                       $out->addWikiMsg( 'accountcreatedtext', $user->getName() );
+                               }
+                               $out->addReturnTo( $this->getPageTitle() );
+                               return;
+                       }
+               }
+
+               $this->clearToken();
+
+               # Run any hooks; display injected HTML
+               $injected_html = '';
+               $welcome_creation_msg = 'welcomecreation-msg';
+               Hooks::run( 'UserLoginComplete', [ &$user, &$injected_html ] );
+
+               /**
+                * Let any extensions change what message is shown.
+                * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation
+                * @since 1.18
+                */
+               Hooks::run( 'BeforeWelcomeCreation', [ &$welcome_creation_msg, &$injected_html ] );
+
+               $this->showSuccessPage( 'signup', $this->msg( 'welcomeuser', $this->getUser()->getName() ),
+                       $welcome_creation_msg, $injected_html, $extraMessages );
+       }
+
+       protected function getToken() {
+               return $this->getRequest()->getSession()->getToken( '', 'createaccount' );
+       }
+
+       protected function clearToken() {
+               return $this->getRequest()->getSession()->resetToken( 'createaccount' );
+       }
+
+       protected function getTokenName() {
+               return 'wpCreateaccountToken';
+       }
+
        protected function getGroupName() {
                return 'login';
        }
+
+       protected function logAuthResult( $success, $status = null ) {
+               LoggerFactory::getInstance( 'authmanager-stats' )->info( 'Account creation attempt', [
+                       'event' => 'accountcreation',
+                       'successful' => $success,
+                       'status' => $status,
+               ] );
+       }
 }
index f2fa921..627dd2c 100644 (file)
@@ -27,6 +27,8 @@ use MediaWiki\Linker\LinkTarget;
  * @ingroup Watchlist
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Provides the UI through which users can perform editing
  * operations on their watchlist
@@ -325,7 +327,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
        private function getWatchlist() {
                $list = [];
 
-               $watchedItems = WatchedItemStore::getDefaultInstance()->getWatchedItemsForUser(
+               $watchedItems = MediaWikiServices::getInstance()->getWatchedItemStore()->getWatchedItemsForUser(
                        $this->getUser(),
                        [ 'forWrite' => $this->getRequest()->wasPosted() ]
                );
@@ -366,7 +368,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
        protected function getWatchlistInfo() {
                $titles = [];
 
-               $watchedItems = WatchedItemStore::getDefaultInstance()
+               $watchedItems = MediaWikiServices::getInstance()->getWatchedItemStore()
                        ->getWatchedItemsForUser( $this->getUser(), [ 'sort' => WatchedItemStore::SORT_ASC ] );
 
                $lb = new LinkBatch();
@@ -421,7 +423,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                }
 
                $user = $this->getUser();
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
 
                foreach ( $this->badItems as $row ) {
                        list( $title, $namespace, $dbKey ) = $row;
@@ -472,7 +474,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                        $expandedTargets[] = new TitleValue( MWNamespace::getTalk( $ns ), $dbKey );
                }
 
-               WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
+               MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
                        $this->getUser(),
                        $expandedTargets
                );
@@ -487,7 +489,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
         * @param array $titles Array of strings, or Title objects
         */
        private function unwatchTitles( $titles ) {
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
 
                foreach ( $titles as $title ) {
                        if ( !$title instanceof Title ) {
diff --git a/includes/specials/SpecialLinkAccounts.php b/includes/specials/SpecialLinkAccounts.php
new file mode 100644 (file)
index 0000000..da10b90
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * Links/unlinks external accounts to the current user.
+ *
+ * To interact with this page, account providers need to register themselves with AuthManager.
+ */
+class SpecialLinkAccounts extends AuthManagerSpecialPage {
+       protected static $allowedActions = [
+               AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
+       ];
+
+       public function __construct() {
+               parent::__construct( 'LinkAccounts' );
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+
+       public function isListed() {
+               return AuthManager::singleton()->canLinkAccounts();
+       }
+
+       protected function getRequestBlacklist() {
+               return $this->getConfig()->get( 'ChangeCredentialsBlacklist' );
+       }
+
+       /**
+        * @param null|string $subPage
+        * @throws MWException
+        * @throws PermissionsError
+        */
+       public function execute( $subPage ) {
+               $this->setHeaders();
+               $this->loadAuth( $subPage );
+
+               if ( !$this->isActionAllowed( $this->authAction ) ) {
+                       if ( $this->authAction === AuthManager::ACTION_LINK ) {
+                               // looks like no linking provider is installed or willing to take this user
+                               $titleMessage = wfMessage( 'cannotlink-no-provider-title' );
+                               $errorMessage = wfMessage( 'cannotlink-no-provider' );
+                               throw new ErrorPageError( $titleMessage, $errorMessage );
+                       } else {
+                               // user probably back-button-navigated into an auth session that no longer exists
+                               // FIXME would be nice to show a message
+                               $this->getOutput()->redirect( $this->getPageTitle()->getFullURL( '', false,
+                                       PROTO_HTTPS ) );
+                               return;
+                       }
+               }
+
+               $this->outputHeader();
+
+               $status = $this->trySubmit();
+
+               if ( $status === false || !$status->isOK() ) {
+                       $this->displayForm( $status );
+                       return;
+               }
+
+               $response = $status->getValue();
+
+               switch ( $response->status ) {
+                       case AuthenticationResponse::PASS:
+                               $this->success();
+                               break;
+                       case AuthenticationResponse::FAIL:
+                               $this->loadAuth( '', AuthManager::ACTION_LINK, true );
+                               $this->displayForm( StatusValue::newFatal( $response->message ) );
+                               break;
+                       case AuthenticationResponse::REDIRECT:
+                               $this->getOutput()->redirect( $response->redirectTarget );
+                               break;
+                       case AuthenticationResponse::UI:
+                               $this->authAction = AuthManager::ACTION_LINK_CONTINUE;
+                               $this->authRequests = $response->neededRequests;
+                               $this->displayForm( StatusValue::newFatal( $response->message ) );
+                               break;
+                       default:
+                               throw new LogicException( 'invalid AuthenticationResponse' );
+               }
+       }
+
+       protected function getDefaultAction( $subPage ) {
+               return AuthManager::ACTION_LINK;
+       }
+
+       /**
+        * @param AuthenticationRequest[] $requests
+        * @param string $action AuthManager action name, should be ACTION_LINK or ACTION_LINK_CONTINUE
+        * @return HTMLForm
+        */
+       protected function getAuthForm( array $requests, $action ) {
+               $form = parent::getAuthForm( $requests, $action );
+               $form->setSubmitTextMsg( 'linkaccounts-submit' );
+               return $form;
+       }
+
+       /**
+        * Show a success message.
+        */
+       protected function success() {
+               $this->loadAuth( '', AuthManager::ACTION_LINK, true );
+               $this->displayForm( StatusValue::newFatal( $this->msg( 'linkaccounts-success-text' ) ) );
+       }
+}
index a229fa3..2d087ca 100644 (file)
@@ -34,7 +34,7 @@ class SpecialLockdb extends FormSpecialPage {
        }
 
        public function doesWrites() {
-               return true;
+               return false;
        }
 
        public function requiresWrite() {
@@ -47,6 +47,9 @@ class SpecialLockdb extends FormSpecialPage {
                if ( !is_writable( dirname( $this->getConfig()->get( 'ReadOnlyFile' ) ) ) ) {
                        throw new ErrorPageError( 'lockdb', 'lockfilenotwritable' );
                }
+               if ( file_exists( $this->getConfig()->get( 'ReadOnlyFile' ) ) ) {
+                       throw new ErrorPageError( 'lockdb', 'databaselocked' );
+               }
        }
 
        protected function getFormFields() {
@@ -65,9 +68,9 @@ class SpecialLockdb extends FormSpecialPage {
        }
 
        protected function alterForm( HTMLForm $form ) {
-               $form->setWrapperLegend( false );
-               $form->setHeaderText( $this->msg( 'lockdbtext' )->parseAsBlock() );
-               $form->setSubmitTextMsg( 'lockbtn' );
+               $form->setWrapperLegend( false )
+                       ->setHeaderText( $this->msg( 'lockdbtext' )->parseAsBlock() )
+                       ->setSubmitTextMsg( 'lockbtn' );
        }
 
        public function onSubmit( array $data ) {
@@ -105,6 +108,10 @@ class SpecialLockdb extends FormSpecialPage {
                $out->addWikiMsg( 'lockdbsuccesstext' );
        }
 
+       protected function getDisplayFormat() {
+               return 'ooui';
+       }
+
        protected function getGroupName() {
                return 'wiki';
        }
index defca7d..e3be225 100644 (file)
@@ -106,21 +106,26 @@ class MIMEsearchPage extends QueryPage {
        }
 
        /**
-        * Return HTML to put just before the results.
+        * Generate and output the form
         */
        function getPageHeader() {
-               return Xml::openElement(
-                               'form',
-                               [ 'id' => 'specialmimesearch', 'method' => 'get', 'action' => wfScript() ]
-                       ) .
-                       Xml::openElement( 'fieldset' ) .
-                       Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
-                       Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
-                       Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $this->mime ) .
-                       ' ' .
-                       Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
-                                       Xml::closeElement( 'fieldset' ) .
-                                       Xml::closeElement( 'form' );
+               $formDescriptor = [
+                       'mime' => [
+                               'type' => 'text',
+                               'name' => 'mime',
+                               'label-message' => 'mimetype',
+                               'required' => true,
+                               'default' => $this->mime,
+                       ],
+               ];
+
+               $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setWrapperLegendMsg( 'mimesearch' )
+                       ->setSubmitTextMsg( 'ilsubmit' )
+                       ->setAction( $this->getPageTitle()->getLocalURL() )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
        }
 
        public function execute( $par ) {
@@ -133,7 +138,7 @@ class MIMEsearchPage extends QueryPage {
                ) {
                        $this->setHeaders();
                        $this->outputHeader();
-                       $this->getOutput()->addHTML( $this->getPageHeader() );
+                       $this->getPageHeader();
                        return;
                }
 
index 8ba90a6..e51e8b5 100644 (file)
@@ -109,12 +109,12 @@ class MediaStatisticsPage extends QueryPage {
        /**
         * Output the results of the query.
         *
-        * @param $out OutputPage
-        * @param $skin Skin (deprecated presumably)
-        * @param $dbr IDatabase
-        * @param $res ResultWrapper Results from query
-        * @param $num integer Number of results
-        * @param $offset integer Paging offset (Should always be 0 in our case)
+        * @param OutputPage $out
+        * @param Skin $skin (deprecated presumably)
+        * @param IDatabase $dbr
+        * @param ResultWrapper $res Results from query
+        * @param int $num Number of results
+        * @param int $offset Paging offset (Should always be 0 in our case)
         */
        protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
                $prevMediaType = null;
@@ -168,9 +168,9 @@ class MediaStatisticsPage extends QueryPage {
        /**
         * Output a row of the stats table
         *
-        * @param $mime String mime type (e.g. image/jpeg)
-        * @param $count integer Number of images of this type
-        * @param $totalBytes integer Total space for images of this type
+        * @param string $mime mime type (e.g. image/jpeg)
+        * @param int $count Number of images of this type
+        * @param int $totalBytes Total space for images of this type
         */
        protected function outputTableRow( $mime, $count, $bytes ) {
                $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
@@ -230,8 +230,8 @@ class MediaStatisticsPage extends QueryPage {
        /**
         * Given a mime type, return a comma separated list of allowed extensions.
         *
-        * @param $mime String mime type
-        * @return String Comma separated list of allowed extensions (e.g. ".ogg, .oga")
+        * @param string $mime mime type
+        * @return string Comma separated list of allowed extensions (e.g. ".ogg, .oga")
         */
        private function getExtensionList( $mime ) {
                $exts = MimeMagic::singleton()->getExtensionsForType( $mime );
@@ -291,7 +291,7 @@ class MediaStatisticsPage extends QueryPage {
        /**
         * Output a header for a new media type section
         *
-        * @param $mediaType string A media type (e.g. from the MEDIATYPE_xxx constants)
+        * @param string $mediaType A media type (e.g. from the MEDIATYPE_xxx constants)
         */
        protected function outputMediaType( $mediaType ) {
                $this->getOutput()->addHTML(
@@ -318,8 +318,8 @@ class MediaStatisticsPage extends QueryPage {
        /**
         * parse the fake title format that this special page abuses querycache with.
         *
-        * @param $fakeTitle String A string formatted as <media type>;<mime type>;<count>;<bytes>
-        * @return Array The constituant parts of $fakeTitle
+        * @param string $fakeTitle A string formatted as <media type>;<mime type>;<count>;<bytes>
+        * @return array The constituant parts of $fakeTitle
         */
        private function splitFakeTitle( $fakeTitle ) {
                return explode( ';', $fakeTitle, 4 );
@@ -337,8 +337,8 @@ class MediaStatisticsPage extends QueryPage {
         * This method isn't used, since we override outputResults, but
         * we need to implement since abstract in parent class.
         *
-        * @param $skin Skin
-        * @param $result stdObject Result row
+        * @param Skin $skin
+        * @param stdObject $result Result row
         * @return bool|string|void
         * @throws MWException
         */
@@ -349,8 +349,8 @@ class MediaStatisticsPage extends QueryPage {
        /**
         * Initialize total values so we can figure out percentages later.
         *
-        * @param $dbr IDatabase
-        * @param $res ResultWrapper
+        * @param IDatabase $dbr
+        * @param ResultWrapper $res
         */
        public function preprocessResults( $dbr, $res ) {
                $this->totalCount = $this->totalBytes = 0;
index b916c1f..162ef60 100644 (file)
  * @ingroup SpecialPage
  */
 class SpecialMergeHistory extends SpecialPage {
-       /** @var string */
-       protected $mAction;
+       /** @var FormOptions */
+       protected $mOpts;
 
-       /** @var string */
-       protected $mTarget;
+       /** @var Status */
+       protected $mStatus;
 
-       /** @var string */
-       protected $mDest;
-
-       /** @var string */
-       protected $mTimestamp;
-
-       /** @var int */
-       protected $mTargetID;
-
-       /** @var int */
-       protected $mDestID;
-
-       /** @var string */
-       protected $mComment;
-
-       /** @var bool Was posted? */
-       protected $mMerge;
-
-       /** @var bool Was submitted? */
-       protected $mSubmitted;
-
-       /** @var Title */
-       protected $mTargetObj;
-
-       /** @var Title */
-       protected $mDestObj;
+       /** @var Title|null */
+       protected $mTargetObj, $mDestObj;
 
        /** @var int[] */
        public $prevId;
@@ -72,124 +48,107 @@ class SpecialMergeHistory extends SpecialPage {
                return true;
        }
 
-       /**
-        * @return void
-        */
-       private function loadRequestParams() {
-               $request = $this->getRequest();
-               $this->mAction = $request->getVal( 'action' );
-               $this->mTarget = $request->getVal( 'target' );
-               $this->mDest = $request->getVal( 'dest' );
-               $this->mSubmitted = $request->getBool( 'submitted' );
-
-               $this->mTargetID = intval( $request->getVal( 'targetID' ) );
-               $this->mDestID = intval( $request->getVal( 'destID' ) );
-               $this->mTimestamp = $request->getVal( 'mergepoint' );
-               if ( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) {
-                       $this->mTimestamp = '';
-               }
-               $this->mComment = $request->getText( 'wpComment' );
-
-               $this->mMerge = $request->wasPosted()
-                       && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
-
-               // target page
-               if ( $this->mSubmitted ) {
-                       $this->mTargetObj = Title::newFromText( $this->mTarget );
-                       $this->mDestObj = Title::newFromText( $this->mDest );
-               } else {
-                       $this->mTargetObj = null;
-                       $this->mDestObj = null;
-               }
-       }
-
        public function execute( $par ) {
                $this->useTransactionalTimeLimit();
 
                $this->checkPermissions();
                $this->checkReadOnly();
 
-               $this->loadRequestParams();
-
                $this->setHeaders();
                $this->outputHeader();
 
-               if ( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
+               $this->addHelpLink( 'Help:Merge history' );
+
+               $opts = new FormOptions();
+
+               $opts->add( 'target', '' );
+               $opts->add( 'dest', '' );
+               $opts->add( 'target', '' );
+               $opts->add( 'mergepoint', '' );
+               $opts->add( 'reason', '' );
+               $opts->add( 'merge', false );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+
+               $target = $opts->getValue( 'target' );
+               $dest = $opts->getValue( 'dest' );
+               $targetObj = Title::newFromText( $target );
+               $destObj = Title::newFromText( $dest );
+               $status = Status::newGood();
+
+               $this->mOpts = $opts;
+               $this->mTargetObj = $targetObj;
+               $this->mDestObj = $destObj;
+
+               if ( $opts->getValue( 'merge' ) && $targetObj &&
+                       $destObj && $opts->getValue( 'mergepoint' ) !== '' ) {
                        $this->merge();
 
                        return;
                }
 
-               if ( !$this->mSubmitted ) {
+               if ( $target === '' && $dest === '' ) {
                        $this->showMergeForm();
 
                        return;
                }
 
-               $errors = [];
-               if ( !$this->mTargetObj instanceof Title ) {
-                       $errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock();
-               } elseif ( !$this->mTargetObj->exists() ) {
-                       $errors[] = $this->msg( 'mergehistory-no-source',
-                               wfEscapeWikiText( $this->mTargetObj->getPrefixedText() )
-                       )->parseAsBlock();
+               if ( !$targetObj instanceof Title ) {
+                       $status->merge( Status::newFatal( 'mergehistory-invalid-source' ) );
+               } elseif ( !$targetObj->exists() ) {
+                       $status->merge( Status::newFatal( 'mergehistory-no-source',
+                               wfEscapeWikiText( $targetObj->getPrefixedText() )
+                       ) );
                }
 
-               if ( !$this->mDestObj instanceof Title ) {
-                       $errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock();
-               } elseif ( !$this->mDestObj->exists() ) {
-                       $errors[] = $this->msg( 'mergehistory-no-destination',
-                               wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
-                       )->parseAsBlock();
+               if ( !$destObj instanceof Title ) {
+                       $status->merge( Status::newFatal( 'mergehistory-invalid-destination' ) );
+               } elseif ( !$destObj->exists() ) {
+                       $status->merge( Status::newFatal( 'mergehistory-no-destination',
+                               wfEscapeWikiText( $destObj->getPrefixedText() )
+                       ) );
                }
 
-               if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) {
-                       $errors[] = $this->msg( 'mergehistory-same-destination' )->parseAsBlock();
+               if ( $targetObj && $destObj && $targetObj->equals( $destObj ) ) {
+                       $status->merge( Status::newFatal( 'mergehistory-same-destination' ) );
                }
 
-               if ( count( $errors ) ) {
-                       $this->showMergeForm();
-                       $this->getOutput()->addHTML( implode( "\n", $errors ) );
-               } else {
+               $this->mStatus = $status;
+
+               $this->showMergeForm();
+
+               if ( $status->isOK() ) {
                        $this->showHistory();
                }
        }
 
        function showMergeForm() {
-               $out = $this->getOutput();
-               $out->addWikiMsg( 'mergehistory-header' );
-
-               $out->addHTML(
-                       Xml::openElement( 'form', [
-                               'method' => 'get',
-                               'action' => wfScript() ] ) .
-                               '<fieldset>' .
-                               Xml::element( 'legend', [],
-                                       $this->msg( 'mergehistory-box' )->text() ) .
-                               Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
-                               Html::hidden( 'submitted', '1' ) .
-                               Html::hidden( 'mergepoint', $this->mTimestamp ) .
-                               Xml::openElement( 'table' ) .
-                               '<tr>
-                               <td>' . Xml::label( $this->msg( 'mergehistory-from' )->text(), 'target' ) . '</td>
-                               <td>' . Xml::input( 'target', 30, $this->mTarget, [ 'id' => 'target' ] ) . '</td>
-                       </tr><tr>
-                               <td>' . Xml::label( $this->msg( 'mergehistory-into' )->text(), 'dest' ) . '</td>
-                               <td>' . Xml::input( 'dest', 30, $this->mDest, [ 'id' => 'dest' ] ) . '</td>
-                       </tr><tr><td>' .
-                               Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) .
-                               '</td></tr>' .
-                               Xml::closeElement( 'table' ) .
-                               '</fieldset>' .
-                               '</form>'
-               );
-
-               $this->addHelpLink( 'Help:Merge history' );
+               $formDescriptor = [
+                       'target' => [
+                               'type' => 'title',
+                               'name' => 'target',
+                               'label-message' => 'mergehistory-from',
+                               'required' => true,
+                       ],
+
+                       'dest' => [
+                               'type' => 'title',
+                               'name' => 'dest',
+                               'label-message' => 'mergehistory-into',
+                               'required' => true,
+                       ],
+               ];
+
+               $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setIntro( $this->msg( 'mergehistory-header' ) )
+                       ->setWrapperLegendMsg( 'mergehistory-box' )
+                       ->setSubmitTextMsg( 'mergehistory-go' )
+                       ->setMethod( 'post' )
+                       ->prepareForm()
+                       ->displayForm( $this->mStatus );
        }
 
        private function showHistory() {
-               $this->showMergeForm();
-
                # List all stored revisions
                $revisions = new MergeHistoryPager(
                        $this, [], $this->mTargetObj, $this->mDestObj
@@ -197,62 +156,46 @@ class SpecialMergeHistory extends SpecialPage {
                $haveRevisions = $revisions && $revisions->getNumRows() > 0;
 
                $out = $this->getOutput();
-               $titleObj = $this->getPageTitle();
-               $action = $titleObj->getLocalURL( [ 'action' => 'submit' ] );
-               # Start the form here
-               $top = Xml::openElement(
-                       'form',
-                       [
-                               'method' => 'post',
-                               'action' => $action,
-                               'id' => 'merge'
-                       ]
-               );
-               $out->addHTML( $top );
-
-               if ( $haveRevisions ) {
-                       # Format the user-visible controls (comment field, submission button)
-                       # in a nice little table
-                       $table =
-                               Xml::openElement( 'fieldset' ) .
-                                       $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(),
-                                               $this->mDestObj->getPrefixedText() )->parse() .
-                                       Xml::openElement( 'table', [ 'id' => 'mw-mergehistory-table' ] ) .
-                                       '<tr>
-                                               <td class="mw-label">' .
-                                       Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) .
-                                       '</td>
-                                       <td class="mw-input">' .
-                                       Xml::input( 'wpComment', 50, $this->mComment, [ 'id' => 'wpComment' ] ) .
-                                       '</td>
-                                       </tr>
-                                       <tr>
-                                               <td>&#160;</td>
-                                               <td class="mw-submit">' .
-                                       Xml::submitButton(
-                                               $this->msg( 'mergehistory-submit' )->text(),
-                                               [ 'name' => 'merge', 'id' => 'mw-merge-submit' ]
-                                       ) .
-                                       '</td>
-                                       </tr>' .
-                                       Xml::closeElement( 'table' ) .
-                                       Xml::closeElement( 'fieldset' );
-
-                       $out->addHTML( $table );
-               }
-
-               $out->addHTML(
-                       '<h2 id="mw-mergehistory">' .
-                               $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n"
-               );
+               $header = '<h2 id="mw-mergehistory">' .
+                       $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n";
 
                if ( $haveRevisions ) {
-                       $out->addHTML( $revisions->getNavigationBar() );
-                       $out->addHTML( '<ul>' );
-                       $out->addHTML( $revisions->getBody() );
-                       $out->addHTML( '</ul>' );
-                       $out->addHTML( $revisions->getNavigationBar() );
+                       $hiddenFields = [
+                               'merge' => true,
+                               'target' => $this->mOpts->getValue( 'target' ),
+                               'dest' => $this->mOpts->getValue( 'dest' ),
+                       ];
+
+                       $formDescriptor = [
+                               'reason' => [
+                                       'type' => 'text',
+                                       'name' => 'reason',
+                                       'label-message' => 'mergehistory-reason',
+                               ],
+                       ];
+
+                       $mergeText = $this->msg( 'mergehistory-merge',
+                               $this->mTargetObj->getPrefixedText(),
+                               $this->mDestObj->getPrefixedText()
+                       )->parse();
+
+                       $history = $header .
+                               $revisions->getNavigationBar() .
+                               '<ul>' .
+                               $revisions->getBody() .
+                               '</ul>' .
+                               $revisions->getNavigationBar();
+
+                       $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                               ->addHiddenFields( $hiddenFields )
+                               ->setPreText( $mergeText )
+                               ->setFooterText( $history )
+                               ->setSubmitTextMsg( 'mergehistory-submit' )
+                               ->setMethod( 'post' )
+                               ->prepareForm()
+                               ->displayForm( false );
                } else {
+                       $out->addHTML( $header );
                        $out->addWikiMsg( 'mergehistory-empty' );
                }
 
@@ -260,18 +203,6 @@ class SpecialMergeHistory extends SpecialPage {
                $mergeLogPage = new LogPage( 'merge' );
                $out->addHTML( '<h2>' . $mergeLogPage->getName()->escaped() . "</h2>\n" );
                LogEventsList::showLogExtract( $out, 'merge', $this->mTargetObj );
-
-               # When we submit, go by page ID to avoid some nasty but unlikely collisions.
-               # Such would happen if a page was renamed after the form loaded, but before submit
-               $misc = Html::hidden( 'targetID', $this->mTargetObj->getArticleID() );
-               $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
-               $misc .= Html::hidden( 'target', $this->mTarget );
-               $misc .= Html::hidden( 'dest', $this->mDest );
-               $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
-               $misc .= Xml::closeElement( 'form' );
-               $out->addHTML( $misc );
-
-               return true;
        }
 
        function formatRevisionRow( $row ) {
@@ -281,7 +212,7 @@ class SpecialMergeHistory extends SpecialPage {
                $last = $this->msg( 'last' )->escaped();
 
                $ts = wfTimestamp( TS_MW, $row->rev_timestamp );
-               $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) );
+               $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mOpts->getValue( 'mergepoint' ) === $ts ) );
 
                $user = $this->getUser();
 
@@ -336,23 +267,24 @@ class SpecialMergeHistory extends SpecialPage {
         * @return bool Success
         */
        function merge() {
+               $opts = $this->mOpts;
+
                # Get the titles directly from the IDs, in case the target page params
                # were spoofed. The queries are done based on the IDs, so it's best to
                # keep it consistent...
-               $targetTitle = Title::newFromID( $this->mTargetID );
-               $destTitle = Title::newFromID( $this->mDestID );
-               if ( is_null( $targetTitle ) || is_null( $destTitle ) ) {
-                       return false; // validate these
-               }
-               if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
+               $targetObj = $this->mTargetObj;
+               $destObj = $this->mDestObj;
+
+               if ( is_null( $targetObj ) || is_null( $destObj ) ||
+                       $targetObj->getArticleID() == $destObj->getArticleID() ) {
                        return false;
                }
 
                // MergeHistory object
-               $mh = new MergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
+               $mh = new MergeHistory( $targetObj, $destObj, $opts->getValue( 'mergepoint' ) );
 
                // Merge!
-               $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
+               $mergeStatus = $mh->merge( $this->getUser(), $opts->getValue( 'reason' ) );
                if ( !$mergeStatus->isOK() ) {
                        // Failed merge
                        $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
@@ -360,7 +292,7 @@ class SpecialMergeHistory extends SpecialPage {
                }
 
                $targetLink = Linker::link(
-                       $targetTitle,
+                       $targetObj,
                        null,
                        [],
                        [ 'redirect' => 'no' ]
@@ -368,7 +300,7 @@ class SpecialMergeHistory extends SpecialPage {
 
                $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
                        ->rawParams( $targetLink )
-                       ->params( $destTitle->getPrefixedText() )
+                       ->params( $destObj->getPrefixedText() )
                        ->numParams( $mh->getMergedRevisionCount() )
                );
 
index c3ed91f..9746ef6 100644 (file)
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Auth\AuthManager;
+
 /**
- * Special page for requesting a password reset email
+ * Special page for requesting a password reset email.
+ *
+ * Requires the TemporaryPasswordPrimaryAuthenticationProvider and the
+ * EmailNotificationSecondaryAuthenticationProvider (or something providing equivalent
+ * functionality) to be enabled.
  *
  * @ingroup SpecialPage
  */
 class SpecialPasswordReset extends FormSpecialPage {
-       /**
-        * @var Message
-        */
-       private $email;
+       /** @var PasswordReset */
+       private $passwordReset;
 
        /**
-        * @var User
+        * @var string[] Temporary storage for the passwords which have been sent out, keyed by username.
         */
-       private $firstUser;
+       private $passwords = [];
 
        /**
         * @var Status
@@ -49,6 +53,7 @@ class SpecialPasswordReset extends FormSpecialPage {
 
        public function __construct() {
                parent::__construct( 'PasswordReset', 'editmyprivateinfo' );
+               $this->passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
        }
 
        public function doesWrites() {
@@ -56,22 +61,19 @@ class SpecialPasswordReset extends FormSpecialPage {
        }
 
        public function userCanExecute( User $user ) {
-               return $this->canChangePassword( $user ) === true && parent::userCanExecute( $user );
+               return $this->passwordReset->isAllowed( $user )->isGood();
        }
 
        public function checkExecutePermissions( User $user ) {
-               $error = $this->canChangePassword( $user );
-               if ( is_string( $error ) ) {
-                       throw new ErrorPageError( 'internalerror', $error );
-               } elseif ( !$error ) {
-                       throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
+               $status = Status::wrap( $this->passwordReset->isAllowed( $user ) );
+               if ( !$status->isGood() ) {
+                       throw new ErrorPageError( 'internalerror', $status->getMessage() );
                }
 
                parent::checkExecutePermissions( $user );
        }
 
        protected function getFormFields() {
-               global $wgAuth;
                $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
                $a = [];
                if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
@@ -92,15 +94,6 @@ class SpecialPasswordReset extends FormSpecialPage {
                        ];
                }
 
-               if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
-                       $domains = $wgAuth->domainList();
-                       $a['Domain'] = [
-                               'type' => 'select',
-                               'options' => $domains,
-                               'label-message' => 'passwordreset-domain',
-                       ];
-               }
-
                if ( $this->getUser()->isAllowed( 'passwordreset' ) ) {
                        $a['Capture'] = [
                                'type' => 'check',
@@ -128,9 +121,6 @@ class SpecialPasswordReset extends FormSpecialPage {
                if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
                        $i++;
                }
-               if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
-                       $i++;
-               }
 
                $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
 
@@ -145,180 +135,54 @@ class SpecialPasswordReset extends FormSpecialPage {
         * @param array $data
         * @throws MWException
         * @throws ThrottledError|PermissionsError
-        * @return bool|array
+        * @return Status
         */
        public function onSubmit( array $data ) {
-               global $wgAuth, $wgMinimalPasswordLength;
-
-               if ( isset( $data['Domain'] ) ) {
-                       if ( $wgAuth->validDomain( $data['Domain'] ) ) {
-                               $wgAuth->setDomain( $data['Domain'] );
-                       } else {
-                               $wgAuth->setDomain( 'invaliddomain' );
-                       }
-               }
-
                if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) {
                        // The user knows they don't have the passwordreset permission,
                        // but they tried to spoof the form. That's naughty
                        throw new PermissionsError( 'passwordreset' );
                }
 
-               /**
-                * @var $firstUser User
-                * @var $users User[]
-                */
-
-               if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
-                       $method = 'username';
-                       $users = [ User::newFromName( $data['Username'] ) ];
-               } elseif ( isset( $data['Email'] )
-                       && $data['Email'] !== ''
-                       && Sanitizer::validateEmail( $data['Email'] )
-               ) {
-                       $method = 'email';
-                       $res = wfGetDB( DB_SLAVE )->select(
-                               'user',
-                               User::selectFields(),
-                               [ 'user_email' => $data['Email'] ],
-                               __METHOD__
-                       );
-
-                       if ( $res ) {
-                               $users = [];
-
-                               foreach ( $res as $row ) {
-                                       $users[] = User::newFromRow( $row );
-                               }
-                       } else {
-                               // Some sort of database error, probably unreachable
-                               throw new MWException( 'Unknown database error in ' . __METHOD__ );
-                       }
-               } else {
-                       // The user didn't supply any data
-                       return false;
-               }
-
-               // Check for hooks (captcha etc), and allow them to modify the users list
-               $error = [];
-               if ( !Hooks::run( 'SpecialPasswordResetOnSubmit', [ &$users, $data, &$error ] ) ) {
-                       return [ $error ];
-               }
-
-               $this->method = $method;
-
-               if ( count( $users ) == 0 ) {
-                       if ( $method == 'email' ) {
-                               // Don't reveal whether or not an email address is in use
-                               return true;
-                       } else {
-                               return [ 'noname' ];
-                       }
-               }
-
-               $firstUser = $users[0];
+               $username = isset( $data['Username'] ) ? $data['Username'] : null;
+               $email = isset( $data['Email'] ) ? $data['Email'] : null;
+               $capture = !empty( $data['Capture'] );
 
-               if ( !$firstUser instanceof User || !$firstUser->getId() ) {
-                       // Don't parse username as wikitext (bug 65501)
-                       return [ [ 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ] ];
+               $this->method = $username ? 'username' : 'email';
+               $this->result = Status::wrap(
+                       $this->passwordReset->execute( $this->getUser(), $username, $email, $capture ) );
+               if ( $capture && $this->result->isOK() ) {
+                       $this->passwords = $this->result->getValue();
                }
 
-               // Check against the rate limiter
-               if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) {
+               if ( $this->result->hasMessage( 'actionthrottledtext' ) ) {
                        throw new ThrottledError;
                }
 
-               // Check against password throttle
-               foreach ( $users as $user ) {
-                       if ( $user->isPasswordReminderThrottled() ) {
-
-                               # Round the time in hours to 3 d.p., in case someone is specifying
-                               # minutes or seconds.
-                               return [ [
-                                       'throttled-mailpassword',
-                                       round( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 )
-                               ] ];
-                       }
-               }
-
-               // All the users will have the same email address
-               if ( $firstUser->getEmail() == '' ) {
-                       // This won't be reachable from the email route, so safe to expose the username
-                       return [ [ 'noemail', wfEscapeWikiText( $firstUser->getName() ) ] ];
-               }
-
-               // We need to have a valid IP address for the hook, but per bug 18347, we should
-               // send the user's name if they're logged in.
-               $ip = $this->getRequest()->getIP();
-               if ( !$ip ) {
-                       return [ 'badipaddress' ];
-               }
-               $caller = $this->getUser();
-               Hooks::run( 'User::mailPasswordInternal', [ &$caller, &$ip, &$firstUser ] );
-               $username = $caller->getName();
-               $msg = IP::isValid( $username )
-                       ? 'passwordreset-emailtext-ip'
-                       : 'passwordreset-emailtext-user';
-
-               // Send in the user's language; which should hopefully be the same
-               $userLanguage = $firstUser->getOption( 'language' );
-
-               $passwords = [];
-               foreach ( $users as $user ) {
-                       $password = PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
-                       $user->setNewpassword( $password );
-                       $user->saveSettings();
-                       $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password )
-                               ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later
-               }
-               $passwordBlock = implode( "\n\n", $passwords );
-
-               $this->email = $this->msg( $msg )->inLanguage( $userLanguage );
-               $this->email->params(
-                       $username,
-                       $passwordBlock,
-                       count( $passwords ),
-                       '<' . Title::newMainPage()->getCanonicalURL() . '>',
-                       round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 )
-               );
-
-               $title = $this->msg( 'passwordreset-emailtitle' )->inLanguage( $userLanguage );
-
-               $this->result = $firstUser->sendMail( $title->text(), $this->email->text() );
-
-               if ( isset( $data['Capture'] ) && $data['Capture'] ) {
-                       // Save the user, will be used if an error occurs when sending the email
-                       $this->firstUser = $firstUser;
-               } else {
-                       // Blank the email if the user is not supposed to see it
-                       $this->email = null;
-               }
-
-               if ( $this->result->isGood() ) {
-                       return true;
-               } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) {
-                       // The email didn't send, but maybe they knew that and that's why they captured it
-                       return true;
-               } else {
-                       // @todo FIXME: The email wasn't sent, but we have already set
-                       // the password throttle timestamp, so they won't be able to try
-                       // again until it expires...  :(
-                       return [ [ 'mailerror', $this->result->getMessage() ] ];
-               }
+               return $this->result;
        }
 
        public function onSuccess() {
-               if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) {
+               if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->passwords ) {
                        // @todo Logging
 
                        if ( $this->result->isGood() ) {
-                               $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' );
+                               $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture2',
+                                       count( $this->passwords ) );
                        } else {
-                               $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture',
-                                       $this->result->getMessage(), $this->firstUser->getName() );
+                               $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture2',
+                                       $this->result->getMessage(), key( $this->passwords ), count( $this->passwords ) );
                        }
 
-                       $this->getOutput()->addHTML( Html::rawElement( 'pre', [], $this->email->escaped() ) );
+                       $this->getOutput()->addHTML( Html::openElement( 'ul' ) );
+                       foreach ( $this->passwords as $username => $pwd ) {
+                               $this->getOutput()->addHTML( Html::rawElement( 'li', [],
+                                       htmlspecialchars( $username, ENT_QUOTES )
+                                       . $this->msg( 'colon-separator' )->text()
+                                       . htmlspecialchars( $pwd, ENT_QUOTES )
+                               ) );
+                       }
+                       $this->getOutput()->addHTML( Html::closeElement( 'ul' ) );
                }
 
                if ( $this->method === 'email' ) {
@@ -330,42 +194,12 @@ class SpecialPasswordReset extends FormSpecialPage {
                $this->getOutput()->returnToMain();
        }
 
-       protected function canChangePassword( User $user ) {
-               global $wgAuth;
-               $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
-
-               // Maybe password resets are disabled, or there are no allowable routes
-               if ( !is_array( $resetRoutes ) ||
-                       !in_array( true, array_values( $resetRoutes ) )
-               ) {
-                       return 'passwordreset-disabled';
-               }
-
-               // Maybe the external auth plugin won't allow local password changes
-               if ( !$wgAuth->allowPasswordChange() ) {
-                       return 'resetpass_forbidden';
-               }
-
-               // Maybe email features have been disabled
-               if ( !$this->getConfig()->get( 'EnableEmail' ) ) {
-                       return 'passwordreset-emaildisabled';
-               }
-
-               // Maybe the user is blocked (check this here rather than relying on the parent
-               // method as we have a more specific error message to use here
-               if ( $user->isBlocked() ) {
-                       return 'blocked-mailpassword';
-               }
-
-               return true;
-       }
-
        /**
         * Hide the password reset page if resets are disabled.
         * @return bool
         */
-       function isListed() {
-               if ( $this->canChangePassword( $this->getUser() ) === true ) {
+       public function isListed() {
+               if ( $this->passwordReset->isAllowed( $this->getUser() )->isGood() ) {
                        return parent::isListed();
                }
 
index b93fb4e..b6398cb 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * A special page that lists last changes made to the wiki
  *
@@ -355,7 +357,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        if ( $showWatcherCount && $obj->rc_namespace >= 0 ) {
                                if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
                                        $watcherCache[$obj->rc_namespace][$obj->rc_title] =
-                                               WatchedItemStore::getDefaultInstance()->countWatchers(
+                                               MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchers(
                                                        new TitleValue( (int)$obj->rc_namespace, $obj->rc_title )
                                                );
                                }
diff --git a/includes/specials/SpecialRemoveCredentials.php b/includes/specials/SpecialRemoveCredentials.php
new file mode 100644 (file)
index 0000000..4efec03
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * Special change to remove credentials (such as a two-factor token).
+ */
+class SpecialRemoveCredentials extends SpecialChangeCredentials {
+       protected static $allowedActions = [ AuthManager::ACTION_REMOVE ];
+
+       protected static $messagePrefix = 'removecredentials';
+
+       protected static $loadUserData = false;
+
+       public function __construct() {
+               parent::__construct( 'RemoveCredentials' );
+       }
+
+       protected function getDefaultAction( $subPage ) {
+               return AuthManager::ACTION_REMOVE;
+       }
+
+       protected function getRequestBlacklist() {
+               return $this->getConfig()->get( 'RemoveCredentialsBlacklist' );
+       }
+}
index b01a45f..d474ba5 100644 (file)
@@ -684,7 +684,10 @@ class SpecialSearch extends SpecialPage {
                                $user->setOption( 'searchNs' . $n, true );
                        }
 
-                       $user->saveSettings();
+                       DeferredUpdates::addCallableUpdate( function () use ( $user ) {
+                               $user->saveSettings();
+                       } );
+
                        return true;
                }
 
@@ -1235,6 +1238,7 @@ class SpecialSearch extends SpecialPage {
                        'name' => 'search',
                        'autofocus' => trim( $term ) === '',
                        'value' => $term,
+                       'dataLocation' => 'content',
                ] );
 
                $out =
index e79fd6e..2139949 100644 (file)
@@ -77,6 +77,7 @@ class SpecialTags extends SpecialPage {
 
                $user = $this->getUser();
                $userCanManage = $user->isAllowed( 'managechangetags' );
+               $userCanDelete = $user->isAllowed( 'deletechangetags' );
                $userCanEditInterface = $user->isAllowed( 'editinterface' );
 
                // Show form to create a tag
@@ -154,12 +155,13 @@ class SpecialTags extends SpecialPage {
 
                // Insert tags that have been applied at least once
                foreach ( $tagStats as $tag => $hitcount ) {
-                       $html .= $this->doTagRow( $tag, $hitcount, $userCanManage, $userCanEditInterface );
+                       $html .= $this->doTagRow( $tag, $hitcount, $userCanManage,
+                               $userCanDelete, $userCanEditInterface );
                }
                // Insert tags defined somewhere but never applied
                foreach ( $definedTags as $tag ) {
                        if ( !isset( $tagStats[$tag] ) ) {
-                               $html .= $this->doTagRow( $tag, 0, $userCanManage, $userCanEditInterface );
+                               $html .= $this->doTagRow( $tag, 0, $userCanManage, $userCanDelete, $userCanEditInterface );
                        }
                }
 
@@ -170,7 +172,7 @@ class SpecialTags extends SpecialPage {
                ) );
        }
 
-       function doTagRow( $tag, $hitcount, $showActions, $showEditLinks ) {
+       function doTagRow( $tag, $hitcount, $showManageActions, $showDeleteActions, $showEditLinks ) {
                $newRow = '';
                $newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) );
 
@@ -229,16 +231,17 @@ class SpecialTags extends SpecialPage {
                $newRow .= Xml::tags( 'td', [ 'data-sort-value' => $hitcount ], $hitcountLabel );
 
                // actions
-               if ( $showActions ) { // we've already checked that the user had the requisite userright
-                       $actionLinks = [];
+               $actionLinks = [];
 
-                       // delete
-                       if ( ChangeTags::canDeleteTag( $tag )->isOK() ) {
-                               $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'delete' ),
-                                       $this->msg( 'tags-delete' )->escaped(),
-                                       [],
-                                       [ 'tag' => $tag ] );
-                       }
+               // delete
+               if ( $showDeleteActions && ChangeTags::canDeleteTag( $tag )->isOK() ) {
+                       $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'delete' ),
+                               $this->msg( 'tags-delete' )->escaped(),
+                               [],
+                               [ 'tag' => $tag ] );
+               }
+
+               if ( $showManageActions ) { // we've already checked that the user had the requisite userright
 
                        // activate
                        if ( ChangeTags::canActivateTag( $tag )->isOK() ) {
@@ -256,6 +259,9 @@ class SpecialTags extends SpecialPage {
                                        [ 'tag' => $tag ] );
                        }
 
+               }
+
+               if ( $actionLinks ) {
                        $newRow .= Xml::tags( 'td', null, $this->getLanguage()->pipeList( $actionLinks ) );
                }
 
@@ -319,8 +325,8 @@ class SpecialTags extends SpecialPage {
 
        protected function showDeleteTagForm( $tag ) {
                $user = $this->getUser();
-               if ( !$user->isAllowed( 'managechangetags' ) ) {
-                       throw new PermissionsError( 'managechangetags' );
+               if ( !$user->isAllowed( 'deletechangetags' ) ) {
+                       throw new PermissionsError( 'deletechangetags' );
                }
 
                $out = $this->getOutput();
index c6d3f6e..5d230c0 100644 (file)
@@ -872,7 +872,7 @@ class SpecialUndelete extends SpecialPage {
                        "ids" => $revisions,
                        "target" => $this->mTargetObj->getPrefixedText()
                ];
-               $url = SpecialPage::getTitleFor( "RevisionDelete" )->getFullURL( $query );
+               $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
                $this->getOutput()->redirect( $url );
        }
 
diff --git a/includes/specials/SpecialUnlinkAccounts.php b/includes/specials/SpecialUnlinkAccounts.php
new file mode 100644 (file)
index 0000000..86bc7ed
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Session\SessionManager;
+
+class SpecialUnlinkAccounts extends AuthManagerSpecialPage {
+       protected static $allowedActions = [ AuthManager::ACTION_UNLINK ];
+
+       public function __construct() {
+               parent::__construct( 'UnlinkAccounts' );
+       }
+
+       protected function getLoginSecurityLevel() {
+               return 'UnlinkAccount';
+       }
+
+       protected function getDefaultAction( $subPage ) {
+               return AuthManager::ACTION_UNLINK;
+       }
+
+       /**
+        * Under which header this special page is listed in Special:SpecialPages.
+        */
+       protected function getGroupName() {
+               return 'users';
+       }
+
+       public function isListed() {
+               return AuthManager::singleton()->canLinkAccounts();
+       }
+
+       protected function getRequestBlacklist() {
+               return $this->getConfig()->get( 'RemoveCredentialsBlacklist' );
+       }
+
+       public function execute( $subPage ) {
+               $this->setHeaders();
+               $this->loadAuth( $subPage );
+               $this->outputHeader();
+
+               $status = $this->trySubmit();
+
+               if ( $status === false || !$status->isOK() ) {
+                       $this->displayForm( $status );
+                       return;
+               }
+
+               /** @var AuthenticationResponse $response */
+               $response = $status->getValue();
+
+               if ( $response->status === AuthenticationResponse::FAIL ) {
+                       $this->displayForm( StatusValue::newFatal( $response->message ) );
+                       return;
+               }
+
+               $status = StatusValue::newGood();
+               $status->warning( wfMessage( 'unlinkaccounts-success' ) );
+               $this->loadAuth( $subPage, null, true ); // update requests so the unlinked one doesn't show up
+
+               // Reset sessions - if the user unlinked an account because it was compromised,
+               // log attackers out from sessions obtained via that account.
+               $session = $this->getRequest()->getSession();
+               $user = $this->getUser();
+               SessionManager::singleton()->invalidateSessionsForUser( $user );
+               $session->setUser( $user );
+               $session->resetId();
+
+               $this->displayForm( $status );
+       }
+
+       public function handleFormSubmit( $data ) {
+               // unlink requests do not accept user input so repeat parent code but skip call to
+               // AuthenticationRequest::loadRequestsFromSubmission
+               $response = $this->performAuthenticationStep( $this->authAction, $this->authRequests );
+               return Status::newGood( $response );
+       }
+}
index 9214c23..8cd86ce 100644 (file)
@@ -33,7 +33,7 @@ class SpecialUnlockdb extends FormSpecialPage {
        }
 
        public function doesWrites() {
-               return true;
+               return false;
        }
 
        public function requiresWrite() {
@@ -58,9 +58,9 @@ class SpecialUnlockdb extends FormSpecialPage {
        }
 
        protected function alterForm( HTMLForm $form ) {
-               $form->setWrapperLegend( false );
-               $form->setHeaderText( $this->msg( 'unlockdbtext' )->parseAsBlock() );
-               $form->setSubmitTextMsg( 'unlockbtn' );
+               $form->setWrapperLegend( false )
+                       ->setHeaderText( $this->msg( 'unlockdbtext' )->parseAsBlock() )
+                       ->setSubmitTextMsg( 'unlockbtn' );
        }
 
        public function onSubmit( array $data ) {
@@ -86,6 +86,10 @@ class SpecialUnlockdb extends FormSpecialPage {
                $out->addWikiMsg( 'unlockdbsuccesstext' );
        }
 
+       protected function getDisplayFormat() {
+               return 'ooui';
+       }
+
        protected function getGroupName() {
                return 'wiki';
        }
index 75308aa..09111f6 100644 (file)
@@ -367,8 +367,11 @@ class SpecialUpload extends SpecialPage {
 
                $sessionKey = $this->mUpload->stashSession();
 
+               // Add styles for the warning, reused from the live preview
+               $this->getOutput()->addModuleStyles( 'mediawiki.special.upload' );
+
                $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
-                       . '<div class="warningbox"><ul>';
+                       . '<div class="mw-destfile-warning"><ul>';
                foreach ( $warnings as $warning => $args ) {
                        if ( $warning == 'badfilename' ) {
                                $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
diff --git a/includes/specials/SpecialUserLogin.php b/includes/specials/SpecialUserLogin.php
new file mode 100644 (file)
index 0000000..28c68aa
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Implements Special:UserLogin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LogLevel;
+
+/**
+ * Implements Special:UserLogin
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUserLogin extends LoginSignupSpecialPage {
+       protected static $allowedActions = [
+               AuthManager::ACTION_LOGIN,
+               AuthManager::ACTION_LOGIN_CONTINUE
+       ];
+
+       protected static $messages = [
+               'authform-newtoken' => 'nocookiesforlogin',
+               'authform-notoken' => 'sessionfailure',
+               'authform-wrongtoken' => 'sessionfailure',
+       ];
+
+       public function __construct() {
+               parent::__construct( 'Userlogin' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       protected function getLoginSecurityLevel() {
+               return false;
+       }
+
+       protected function getDefaultAction( $subPage ) {
+               return AuthManager::ACTION_LOGIN;
+       }
+
+       public function getDescription() {
+               return $this->msg( 'login' )->text();
+       }
+
+       public function setHeaders() {
+               // override the page title if we are doing a forced reauthentication
+               parent::setHeaders();
+               if ( $this->securityLevel && $this->getUser()->isLoggedIn() ) {
+                       $this->getOutput()->setPageTitle( $this->msg( 'login-security' ) );
+               }
+       }
+
+       protected function isSignup() {
+               return false;
+       }
+
+       protected function beforeExecute( $subPage ) {
+               if ( $subPage === 'signup' || $this->getRequest()->getText( 'type' ) === 'signup' ) {
+                       // B/C for old account creation URLs
+                       $title = SpecialPage::getTitleFor( 'CreateAccount' );
+                       $query = array_diff_key( $this->getRequest()->getValues(),
+                               array_fill_keys( [ 'type', 'title' ], true ) );
+                       $url = $title->getFullURL( $query, false, PROTO_CURRENT );
+                       $this->getOutput()->redirect( $url );
+                       return false;
+               }
+               return parent::beforeExecute( $subPage );
+       }
+
+       /**
+        * Run any hooks registered for logins, then HTTP redirect to
+        * $this->mReturnTo (or Main Page if that's undefined).  Formerly we had a
+        * nice message here, but that's really not as useful as just being sent to
+        * wherever you logged in from.  It should be clear that the action was
+        * successful, given the lack of error messages plus the appearance of your
+        * name in the upper right.
+        * @param bool $direct True if the action was successful just now; false if that happened
+        *    pre-redirection (so this handler was called already)
+        * @param StatusValue|null $extraMessages
+        */
+       protected function successfulAction( $direct = false, $extraMessages = null ) {
+               global $wgSecureLogin;
+
+               $user = $this->targetUser ?: $this->getUser();
+               $session = $this->getRequest()->getSession();
+
+               if ( $direct ) {
+                       $user->touch();
+
+                       $this->clearToken();
+
+                       if ( $user->requiresHTTPS() ) {
+                               $this->mStickHTTPS = true;
+                       }
+                       $session->setForceHTTPS( $wgSecureLogin && $this->mStickHTTPS );
+
+                       // If the user does not have a session cookie at this point, they probably need to
+                       // do something to their browser.
+                       if ( !$this->hasSessionCookie() ) {
+                               $this->mainLoginForm( [ /*?*/ ], $session->getProvider()->whyNoSession() );
+                               // TODO something more specific? This used to use nocookieslogin
+                               return;
+                       }
+               }
+
+               # Run any hooks; display injected HTML if any, else redirect
+               $injected_html = '';
+               Hooks::run( 'UserLoginComplete', [ &$user, &$injected_html ] );
+
+               if ( $injected_html !== '' || $extraMessages ) {
+                       $this->showSuccessPage( 'success', $this->msg( 'loginsuccesstitle' ),
+                               'loginsuccess', $injected_html, $extraMessages );
+               } else {
+                       $helper = new LoginHelper( $this->getContext() );
+                       $helper->showReturnToPage( 'successredirect', $this->mReturnTo, $this->mReturnToQuery,
+                               $this->mStickHTTPS );
+               }
+       }
+
+       protected function getToken() {
+               return $this->getRequest()->getSession()->getToken( '', 'login' );
+       }
+
+       protected function clearToken() {
+               return $this->getRequest()->getSession()->resetToken( 'login' );
+       }
+
+       protected function getTokenName() {
+               return 'wpLoginToken';
+       }
+
+       protected function getGroupName() {
+               return 'login';
+       }
+
+       protected function logAuthResult( $success, $status = null ) {
+               LoggerFactory::getInstance( 'authmanager-stats' )->info( 'Login attempt', [
+                       'event' => 'login',
+                       'successful' => $success,
+                       'status' => $status,
+               ] );
+       }
+}
diff --git a/includes/specials/SpecialUserLogout.php b/includes/specials/SpecialUserLogout.php
new file mode 100644 (file)
index 0000000..c067f44
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Implements Special:Userlogout
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Implements Special:Userlogout
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUserLogout extends UnlistedSpecialPage {
+       function __construct() {
+               parent::__construct( 'Userlogout' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       function execute( $par ) {
+               /**
+                * Some satellite ISPs use broken precaching schemes that log people out straight after
+                * they're logged in (bug 17790). Luckily, there's a way to detect such requests.
+                */
+               if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
+                       wfDebug( "Special:UserLogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
+                       throw new HttpError( 400, $this->msg( 'suspicious-userlogout' ), $this->msg( 'loginerror' ) );
+               }
+
+               $this->setHeaders();
+               $this->outputHeader();
+
+               // Make sure it's possible to log out
+               $session = MediaWiki\Session\SessionManager::getGlobalSession();
+               if ( !$session->canSetUser() ) {
+                       throw new ErrorPageError(
+                               'cannotlogoutnow-title',
+                               'cannotlogoutnow-text',
+                               [
+                                       $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
+                               ]
+                       );
+               }
+
+               $user = $this->getUser();
+               $oldName = $user->getName();
+
+               $user->logout();
+
+               $loginURL = SpecialPage::getTitleFor( 'Userlogin' )->getFullURL(
+                       $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
+               $out = $this->getOutput();
+               $out->addWikiMsg( 'logouttext', $loginURL );
+
+               // Hook.
+               $injected_html = '';
+               Hooks::run( 'UserLogoutComplete', [ &$user, &$injected_html, $oldName ] );
+               $out->addHTML( $injected_html );
+
+               $out->returnToMain();
+       }
+
+       protected function getGroupName() {
+               return 'login';
+       }
+}
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
deleted file mode 100644 (file)
index a77c79e..0000000
+++ /dev/null
@@ -1,1842 +0,0 @@
-<?php
-/**
- * Implements Special:UserLogin
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-use MediaWiki\Logger\LoggerFactory;
-use Psr\Log\LogLevel;
-use MediaWiki\Session\SessionManager;
-
-/**
- * Implements Special:UserLogin
- *
- * @ingroup SpecialPage
- */
-class LoginForm extends SpecialPage {
-       const SUCCESS = 0;
-       const NO_NAME = 1;
-       const ILLEGAL = 2;
-       const WRONG_PLUGIN_PASS = 3;
-       const NOT_EXISTS = 4;
-       const WRONG_PASS = 5;
-       const EMPTY_PASS = 6;
-       const RESET_PASS = 7;
-       const ABORTED = 8;
-       const CREATE_BLOCKED = 9;
-       const THROTTLED = 10;
-       const USER_BLOCKED = 11;
-       const NEED_TOKEN = 12;
-       const WRONG_TOKEN = 13;
-       const USER_MIGRATED = 14;
-
-       public static $statusCodes = [
-               self::SUCCESS => 'success',
-               self::NO_NAME => 'no_name',
-               self::ILLEGAL => 'illegal',
-               self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
-               self::NOT_EXISTS => 'not_exists',
-               self::WRONG_PASS => 'wrong_pass',
-               self::EMPTY_PASS => 'empty_pass',
-               self::RESET_PASS => 'reset_pass',
-               self::ABORTED => 'aborted',
-               self::CREATE_BLOCKED => 'create_blocked',
-               self::THROTTLED => 'throttled',
-               self::USER_BLOCKED => 'user_blocked',
-               self::NEED_TOKEN => 'need_token',
-               self::WRONG_TOKEN => 'wrong_token',
-               self::USER_MIGRATED => 'user_migrated',
-       ];
-
-       /**
-        * Valid error and warning messages
-        *
-        * Special:Userlogin can show an error or warning message on the form when
-        * coming from another page. This is done via the ?error= or ?warning= GET
-        * parameters.
-        *
-        * This array is the list of valid message keys. All other values will be
-        * ignored.
-        *
-        * @since 1.24
-        * @var string[]
-        */
-       public static $validErrorMessages = [
-               'exception-nologin-text',
-               'watchlistanontext',
-               'changeemail-no-info',
-               'resetpass-no-info',
-               'confirmemail_needlogin',
-               'prefsnologintext2',
-       ];
-
-       public $mAbortLoginErrorMsg = null;
-       /**
-        * @var int How many seconds user is throttled for
-        * @since 1.27
-        */
-       public $mThrottleWait = '?';
-
-       protected $mUsername;
-       protected $mPassword;
-       protected $mRetype;
-       protected $mReturnTo;
-       protected $mCookieCheck;
-       protected $mPosted;
-       protected $mAction;
-       protected $mCreateaccount;
-       protected $mCreateaccountMail;
-       protected $mLoginattempt;
-       protected $mRemember;
-       protected $mEmail;
-       protected $mDomain;
-       protected $mLanguage;
-       protected $mSkipCookieCheck;
-       protected $mReturnToQuery;
-       protected $mToken;
-       protected $mStickHTTPS;
-       protected $mType;
-       protected $mReason;
-       protected $mRealName;
-       protected $mEntryError = '';
-       protected $mEntryErrorType = 'error';
-
-       private $mTempPasswordUsed;
-       private $mLoaded = false;
-       private $mSecureLoginUrl;
-
-       /** @var WebRequest */
-       private $mOverrideRequest = null;
-
-       /** @var WebRequest Effective request; set at the beginning of load */
-       private $mRequest = null;
-
-       /**
-        * @param WebRequest $request
-        */
-       public function __construct( $request = null ) {
-               global $wgUseMediaWikiUIEverywhere;
-               parent::__construct( 'Userlogin' );
-
-               $this->mOverrideRequest = $request;
-               // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
-               $wgUseMediaWikiUIEverywhere = true;
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-
-       /**
-        * Returns an array of all valid error messages.
-        *
-        * @return array
-        */
-       public static function getValidErrorMessages() {
-               static $messages = null;
-               if ( !$messages ) {
-                       $messages = self::$validErrorMessages;
-                       Hooks::run( 'LoginFormValidErrorMessages', [ &$messages ] );
-               }
-
-               return $messages;
-       }
-
-       /**
-        * Loader
-        */
-       function load() {
-               global $wgAuth, $wgHiddenPrefs, $wgEnableEmail;
-
-               if ( $this->mLoaded ) {
-                       return;
-               }
-               $this->mLoaded = true;
-
-               if ( $this->mOverrideRequest === null ) {
-                       $request = $this->getRequest();
-               } else {
-                       $request = $this->mOverrideRequest;
-               }
-               $this->mRequest = $request;
-
-               $this->mType = $request->getText( 'type' );
-               $this->mUsername = $request->getText( 'wpName' );
-               $this->mPassword = $request->getText( 'wpPassword' );
-               $this->mRetype = $request->getText( 'wpRetype' );
-               $this->mDomain = $request->getText( 'wpDomain' );
-               $this->mReason = $request->getText( 'wpReason' );
-               $this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
-               $this->mPosted = $request->wasPosted();
-               $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
-                       && $wgEnableEmail;
-               $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail;
-               $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
-               $this->mAction = $request->getVal( 'action' );
-               $this->mRemember = $request->getCheck( 'wpRemember' );
-               $this->mFromHTTP = $request->getBool( 'fromhttp', false )
-                       || $request->getBool( 'wpFromhttp', false );
-               $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
-                       || $request->getBool( 'wpForceHttps', false );
-               $this->mLanguage = $request->getText( 'uselang' );
-               $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
-               $this->mToken = $this->mType == 'signup'
-                       ? $request->getVal( 'wpCreateaccountToken' )
-                       : $request->getVal( 'wpLoginToken' );
-               $this->mReturnTo = $request->getVal( 'returnto', '' );
-               $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
-
-               // Show an error or warning passed on from a previous page
-               $entryError = $this->msg( $request->getVal( 'error', '' ) );
-               $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
-               // bc: provide login link as a parameter for messages where the translation
-               // was not updated
-               $loginreqlink = Linker::linkKnown(
-                       $this->getPageTitle(),
-                       $this->msg( 'loginreqlink' )->escaped(),
-                       [],
-                       [
-                               'returnto' => $this->mReturnTo,
-                               'returntoquery' => $this->mReturnToQuery,
-                               'uselang' => $this->mLanguage,
-                               'fromhttp' => $this->mFromHTTP ? '1' : '0',
-                       ]
-               );
-
-               // Only show valid error or warning messages.
-               if ( $entryError->exists()
-                       && in_array( $entryError->getKey(), self::getValidErrorMessages() )
-               ) {
-                       $this->mEntryErrorType = 'error';
-                       $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
-
-               } elseif ( $entryWarning->exists()
-                       && in_array( $entryWarning->getKey(), self::getValidErrorMessages() )
-               ) {
-                       $this->mEntryErrorType = 'warning';
-                       $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
-               }
-
-               if ( $wgEnableEmail ) {
-                       $this->mEmail = $request->getText( 'wpEmail' );
-               } else {
-                       $this->mEmail = '';
-               }
-               if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
-                       $this->mRealName = $request->getText( 'wpRealName' );
-               } else {
-                       $this->mRealName = '';
-               }
-
-               if ( !$wgAuth->validDomain( $this->mDomain ) ) {
-                       $this->mDomain = $wgAuth->getDomain();
-               }
-               $wgAuth->setDomain( $this->mDomain );
-
-               # 1. When switching accounts, it sucks to get automatically logged out
-               # 2. Do not return to PasswordReset after a successful password change
-               #    but goto Wiki start page (Main_Page) instead ( bug 33997 )
-               $returnToTitle = Title::newFromText( $this->mReturnTo );
-               if ( is_object( $returnToTitle )
-                       && ( $returnToTitle->isSpecial( 'Userlogout' )
-                               || $returnToTitle->isSpecial( 'PasswordReset' ) )
-               ) {
-                       $this->mReturnTo = '';
-                       $this->mReturnToQuery = '';
-               }
-       }
-
-       function getDescription() {
-               if ( $this->mType === 'signup' ) {
-                       return $this->msg( 'createaccount' )->text();
-               } else {
-                       return $this->msg( 'login' )->text();
-               }
-       }
-
-       /**
-        * @param string|null $subPage
-        */
-       public function execute( $subPage ) {
-               // Make sure session is persisted
-               $session = SessionManager::getGlobalSession();
-               $session->persist();
-
-               $this->load();
-
-               // Check for [[Special:Userlogin/signup]]. This affects form display and
-               // page title.
-               if ( $subPage == 'signup' ) {
-                       $this->mType = 'signup';
-               }
-               $this->setHeaders();
-
-               // Make sure it's possible to log in
-               if ( $this->mType !== 'signup' && !$session->canSetUser() ) {
-                       throw new ErrorPageError(
-                               'cannotloginnow-title',
-                               'cannotloginnow-text',
-                               [
-                                       $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
-                               ]
-                       );
-               }
-
-               /**
-                * In the case where the user is already logged in, and was redirected to
-                * the login form from a page that requires login, do not show the login
-                * page. The use case scenario for this is when a user opens a large number
-                * of tabs, is redirected to the login page on all of them, and then logs
-                * in on one, expecting all the others to work properly.
-                *
-                * However, do show the form if it was visited intentionally (no 'returnto'
-                * is present). People who often switch between several accounts have grown
-                * accustomed to this behavior.
-                */
-               if (
-                       $this->mType !== 'signup' &&
-                       !$this->mPosted &&
-                       $this->getUser()->isLoggedIn() &&
-                       ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' )
-               ) {
-                       $this->successfulLogin();
-               }
-
-               // If logging in and not on HTTPS, either redirect to it or offer a link.
-               global $wgSecureLogin;
-               if ( $this->mRequest->getProtocol() !== 'https' ) {
-                       $title = $this->getFullTitle();
-                       $query = [
-                               'returnto' => $this->mReturnTo !== '' ? $this->mReturnTo : null,
-                               'returntoquery' => $this->mReturnToQuery !== '' ?
-                                       $this->mReturnToQuery : null,
-                               'title' => null,
-                               ( $this->mEntryErrorType === 'error' ? 'error' : 'warning' ) => $this->mEntryError,
-                       ] + $this->mRequest->getQueryValues();
-                       $url = $title->getFullURL( $query, false, PROTO_HTTPS );
-                       if ( $wgSecureLogin
-                               && wfCanIPUseHTTPS( $this->getRequest()->getIP() )
-                               && !$this->mFromHTTP ) // Avoid infinite redirect
-                       {
-                               $url = wfAppendQuery( $url, 'fromhttp=1' );
-                               $this->getOutput()->redirect( $url );
-                               // Since we only do this redir to change proto, always vary
-                               $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
-
-                               return;
-                       } else {
-                               // A wiki without HTTPS login support should set $wgServer to
-                               // http://somehost, in which case the secure URL generated
-                               // above won't actually start with https://
-                               if ( substr( $url, 0, 8 ) === 'https://' ) {
-                                       $this->mSecureLoginUrl = $url;
-                               }
-                       }
-               }
-
-               if ( !is_null( $this->mCookieCheck ) ) {
-                       $this->onCookieRedirectCheck( $this->mCookieCheck );
-
-                       return;
-               } elseif ( $this->mPosted ) {
-                       if ( $this->mCreateaccount ) {
-                               $this->addNewAccount();
-
-                               return;
-                       } elseif ( $this->mCreateaccountMail ) {
-                               $this->addNewAccountMailPassword();
-
-                               return;
-                       } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
-                               $this->processLogin();
-
-                               return;
-                       }
-               }
-               $this->mainLoginForm( $this->mEntryError, $this->mEntryErrorType );
-       }
-
-       /**
-        * @private
-        */
-       function addNewAccountMailPassword() {
-               if ( $this->mEmail == '' ) {
-                       $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
-
-                       return;
-               }
-
-               $status = $this->addNewAccountInternal();
-               LoggerFactory::getInstance( 'authmanager' )->info(
-                       'Account creation attempt with mailed password',
-                       [ 'event' => 'accountcreation', 'status' => $status ]
-               );
-               if ( !$status->isGood() ) {
-                       $error = $status->getMessage();
-                       $this->mainLoginForm( $error->toString() );
-
-                       return;
-               }
-
-               /** @var User $u */
-               $u = $status->getValue();
-
-               // Wipe the initial password and mail a temporary one
-               $u->setPassword( null );
-               $u->saveSettings();
-               $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
-
-               Hooks::run( 'AddNewAccount', [ $u, true ] );
-               $u->addNewUserLogEntry( 'byemail', $this->mReason );
-
-               $out = $this->getOutput();
-               $out->setPageTitle( $this->msg( 'accmailtitle' ) );
-
-               if ( !$result->isGood() ) {
-                       $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
-               } else {
-                       $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
-                       $this->executeReturnTo( 'success' );
-               }
-       }
-
-       /**
-        * @private
-        * @return bool
-        */
-       function addNewAccount() {
-               global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
-
-               # Create the account and abort if there's a problem doing so
-               $status = $this->addNewAccountInternal();
-               LoggerFactory::getInstance( 'authmanager' )->info( 'Account creation attempt', [
-                       'event' => 'accountcreation',
-                       'status' => $status,
-               ] );
-
-               if ( !$status->isGood() ) {
-                       $error = $status->getMessage();
-                       $this->mainLoginForm( $error->toString() );
-
-                       return false;
-               }
-
-               $u = $status->getValue();
-
-               # Only save preferences if the user is not creating an account for someone else.
-               if ( $this->getUser()->isAnon() ) {
-                       # If we showed up language selection links, and one was in use, be
-                       # smart (and sensible) and save that language as the user's preference
-                       if ( $wgLoginLanguageSelector && $this->mLanguage ) {
-                               $u->setOption( 'language', $this->mLanguage );
-                       } else {
-
-                               # Otherwise the user's language preference defaults to $wgContLang,
-                               # but it may be better to set it to their preferred $wgContLang variant,
-                               # based on browser preferences or URL parameters.
-                               $u->setOption( 'language', $wgContLang->getPreferredVariant() );
-                       }
-                       if ( $wgContLang->hasVariants() ) {
-                               $u->setOption( 'variant', $wgContLang->getPreferredVariant() );
-                       }
-               }
-
-               $out = $this->getOutput();
-
-               # Send out an email authentication message if needed
-               if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
-                       $status = $u->sendConfirmationMail();
-                       if ( $status->isGood() ) {
-                               $out->addWikiMsg( 'confirmemail_oncreate' );
-                       } else {
-                               $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
-                       }
-               }
-
-               # Save settings (including confirmation token)
-               $u->saveSettings();
-
-               # If not logged in, assume the new account as the current one and set
-               # session cookies then show a "welcome" message or a "need cookies"
-               # message as needed
-               if ( $this->getUser()->isAnon() ) {
-                       $u->setCookies();
-                       $wgUser = $u;
-                       // This should set it for OutputPage and the Skin
-                       // which is needed or the personal links will be
-                       // wrong.
-                       $this->getContext()->setUser( $u );
-                       Hooks::run( 'AddNewAccount', [ $u, false ] );
-                       $u->addNewUserLogEntry( 'create' );
-                       if ( $this->hasSessionCookie() ) {
-                               $this->successfulCreation();
-                       } else {
-                               $this->cookieRedirectCheck( 'new' );
-                       }
-               } else {
-                       # Confirm that the account was created
-                       $out->setPageTitle( $this->msg( 'accountcreated' ) );
-                       $out->addWikiMsg( 'accountcreatedtext', $u->getName() );
-                       $out->addReturnTo( $this->getPageTitle() );
-                       Hooks::run( 'AddNewAccount', [ $u, false ] );
-                       $u->addNewUserLogEntry( 'create2', $this->mReason );
-               }
-
-               return true;
-       }
-
-       /**
-        * Make a new user account using the loaded data.
-        * @private
-        * @throws PermissionsError|ReadOnlyError
-        * @return Status
-        */
-       public function addNewAccountInternal() {
-               global $wgAuth, $wgAccountCreationThrottle, $wgEmailConfirmToEdit;
-
-               // If the user passes an invalid domain, something is fishy
-               if ( !$wgAuth->validDomain( $this->mDomain ) ) {
-                       return Status::newFatal( 'wrongpassword' );
-               }
-
-               // If we are not allowing users to login locally, we should be checking
-               // to see if the user is actually able to authenticate to the authenti-
-               // cation server before they create an account (otherwise, they can
-               // create a local account and login as any domain user). We only need
-               // to check this for domains that aren't local.
-               if ( 'local' != $this->mDomain && $this->mDomain != '' ) {
-                       if (
-                               !$wgAuth->canCreateAccounts() &&
-                               (
-                                       !$wgAuth->userExists( $this->mUsername ) ||
-                                       !$wgAuth->authenticate( $this->mUsername, $this->mPassword )
-                               )
-                       ) {
-                               return Status::newFatal( 'wrongpassword' );
-                       }
-               }
-
-               if ( wfReadOnly() ) {
-                       throw new ReadOnlyError;
-               }
-
-               # Request forgery checks.
-               $token = self::getCreateaccountToken();
-               if ( $token->wasNew() ) {
-                       return Status::newFatal( 'nocookiesfornew' );
-               }
-
-               # The user didn't pass a createaccount token
-               if ( !$this->mToken ) {
-                       return Status::newFatal( 'sessionfailure' );
-               }
-
-               # Validate the createaccount token
-               if ( !$token->match( $this->mToken ) ) {
-                       return Status::newFatal( 'sessionfailure' );
-               }
-
-               # Check permissions
-               $currentUser = $this->getUser();
-               $creationBlock = $currentUser->isBlockedFromCreateAccount();
-               if ( !$currentUser->isAllowed( 'createaccount' ) ) {
-                       throw new PermissionsError( 'createaccount' );
-               } elseif ( $creationBlock instanceof Block ) {
-                       // Throws an ErrorPageError.
-                       $this->userBlockedMessage( $creationBlock );
-
-                       // This should never be reached.
-                       return false;
-               }
-
-               # Include checks that will include GlobalBlocking (Bug 38333)
-               $permErrors = $this->getPageTitle()->getUserPermissionsErrors(
-                       'createaccount',
-                       $currentUser,
-                       true
-               );
-
-               if ( count( $permErrors ) ) {
-                       throw new PermissionsError( 'createaccount', $permErrors );
-               }
-
-               $ip = $this->getRequest()->getIP();
-               if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
-                       return Status::newFatal( 'sorbs_create_account_reason' );
-               }
-
-               # Now create a dummy user ($u) and check if it is valid
-               $u = User::newFromName( $this->mUsername, 'creatable' );
-               if ( !$u ) {
-                       return Status::newFatal( 'noname' );
-               }
-
-               $cache = ObjectCache::getLocalClusterInstance();
-               # Make sure the user does not exist already
-               $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $this->mUsername ) ) );
-               if ( !$lock ) {
-                       return Status::newFatal( 'usernameinprogress' );
-               } elseif ( $u->idForName( User::READ_LOCKING ) ) {
-                       return Status::newFatal( 'userexists' );
-               }
-
-               if ( $this->mCreateaccountMail ) {
-                       # do not force a password for account creation by email
-                       # set invalid password, it will be replaced later by a random generated password
-                       $this->mPassword = null;
-               } else {
-                       if ( $this->mPassword !== $this->mRetype ) {
-                               return Status::newFatal( 'badretype' );
-                       }
-
-                       # check for password validity, return a fatal Status if invalid
-                       $validity = $u->checkPasswordValidity( $this->mPassword, 'create' );
-                       if ( !$validity->isGood() ) {
-                               $validity->ok = false; // make sure this Status is fatal
-                               return $validity;
-                       }
-               }
-
-               # if you need a confirmed email address to edit, then obviously you
-               # need an email address.
-               if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) {
-                       return Status::newFatal( 'noemailtitle' );
-               }
-
-               if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) {
-                       return Status::newFatal( 'invalidemailaddress' );
-               }
-
-               # Set some additional data so the AbortNewAccount hook can be used for
-               # more than just username validation
-               $u->setEmail( $this->mEmail );
-               $u->setRealName( $this->mRealName );
-
-               $abortError = '';
-               $abortStatus = null;
-               if ( !Hooks::run( 'AbortNewAccount', [ $u, &$abortError, &$abortStatus ] ) ) {
-                       // Hook point to add extra creation throttles and blocks
-                       wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
-                       if ( $abortStatus === null ) {
-                               // Report back the old string as a raw message status.
-                               // This will report the error back as 'createaccount-hook-aborted'
-                               // with the given string as the message.
-                               // To return a different error code, return a Status object.
-                               $abortError = new Message( 'createaccount-hook-aborted', [ $abortError ] );
-                               $abortError->text();
-
-                               return Status::newFatal( $abortError );
-                       } else {
-                               // For MediaWiki 1.23+ and updated hooks, return the Status object
-                               // returned from the hook.
-                               return $abortStatus;
-                       }
-               }
-
-               // Hook point to check for exempt from account creation throttle
-               if ( !Hooks::run( 'ExemptFromAccountCreationThrottle', [ $ip ] ) ) {
-                       wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " .
-                               "allowed account creation w/o throttle\n" );
-               } else {
-                       if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
-                               $key = wfGlobalCacheKey( 'acctcreate', 'ip', $ip );
-                               $value = $cache->get( $key );
-                               if ( !$value ) {
-                                       $cache->set( $key, 0, $cache::TTL_DAY );
-                               }
-                               if ( $value >= $wgAccountCreationThrottle ) {
-                                       return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle );
-                               }
-                               $cache->incr( $key );
-                       }
-               }
-
-               if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
-                       return Status::newFatal( 'externaldberror' );
-               }
-
-               self::clearCreateaccountToken();
-
-               return $this->initUser( $u, false );
-       }
-
-       /**
-        * Actually add a user to the database.
-        * Give it a User object that has been initialised with a name.
-        *
-        * @param User $u
-        * @param bool $autocreate True if this is an autocreation via auth plugin
-        * @return Status Status object, with the User object in the value member on success
-        * @private
-        */
-       function initUser( $u, $autocreate ) {
-               global $wgAuth;
-
-               $status = $u->addToDatabase();
-               if ( !$status->isOK() ) {
-                       return $status;
-               }
-
-               if ( $wgAuth->allowPasswordChange() ) {
-                       $u->setPassword( $this->mPassword );
-               }
-
-               $u->setEmail( $this->mEmail );
-               $u->setRealName( $this->mRealName );
-               $u->setToken();
-
-               Hooks::run( 'LocalUserCreated', [ $u, $autocreate ] );
-               $oldUser = $u;
-               $wgAuth->initUser( $u, $autocreate );
-               if ( $oldUser !== $u ) {
-                       wfWarn( get_class( $wgAuth ) . '::initUser() replaced the user object' );
-               }
-
-               $u->saveSettings();
-
-               // Update user count
-               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
-
-               // Watch user's userpage and talk page
-               $u->addWatch( $u->getUserPage(), User::IGNORE_USER_RIGHTS );
-
-               return Status::newGood( $u );
-       }
-
-       /**
-        * Internally authenticate the login request.
-        *
-        * This may create a local account as a side effect if the
-        * authentication plugin allows transparent local account
-        * creation.
-        * @return int
-        */
-       public function authenticateUserData() {
-               global $wgUser, $wgAuth;
-
-               $this->load();
-
-               if ( $this->mUsername == '' ) {
-                       return self::NO_NAME;
-               }
-
-               // We require a login token to prevent login CSRF
-               // Handle part of this before incrementing the throttle so
-               // token-less login attempts don't count towards the throttle
-               // but wrong-token attempts do.
-
-               // If the user doesn't have a login token yet, set one.
-               $token = self::getLoginToken();
-               if ( $token->wasNew() ) {
-                       return self::NEED_TOKEN;
-               }
-               // If the user didn't pass a login token, tell them we need one
-               if ( !$this->mToken ) {
-                       return self::NEED_TOKEN;
-               }
-
-               $throttleCount = self::incrementLoginThrottle( $this->mUsername );
-               if ( $throttleCount ) {
-                       $this->mThrottleWait = $throttleCount['wait'];
-                       return self::THROTTLED;
-               }
-
-               // Validate the login token
-               if ( !$token->match( $this->mToken ) ) {
-                       return self::WRONG_TOKEN;
-               }
-
-               // Load the current user now, and check to see if we're logging in as
-               // the same name. This is necessary because loading the current user
-               // (say by calling getName()) calls the UserLoadFromSession hook, which
-               // potentially creates the user in the database. Until we load $wgUser,
-               // checking for user existence using User::newFromName($name)->getId() below
-               // will effectively be using stale data.
-               if ( $this->getUser()->getName() === $this->mUsername ) {
-                       wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
-
-                       return self::SUCCESS;
-               }
-
-               $u = User::newFromName( $this->mUsername );
-               if ( $u === false ) {
-                       return self::ILLEGAL;
-               }
-
-               $msg = null;
-               // Give extensions a way to indicate the username has been updated,
-               // rather than telling the user the account doesn't exist.
-               if ( !Hooks::run( 'LoginUserMigrated', [ $u, &$msg ] ) ) {
-                       $this->mAbortLoginErrorMsg = $msg;
-                       return self::USER_MIGRATED;
-               }
-
-               if ( !User::isUsableName( $u->getName() ) ) {
-                       return self::ILLEGAL;
-               }
-
-               $isAutoCreated = false;
-               if ( $u->getId() == 0 ) {
-                       $status = $this->attemptAutoCreate( $u );
-                       if ( $status !== self::SUCCESS ) {
-                               return $status;
-                       } else {
-                               $isAutoCreated = true;
-                       }
-               } else {
-                       $u->load();
-               }
-
-               // Give general extensions, such as a captcha, a chance to abort logins
-               $abort = self::ABORTED;
-               if ( !Hooks::run( 'AbortLogin', [ $u, $this->mPassword, &$abort, &$msg ] ) ) {
-                       if ( !in_array( $abort, array_keys( self::$statusCodes ), true ) ) {
-                               throw new Exception( 'Invalid status code returned from AbortLogin hook: ' . $abort );
-                       }
-                       $this->mAbortLoginErrorMsg = $msg;
-                       return $abort;
-               }
-
-               global $wgBlockDisablesLogin;
-               if ( !$u->checkPassword( $this->mPassword ) ) {
-                       if ( $u->checkTemporaryPassword( $this->mPassword ) ) {
-                               /**
-                                * The e-mailed temporary password should not be used for actu-
-                                * al logins; that's a very sloppy habit, and insecure if an
-                                * attacker has a few seconds to click "search" on someone's
-                                * open mail reader.
-                                *
-                                * Allow it to be used only to reset the password a single time
-                                * to a new value, which won't be in the user's e-mail ar-
-                                * chives.
-                                *
-                                * For backwards compatibility, we'll still recognize it at the
-                                * login form to minimize surprises for people who have been
-                                * logging in with a temporary password for some time.
-                                *
-                                * As a side-effect, we can authenticate the user's e-mail ad-
-                                * dress if it's not already done, since the temporary password
-                                * was sent via e-mail.
-                                */
-                               if ( !$u->isEmailConfirmed() && !wfReadOnly() ) {
-                                       $u->confirmEmail();
-                                       $u->saveSettings();
-                               }
-
-                               // At this point we just return an appropriate code/ indicating
-                               // that the UI should show a password reset form; bot inter-
-                               // faces etc will probably just fail cleanly here.
-                               $this->mAbortLoginErrorMsg = 'resetpass-temp-emailed';
-                               $this->mTempPasswordUsed = true;
-                               $retval = self::RESET_PASS;
-                       } else {
-                               $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
-                       }
-               } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
-                       // If we've enabled it, make it so that a blocked user cannot login
-                       $retval = self::USER_BLOCKED;
-               } elseif ( $this->checkUserPasswordExpired( $u ) == 'hard' ) {
-                       // Force reset now, without logging in
-                       $retval = self::RESET_PASS;
-                       $this->mAbortLoginErrorMsg = 'resetpass-expired';
-               } else {
-                       Hooks::run( 'UserLoggedIn', [ $u ] );
-                       $oldUser = $u;
-                       $wgAuth->updateUser( $u );
-                       if ( $oldUser !== $u ) {
-                               wfWarn( get_class( $wgAuth ) . '::updateUser() replaced the user object' );
-                       }
-                       $wgUser = $u;
-                       // This should set it for OutputPage and the Skin
-                       // which is needed or the personal links will be
-                       // wrong.
-                       $this->getContext()->setUser( $u );
-
-                       // Please reset throttle for successful logins, thanks!
-                       self::clearLoginThrottle( $this->mUsername );
-
-                       if ( $isAutoCreated ) {
-                               // Must be run after $wgUser is set, for correct new user log
-                               Hooks::run( 'AuthPluginAutoCreate', [ $u ] );
-                       }
-
-                       $retval = self::SUCCESS;
-               }
-               Hooks::run( 'LoginAuthenticateAudit', [ $u, $this->mPassword, $retval ] );
-
-               return $retval;
-       }
-
-       /**
-        * Increment the login attempt throttle hit count for the (username,current IP)
-        * tuple unless the throttle was already reached.
-        *
-        * @since 1.27 Return value changed.
-        * @param string $username The user name
-        * @return bool|array false if below limit or an array if above limit
-        *   Array contains keys wait, count, and throttleIndex
-        */
-       public static function incrementLoginThrottle( $username ) {
-               global $wgPasswordAttemptThrottle, $wgRequest;
-               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
-
-               $throttleCount = 0;
-               if ( is_array( $wgPasswordAttemptThrottle ) ) {
-                       $throttleConfig = $wgPasswordAttemptThrottle;
-                       if ( isset( $wgPasswordAttemptThrottle['count'] ) ) {
-                               // old style. Convert for backwards compat.
-                               $throttleConfig = [ $wgPasswordAttemptThrottle ];
-                       }
-                       foreach ( $throttleConfig as $index => $specificThrottle ) {
-                               if ( isset( $specificThrottle['allIPs'] ) ) {
-                                       $ip = 'All';
-                               } else {
-                                       $ip = $wgRequest->getIP();
-                               }
-                               $throttleKey = wfGlobalCacheKey( 'password-throttle',
-                                       $index, $ip, md5( $username )
-                               );
-                               $count = $specificThrottle['count'];
-                               $period = $specificThrottle['seconds'];
-
-                               $cache = ObjectCache::getLocalClusterInstance();
-                               $throttleCount = $cache->get( $throttleKey );
-                               if ( !$throttleCount ) {
-                                       $cache->add( $throttleKey, 1, $period ); // start counter
-                               } elseif ( $throttleCount < $count ) {
-                                       $cache->incr( $throttleKey );
-                               } elseif ( $throttleCount >= $count ) {
-                                       $logMsg = 'Login attempt rejected because logins to '
-                                               . '{acct} from IP {ip} have been throttled for '
-                                               . '{period} seconds due to {count} failed attempts';
-                                       // If we are hitting a throttle for >= 50 attempts,
-                                       // it is much more likely to be an attack than someone
-                                       // simply forgetting their password, so log it at a
-                                       // higher level.
-                                       $level = $count >= 50 ? LogLevel::WARNING : LogLevel::INFO;
-                                       // It should be noted that once the throttle is hit,
-                                       // every attempt to login will generate the log message
-                                       // until the throttle expires, not just the attempt that
-                                       // puts the throttle over the top.
-                                       LoggerFactory::getInstance( 'password-throttle' )->log(
-                                               $level,
-                                               $logMsg,
-                                               [
-                                                       'ip' => $ip,
-                                                       'period' => $period,
-                                                       'acct' => $username,
-                                                       'count' => $count,
-                                                       'throttleIdentifier' => $index,
-                                                       'method' => __METHOD__
-                                               ]
-                                       );
-
-                                       return [
-                                               'throttleIndex' => $index,
-                                               'wait' => $period,
-                                               'count' => $count
-                                       ];
-                               }
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Increment the login attempt throttle hit count for the (username,current IP)
-        * tuple unless the throttle was already reached.
-        *
-        * @deprecated Use LoginForm::incrementLoginThrottle instead
-        * @param string $username The user name
-        * @return bool|int true if above throttle, or 0 (prior to 1.27, returned current count)
-        */
-       public static function incLoginThrottle( $username ) {
-               wfDeprecated( __METHOD__, "1.27" );
-               $res = self::incrementLoginThrottle( $username );
-               return is_array( $res ) ? true : 0;
-       }
-
-       /**
-        * Clear the login attempt throttle hit count for the (username,current IP) tuple.
-        * @param string $username The user name
-        * @return void
-        */
-       public static function clearLoginThrottle( $username ) {
-               global $wgRequest, $wgPasswordAttemptThrottle;
-               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
-
-               if ( is_array( $wgPasswordAttemptThrottle ) ) {
-                       $throttleConfig = $wgPasswordAttemptThrottle;
-                       if ( isset( $wgPasswordAttemptThrottle['count'] ) ) {
-                               // old style. Convert for backwards compat.
-                               $throttleConfig = [ $wgPasswordAttemptThrottle ];
-                       }
-                       foreach ( $throttleConfig as $index => $specificThrottle ) {
-                               if ( isset( $specificThrottle['allIPs'] ) ) {
-                                       $ip = 'All';
-                               } else {
-                                       $ip = $wgRequest->getIP();
-                               }
-                               $throttleKey = wfGlobalCacheKey( 'password-throttle', $index,
-                                       $ip, md5( $username )
-                               );
-                               ObjectCache::getLocalClusterInstance()->delete( $throttleKey );
-                       }
-               }
-       }
-
-       /**
-        * Attempt to automatically create a user on login. Only succeeds if there
-        * is an external authentication method which allows it.
-        *
-        * @param User $user
-        *
-        * @return int Status code
-        */
-       function attemptAutoCreate( $user ) {
-               global $wgAuth;
-
-               if ( $this->getUser()->isBlockedFromCreateAccount() ) {
-                       wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
-
-                       return self::CREATE_BLOCKED;
-               }
-
-               if ( !$wgAuth->autoCreate() ) {
-                       return self::NOT_EXISTS;
-               }
-
-               if ( !$wgAuth->userExists( $user->getName() ) ) {
-                       wfDebug( __METHOD__ . ": user does not exist\n" );
-
-                       return self::NOT_EXISTS;
-               }
-
-               if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
-                       wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
-
-                       return self::WRONG_PLUGIN_PASS;
-               }
-
-               $abortError = '';
-               if ( !Hooks::run( 'AbortAutoAccount', [ $user, &$abortError ] ) ) {
-                       // Hook point to add extra creation throttles and blocks
-                       wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
-                       $this->mAbortLoginErrorMsg = $abortError;
-
-                       return self::ABORTED;
-               }
-
-               wfDebug( __METHOD__ . ": creating account\n" );
-               $status = $this->initUser( $user, true );
-
-               if ( !$status->isOK() ) {
-                       $errors = $status->getErrorsByType( 'error' );
-                       $this->mAbortLoginErrorMsg = $errors[0]['message'];
-
-                       return self::ABORTED;
-               }
-
-               return self::SUCCESS;
-       }
-
-       function processLogin() {
-               global $wgLang, $wgSecureLogin, $wgInvalidPasswordReset;
-
-               $authRes = $this->authenticateUserData();
-               switch ( $authRes ) {
-                       case self::SUCCESS:
-                               # We've verified now, update the real record
-                               $user = $this->getUser();
-                               $user->touch();
-
-                               if ( $user->requiresHTTPS() ) {
-                                       $this->mStickHTTPS = true;
-                               }
-
-                               if ( $wgSecureLogin && !$this->mStickHTTPS ) {
-                                       $user->setCookies( $this->mRequest, false, $this->mRemember );
-                               } else {
-                                       $user->setCookies( $this->mRequest, null, $this->mRemember );
-                               }
-                               self::clearLoginToken();
-
-                               // Reset the throttle
-                               self::clearLoginThrottle( $this->mUsername );
-
-                               $request = $this->getRequest();
-                               if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
-                                       /* Replace the language object to provide user interface in
-                                        * correct language immediately on this first page load.
-                                        */
-                                       $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
-                                       $userLang = Language::factory( $code );
-                                       $wgLang = $userLang;
-                                       RequestContext::getMain()->setLanguage( $userLang );
-                                       $this->getContext()->setLanguage( $userLang );
-                                       // Reset SessionID on Successful login (bug 40995)
-                                       $this->renewSessionId();
-                                       if ( $this->checkUserPasswordExpired( $this->getUser() ) == 'soft' ) {
-                                               $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) );
-                                       } elseif ( $wgInvalidPasswordReset
-                                               && !$user->isValidPassword( $this->mPassword )
-                                       ) {
-                                               $status = $user->checkPasswordValidity(
-                                                       $this->mPassword,
-                                                       'login'
-                                               );
-                                               $this->resetLoginForm(
-                                                       $status->getMessage( 'resetpass-validity-soft' )
-                                               );
-                                       } else {
-                                               $this->successfulLogin();
-                                       }
-                               } else {
-                                       $this->cookieRedirectCheck( 'login' );
-                               }
-                               break;
-
-                       case self::NEED_TOKEN:
-                               $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin';
-                               $this->mainLoginForm( $this->msg( $error )->parse() );
-                               break;
-                       case self::WRONG_TOKEN:
-                               $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure';
-                               $this->mainLoginForm( $this->msg( $error )->text() );
-                               break;
-                       case self::NO_NAME:
-                       case self::ILLEGAL:
-                               $error = $this->mAbortLoginErrorMsg ?: 'noname';
-                               $this->mainLoginForm( $this->msg( $error )->text() );
-                               break;
-                       case self::WRONG_PLUGIN_PASS:
-                               $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
-                               $this->mainLoginForm( $this->msg( $error )->text() );
-                               break;
-                       case self::NOT_EXISTS:
-                               if ( $this->getUser()->isAllowed( 'createaccount' ) ) {
-                                       $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser';
-                                       $this->mainLoginForm( $this->msg( $error,
-                                               wfEscapeWikiText( $this->mUsername ) )->parse() );
-                               } else {
-                                       $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort';
-                                       $this->mainLoginForm( $this->msg( $error,
-                                               wfEscapeWikiText( $this->mUsername ) )->text() );
-                               }
-                               break;
-                       case self::WRONG_PASS:
-                               $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
-                               $this->mainLoginForm( $this->msg( $error )->text() );
-                               break;
-                       case self::EMPTY_PASS:
-                               $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty';
-                               $this->mainLoginForm( $this->msg( $error )->text() );
-                               break;
-                       case self::RESET_PASS:
-                               $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce';
-                               $this->resetLoginForm( $this->msg( $error ) );
-                               break;
-                       case self::CREATE_BLOCKED:
-                               $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() );
-                               break;
-                       case self::THROTTLED:
-                               $error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
-                               $this->mainLoginForm( $this->msg( $error )
-                                       ->durationParams( $this->mThrottleWait )->text()
-                               );
-                               break;
-                       case self::USER_BLOCKED:
-                               $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked';
-                               $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() );
-                               break;
-                       case self::ABORTED:
-                               $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic';
-                               $this->mainLoginForm( $this->msg( $error,
-                                               wfEscapeWikiText( $this->mUsername ) )->text() );
-                               break;
-                       case self::USER_MIGRATED:
-                               $error = $this->mAbortLoginErrorMsg ?: 'login-migrated-generic';
-                               $params = [];
-                               if ( is_array( $error ) ) {
-                                       $error = array_shift( $this->mAbortLoginErrorMsg );
-                                       $params = $this->mAbortLoginErrorMsg;
-                               }
-                               $this->mainLoginForm( $this->msg( $error, $params )->text() );
-                               break;
-                       default:
-                               throw new MWException( 'Unhandled case value' );
-               }
-
-               LoggerFactory::getInstance( 'authmanager' )->info( 'Login attempt', [
-                       'event' => 'login',
-                       'successful' => $authRes === self::SUCCESS,
-                       'status' => LoginForm::$statusCodes[$authRes],
-               ] );
-       }
-
-       /**
-        * Show the Special:ChangePassword form, with custom message
-        * @param Message $msg
-        */
-       protected function resetLoginForm( Message $msg ) {
-               // Allow hooks to explain this password reset in more detail
-               Hooks::run( 'LoginPasswordResetMessage', [ &$msg, $this->mUsername ] );
-               $reset = new SpecialChangePassword();
-               $derivative = new DerivativeContext( $this->getContext() );
-               $derivative->setTitle( $reset->getPageTitle() );
-               $reset->setContext( $derivative );
-               if ( !$this->mTempPasswordUsed ) {
-                       $reset->setOldPasswordMessage( 'oldpassword' );
-               }
-               $reset->setChangeMessage( $msg );
-               $reset->execute( null );
-       }
-
-       /**
-        * @param User $u
-        * @param bool $throttle
-        * @param string $emailTitle Message name of email title
-        * @param string $emailText Message name of email text
-        * @return Status
-        */
-       function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle',
-               $emailText = 'passwordremindertext'
-       ) {
-               global $wgNewPasswordExpiry, $wgMinimalPasswordLength;
-
-               if ( $u->getEmail() == '' ) {
-                       return Status::newFatal( 'noemail', $u->getName() );
-               }
-               $ip = $this->getRequest()->getIP();
-               if ( !$ip ) {
-                       return Status::newFatal( 'badipaddress' );
-               }
-
-               $currentUser = $this->getUser();
-               Hooks::run( 'User::mailPasswordInternal', [ &$currentUser, &$ip, &$u ] );
-
-               $np = PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
-               $u->setNewpassword( $np, $throttle );
-               $u->saveSettings();
-               $userLanguage = $u->getOption( 'language' );
-
-               $mainPage = Title::newMainPage();
-               $mainPageUrl = $mainPage->getCanonicalURL();
-
-               $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $mainPageUrl . '>',
-                       round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
-               $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
-
-               return $result;
-       }
-
-       /**
-        * Run any hooks registered for logins, then HTTP redirect to
-        * $this->mReturnTo (or Main Page if that's undefined).  Formerly we had a
-        * nice message here, but that's really not as useful as just being sent to
-        * wherever you logged in from.  It should be clear that the action was
-        * successful, given the lack of error messages plus the appearance of your
-        * name in the upper right.
-        *
-        * @private
-        */
-       function successfulLogin() {
-               # Run any hooks; display injected HTML if any, else redirect
-               $currentUser = $this->getUser();
-               $injected_html = '';
-               Hooks::run( 'UserLoginComplete', [ &$currentUser, &$injected_html ] );
-
-               if ( $injected_html !== '' ) {
-                       $this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ),
-                               'loginsuccess', $injected_html );
-               } else {
-                       $this->executeReturnTo( 'successredirect' );
-               }
-       }
-
-       /**
-        * Run any hooks registered for logins, then display a message welcoming
-        * the user.
-        *
-        * @private
-        */
-       function successfulCreation() {
-               # Run any hooks; display injected HTML
-               $currentUser = $this->getUser();
-               $injected_html = '';
-               $welcome_creation_msg = 'welcomecreation-msg';
-
-               Hooks::run( 'UserLoginComplete', [ &$currentUser, &$injected_html ] );
-
-               /**
-                * Let any extensions change what message is shown.
-                * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation
-                * @since 1.18
-                */
-               Hooks::run( 'BeforeWelcomeCreation', [ &$welcome_creation_msg, &$injected_html ] );
-
-               $this->displaySuccessfulAction(
-                       'signup',
-                       $this->msg( 'welcomeuser', $this->getUser()->getName() ),
-                       $welcome_creation_msg, $injected_html
-               );
-       }
-
-       /**
-        * Display a "successful action" page.
-        *
-        * @param string $type Condition of return to; see `executeReturnTo`
-        * @param string|Message $title Page's title
-        * @param string $msgname
-        * @param string $injected_html
-        */
-       private function displaySuccessfulAction( $type, $title, $msgname, $injected_html ) {
-               $out = $this->getOutput();
-               $out->setPageTitle( $title );
-               if ( $msgname ) {
-                       $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
-               }
-
-               $out->addHTML( $injected_html );
-
-               $this->executeReturnTo( $type );
-       }
-
-       /**
-        * Output a message that informs the user that they cannot create an account because
-        * there is a block on them or their IP which prevents account creation.  Note that
-        * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
-        * setting on blocks (bug 13611).
-        * @param Block $block The block causing this error
-        * @throws ErrorPageError
-        */
-       function userBlockedMessage( Block $block ) {
-               # Let's be nice about this, it's likely that this feature will be used
-               # for blocking large numbers of innocent people, e.g. range blocks on
-               # schools. Don't blame it on the user. There's a small chance that it
-               # really is the user's fault, i.e. the username is blocked and they
-               # haven't bothered to log out before trying to create an account to
-               # evade it, but we'll leave that to their guilty conscience to figure
-               # out.
-               $errorParams = [
-                       $block->getTarget(),
-                       $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
-                       $block->getByName()
-               ];
-
-               if ( $block->getType() === Block::TYPE_RANGE ) {
-                       $errorMessage = 'cantcreateaccount-range-text';
-                       $errorParams[] = $this->getRequest()->getIP();
-               } else {
-                       $errorMessage = 'cantcreateaccount-text';
-               }
-
-               throw new ErrorPageError(
-                       'cantcreateaccounttitle',
-                       $errorMessage,
-                       $errorParams
-               );
-       }
-
-       /**
-        * Add a "return to" link or redirect to it.
-        * Extensions can use this to reuse the "return to" logic after
-        * inject steps (such as redirection) into the login process.
-        *
-        * @param string $type One of the following:
-        *    - error: display a return to link ignoring $wgRedirectOnLogin
-        *    - signup: display a return to link using $wgRedirectOnLogin if needed
-        *    - success: display a return to link using $wgRedirectOnLogin if needed
-        *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
-        * @param string $returnTo
-        * @param array|string $returnToQuery
-        * @param bool $stickHTTPs Keep redirect link on HTTPs
-        * @since 1.22
-        */
-       public function showReturnToPage(
-               $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false
-       ) {
-               $this->mReturnTo = $returnTo;
-               $this->mReturnToQuery = $returnToQuery;
-               $this->mStickHTTPS = $stickHTTPs;
-               $this->executeReturnTo( $type );
-       }
-
-       /**
-        * Add a "return to" link or redirect to it.
-        *
-        * @param string $type One of the following:
-        *    - error: display a return to link ignoring $wgRedirectOnLogin
-        *    - signup: display a return to link using $wgRedirectOnLogin if needed
-        *    - success: display a return to link using $wgRedirectOnLogin if needed
-        *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
-        */
-       private function executeReturnTo( $type ) {
-               global $wgRedirectOnLogin, $wgSecureLogin;
-
-               if ( $type != 'error' && $wgRedirectOnLogin !== null ) {
-                       $returnTo = $wgRedirectOnLogin;
-                       $returnToQuery = [];
-               } else {
-                       $returnTo = $this->mReturnTo;
-                       $returnToQuery = wfCgiToArray( $this->mReturnToQuery );
-               }
-
-               // Allow modification of redirect behavior
-               Hooks::run( 'PostLoginRedirect', [ &$returnTo, &$returnToQuery, &$type ] );
-
-               $returnToTitle = Title::newFromText( $returnTo );
-               if ( !$returnToTitle ) {
-                       $returnToTitle = Title::newMainPage();
-               }
-
-               if ( $wgSecureLogin && !$this->mStickHTTPS ) {
-                       $options = [ 'http' ];
-                       $proto = PROTO_HTTP;
-               } elseif ( $wgSecureLogin ) {
-                       $options = [ 'https' ];
-                       $proto = PROTO_HTTPS;
-               } else {
-                       $options = [];
-                       $proto = PROTO_RELATIVE;
-               }
-
-               if ( $type == 'successredirect' ) {
-                       $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto );
-                       $this->getOutput()->redirect( $redirectUrl );
-               } else {
-                       $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options );
-               }
-       }
-
-       /**
-        * @param string $msg
-        * @param string $msgtype
-        * @throws ErrorPageError
-        * @throws Exception
-        * @throws FatalError
-        * @throws MWException
-        * @throws PermissionsError
-        * @throws ReadOnlyError
-        * @private
-        */
-       function mainLoginForm( $msg, $msgtype = 'error' ) {
-               global $wgEnableEmail, $wgEnableUserEmail;
-               global $wgHiddenPrefs, $wgLoginLanguageSelector;
-               global $wgAuth, $wgEmailConfirmToEdit;
-               global $wgSecureLogin, $wgPasswordResetRoutes;
-               global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
-
-               $titleObj = $this->getPageTitle();
-               $user = $this->getUser();
-               $out = $this->getOutput();
-
-               if ( $this->mType == 'signup' ) {
-                       // Block signup here if in readonly. Keeps user from
-                       // going through the process (filling out data, etc)
-                       // and being informed later.
-                       $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true );
-                       if ( count( $permErrors ) ) {
-                               throw new PermissionsError( 'createaccount', $permErrors );
-                       } elseif ( $user->isBlockedFromCreateAccount() ) {
-                               $this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
-
-                               return;
-                       } elseif ( wfReadOnly() ) {
-                               throw new ReadOnlyError;
-                       }
-               }
-
-               // Pre-fill username (if not creating an account, bug 44775).
-               if ( $this->mUsername == '' && $this->mType != 'signup' ) {
-                       if ( $user->isLoggedIn() ) {
-                               $this->mUsername = $user->getName();
-                       } else {
-                               $this->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
-                       }
-               }
-
-               // Generic styles and scripts for both login and signup form
-               $out->addModuleStyles( [
-                       'mediawiki.ui',
-                       'mediawiki.ui.button',
-                       'mediawiki.ui.checkbox',
-                       'mediawiki.ui.input',
-                       'mediawiki.special.userlogin.common.styles'
-               ] );
-
-               if ( $this->mType == 'signup' ) {
-                       // Additional styles and scripts for signup form
-                       $out->addModules( [
-                               'mediawiki.special.userlogin.signup.js'
-                       ] );
-                       $out->addModuleStyles( [
-                               'mediawiki.special.userlogin.signup.styles'
-                       ] );
-
-                       $template = new UsercreateTemplate( $this->getConfig() );
-
-                       // Must match number of benefits defined in messages
-                       $template->set( 'benefitCount', 3 );
-
-                       $q = 'action=submitlogin&type=signup';
-                       $linkq = 'type=login';
-               } else {
-                       // Additional styles for login form
-                       $out->addModuleStyles( [
-                               'mediawiki.special.userlogin.login.styles'
-                       ] );
-
-                       $template = new UserloginTemplate( $this->getConfig() );
-
-                       $q = 'action=submitlogin&type=login';
-                       $linkq = 'type=signup';
-               }
-
-               if ( $this->mReturnTo !== '' ) {
-                       $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
-                       if ( $this->mReturnToQuery !== '' ) {
-                               $returnto .= '&returntoquery=' .
-                                       wfUrlencode( $this->mReturnToQuery );
-                       }
-                       $q .= $returnto;
-                       $linkq .= $returnto;
-               }
-
-               # Don't show a "create account" link if the user can't.
-               if ( $this->showCreateOrLoginLink( $user ) ) {
-                       # Pass any language selection on to the mode switch link
-                       if ( $wgLoginLanguageSelector && $this->mLanguage ) {
-                               $linkq .= '&uselang=' . $this->mLanguage;
-                       }
-                       // Supply URL, login template creates the button.
-                       $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
-               } else {
-                       $template->set( 'link', '' );
-               }
-
-               $resetLink = $this->mType == 'signup'
-                       ? null
-                       : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
-
-               $template->set( 'header', '' );
-               $template->set( 'formheader', '' );
-               $template->set( 'skin', $this->getSkin() );
-               $template->set( 'name', $this->mUsername );
-               $template->set( 'password', $this->mPassword );
-               $template->set( 'retype', $this->mRetype );
-               $template->set( 'createemailset', $this->mCreateaccountMail );
-               $template->set( 'email', $this->mEmail );
-               $template->set( 'realname', $this->mRealName );
-               $template->set( 'domain', $this->mDomain );
-               $template->set( 'reason', $this->mReason );
-
-               $template->set( 'action', $titleObj->getLocalURL( $q ) );
-               $template->set( 'message', $msg );
-               $template->set( 'messagetype', $msgtype );
-               $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
-               $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
-               $template->set( 'useemail', $wgEnableEmail );
-               $template->set( 'emailrequired', $wgEmailConfirmToEdit );
-               $template->set( 'emailothers', $wgEnableUserEmail );
-               $template->set( 'canreset', $wgAuth->allowPasswordChange() );
-               $template->set( 'resetlink', $resetLink );
-               $template->set( 'canremember', $wgExtendedLoginCookieExpiration === null ?
-                       ( $wgCookieExpiration > 0 ) :
-                       ( $wgExtendedLoginCookieExpiration > 0 ) );
-               $template->set( 'usereason', $user->isLoggedIn() );
-               $template->set( 'remember', $this->mRemember );
-               $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
-               $template->set( 'stickhttps', (int)$this->mStickHTTPS );
-               $template->set( 'loggedin', $user->isLoggedIn() );
-               $template->set( 'loggedinuser', $user->getName() );
-
-               if ( $this->mType == 'signup' ) {
-                       $template->set( 'token', self::getCreateaccountToken()->toString() );
-               } else {
-                       $template->set( 'token', self::getLoginToken()->toString() );
-               }
-
-               # Prepare language selection links as needed
-               if ( $wgLoginLanguageSelector ) {
-                       $template->set( 'languages', $this->makeLanguageSelector() );
-                       if ( $this->mLanguage ) {
-                               $template->set( 'uselang', $this->mLanguage );
-                       }
-               }
-
-               $template->set( 'secureLoginUrl', $this->mSecureLoginUrl );
-               // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
-               $usingHTTPS = $this->mRequest->getProtocol() == 'https';
-               $signupendHTTPS = $this->msg( 'signupend-https' );
-               if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
-                       $template->set( 'signupend', $signupendHTTPS->parse() );
-               } else {
-                       $template->set( 'signupend', $this->msg( 'signupend' )->parse() );
-               }
-
-               // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
-               if ( $usingHTTPS ) {
-                       $template->set( 'fromhttp', $this->mFromHTTP );
-               }
-
-               // Give authentication and captcha plugins a chance to modify the form
-               $wgAuth->modifyUITemplate( $template, $this->mType );
-               if ( $this->mType == 'signup' ) {
-                       Hooks::run( 'UserCreateForm', [ &$template ] );
-               } else {
-                       Hooks::run( 'UserLoginForm', [ &$template ] );
-               }
-
-               $out->disallowUserJs(); // just in case...
-               $out->addTemplate( $template );
-       }
-
-       /**
-        * Whether the login/create account form should display a link to the
-        * other form (in addition to whatever the skin provides).
-        *
-        * @param User $user
-        * @return bool
-        */
-       private function showCreateOrLoginLink( &$user ) {
-               if ( $this->mType == 'signup' ) {
-                       return true;
-               } elseif ( $user->isAllowed( 'createaccount' ) ) {
-                       return true;
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Check if a session cookie is present.
-        *
-        * This will not pick up a cookie set during _this_ request, but is meant
-        * to ensure that the client is returning the cookie which was set on a
-        * previous pass through the system.
-        *
-        * @private
-        * @return bool
-        */
-       function hasSessionCookie() {
-               global $wgDisableCookieCheck, $wgInitialSessionId;
-
-               return $wgDisableCookieCheck || (
-                       $wgInitialSessionId &&
-                       $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId
-               );
-       }
-
-       /**
-        * Get the login token from the current session
-        * @since 1.27 returns a MediaWiki\Session\Token instead of a string
-        * @return MediaWiki\Session\Token
-        */
-       public static function getLoginToken() {
-               global $wgRequest;
-               return $wgRequest->getSession()->getToken( '', 'login' );
-       }
-
-       /**
-        * Formerly randomly generated a login token that would be returned by
-        * $this->getLoginToken().
-        *
-        * Since 1.27, this is a no-op. The token is generated as necessary by
-        * $this->getLoginToken().
-        *
-        * @deprecated since 1.27
-        */
-       public static function setLoginToken() {
-               wfDeprecated( __METHOD__, '1.27' );
-       }
-
-       /**
-        * Remove any login token attached to the current session
-        */
-       public static function clearLoginToken() {
-               global $wgRequest;
-               $wgRequest->getSession()->resetToken( 'login' );
-       }
-
-       /**
-        * Get the createaccount token from the current session
-        * @since 1.27 returns a MediaWiki\Session\Token instead of a string
-        * @return MediaWiki\Session\Token
-        */
-       public static function getCreateaccountToken() {
-               global $wgRequest;
-               return $wgRequest->getSession()->getToken( '', 'createaccount' );
-       }
-
-       /**
-        * Formerly randomly generated a createaccount token that would be returned
-        * by $this->getCreateaccountToken().
-        *
-        * Since 1.27, this is a no-op. The token is generated as necessary by
-        * $this->getCreateaccountToken().
-        *
-        * @deprecated since 1.27
-        */
-       public static function setCreateaccountToken() {
-               wfDeprecated( __METHOD__, '1.27' );
-       }
-
-       /**
-        * Remove any createaccount token attached to the current session
-        */
-       public static function clearCreateaccountToken() {
-               global $wgRequest;
-               $wgRequest->getSession()->resetToken( 'createaccount' );
-       }
-
-       /**
-        * Renew the user's session id, using strong entropy
-        */
-       private function renewSessionId() {
-               global $wgSecureLogin, $wgCookieSecure;
-               if ( $wgSecureLogin && !$this->mStickHTTPS ) {
-                       $wgCookieSecure = false;
-               }
-
-               SessionManager::getGlobalSession()->resetId();
-       }
-
-       /**
-        * @param string $type
-        * @private
-        */
-       function cookieRedirectCheck( $type ) {
-               $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
-               $query = [ 'wpCookieCheck' => $type ];
-               if ( $this->mReturnTo !== '' ) {
-                       $query['returnto'] = $this->mReturnTo;
-                       $query['returntoquery'] = $this->mReturnToQuery;
-               }
-               $check = $titleObj->getFullURL( $query );
-
-               $this->getOutput()->redirect( $check );
-       }
-
-       /**
-        * @param string $type
-        * @private
-        */
-       function onCookieRedirectCheck( $type ) {
-               if ( !$this->hasSessionCookie() ) {
-                       if ( $type == 'new' ) {
-                               $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() );
-                       } elseif ( $type == 'login' ) {
-                               $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() );
-                       } else {
-                               # shouldn't happen
-                               $this->mainLoginForm( $this->msg( 'error' )->text() );
-                       }
-               } else {
-                       $this->successfulLogin();
-               }
-       }
-
-       /**
-        * Produce a bar of links which allow the user to select another language
-        * during login/registration but retain "returnto"
-        *
-        * @return string
-        */
-       function makeLanguageSelector() {
-               $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
-               if ( $msg->isBlank() ) {
-                       return '';
-               }
-               $langs = explode( "\n", $msg->text() );
-               $links = [];
-               foreach ( $langs as $lang ) {
-                       $lang = trim( $lang, '* ' );
-                       $parts = explode( '|', $lang );
-                       if ( count( $parts ) >= 2 ) {
-                               $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
-                       }
-               }
-
-               return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
-                       $this->getLanguage()->pipeList( $links ) )->escaped() : '';
-       }
-
-       /**
-        * Create a language selector link for a particular language
-        * Links back to this page preserving type and returnto
-        *
-        * @param string $text Link text
-        * @param string $lang Language code
-        * @return string
-        */
-       function makeLanguageSelectorLink( $text, $lang ) {
-               if ( $this->getLanguage()->getCode() == $lang ) {
-                       // no link for currently used language
-                       return htmlspecialchars( $text );
-               }
-               $query = [ 'uselang' => $lang ];
-               if ( $this->mType == 'signup' ) {
-                       $query['type'] = 'signup';
-               }
-               if ( $this->mReturnTo !== '' ) {
-                       $query['returnto'] = $this->mReturnTo;
-                       $query['returntoquery'] = $this->mReturnToQuery;
-               }
-
-               $attr = [];
-               $targetLanguage = Language::factory( $lang );
-               $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
-
-               return Linker::linkKnown(
-                       $this->getPageTitle(),
-                       htmlspecialchars( $text ),
-                       $attr,
-                       $query
-               );
-       }
-
-       protected function getGroupName() {
-               return 'login';
-       }
-
-       /**
-        * Private function to check password expiration, until AuthManager comes
-        * along to handle that.
-        * @param User $user
-        * @return string|bool
-        */
-       private function checkUserPasswordExpired( User $user ) {
-               global $wgPasswordExpireGrace;
-               $dbr = wfGetDB( DB_SLAVE );
-               $ts = $dbr->selectField( 'user', 'user_password_expires', [ 'user_id' => $user->getId() ] );
-
-               $expired = false;
-               $now = wfTimestamp();
-               $expUnix = wfTimestamp( TS_UNIX, $ts );
-               if ( $ts !== null && $expUnix < $now ) {
-                       $expired = ( $expUnix + $wgPasswordExpireGrace < $now ) ? 'hard' : 'soft';
-               }
-               return $expired;
-       }
-
-       protected function getSubpagesForPrefixSearch() {
-               return [ 'signup' ];
-       }
-}
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
deleted file mode 100644 (file)
index 5789e3a..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * Implements Special:Userlogout
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * Implements Special:Userlogout
- *
- * @ingroup SpecialPage
- */
-class SpecialUserlogout extends UnlistedSpecialPage {
-       function __construct() {
-               parent::__construct( 'Userlogout' );
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-
-       function execute( $par ) {
-               /**
-                * Some satellite ISPs use broken precaching schemes that log people out straight after
-                * they're logged in (bug 17790). Luckily, there's a way to detect such requests.
-                */
-               if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
-                       wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
-                       throw new HttpError( 400, $this->msg( 'suspicious-userlogout' ), $this->msg( 'loginerror' ) );
-               }
-
-               $this->setHeaders();
-               $this->outputHeader();
-
-               // Make sure it's possible to log out
-               $session = MediaWiki\Session\SessionManager::getGlobalSession();
-               if ( !$session->canSetUser() ) {
-                       throw new ErrorPageError(
-                               'cannotlogoutnow-title',
-                               'cannotlogoutnow-text',
-                               [
-                                       $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
-                               ]
-                       );
-               }
-
-               $user = $this->getUser();
-               $oldName = $user->getName();
-               $user->logout();
-
-               $loginURL = SpecialPage::getTitleFor( 'Userlogin' )->getFullURL(
-                       $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
-
-               $out = $this->getOutput();
-               $out->addWikiMsg( 'logouttext', $loginURL );
-
-               // Hook.
-               $injected_html = '';
-               Hooks::run( 'UserLogoutComplete', [ &$user, &$injected_html, $oldName ] );
-               $out->addHTML( $injected_html );
-
-               $out->returnToMain();
-       }
-
-       protected function getGroupName() {
-               return 'login';
-       }
-}
index be110aa..d5affc7 100644 (file)
@@ -250,8 +250,6 @@ class UserrightsPage extends SpecialPage {
         * @return array Tuple of added, then removed groups
         */
        function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
-               global $wgAuth;
-
                // Validate input set...
                $isself = $user->getName() == $this->getUser()->getName();
                $groups = $user->getGroups();
@@ -293,7 +291,9 @@ class UserrightsPage extends SpecialPage {
 
                // update groups in external authentication database
                Hooks::run( 'UserGroupsChanged', [ $user, $add, $remove, $this->getUser(), $reason ] );
-               $wgAuth->updateExternalDBGroups( $user, $add, $remove );
+               MediaWiki\Auth\AuthManager::callLegacyAuthPlugin(
+                       'updateExternalDBGroups', [ $user, $add, $remove ]
+               );
 
                wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) . "\n" );
                wfDebug( 'newGroups: ' . print_r( $newGroups, true ) . "\n" );
index 15691f2..58cde7e 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * A special page that lists last changes made to the wiki,
  * limited to user-defined list of titles.
@@ -365,7 +367,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                if ( $this->getConfig()->get( 'RCShowWatchingUsers' )
                        && $user->getOption( 'shownumberswatching' )
                ) {
-                       $watchedItemStore = WatchedItemStore::getDefaultInstance();
+                       $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
                }
 
                $s = $list->beginRecentChangesList();
@@ -646,7 +648,8 @@ class SpecialWatchlist extends ChangesListSpecialPage {
         * @return int
         */
        protected function countItems() {
-               $count = WatchedItemStore::getDefaultInstance()->countWatchedItems( $this->getUser() );
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+               $count = $store->countWatchedItems( $this->getUser() );
                return floor( $count / 2 );
        }
 }
index baa55f0..b4ea732 100644 (file)
@@ -75,7 +75,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                $this->target = Title::newFromText( $opts->getValue( 'target' ) );
                if ( !$this->target ) {
                        if ( !$this->including() ) {
-                               $out->addHTML( $this->whatlinkshereForm() );
+                               $this->buildForm();
                        }
 
                        return;
@@ -200,12 +200,8 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                ) {
                        if ( 0 == $level ) {
                                if ( !$this->including() ) {
-                                       $out->addHTML( $this->whatlinkshereForm() );
+                                       $this->buildForm();
 
-                                       // Show filters only if there are links
-                                       if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
-                                               $out->addHTML( $this->getFilterPanel() );
-                                       }
                                        $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
                                        $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
                                        $out->setStatusCode( 404 );
@@ -269,8 +265,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
 
                if ( $level == 0 ) {
                        if ( !$this->including() ) {
-                               $out->addHTML( $this->whatlinkshereForm() );
-                               $out->addHTML( $this->getFilterPanel() );
+                               $this->buildForm();
                                $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
 
                                $prevnext = $this->getPrevNext( $prevId, $nextId );
@@ -444,7 +439,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
        }
 
-       function whatlinkshereForm() {
+       protected function buildForm() {
                // We get nicer value from the title object
                $this->opts->consumeValue( 'target' );
                // Reset these for new requests
@@ -455,88 +450,57 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                $nsinvert = $this->opts->consumeValue( 'invert' );
 
                # Build up the form
-               $f = Xml::openElement( 'form', [ 'action' => wfScript() ] );
 
-               # Values that should not be forgotten
-               $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
-               foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
-                       $f .= Html::hidden( $name, $value );
-               }
-
-               $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() );
-
-               # Target input (.mw-searchInput enables suggestions)
-               $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
-                       'mw-whatlinkshere-target', 40, $target, [ 'class' => 'mw-searchInput' ] );
+               $hiddenFields = [
+                       'title' => $this->getPageTitle()->getPrefixedDBkey(),
+               ];
 
-               $f .= ' ';
+               $formDescriptor = [
+                       'target' => [
+                               'type' => 'title',
+                               'name' => 'target',
+                               'label-message' => 'whatlinkshere-page',
+                               'default' => $this->opts->getValue( 'target' ),
+                       ],
 
-               # Namespace selector
-               $f .= Html::namespaceSelector(
-                       [
-                               'selected' => $namespace,
-                               'all' => '',
-                               'label' => $this->msg( 'namespace' )->text()
-                       ], [
+                       'namespace' => [
+                               'type' => 'namespaceselect',
                                'name' => 'namespace',
-                               'id' => 'namespace',
-                               'class' => 'namespaceselector',
-                       ]
-               );
-
-               $f .= '&#160;' .
-                       Xml::checkLabel(
-                               $this->msg( 'invert' )->text(),
-                               'invert',
-                               'nsinvert',
-                               $nsinvert,
-                               [ 'title' => $this->msg( 'tooltip-whatlinkshere-invert' )->text() ]
-                       );
-
-               $f .= ' ';
-
-               # Submit
-               $f .= Xml::submitButton( $this->msg( 'whatlinkshere-submit' )->text() );
-
-               # Close
-               $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n";
-
-               return $f;
-       }
-
-       /**
-        * Create filter panel
-        *
-        * @return string HTML fieldset and filter panel with the show/hide links
-        */
-       function getFilterPanel() {
-               $show = $this->msg( 'show' )->escaped();
-               $hide = $this->msg( 'hide' )->escaped();
-
-               $changed = $this->opts->getChangedValues();
-               unset( $changed['target'] ); // Already in the request title
+                               'label-message' => 'namespace',
+                               'all' => '',
+                       ],
+
+                       'invert' => [
+                               'type' => 'check',
+                               'name' => 'invert',
+                               'label-message' => 'invert',
+                               'default' => false,
+                       ],
+               ];
 
-               $links = [];
-               $types = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
-               if ( $this->target->getNamespace() == NS_FILE ) {
-                       $types[] = 'hideimages';
+               $filters = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
+               if ( $this->target instanceof Title &&
+                       $this->target->getNamespace() == NS_FILE ) {
+                       $filters[] = 'hideimages';
                }
 
-               // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
-               // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
-               // To be sure they will be found by grep
-               foreach ( $types as $type ) {
-                       $chosen = $this->opts->getValue( $type );
-                       $msg = $chosen ? $show : $hide;
-                       $overrides = [ $type => !$chosen ];
-                       $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
-                               $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
+               foreach ( $filters as $filter ) {
+                       $formDescriptor[$filter] = [
+                               'type' => 'check',
+                               'name' => $filter,
+                               'label' => $this->msg( 'whatlinkshere-' . $filter ),
+                               'value' => false,
+                       ];
                }
 
-               return Xml::fieldset(
-                       $this->msg( 'whatlinkshere-filters' )->text(),
-                       $this->getLanguage()->pipeList( $links )
-               );
+               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->addHiddenFields( $hiddenFields )
+                       ->setWrapperLegendMsg( 'whatlinkshere' )
+                       ->setSubmitTextMsg( 'whatlinkshere-submit' )
+                       ->setAction( $this->getPageTitle()->getLocalURL() )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
        }
 
        /**
diff --git a/includes/specials/helpers/LoginHelper.php b/includes/specials/helpers/LoginHelper.php
new file mode 100644 (file)
index 0000000..f853f41
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * Helper functions for the login form that need to be shared with other special pages
+ * (such as CentralAuth's SpecialCentralLogin).
+ * @since 1.27
+ */
+class LoginHelper extends ContextSource {
+       /**
+        * Valid error and warning messages
+        *
+        * Special:Userlogin can show an error or warning message on the form when
+        * coming from another page. This is done via the ?error= or ?warning= GET
+        * parameters.
+        *
+        * This array is the list of valid message keys. Further keys can be added by the
+        * LoginFormValidErrorMessages hook. All other values will be ignored.
+        *
+        * @var string[]
+        */
+       public static $validErrorMessages = [
+               'exception-nologin-text',
+               'watchlistanontext',
+               'changeemail-no-info',
+               'resetpass-no-info',
+               'confirmemail_needlogin',
+               'prefsnologintext2',
+       ];
+
+       /**
+        * Returns an array of all valid error messages.
+        *
+        * @return array
+        * @see LoginHelper::$validErrorMessages
+        */
+       public static function getValidErrorMessages() {
+               static $messages = null;
+               if ( !$messages ) {
+                       $messages = self::$validErrorMessages;
+                       Hooks::run( 'LoginFormValidErrorMessages', [ &$messages ] );
+               }
+
+               return $messages;
+       }
+
+       public function __construct( IContextSource $context ) {
+               $this->setContext( $context );
+       }
+
+       /**
+        * Show a return link or redirect to it.
+        * Extensions can change where the link should point or inject content into the page
+        * (which will change it from redirect to link mode).
+        *
+        * @param string $type One of the following:
+        *    - error: display a return to link ignoring $wgRedirectOnLogin
+        *    - success: display a return to link using $wgRedirectOnLogin if needed
+        *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
+        * @param string $returnTo
+        * @param array|string $returnToQuery
+        * @param bool $stickHTTPS Keep redirect link on HTTPS
+        */
+       public function showReturnToPage(
+               $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
+       ) {
+               global $wgRedirectOnLogin, $wgSecureLogin;
+
+               if ( $type !== 'error' && $wgRedirectOnLogin !== null ) {
+                       $returnTo = $wgRedirectOnLogin;
+                       $returnToQuery = [];
+               } elseif ( is_string( $returnToQuery ) ) {
+                       $returnToQuery = wfCgiToArray( $returnToQuery );
+               }
+
+               // Allow modification of redirect behavior
+               Hooks::run( 'PostLoginRedirect', [ &$returnTo, &$returnToQuery, &$type ] );
+
+               $returnToTitle = Title::newFromText( $returnTo ) ?:  Title::newMainPage();
+
+               if ( $wgSecureLogin && !$stickHTTPS ) {
+                       $options = [ 'http' ];
+                       $proto = PROTO_HTTP;
+               } elseif ( $wgSecureLogin ) {
+                       $options = [ 'https' ];
+                       $proto = PROTO_HTTPS;
+               } else {
+                       $options = [];
+                       $proto = PROTO_RELATIVE;
+               }
+
+               if ( $type === 'successredirect' ) {
+                       $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto );
+                       $this->getOutput()->redirect( $redirectUrl );
+               } else {
+                       $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options );
+               }
+       }
+}
index 0d3bc9a..73ab0ad 100644 (file)
@@ -52,15 +52,15 @@ class ActiveUsersPager extends UsersPager {
 
        /**
         * @param IContextSource $context
-        * @param null $group Unused
-        * @param string $par Parameter passed to the page
+        * @param FormOptions $opts
         */
-       function __construct( IContextSource $context = null, $group = null, $par = null ) {
+       function __construct( IContextSource $context = null, FormOptions $opts ) {
                parent::__construct( $context );
 
                $this->RCMaxAge = $this->getConfig()->get( 'ActiveUserDays' );
-               $un = $this->getRequest()->getText( 'username', $par );
                $this->requestedUser = '';
+
+               $un = $opts->getValue( 'username' );
                if ( $un != '' ) {
                        $username = Title::makeTitleSafe( NS_USER, $un );
                        if ( !is_null( $username ) ) {
@@ -68,21 +68,10 @@ class ActiveUsersPager extends UsersPager {
                        }
                }
 
-               $this->setupOptions();
-       }
-
-       public function setupOptions() {
-               $this->opts = new FormOptions();
-
-               $this->opts->add( 'hidebots', false, FormOptions::BOOL );
-               $this->opts->add( 'hidesysops', false, FormOptions::BOOL );
-
-               $this->opts->fetchValuesFromRequest( $this->getRequest() );
-
-               if ( $this->opts->getValue( 'hidebots' ) == 1 ) {
+               if ( $opts->getValue( 'hidebots' ) == 1 ) {
                        $this->hideRights[] = 'bot';
                }
-               if ( $this->opts->getValue( 'hidesysops' ) == 1 ) {
+               if ( $opts->getValue( 'hidesysops' ) == 1 ) {
                        $this->hideGroups[] = 'sysop';
                }
        }
@@ -203,52 +192,4 @@ class ActiveUsersPager extends UsersPager {
                return Html::rawElement( 'li', [], "{$item} [{$count}]{$blocked}" );
        }
 
-       function getPageHeader() {
-               $self = $this->getTitle();
-               $limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : '';
-
-               # Form tag
-               $out = Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] );
-               $out .= Xml::fieldset( $this->msg( 'activeusers' )->text() ) . "\n";
-               $out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
-
-               # Username field (with autocompletion support)
-               $this->getOutput()->addModules( 'mediawiki.userSuggest' );
-               $out .= Xml::inputLabel(
-                               $this->msg( 'activeusers-from' )->text(),
-                               'username',
-                               'offset',
-                               20,
-                               $this->requestedUser,
-                               [
-                                       'class' => 'mw-ui-input-inline mw-autocomplete-user',
-                                       'tabindex' => 1,
-                               ] + (
-                                       // Set autofocus on blank input
-                               $this->requestedUser === '' ? [ 'autofocus' => '' ] : []
-                               )
-                       ) . '<br />';
-
-               $out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(),
-                       'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), [ 'tabindex' => 2 ] );
-
-               $out .= Xml::checkLabel(
-                               $this->msg( 'activeusers-hidesysops' )->text(),
-                               'hidesysops',
-                               'hidesysops',
-                               $this->opts->getValue( 'hidesysops' ),
-                               [ 'tabindex' => 3 ]
-                       ) . '<br />';
-
-               # Submit button and form bottom
-               $out .= Xml::submitButton(
-                               $this->msg( 'activeusers-submit' )->text(),
-                               [ 'tabindex' => 4 ]
-                       ) . "\n";
-               $out .= Xml::closeElement( 'fieldset' );
-               $out .= Xml::closeElement( 'form' );
-
-               return $out;
-       }
-
 }
diff --git a/includes/specials/pre-authmanager/README b/includes/specials/pre-authmanager/README
new file mode 100644 (file)
index 0000000..1cfdd5f
--- /dev/null
@@ -0,0 +1,10 @@
+This directory temporarily hosts pre-AuthManager code as a way of feature-flagging.
+Class names are postfixed with 'PreAuthManager' and SpecialPageFactory adds/removes
+that postfix based on the feature flag.
+
+This is a horrible hack that will only be in place for a few weeks, to allow instant
+rollback while AuthManager is tested in WMF production and major problems are ironed
+out. In the past such issues have been handled via deployment branches, but that
+meant blocking the work of all WMF developers from being deployed. This is hoped
+to be a less disruptive method.
+
diff --git a/includes/specials/pre-authmanager/SpecialChangeEmail.php b/includes/specials/pre-authmanager/SpecialChangeEmail.php
new file mode 100644 (file)
index 0000000..7861562
--- /dev/null
@@ -0,0 +1,216 @@
+<?php
+/**
+ * Implements Special:ChangeEmail
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Let users change their email address.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialChangeEmailPreAuthManager extends FormSpecialPage {
+       /**
+        * @var Status
+        */
+       private $status;
+
+       public function __construct() {
+               parent::__construct( 'ChangeEmail', 'editmyprivateinfo' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       /**
+        * @return bool
+        */
+       public function isListed() {
+               global $wgAuth;
+
+               return $wgAuth->allowPropChange( 'emailaddress' );
+       }
+
+       /**
+        * Main execution point
+        * @param string $par
+        */
+       function execute( $par ) {
+               $out = $this->getOutput();
+               $out->disallowUserJs();
+
+               parent::execute( $par );
+       }
+
+       protected function checkExecutePermissions( User $user ) {
+               global $wgAuth;
+
+               if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
+                       throw new ErrorPageError( 'changeemail', 'cannotchangeemail' );
+               }
+
+               $this->requireLogin( 'changeemail-no-info' );
+
+               // This could also let someone check the current email address, so
+               // require both permissions.
+               if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
+                       throw new PermissionsError( 'viewmyprivateinfo' );
+               }
+
+               parent::checkExecutePermissions( $user );
+       }
+
+       protected function getFormFields() {
+               $user = $this->getUser();
+
+               $fields = [
+                       'Name' => [
+                               'type' => 'info',
+                               'label-message' => 'username',
+                               'default' => $user->getName(),
+                       ],
+                       'OldEmail' => [
+                               'type' => 'info',
+                               'label-message' => 'changeemail-oldemail',
+                               'default' => $user->getEmail() ?: $this->msg( 'changeemail-none' )->text(),
+                       ],
+                       'NewEmail' => [
+                               'type' => 'email',
+                               'label-message' => 'changeemail-newemail',
+                               'autofocus' => true,
+                               'help-message' => 'changeemail-newemail-help',
+                       ],
+               ];
+
+               if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
+                       $fields['Password'] = [
+                               'type' => 'password',
+                               'label-message' => 'changeemail-password'
+                       ];
+               }
+
+               return $fields;
+       }
+
+       protected function getDisplayFormat() {
+               return 'ooui';
+       }
+
+       protected function alterForm( HTMLForm $form ) {
+               $form->setId( 'mw-changeemail-form' );
+               $form->setTableId( 'mw-changeemail-table' );
+               $form->setSubmitTextMsg( 'changeemail-submit' );
+               $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
+               $form->addHeaderText( $this->msg( 'changeemail-header' )->parseAsBlock() );
+               if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
+                       $form->addHeaderText( $this->msg( 'changeemail-passwordrequired' )->parseAsBlock() );
+               }
+       }
+
+       public function onSubmit( array $data ) {
+               $password = isset( $data['Password'] ) ? $data['Password'] : null;
+               $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] );
+
+               $this->status = $status;
+
+               return $status;
+       }
+
+       public function onSuccess() {
+               $request = $this->getRequest();
+
+               $returnto = $request->getVal( 'returnto' );
+               $titleObj = $returnto !== null ? Title::newFromText( $returnto ) : null;
+               if ( !$titleObj instanceof Title ) {
+                       $titleObj = Title::newMainPage();
+               }
+               $query = $request->getVal( 'returntoquery' );
+
+               if ( $this->status->value === true ) {
+                       $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+               } elseif ( $this->status->value === 'eauth' ) {
+                       # Notify user that a confirmation email has been sent...
+                       $this->getOutput()->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
+                               'eauthentsent', $this->getUser()->getName() );
+                       // just show the link to go back
+                       $this->getOutput()->addReturnTo( $titleObj, wfCgiToArray( $query ) );
+               }
+       }
+
+       /**
+        * @param User $user
+        * @param string $pass
+        * @param string $newaddr
+        * @return Status
+        */
+       private function attemptChange( User $user, $pass, $newaddr ) {
+               global $wgAuth;
+
+               if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
+                       return Status::newFatal( 'invalidemailaddress' );
+               }
+
+               if ( $newaddr === $user->getEmail() ) {
+                       return Status::newFatal( 'changeemail-nochange' );
+               }
+
+               $throttleInfo = LoginForm::incrementLoginThrottle( $user->getName() );
+               if ( $throttleInfo ) {
+                       $lang = $this->getLanguage();
+                       return Status::newFatal(
+                               'changeemail-throttled',
+                               $lang->formatDuration( $throttleInfo['wait'] )
+                       );
+               }
+
+               if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' )
+                       && !$user->checkTemporaryPassword( $pass )
+                       && !$user->checkPassword( $pass )
+               ) {
+                       return Status::newFatal( 'wrongpassword' );
+               }
+
+               LoginForm::clearLoginThrottle( $user->getName() );
+
+               $oldaddr = $user->getEmail();
+               $status = $user->setEmailWithConfirmation( $newaddr );
+               if ( !$status->isGood() ) {
+                       return $status;
+               }
+
+               Hooks::run( 'PrefsEmailAudit', [ $user, $oldaddr, $newaddr ] );
+
+               $user->saveSettings();
+
+               $wgAuth->updateExternalDB( $user );
+
+               return $status;
+       }
+
+       public function requiresUnblock() {
+               return false;
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+}
diff --git a/includes/specials/pre-authmanager/SpecialChangePassword.php b/includes/specials/pre-authmanager/SpecialChangePassword.php
new file mode 100644 (file)
index 0000000..3955fee
--- /dev/null
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Implements Special:ChangePassword
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Let users recover their password.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialChangePasswordPreAuthManager extends FormSpecialPage {
+       protected $mUserName;
+       protected $mDomain;
+
+       // Optional Wikitext Message to show above the password change form
+       protected $mPreTextMessage = null;
+
+       // label for old password input
+       protected $mOldPassMsg = null;
+
+       public function __construct() {
+               parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
+               $this->listed( false );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       /**
+        * Main execution point
+        * @param string|null $par
+        */
+       function execute( $par ) {
+               $this->getOutput()->disallowUserJs();
+
+               parent::execute( $par );
+       }
+
+       protected function checkExecutePermissions( User $user ) {
+               parent::checkExecutePermissions( $user );
+
+               if ( !$this->getRequest()->wasPosted() ) {
+                       $this->requireLogin( 'resetpass-no-info' );
+               }
+       }
+
+       /**
+        * Set a message at the top of the Change Password form
+        * @since 1.23
+        * @param Message $msg Message to parse and add to the form header
+        */
+       public function setChangeMessage( Message $msg ) {
+               $this->mPreTextMessage = $msg;
+       }
+
+       /**
+        * Set a message at the top of the Change Password form
+        * @since 1.23
+        * @param string $msg Message label for old/temp password field
+        */
+       public function setOldPasswordMessage( $msg ) {
+               $this->mOldPassMsg = $msg;
+       }
+
+       protected function getFormFields() {
+               $user = $this->getUser();
+               $request = $this->getRequest();
+
+               $oldpassMsg = $this->mOldPassMsg;
+               if ( $oldpassMsg === null ) {
+                       $oldpassMsg = $user->isLoggedIn() ? 'oldpassword' : 'resetpass-temp-password';
+               }
+
+               $fields = [
+                       'Name' => [
+                               'type' => 'info',
+                               'label-message' => 'username',
+                               'default' => $request->getVal( 'wpName', $user->getName() ),
+                       ],
+                       'Password' => [
+                               'type' => 'password',
+                               'label-message' => $oldpassMsg,
+                       ],
+                       'NewPassword' => [
+                               'type' => 'password',
+                               'label-message' => 'newpassword',
+                       ],
+                       'Retype' => [
+                               'type' => 'password',
+                               'label-message' => 'retypenew',
+                       ],
+               ];
+
+               if ( !$this->getUser()->isLoggedIn() ) {
+                       $fields['LoginOnChangeToken'] = [
+                               'type' => 'hidden',
+                               'label' => 'Change Password Token',
+                               'default' => LoginForm::getLoginToken()->toString(),
+                       ];
+               }
+
+               $extraFields = [];
+               Hooks::run( 'ChangePasswordForm', [ &$extraFields ] );
+               foreach ( $extraFields as $extra ) {
+                       list( $name, $label, $type, $default ) = $extra;
+                       $fields[$name] = [
+                               'type' => $type,
+                               'name' => $name,
+                               'label-message' => $label,
+                               'default' => $default,
+                       ];
+               }
+
+               if ( !$user->isLoggedIn() ) {
+                       $fields['Remember'] = [
+                               'type' => 'check',
+                               'label' => $this->msg( 'remembermypassword' )
+                                               ->numParams(
+                                                       ceil( $this->getConfig()->get( 'CookieExpiration' ) / ( 3600 * 24 ) )
+                                               )->text(),
+                               'default' => $request->getVal( 'wpRemember' ),
+                       ];
+               }
+
+               return $fields;
+       }
+
+       protected function alterForm( HTMLForm $form ) {
+               $form->setId( 'mw-resetpass-form' );
+               $form->setTableId( 'mw-resetpass-table' );
+               $form->setWrapperLegendMsg( 'resetpass_header' );
+               $form->setSubmitTextMsg(
+                       $this->getUser()->isLoggedIn()
+                               ? 'resetpass-submit-loggedin'
+                               : 'resetpass_submit'
+               );
+               $form->addButton( [
+                       'name' => 'wpCancel',
+                       'value' => $this->msg( 'resetpass-submit-cancel' )->text()
+               ] );
+               $form->setHeaderText( $this->msg( 'resetpass_text' )->parseAsBlock() );
+               if ( $this->mPreTextMessage instanceof Message ) {
+                       $form->addPreText( $this->mPreTextMessage->parseAsBlock() );
+               }
+               $form->addHiddenFields(
+                       $this->getRequest()->getValues( 'wpName', 'wpDomain', 'returnto', 'returntoquery' ) );
+       }
+
+       public function onSubmit( array $data ) {
+               global $wgAuth;
+
+               $request = $this->getRequest();
+
+               if ( $request->getCheck( 'wpLoginToken' ) ) {
+                       // This comes from Special:Userlogin when logging in with a temporary password
+                       return false;
+               }
+
+               if ( !$this->getUser()->isLoggedIn()
+                       && !LoginForm::getLoginToken()->match( $request->getVal( 'wpLoginOnChangeToken' ) )
+               ) {
+                       // Potential CSRF (bug 62497)
+                       return false;
+               }
+
+               if ( $request->getCheck( 'wpCancel' ) ) {
+                       $returnto = $request->getVal( 'returnto' );
+                       $titleObj = $returnto !== null ? Title::newFromText( $returnto ) : null;
+                       if ( !$titleObj instanceof Title ) {
+                               $titleObj = Title::newMainPage();
+                       }
+                       $query = $request->getVal( 'returntoquery' );
+                       $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+
+                       return true;
+               }
+
+               $this->mUserName = $request->getVal( 'wpName', $this->getUser()->getName() );
+               $this->mDomain = $wgAuth->getDomain();
+
+               if ( !$wgAuth->allowPasswordChange() ) {
+                       throw new ErrorPageError( 'changepassword', 'resetpass_forbidden' );
+               }
+
+               $status = $this->attemptReset( $data['Password'], $data['NewPassword'], $data['Retype'] );
+
+               return $status;
+       }
+
+       public function onSuccess() {
+               if ( $this->getUser()->isLoggedIn() ) {
+                       $this->getOutput()->wrapWikiMsg(
+                               "<div class=\"successbox\">\n$1\n</div>",
+                               'changepassword-success'
+                       );
+                       $this->getOutput()->returnToMain();
+               } else {
+                       $request = $this->getRequest();
+                       LoginForm::clearLoginToken();
+                       $token = LoginForm::getLoginToken()->toString();
+                       $data = [
+                               'action' => 'submitlogin',
+                               'wpName' => $this->mUserName,
+                               'wpDomain' => $this->mDomain,
+                               'wpLoginToken' => $token,
+                               'wpPassword' => $request->getVal( 'wpNewPassword' ),
+                       ] + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
+                       $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
+                       $login->setContext( $this->getContext() );
+                       $login->execute( null );
+               }
+       }
+
+       /**
+        * Checks the new password if it meets the requirements for passwords and set
+        * it as a current password, otherwise set the passed Status object to fatal
+        * and doesn't change anything
+        *
+        * @param string $oldpass The current (temporary) password.
+        * @param string $newpass The password to set.
+        * @param string $retype The string of the retype password field to check with newpass
+        * @return Status
+        */
+       protected function attemptReset( $oldpass, $newpass, $retype ) {
+               $isSelf = ( $this->mUserName === $this->getUser()->getName() );
+               if ( $isSelf ) {
+                       $user = $this->getUser();
+               } else {
+                       $user = User::newFromName( $this->mUserName );
+               }
+
+               if ( !$user || $user->isAnon() ) {
+                       return Status::newFatal( $this->msg( 'nosuchusershort', $this->mUserName ) );
+               }
+
+               if ( $newpass !== $retype ) {
+                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'badretype' ] );
+                       return Status::newFatal( $this->msg( 'badretype' ) );
+               }
+
+               $throttleInfo = LoginForm::incrementLoginThrottle( $this->mUserName );
+               if ( $throttleInfo ) {
+                       return Status::newFatal( $this->msg( 'changepassword-throttled' )
+                               ->durationParams( $throttleInfo['wait'] )
+                       );
+               }
+
+               // @todo Make these separate messages, since the message is written for both cases
+               if ( !$user->checkTemporaryPassword( $oldpass ) && !$user->checkPassword( $oldpass ) ) {
+                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'wrongpassword' ] );
+                       return Status::newFatal( $this->msg( 'resetpass-wrong-oldpass' ) );
+               }
+
+               // User is resetting their password to their old password
+               if ( $oldpass === $newpass ) {
+                       return Status::newFatal( $this->msg( 'resetpass-recycled' ) );
+               }
+
+               // Do AbortChangePassword after checking mOldpass, so we don't leak information
+               // by possibly aborting a new password before verifying the old password.
+               $abortMsg = 'resetpass-abort-generic';
+               if ( !Hooks::run( 'AbortChangePassword', [ $user, $oldpass, $newpass, &$abortMsg ] ) ) {
+                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'abortreset' ] );
+                       return Status::newFatal( $this->msg( $abortMsg ) );
+               }
+
+               // Please reset throttle for successful logins, thanks!
+               LoginForm::clearLoginThrottle( $this->mUserName );
+
+               try {
+                       $user->setPassword( $newpass );
+                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'success' ] );
+               } catch ( PasswordError $e ) {
+                       Hooks::run( 'PrefsPasswordAudit', [ $user, $newpass, 'error' ] );
+                       return Status::newFatal( new RawMessage( $e->getMessage() ) );
+               }
+
+               if ( $isSelf ) {
+                       // This is needed to keep the user connected since
+                       // changing the password also modifies the user's token.
+                       $remember = $this->getRequest()->getCookie( 'Token' ) !== null;
+                       $user->setCookies( null, null, $remember );
+               }
+               $user->saveSettings();
+               $this->resetPasswordExpiration( $user );
+               return Status::newGood();
+       }
+
+       public function requiresUnblock() {
+               return false;
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+
+       /**
+        * For resetting user password expiration, until AuthManager comes along
+        * @param User $user
+        */
+       private function resetPasswordExpiration( User $user ) {
+               global $wgPasswordExpirationDays;
+               $newExpire = null;
+               if ( $wgPasswordExpirationDays ) {
+                       $newExpire = wfTimestamp(
+                               TS_MW,
+                               time() + ( $wgPasswordExpirationDays * 24 * 3600 )
+                       );
+               }
+               // Give extensions a chance to force an expiration
+               Hooks::run( 'ResetPasswordExpiration', [ $this, &$newExpire ] );
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->update(
+                       'user',
+                       [ 'user_password_expires' => $dbw->timestampOrNull( $newExpire ) ],
+                       [ 'user_id' => $user->getId() ],
+                       __METHOD__
+               );
+       }
+
+       protected function getDisplayFormat() {
+               return 'ooui';
+       }
+}
diff --git a/includes/specials/pre-authmanager/SpecialCreateAccount.php b/includes/specials/pre-authmanager/SpecialCreateAccount.php
new file mode 100644 (file)
index 0000000..14f70b5
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ * @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialCreateAccountPreAuthManager extends SpecialRedirectToSpecial {
+       function __construct() {
+               parent::__construct(
+                       'CreateAccount',
+                       'Userlogin',
+                       'signup',
+                       [ 'returnto', 'returntoquery', 'uselang' ]
+               );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       // No reason to hide this link on Special:Specialpages
+       public function isListed() {
+               return true;
+       }
+
+       public function isRestricted() {
+               return !User::groupHasPermission( '*', 'createaccount' );
+       }
+
+       public function userCanExecute( User $user ) {
+               return $user->isAllowed( 'createaccount' );
+       }
+
+       protected function getGroupName() {
+               return 'login';
+       }
+}
diff --git a/includes/specials/pre-authmanager/SpecialPasswordReset.php b/includes/specials/pre-authmanager/SpecialPasswordReset.php
new file mode 100644 (file)
index 0000000..e8719a7
--- /dev/null
@@ -0,0 +1,378 @@
+<?php
+/**
+ * Implements Special:PasswordReset
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Special page for requesting a password reset email
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPasswordResetPreAuthManager extends FormSpecialPage {
+       /**
+        * @var Message
+        */
+       private $email;
+
+       /**
+        * @var User
+        */
+       private $firstUser;
+
+       /**
+        * @var Status
+        */
+       private $result;
+
+       /**
+        * @var string $method Identifies which password reset field was specified by the user.
+        */
+       private $method;
+
+       public function __construct() {
+               parent::__construct( 'PasswordReset', 'editmyprivateinfo' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       public function userCanExecute( User $user ) {
+               return $this->canChangePassword( $user ) === true && parent::userCanExecute( $user );
+       }
+
+       public function checkExecutePermissions( User $user ) {
+               $error = $this->canChangePassword( $user );
+               if ( is_string( $error ) ) {
+                       throw new ErrorPageError( 'internalerror', $error );
+               } elseif ( !$error ) {
+                       throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
+               }
+
+               parent::checkExecutePermissions( $user );
+       }
+
+       protected function getFormFields() {
+               global $wgAuth;
+               $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
+               $a = [];
+               if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
+                       $a['Username'] = [
+                               'type' => 'text',
+                               'label-message' => 'passwordreset-username',
+                       ];
+
+                       if ( $this->getUser()->isLoggedIn() ) {
+                               $a['Username']['default'] = $this->getUser()->getName();
+                       }
+               }
+
+               if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
+                       $a['Email'] = [
+                               'type' => 'email',
+                               'label-message' => 'passwordreset-email',
+                       ];
+               }
+
+               if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
+                       $domains = $wgAuth->domainList();
+                       $a['Domain'] = [
+                               'type' => 'select',
+                               'options' => $domains,
+                               'label-message' => 'passwordreset-domain',
+                       ];
+               }
+
+               if ( $this->getUser()->isAllowed( 'passwordreset' ) ) {
+                       $a['Capture'] = [
+                               'type' => 'check',
+                               'label-message' => 'passwordreset-capture',
+                               'help-message' => 'passwordreset-capture-help',
+                       ];
+               }
+
+               return $a;
+       }
+
+       protected function getDisplayFormat() {
+               return 'ooui';
+       }
+
+       public function alterForm( HTMLForm $form ) {
+               $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
+
+               $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
+               $i = 0;
+               if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
+                       $i++;
+               }
+               if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
+                       $i++;
+               }
+               if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
+                       $i++;
+               }
+
+               $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
+
+               $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() );
+               $form->setSubmitTextMsg( 'mailmypassword' );
+       }
+
+       /**
+        * Process the form.  At this point we know that the user passes all the criteria in
+        * userCanExecute(), and if the data array contains 'Username', etc, then Username
+        * resets are allowed.
+        * @param array $data
+        * @throws MWException
+        * @throws ThrottledError|PermissionsError
+        * @return bool|array
+        */
+       public function onSubmit( array $data ) {
+               global $wgAuth, $wgMinimalPasswordLength;
+
+               if ( isset( $data['Domain'] ) ) {
+                       if ( $wgAuth->validDomain( $data['Domain'] ) ) {
+                               $wgAuth->setDomain( $data['Domain'] );
+                       } else {
+                               $wgAuth->setDomain( 'invaliddomain' );
+                       }
+               }
+
+               if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) {
+                       // The user knows they don't have the passwordreset permission,
+                       // but they tried to spoof the form. That's naughty
+                       throw new PermissionsError( 'passwordreset' );
+               }
+
+               /**
+                * @var $firstUser User
+                * @var $users User[]
+                */
+
+               if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
+                       $method = 'username';
+                       $users = [ User::newFromName( $data['Username'] ) ];
+               } elseif ( isset( $data['Email'] )
+                       && $data['Email'] !== ''
+                       && Sanitizer::validateEmail( $data['Email'] )
+               ) {
+                       $method = 'email';
+                       $res = wfGetDB( DB_SLAVE )->select(
+                               'user',
+                               User::selectFields(),
+                               [ 'user_email' => $data['Email'] ],
+                               __METHOD__
+                       );
+
+                       if ( $res ) {
+                               $users = [];
+
+                               foreach ( $res as $row ) {
+                                       $users[] = User::newFromRow( $row );
+                               }
+                       } else {
+                               // Some sort of database error, probably unreachable
+                               throw new MWException( 'Unknown database error in ' . __METHOD__ );
+                       }
+               } else {
+                       // The user didn't supply any data
+                       return false;
+               }
+
+               // Check for hooks (captcha etc), and allow them to modify the users list
+               $error = [];
+               if ( !Hooks::run( 'SpecialPasswordResetOnSubmit', [ &$users, $data, &$error ] ) ) {
+                       return [ $error ];
+               }
+
+               $this->method = $method;
+
+               if ( count( $users ) == 0 ) {
+                       if ( $method == 'email' ) {
+                               // Don't reveal whether or not an email address is in use
+                               return true;
+                       } else {
+                               return [ 'noname' ];
+                       }
+               }
+
+               $firstUser = $users[0];
+
+               if ( !$firstUser instanceof User || !$firstUser->getId() ) {
+                       // Don't parse username as wikitext (bug 65501)
+                       return [ [ 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ] ];
+               }
+
+               // Check against the rate limiter
+               if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) {
+                       throw new ThrottledError;
+               }
+
+               // Check against password throttle
+               foreach ( $users as $user ) {
+                       if ( $user->isPasswordReminderThrottled() ) {
+
+                               # Round the time in hours to 3 d.p., in case someone is specifying
+                               # minutes or seconds.
+                               return [ [
+                                       'throttled-mailpassword',
+                                       round( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 )
+                               ] ];
+                       }
+               }
+
+               // All the users will have the same email address
+               if ( $firstUser->getEmail() == '' ) {
+                       // This won't be reachable from the email route, so safe to expose the username
+                       return [ [ 'noemail', wfEscapeWikiText( $firstUser->getName() ) ] ];
+               }
+
+               // We need to have a valid IP address for the hook, but per bug 18347, we should
+               // send the user's name if they're logged in.
+               $ip = $this->getRequest()->getIP();
+               if ( !$ip ) {
+                       return [ 'badipaddress' ];
+               }
+               $caller = $this->getUser();
+               Hooks::run( 'User::mailPasswordInternal', [ &$caller, &$ip, &$firstUser ] );
+               $username = $caller->getName();
+               $msg = IP::isValid( $username )
+                       ? 'passwordreset-emailtext-ip'
+                       : 'passwordreset-emailtext-user';
+
+               // Send in the user's language; which should hopefully be the same
+               $userLanguage = $firstUser->getOption( 'language' );
+
+               $passwords = [];
+               foreach ( $users as $user ) {
+                       $password = PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
+                       $user->setNewpassword( $password );
+                       $user->saveSettings();
+                       $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password )
+                               ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later
+               }
+               $passwordBlock = implode( "\n\n", $passwords );
+
+               $this->email = $this->msg( $msg )->inLanguage( $userLanguage );
+               $this->email->params(
+                       $username,
+                       $passwordBlock,
+                       count( $passwords ),
+                       '<' . Title::newMainPage()->getCanonicalURL() . '>',
+                       round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 )
+               );
+
+               $title = $this->msg( 'passwordreset-emailtitle' )->inLanguage( $userLanguage );
+
+               $this->result = $firstUser->sendMail( $title->text(), $this->email->text() );
+
+               if ( isset( $data['Capture'] ) && $data['Capture'] ) {
+                       // Save the user, will be used if an error occurs when sending the email
+                       $this->firstUser = $firstUser;
+               } else {
+                       // Blank the email if the user is not supposed to see it
+                       $this->email = null;
+               }
+
+               if ( $this->result->isGood() ) {
+                       return true;
+               } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) {
+                       // The email didn't send, but maybe they knew that and that's why they captured it
+                       return true;
+               } else {
+                       // @todo FIXME: The email wasn't sent, but we have already set
+                       // the password throttle timestamp, so they won't be able to try
+                       // again until it expires...  :(
+                       return [ [ 'mailerror', $this->result->getMessage() ] ];
+               }
+       }
+
+       public function onSuccess() {
+               if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) {
+                       // @todo Logging
+
+                       if ( $this->result->isGood() ) {
+                               $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' );
+                       } else {
+                               $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture',
+                                       $this->result->getMessage(), $this->firstUser->getName() );
+                       }
+
+                       $this->getOutput()->addHTML( Html::rawElement( 'pre', [], $this->email->escaped() ) );
+               }
+
+               if ( $this->method === 'email' ) {
+                       $this->getOutput()->addWikiMsg( 'passwordreset-emailsentemail' );
+               } else {
+                       $this->getOutput()->addWikiMsg( 'passwordreset-emailsentusername' );
+               }
+
+               $this->getOutput()->returnToMain();
+       }
+
+       protected function canChangePassword( User $user ) {
+               global $wgAuth;
+               $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
+
+               // Maybe password resets are disabled, or there are no allowable routes
+               if ( !is_array( $resetRoutes ) ||
+                       !in_array( true, array_values( $resetRoutes ) )
+               ) {
+                       return 'passwordreset-disabled';
+               }
+
+               // Maybe the external auth plugin won't allow local password changes
+               if ( !$wgAuth->allowPasswordChange() ) {
+                       return 'resetpass_forbidden';
+               }
+
+               // Maybe email features have been disabled
+               if ( !$this->getConfig()->get( 'EnableEmail' ) ) {
+                       return 'passwordreset-emaildisabled';
+               }
+
+               // Maybe the user is blocked (check this here rather than relying on the parent
+               // method as we have a more specific error message to use here
+               if ( $user->isBlocked() ) {
+                       return 'blocked-mailpassword';
+               }
+
+               return true;
+       }
+
+       /**
+        * Hide the password reset page if resets are disabled.
+        * @return bool
+        */
+       function isListed() {
+               if ( $this->canChangePassword( $this->getUser() ) === true ) {
+                       return parent::isListed();
+               }
+
+               return false;
+       }
+
+       protected function getGroupName() {
+               return 'users';
+       }
+}
diff --git a/includes/specials/pre-authmanager/SpecialUserlogin.php b/includes/specials/pre-authmanager/SpecialUserlogin.php
new file mode 100644 (file)
index 0000000..4af5cf6
--- /dev/null
@@ -0,0 +1,1842 @@
+<?php
+/**
+ * Implements Special:UserLogin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LogLevel;
+use MediaWiki\Session\SessionManager;
+
+/**
+ * Implements Special:UserLogin
+ *
+ * @ingroup SpecialPage
+ */
+class LoginFormPreAuthManager extends SpecialPage {
+       const SUCCESS = 0;
+       const NO_NAME = 1;
+       const ILLEGAL = 2;
+       const WRONG_PLUGIN_PASS = 3;
+       const NOT_EXISTS = 4;
+       const WRONG_PASS = 5;
+       const EMPTY_PASS = 6;
+       const RESET_PASS = 7;
+       const ABORTED = 8;
+       const CREATE_BLOCKED = 9;
+       const THROTTLED = 10;
+       const USER_BLOCKED = 11;
+       const NEED_TOKEN = 12;
+       const WRONG_TOKEN = 13;
+       const USER_MIGRATED = 14;
+
+       public static $statusCodes = [
+               self::SUCCESS => 'success',
+               self::NO_NAME => 'no_name',
+               self::ILLEGAL => 'illegal',
+               self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
+               self::NOT_EXISTS => 'not_exists',
+               self::WRONG_PASS => 'wrong_pass',
+               self::EMPTY_PASS => 'empty_pass',
+               self::RESET_PASS => 'reset_pass',
+               self::ABORTED => 'aborted',
+               self::CREATE_BLOCKED => 'create_blocked',
+               self::THROTTLED => 'throttled',
+               self::USER_BLOCKED => 'user_blocked',
+               self::NEED_TOKEN => 'need_token',
+               self::WRONG_TOKEN => 'wrong_token',
+               self::USER_MIGRATED => 'user_migrated',
+       ];
+
+       /**
+        * Valid error and warning messages
+        *
+        * Special:Userlogin can show an error or warning message on the form when
+        * coming from another page. This is done via the ?error= or ?warning= GET
+        * parameters.
+        *
+        * This array is the list of valid message keys. All other values will be
+        * ignored.
+        *
+        * @since 1.24
+        * @var string[]
+        */
+       public static $validErrorMessages = [
+               'exception-nologin-text',
+               'watchlistanontext',
+               'changeemail-no-info',
+               'resetpass-no-info',
+               'confirmemail_needlogin',
+               'prefsnologintext2',
+       ];
+
+       public $mAbortLoginErrorMsg = null;
+       /**
+        * @var int How many seconds user is throttled for
+        * @since 1.27
+        */
+       public $mThrottleWait = '?';
+
+       protected $mUsername;
+       protected $mPassword;
+       protected $mRetype;
+       protected $mReturnTo;
+       protected $mCookieCheck;
+       protected $mPosted;
+       protected $mAction;
+       protected $mCreateaccount;
+       protected $mCreateaccountMail;
+       protected $mLoginattempt;
+       protected $mRemember;
+       protected $mEmail;
+       protected $mDomain;
+       protected $mLanguage;
+       protected $mSkipCookieCheck;
+       protected $mReturnToQuery;
+       protected $mToken;
+       protected $mStickHTTPS;
+       protected $mType;
+       protected $mReason;
+       protected $mRealName;
+       protected $mEntryError = '';
+       protected $mEntryErrorType = 'error';
+
+       private $mTempPasswordUsed;
+       private $mLoaded = false;
+       private $mSecureLoginUrl;
+
+       /** @var WebRequest */
+       private $mOverrideRequest = null;
+
+       /** @var WebRequest Effective request; set at the beginning of load */
+       private $mRequest = null;
+
+       /**
+        * @param WebRequest $request
+        */
+       public function __construct( $request = null ) {
+               global $wgUseMediaWikiUIEverywhere;
+               parent::__construct( 'Userlogin' );
+
+               $this->mOverrideRequest = $request;
+               // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
+               $wgUseMediaWikiUIEverywhere = true;
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       /**
+        * Returns an array of all valid error messages.
+        *
+        * @return array
+        */
+       public static function getValidErrorMessages() {
+               static $messages = null;
+               if ( !$messages ) {
+                       $messages = self::$validErrorMessages;
+                       Hooks::run( 'LoginFormValidErrorMessages', [ &$messages ] );
+               }
+
+               return $messages;
+       }
+
+       /**
+        * Loader
+        */
+       function load() {
+               global $wgAuth, $wgHiddenPrefs, $wgEnableEmail;
+
+               if ( $this->mLoaded ) {
+                       return;
+               }
+               $this->mLoaded = true;
+
+               if ( $this->mOverrideRequest === null ) {
+                       $request = $this->getRequest();
+               } else {
+                       $request = $this->mOverrideRequest;
+               }
+               $this->mRequest = $request;
+
+               $this->mType = $request->getText( 'type' );
+               $this->mUsername = $request->getText( 'wpName' );
+               $this->mPassword = $request->getText( 'wpPassword' );
+               $this->mRetype = $request->getText( 'wpRetype' );
+               $this->mDomain = $request->getText( 'wpDomain' );
+               $this->mReason = $request->getText( 'wpReason' );
+               $this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
+               $this->mPosted = $request->wasPosted();
+               $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
+                       && $wgEnableEmail;
+               $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail;
+               $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
+               $this->mAction = $request->getVal( 'action' );
+               $this->mRemember = $request->getCheck( 'wpRemember' );
+               $this->mFromHTTP = $request->getBool( 'fromhttp', false )
+                       || $request->getBool( 'wpFromhttp', false );
+               $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
+                       || $request->getBool( 'wpForceHttps', false );
+               $this->mLanguage = $request->getText( 'uselang' );
+               $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
+               $this->mToken = $this->mType == 'signup'
+                       ? $request->getVal( 'wpCreateaccountToken' )
+                       : $request->getVal( 'wpLoginToken' );
+               $this->mReturnTo = $request->getVal( 'returnto', '' );
+               $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
+
+               // Show an error or warning passed on from a previous page
+               $entryError = $this->msg( $request->getVal( 'error', '' ) );
+               $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
+               // bc: provide login link as a parameter for messages where the translation
+               // was not updated
+               $loginreqlink = Linker::linkKnown(
+                       $this->getPageTitle(),
+                       $this->msg( 'loginreqlink' )->escaped(),
+                       [],
+                       [
+                               'returnto' => $this->mReturnTo,
+                               'returntoquery' => $this->mReturnToQuery,
+                               'uselang' => $this->mLanguage,
+                               'fromhttp' => $this->mFromHTTP ? '1' : '0',
+                       ]
+               );
+
+               // Only show valid error or warning messages.
+               if ( $entryError->exists()
+                       && in_array( $entryError->getKey(), self::getValidErrorMessages() )
+               ) {
+                       $this->mEntryErrorType = 'error';
+                       $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
+
+               } elseif ( $entryWarning->exists()
+                       && in_array( $entryWarning->getKey(), self::getValidErrorMessages() )
+               ) {
+                       $this->mEntryErrorType = 'warning';
+                       $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
+               }
+
+               if ( $wgEnableEmail ) {
+                       $this->mEmail = $request->getText( 'wpEmail' );
+               } else {
+                       $this->mEmail = '';
+               }
+               if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
+                       $this->mRealName = $request->getText( 'wpRealName' );
+               } else {
+                       $this->mRealName = '';
+               }
+
+               if ( !$wgAuth->validDomain( $this->mDomain ) ) {
+                       $this->mDomain = $wgAuth->getDomain();
+               }
+               $wgAuth->setDomain( $this->mDomain );
+
+               # 1. When switching accounts, it sucks to get automatically logged out
+               # 2. Do not return to PasswordReset after a successful password change
+               #    but goto Wiki start page (Main_Page) instead ( bug 33997 )
+               $returnToTitle = Title::newFromText( $this->mReturnTo );
+               if ( is_object( $returnToTitle )
+                       && ( $returnToTitle->isSpecial( 'Userlogout' )
+                               || $returnToTitle->isSpecial( 'PasswordReset' ) )
+               ) {
+                       $this->mReturnTo = '';
+                       $this->mReturnToQuery = '';
+               }
+       }
+
+       function getDescription() {
+               if ( $this->mType === 'signup' ) {
+                       return $this->msg( 'createaccount' )->text();
+               } else {
+                       return $this->msg( 'login' )->text();
+               }
+       }
+
+       /**
+        * @param string|null $subPage
+        */
+       public function execute( $subPage ) {
+               // Make sure session is persisted
+               $session = SessionManager::getGlobalSession();
+               $session->persist();
+
+               $this->load();
+
+               // Check for [[Special:Userlogin/signup]]. This affects form display and
+               // page title.
+               if ( $subPage == 'signup' ) {
+                       $this->mType = 'signup';
+               }
+               $this->setHeaders();
+
+               // Make sure it's possible to log in
+               if ( $this->mType !== 'signup' && !$session->canSetUser() ) {
+                       throw new ErrorPageError(
+                               'cannotloginnow-title',
+                               'cannotloginnow-text',
+                               [
+                                       $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
+                               ]
+                       );
+               }
+
+               /**
+                * In the case where the user is already logged in, and was redirected to
+                * the login form from a page that requires login, do not show the login
+                * page. The use case scenario for this is when a user opens a large number
+                * of tabs, is redirected to the login page on all of them, and then logs
+                * in on one, expecting all the others to work properly.
+                *
+                * However, do show the form if it was visited intentionally (no 'returnto'
+                * is present). People who often switch between several accounts have grown
+                * accustomed to this behavior.
+                */
+               if (
+                       $this->mType !== 'signup' &&
+                       !$this->mPosted &&
+                       $this->getUser()->isLoggedIn() &&
+                       ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' )
+               ) {
+                       $this->successfulLogin();
+               }
+
+               // If logging in and not on HTTPS, either redirect to it or offer a link.
+               global $wgSecureLogin;
+               if ( $this->mRequest->getProtocol() !== 'https' ) {
+                       $title = $this->getFullTitle();
+                       $query = [
+                               'returnto' => $this->mReturnTo !== '' ? $this->mReturnTo : null,
+                               'returntoquery' => $this->mReturnToQuery !== '' ?
+                                       $this->mReturnToQuery : null,
+                               'title' => null,
+                               ( $this->mEntryErrorType === 'error' ? 'error' : 'warning' ) => $this->mEntryError,
+                       ] + $this->mRequest->getQueryValues();
+                       $url = $title->getFullURL( $query, false, PROTO_HTTPS );
+                       if ( $wgSecureLogin
+                               && wfCanIPUseHTTPS( $this->getRequest()->getIP() )
+                               && !$this->mFromHTTP ) // Avoid infinite redirect
+                       {
+                               $url = wfAppendQuery( $url, 'fromhttp=1' );
+                               $this->getOutput()->redirect( $url );
+                               // Since we only do this redir to change proto, always vary
+                               $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
+
+                               return;
+                       } else {
+                               // A wiki without HTTPS login support should set $wgServer to
+                               // http://somehost, in which case the secure URL generated
+                               // above won't actually start with https://
+                               if ( substr( $url, 0, 8 ) === 'https://' ) {
+                                       $this->mSecureLoginUrl = $url;
+                               }
+                       }
+               }
+
+               if ( !is_null( $this->mCookieCheck ) ) {
+                       $this->onCookieRedirectCheck( $this->mCookieCheck );
+
+                       return;
+               } elseif ( $this->mPosted ) {
+                       if ( $this->mCreateaccount ) {
+                               $this->addNewAccount();
+
+                               return;
+                       } elseif ( $this->mCreateaccountMail ) {
+                               $this->addNewAccountMailPassword();
+
+                               return;
+                       } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
+                               $this->processLogin();
+
+                               return;
+                       }
+               }
+               $this->mainLoginForm( $this->mEntryError, $this->mEntryErrorType );
+       }
+
+       /**
+        * @private
+        */
+       function addNewAccountMailPassword() {
+               if ( $this->mEmail == '' ) {
+                       $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
+
+                       return;
+               }
+
+               $status = $this->addNewAccountInternal();
+               LoggerFactory::getInstance( 'authmanager' )->info(
+                       'Account creation attempt with mailed password',
+                       [ 'event' => 'accountcreation', 'status' => $status ]
+               );
+               if ( !$status->isGood() ) {
+                       $error = $status->getMessage();
+                       $this->mainLoginForm( $error->toString() );
+
+                       return;
+               }
+
+               /** @var User $u */
+               $u = $status->getValue();
+
+               // Wipe the initial password and mail a temporary one
+               $u->setPassword( null );
+               $u->saveSettings();
+               $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
+
+               Hooks::run( 'AddNewAccount', [ $u, true ] );
+               $u->addNewUserLogEntry( 'byemail', $this->mReason );
+
+               $out = $this->getOutput();
+               $out->setPageTitle( $this->msg( 'accmailtitle' ) );
+
+               if ( !$result->isGood() ) {
+                       $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
+               } else {
+                       $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
+                       $this->executeReturnTo( 'success' );
+               }
+       }
+
+       /**
+        * @private
+        * @return bool
+        */
+       function addNewAccount() {
+               global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
+
+               # Create the account and abort if there's a problem doing so
+               $status = $this->addNewAccountInternal();
+               LoggerFactory::getInstance( 'authmanager' )->info( 'Account creation attempt', [
+                       'event' => 'accountcreation',
+                       'status' => $status,
+               ] );
+
+               if ( !$status->isGood() ) {
+                       $error = $status->getMessage();
+                       $this->mainLoginForm( $error->toString() );
+
+                       return false;
+               }
+
+               $u = $status->getValue();
+
+               # Only save preferences if the user is not creating an account for someone else.
+               if ( $this->getUser()->isAnon() ) {
+                       # If we showed up language selection links, and one was in use, be
+                       # smart (and sensible) and save that language as the user's preference
+                       if ( $wgLoginLanguageSelector && $this->mLanguage ) {
+                               $u->setOption( 'language', $this->mLanguage );
+                       } else {
+
+                               # Otherwise the user's language preference defaults to $wgContLang,
+                               # but it may be better to set it to their preferred $wgContLang variant,
+                               # based on browser preferences or URL parameters.
+                               $u->setOption( 'language', $wgContLang->getPreferredVariant() );
+                       }
+                       if ( $wgContLang->hasVariants() ) {
+                               $u->setOption( 'variant', $wgContLang->getPreferredVariant() );
+                       }
+               }
+
+               $out = $this->getOutput();
+
+               # Send out an email authentication message if needed
+               if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
+                       $status = $u->sendConfirmationMail();
+                       if ( $status->isGood() ) {
+                               $out->addWikiMsg( 'confirmemail_oncreate' );
+                       } else {
+                               $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
+                       }
+               }
+
+               # Save settings (including confirmation token)
+               $u->saveSettings();
+
+               # If not logged in, assume the new account as the current one and set
+               # session cookies then show a "welcome" message or a "need cookies"
+               # message as needed
+               if ( $this->getUser()->isAnon() ) {
+                       $u->setCookies();
+                       $wgUser = $u;
+                       // This should set it for OutputPage and the Skin
+                       // which is needed or the personal links will be
+                       // wrong.
+                       $this->getContext()->setUser( $u );
+                       Hooks::run( 'AddNewAccount', [ $u, false ] );
+                       $u->addNewUserLogEntry( 'create' );
+                       if ( $this->hasSessionCookie() ) {
+                               $this->successfulCreation();
+                       } else {
+                               $this->cookieRedirectCheck( 'new' );
+                       }
+               } else {
+                       # Confirm that the account was created
+                       $out->setPageTitle( $this->msg( 'accountcreated' ) );
+                       $out->addWikiMsg( 'accountcreatedtext', $u->getName() );
+                       $out->addReturnTo( $this->getPageTitle() );
+                       Hooks::run( 'AddNewAccount', [ $u, false ] );
+                       $u->addNewUserLogEntry( 'create2', $this->mReason );
+               }
+
+               return true;
+       }
+
+       /**
+        * Make a new user account using the loaded data.
+        * @private
+        * @throws PermissionsError|ReadOnlyError
+        * @return Status
+        */
+       public function addNewAccountInternal() {
+               global $wgAuth, $wgAccountCreationThrottle, $wgEmailConfirmToEdit;
+
+               // If the user passes an invalid domain, something is fishy
+               if ( !$wgAuth->validDomain( $this->mDomain ) ) {
+                       return Status::newFatal( 'wrongpassword' );
+               }
+
+               // If we are not allowing users to login locally, we should be checking
+               // to see if the user is actually able to authenticate to the authenti-
+               // cation server before they create an account (otherwise, they can
+               // create a local account and login as any domain user). We only need
+               // to check this for domains that aren't local.
+               if ( 'local' != $this->mDomain && $this->mDomain != '' ) {
+                       if (
+                               !$wgAuth->canCreateAccounts() &&
+                               (
+                                       !$wgAuth->userExists( $this->mUsername ) ||
+                                       !$wgAuth->authenticate( $this->mUsername, $this->mPassword )
+                               )
+                       ) {
+                               return Status::newFatal( 'wrongpassword' );
+                       }
+               }
+
+               if ( wfReadOnly() ) {
+                       throw new ReadOnlyError;
+               }
+
+               # Request forgery checks.
+               $token = self::getCreateaccountToken();
+               if ( $token->wasNew() ) {
+                       return Status::newFatal( 'nocookiesfornew' );
+               }
+
+               # The user didn't pass a createaccount token
+               if ( !$this->mToken ) {
+                       return Status::newFatal( 'sessionfailure' );
+               }
+
+               # Validate the createaccount token
+               if ( !$token->match( $this->mToken ) ) {
+                       return Status::newFatal( 'sessionfailure' );
+               }
+
+               # Check permissions
+               $currentUser = $this->getUser();
+               $creationBlock = $currentUser->isBlockedFromCreateAccount();
+               if ( !$currentUser->isAllowed( 'createaccount' ) ) {
+                       throw new PermissionsError( 'createaccount' );
+               } elseif ( $creationBlock instanceof Block ) {
+                       // Throws an ErrorPageError.
+                       $this->userBlockedMessage( $creationBlock );
+
+                       // This should never be reached.
+                       return false;
+               }
+
+               # Include checks that will include GlobalBlocking (Bug 38333)
+               $permErrors = $this->getPageTitle()->getUserPermissionsErrors(
+                       'createaccount',
+                       $currentUser,
+                       true
+               );
+
+               if ( count( $permErrors ) ) {
+                       throw new PermissionsError( 'createaccount', $permErrors );
+               }
+
+               $ip = $this->getRequest()->getIP();
+               if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
+                       return Status::newFatal( 'sorbs_create_account_reason' );
+               }
+
+               # Now create a dummy user ($u) and check if it is valid
+               $u = User::newFromName( $this->mUsername, 'creatable' );
+               if ( !$u ) {
+                       return Status::newFatal( 'noname' );
+               }
+
+               $cache = ObjectCache::getLocalClusterInstance();
+               # Make sure the user does not exist already
+               $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $this->mUsername ) ) );
+               if ( !$lock ) {
+                       return Status::newFatal( 'usernameinprogress' );
+               } elseif ( $u->idForName( User::READ_LOCKING ) ) {
+                       return Status::newFatal( 'userexists' );
+               }
+
+               if ( $this->mCreateaccountMail ) {
+                       # do not force a password for account creation by email
+                       # set invalid password, it will be replaced later by a random generated password
+                       $this->mPassword = null;
+               } else {
+                       if ( $this->mPassword !== $this->mRetype ) {
+                               return Status::newFatal( 'badretype' );
+                       }
+
+                       # check for password validity, return a fatal Status if invalid
+                       $validity = $u->checkPasswordValidity( $this->mPassword, 'create' );
+                       if ( !$validity->isGood() ) {
+                               $validity->ok = false; // make sure this Status is fatal
+                               return $validity;
+                       }
+               }
+
+               # if you need a confirmed email address to edit, then obviously you
+               # need an email address.
+               if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) {
+                       return Status::newFatal( 'noemailtitle' );
+               }
+
+               if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) {
+                       return Status::newFatal( 'invalidemailaddress' );
+               }
+
+               # Set some additional data so the AbortNewAccount hook can be used for
+               # more than just username validation
+               $u->setEmail( $this->mEmail );
+               $u->setRealName( $this->mRealName );
+
+               $abortError = '';
+               $abortStatus = null;
+               if ( !Hooks::run( 'AbortNewAccount', [ $u, &$abortError, &$abortStatus ] ) ) {
+                       // Hook point to add extra creation throttles and blocks
+                       wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
+                       if ( $abortStatus === null ) {
+                               // Report back the old string as a raw message status.
+                               // This will report the error back as 'createaccount-hook-aborted'
+                               // with the given string as the message.
+                               // To return a different error code, return a Status object.
+                               $abortError = new Message( 'createaccount-hook-aborted', [ $abortError ] );
+                               $abortError->text();
+
+                               return Status::newFatal( $abortError );
+                       } else {
+                               // For MediaWiki 1.23+ and updated hooks, return the Status object
+                               // returned from the hook.
+                               return $abortStatus;
+                       }
+               }
+
+               // Hook point to check for exempt from account creation throttle
+               if ( !Hooks::run( 'ExemptFromAccountCreationThrottle', [ $ip ] ) ) {
+                       wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " .
+                               "allowed account creation w/o throttle\n" );
+               } else {
+                       if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
+                               $key = wfGlobalCacheKey( 'acctcreate', 'ip', $ip );
+                               $value = $cache->get( $key );
+                               if ( !$value ) {
+                                       $cache->set( $key, 0, $cache::TTL_DAY );
+                               }
+                               if ( $value >= $wgAccountCreationThrottle ) {
+                                       return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle );
+                               }
+                               $cache->incr( $key );
+                       }
+               }
+
+               if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
+                       return Status::newFatal( 'externaldberror' );
+               }
+
+               self::clearCreateaccountToken();
+
+               return $this->initUser( $u, false );
+       }
+
+       /**
+        * Actually add a user to the database.
+        * Give it a User object that has been initialised with a name.
+        *
+        * @param User $u
+        * @param bool $autocreate True if this is an autocreation via auth plugin
+        * @return Status Status object, with the User object in the value member on success
+        * @private
+        */
+       function initUser( $u, $autocreate ) {
+               global $wgAuth;
+
+               $status = $u->addToDatabase();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               if ( $wgAuth->allowPasswordChange() ) {
+                       $u->setPassword( $this->mPassword );
+               }
+
+               $u->setEmail( $this->mEmail );
+               $u->setRealName( $this->mRealName );
+               SessionManager::singleton()->invalidateSessionsForUser( $u );
+
+               Hooks::run( 'LocalUserCreated', [ $u, $autocreate ] );
+               $oldUser = $u;
+               $wgAuth->initUser( $u, $autocreate );
+               if ( $oldUser !== $u ) {
+                       wfWarn( get_class( $wgAuth ) . '::initUser() replaced the user object' );
+               }
+
+               $u->saveSettings();
+
+               // Update user count
+               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
+
+               // Watch user's userpage and talk page
+               $u->addWatch( $u->getUserPage(), User::IGNORE_USER_RIGHTS );
+
+               return Status::newGood( $u );
+       }
+
+       /**
+        * Internally authenticate the login request.
+        *
+        * This may create a local account as a side effect if the
+        * authentication plugin allows transparent local account
+        * creation.
+        * @return int
+        */
+       public function authenticateUserData() {
+               global $wgUser, $wgAuth;
+
+               $this->load();
+
+               if ( $this->mUsername == '' ) {
+                       return self::NO_NAME;
+               }
+
+               // We require a login token to prevent login CSRF
+               // Handle part of this before incrementing the throttle so
+               // token-less login attempts don't count towards the throttle
+               // but wrong-token attempts do.
+
+               // If the user doesn't have a login token yet, set one.
+               $token = self::getLoginToken();
+               if ( $token->wasNew() ) {
+                       return self::NEED_TOKEN;
+               }
+               // If the user didn't pass a login token, tell them we need one
+               if ( !$this->mToken ) {
+                       return self::NEED_TOKEN;
+               }
+
+               $throttleCount = self::incrementLoginThrottle( $this->mUsername );
+               if ( $throttleCount ) {
+                       $this->mThrottleWait = $throttleCount['wait'];
+                       return self::THROTTLED;
+               }
+
+               // Validate the login token
+               if ( !$token->match( $this->mToken ) ) {
+                       return self::WRONG_TOKEN;
+               }
+
+               // Load the current user now, and check to see if we're logging in as
+               // the same name. This is necessary because loading the current user
+               // (say by calling getName()) calls the UserLoadFromSession hook, which
+               // potentially creates the user in the database. Until we load $wgUser,
+               // checking for user existence using User::newFromName($name)->getId() below
+               // will effectively be using stale data.
+               if ( $this->getUser()->getName() === $this->mUsername ) {
+                       wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
+
+                       return self::SUCCESS;
+               }
+
+               $u = User::newFromName( $this->mUsername );
+               if ( $u === false ) {
+                       return self::ILLEGAL;
+               }
+
+               $msg = null;
+               // Give extensions a way to indicate the username has been updated,
+               // rather than telling the user the account doesn't exist.
+               if ( !Hooks::run( 'LoginUserMigrated', [ $u, &$msg ] ) ) {
+                       $this->mAbortLoginErrorMsg = $msg;
+                       return self::USER_MIGRATED;
+               }
+
+               if ( !User::isUsableName( $u->getName() ) ) {
+                       return self::ILLEGAL;
+               }
+
+               $isAutoCreated = false;
+               if ( $u->getId() == 0 ) {
+                       $status = $this->attemptAutoCreate( $u );
+                       if ( $status !== self::SUCCESS ) {
+                               return $status;
+                       } else {
+                               $isAutoCreated = true;
+                       }
+               } else {
+                       $u->load();
+               }
+
+               // Give general extensions, such as a captcha, a chance to abort logins
+               $abort = self::ABORTED;
+               if ( !Hooks::run( 'AbortLogin', [ $u, $this->mPassword, &$abort, &$msg ] ) ) {
+                       if ( !in_array( $abort, array_keys( self::$statusCodes ), true ) ) {
+                               throw new Exception( 'Invalid status code returned from AbortLogin hook: ' . $abort );
+                       }
+                       $this->mAbortLoginErrorMsg = $msg;
+                       return $abort;
+               }
+
+               global $wgBlockDisablesLogin;
+               if ( !$u->checkPassword( $this->mPassword ) ) {
+                       if ( $u->checkTemporaryPassword( $this->mPassword ) ) {
+                               /**
+                                * The e-mailed temporary password should not be used for actu-
+                                * al logins; that's a very sloppy habit, and insecure if an
+                                * attacker has a few seconds to click "search" on someone's
+                                * open mail reader.
+                                *
+                                * Allow it to be used only to reset the password a single time
+                                * to a new value, which won't be in the user's e-mail ar-
+                                * chives.
+                                *
+                                * For backwards compatibility, we'll still recognize it at the
+                                * login form to minimize surprises for people who have been
+                                * logging in with a temporary password for some time.
+                                *
+                                * As a side-effect, we can authenticate the user's e-mail ad-
+                                * dress if it's not already done, since the temporary password
+                                * was sent via e-mail.
+                                */
+                               if ( !$u->isEmailConfirmed() && !wfReadOnly() ) {
+                                       $u->confirmEmail();
+                                       $u->saveSettings();
+                               }
+
+                               // At this point we just return an appropriate code/ indicating
+                               // that the UI should show a password reset form; bot inter-
+                               // faces etc will probably just fail cleanly here.
+                               $this->mAbortLoginErrorMsg = 'resetpass-temp-emailed';
+                               $this->mTempPasswordUsed = true;
+                               $retval = self::RESET_PASS;
+                       } else {
+                               $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
+                       }
+               } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
+                       // If we've enabled it, make it so that a blocked user cannot login
+                       $retval = self::USER_BLOCKED;
+               } elseif ( $this->checkUserPasswordExpired( $u ) == 'hard' ) {
+                       // Force reset now, without logging in
+                       $retval = self::RESET_PASS;
+                       $this->mAbortLoginErrorMsg = 'resetpass-expired';
+               } else {
+                       Hooks::run( 'UserLoggedIn', [ $u ] );
+                       $oldUser = $u;
+                       $wgAuth->updateUser( $u );
+                       if ( $oldUser !== $u ) {
+                               wfWarn( get_class( $wgAuth ) . '::updateUser() replaced the user object' );
+                       }
+                       $wgUser = $u;
+                       // This should set it for OutputPage and the Skin
+                       // which is needed or the personal links will be
+                       // wrong.
+                       $this->getContext()->setUser( $u );
+
+                       // Please reset throttle for successful logins, thanks!
+                       self::clearLoginThrottle( $this->mUsername );
+
+                       if ( $isAutoCreated ) {
+                               // Must be run after $wgUser is set, for correct new user log
+                               Hooks::run( 'AuthPluginAutoCreate', [ $u ] );
+                       }
+
+                       $retval = self::SUCCESS;
+               }
+               Hooks::run( 'LoginAuthenticateAudit', [ $u, $this->mPassword, $retval ] );
+
+               return $retval;
+       }
+
+       /**
+        * Increment the login attempt throttle hit count for the (username,current IP)
+        * tuple unless the throttle was already reached.
+        *
+        * @since 1.27 Return value changed.
+        * @param string $username The user name
+        * @return bool|array false if below limit or an array if above limit
+        *   Array contains keys wait, count, and throttleIndex
+        */
+       public static function incrementLoginThrottle( $username ) {
+               global $wgPasswordAttemptThrottle, $wgRequest;
+               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
+
+               $throttleCount = 0;
+               if ( is_array( $wgPasswordAttemptThrottle ) ) {
+                       $throttleConfig = $wgPasswordAttemptThrottle;
+                       if ( isset( $wgPasswordAttemptThrottle['count'] ) ) {
+                               // old style. Convert for backwards compat.
+                               $throttleConfig = [ $wgPasswordAttemptThrottle ];
+                       }
+                       foreach ( $throttleConfig as $index => $specificThrottle ) {
+                               if ( isset( $specificThrottle['allIPs'] ) ) {
+                                       $ip = 'All';
+                               } else {
+                                       $ip = $wgRequest->getIP();
+                               }
+                               $throttleKey = wfGlobalCacheKey( 'password-throttle',
+                                       $index, $ip, md5( $username )
+                               );
+                               $count = $specificThrottle['count'];
+                               $period = $specificThrottle['seconds'];
+
+                               $cache = ObjectCache::getLocalClusterInstance();
+                               $throttleCount = $cache->get( $throttleKey );
+                               if ( !$throttleCount ) {
+                                       $cache->add( $throttleKey, 1, $period ); // start counter
+                               } elseif ( $throttleCount < $count ) {
+                                       $cache->incr( $throttleKey );
+                               } elseif ( $throttleCount >= $count ) {
+                                       $logMsg = 'Login attempt rejected because logins to '
+                                               . '{acct} from IP {ip} have been throttled for '
+                                               . '{period} seconds due to {count} failed attempts';
+                                       // If we are hitting a throttle for >= 50 attempts,
+                                       // it is much more likely to be an attack than someone
+                                       // simply forgetting their password, so log it at a
+                                       // higher level.
+                                       $level = $count >= 50 ? LogLevel::WARNING : LogLevel::INFO;
+                                       // It should be noted that once the throttle is hit,
+                                       // every attempt to login will generate the log message
+                                       // until the throttle expires, not just the attempt that
+                                       // puts the throttle over the top.
+                                       LoggerFactory::getInstance( 'password-throttle' )->log(
+                                               $level,
+                                               $logMsg,
+                                               [
+                                                       'ip' => $ip,
+                                                       'period' => $period,
+                                                       'acct' => $username,
+                                                       'count' => $count,
+                                                       'throttleIdentifier' => $index,
+                                                       'method' => __METHOD__
+                                               ]
+                                       );
+
+                                       return [
+                                               'throttleIndex' => $index,
+                                               'wait' => $period,
+                                               'count' => $count
+                                       ];
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Increment the login attempt throttle hit count for the (username,current IP)
+        * tuple unless the throttle was already reached.
+        *
+        * @deprecated Use LoginForm::incrementLoginThrottle instead
+        * @param string $username The user name
+        * @return bool|int true if above throttle, or 0 (prior to 1.27, returned current count)
+        */
+       public static function incLoginThrottle( $username ) {
+               wfDeprecated( __METHOD__, "1.27" );
+               $res = self::incrementLoginThrottle( $username );
+               return is_array( $res ) ? true : 0;
+       }
+
+       /**
+        * Clear the login attempt throttle hit count for the (username,current IP) tuple.
+        * @param string $username The user name
+        * @return void
+        */
+       public static function clearLoginThrottle( $username ) {
+               global $wgRequest, $wgPasswordAttemptThrottle;
+               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
+
+               if ( is_array( $wgPasswordAttemptThrottle ) ) {
+                       $throttleConfig = $wgPasswordAttemptThrottle;
+                       if ( isset( $wgPasswordAttemptThrottle['count'] ) ) {
+                               // old style. Convert for backwards compat.
+                               $throttleConfig = [ $wgPasswordAttemptThrottle ];
+                       }
+                       foreach ( $throttleConfig as $index => $specificThrottle ) {
+                               if ( isset( $specificThrottle['allIPs'] ) ) {
+                                       $ip = 'All';
+                               } else {
+                                       $ip = $wgRequest->getIP();
+                               }
+                               $throttleKey = wfGlobalCacheKey( 'password-throttle', $index,
+                                       $ip, md5( $username )
+                               );
+                               ObjectCache::getLocalClusterInstance()->delete( $throttleKey );
+                       }
+               }
+       }
+
+       /**
+        * Attempt to automatically create a user on login. Only succeeds if there
+        * is an external authentication method which allows it.
+        *
+        * @param User $user
+        *
+        * @return int Status code
+        */
+       function attemptAutoCreate( $user ) {
+               global $wgAuth;
+
+               if ( $this->getUser()->isBlockedFromCreateAccount() ) {
+                       wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
+
+                       return self::CREATE_BLOCKED;
+               }
+
+               if ( !$wgAuth->autoCreate() ) {
+                       return self::NOT_EXISTS;
+               }
+
+               if ( !$wgAuth->userExists( $user->getName() ) ) {
+                       wfDebug( __METHOD__ . ": user does not exist\n" );
+
+                       return self::NOT_EXISTS;
+               }
+
+               if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
+                       wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
+
+                       return self::WRONG_PLUGIN_PASS;
+               }
+
+               $abortError = '';
+               if ( !Hooks::run( 'AbortAutoAccount', [ $user, &$abortError ] ) ) {
+                       // Hook point to add extra creation throttles and blocks
+                       wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
+                       $this->mAbortLoginErrorMsg = $abortError;
+
+                       return self::ABORTED;
+               }
+
+               wfDebug( __METHOD__ . ": creating account\n" );
+               $status = $this->initUser( $user, true );
+
+               if ( !$status->isOK() ) {
+                       $errors = $status->getErrorsByType( 'error' );
+                       $this->mAbortLoginErrorMsg = $errors[0]['message'];
+
+                       return self::ABORTED;
+               }
+
+               return self::SUCCESS;
+       }
+
+       function processLogin() {
+               global $wgLang, $wgSecureLogin, $wgInvalidPasswordReset;
+
+               $authRes = $this->authenticateUserData();
+               switch ( $authRes ) {
+                       case self::SUCCESS:
+                               # We've verified now, update the real record
+                               $user = $this->getUser();
+                               $user->touch();
+
+                               if ( $user->requiresHTTPS() ) {
+                                       $this->mStickHTTPS = true;
+                               }
+
+                               if ( $wgSecureLogin && !$this->mStickHTTPS ) {
+                                       $user->setCookies( $this->mRequest, false, $this->mRemember );
+                               } else {
+                                       $user->setCookies( $this->mRequest, null, $this->mRemember );
+                               }
+                               self::clearLoginToken();
+
+                               // Reset the throttle
+                               self::clearLoginThrottle( $this->mUsername );
+
+                               $request = $this->getRequest();
+                               if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
+                                       /* Replace the language object to provide user interface in
+                                        * correct language immediately on this first page load.
+                                        */
+                                       $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
+                                       $userLang = Language::factory( $code );
+                                       $wgLang = $userLang;
+                                       RequestContext::getMain()->setLanguage( $userLang );
+                                       $this->getContext()->setLanguage( $userLang );
+                                       // Reset SessionID on Successful login (bug 40995)
+                                       $this->renewSessionId();
+                                       if ( $this->checkUserPasswordExpired( $this->getUser() ) == 'soft' ) {
+                                               $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) );
+                                       } elseif ( $wgInvalidPasswordReset
+                                               && !$user->isValidPassword( $this->mPassword )
+                                       ) {
+                                               $status = $user->checkPasswordValidity(
+                                                       $this->mPassword,
+                                                       'login'
+                                               );
+                                               $this->resetLoginForm(
+                                                       $status->getMessage( 'resetpass-validity-soft' )
+                                               );
+                                       } else {
+                                               $this->successfulLogin();
+                                       }
+                               } else {
+                                       $this->cookieRedirectCheck( 'login' );
+                               }
+                               break;
+
+                       case self::NEED_TOKEN:
+                               $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin';
+                               $this->mainLoginForm( $this->msg( $error )->parse() );
+                               break;
+                       case self::WRONG_TOKEN:
+                               $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure';
+                               $this->mainLoginForm( $this->msg( $error )->text() );
+                               break;
+                       case self::NO_NAME:
+                       case self::ILLEGAL:
+                               $error = $this->mAbortLoginErrorMsg ?: 'noname';
+                               $this->mainLoginForm( $this->msg( $error )->text() );
+                               break;
+                       case self::WRONG_PLUGIN_PASS:
+                               $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
+                               $this->mainLoginForm( $this->msg( $error )->text() );
+                               break;
+                       case self::NOT_EXISTS:
+                               if ( $this->getUser()->isAllowed( 'createaccount' ) ) {
+                                       $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser';
+                                       $this->mainLoginForm( $this->msg( $error,
+                                               wfEscapeWikiText( $this->mUsername ) )->parse() );
+                               } else {
+                                       $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort';
+                                       $this->mainLoginForm( $this->msg( $error,
+                                               wfEscapeWikiText( $this->mUsername ) )->text() );
+                               }
+                               break;
+                       case self::WRONG_PASS:
+                               $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
+                               $this->mainLoginForm( $this->msg( $error )->text() );
+                               break;
+                       case self::EMPTY_PASS:
+                               $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty';
+                               $this->mainLoginForm( $this->msg( $error )->text() );
+                               break;
+                       case self::RESET_PASS:
+                               $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce';
+                               $this->resetLoginForm( $this->msg( $error ) );
+                               break;
+                       case self::CREATE_BLOCKED:
+                               $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() );
+                               break;
+                       case self::THROTTLED:
+                               $error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
+                               $this->mainLoginForm( $this->msg( $error )
+                                       ->durationParams( $this->mThrottleWait )->text()
+                               );
+                               break;
+                       case self::USER_BLOCKED:
+                               $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked';
+                               $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() );
+                               break;
+                       case self::ABORTED:
+                               $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic';
+                               $this->mainLoginForm( $this->msg( $error,
+                                               wfEscapeWikiText( $this->mUsername ) )->text() );
+                               break;
+                       case self::USER_MIGRATED:
+                               $error = $this->mAbortLoginErrorMsg ?: 'login-migrated-generic';
+                               $params = [];
+                               if ( is_array( $error ) ) {
+                                       $error = array_shift( $this->mAbortLoginErrorMsg );
+                                       $params = $this->mAbortLoginErrorMsg;
+                               }
+                               $this->mainLoginForm( $this->msg( $error, $params )->text() );
+                               break;
+                       default:
+                               throw new MWException( 'Unhandled case value' );
+               }
+
+               LoggerFactory::getInstance( 'authmanager' )->info( 'Login attempt', [
+                       'event' => 'login',
+                       'successful' => $authRes === self::SUCCESS,
+                       'status' => LoginForm::$statusCodes[$authRes],
+               ] );
+       }
+
+       /**
+        * Show the Special:ChangePassword form, with custom message
+        * @param Message $msg
+        */
+       protected function resetLoginForm( Message $msg ) {
+               // Allow hooks to explain this password reset in more detail
+               Hooks::run( 'LoginPasswordResetMessage', [ &$msg, $this->mUsername ] );
+               $reset = new SpecialChangePasswordPreAuthManager();
+               $derivative = new DerivativeContext( $this->getContext() );
+               $derivative->setTitle( $reset->getPageTitle() );
+               $reset->setContext( $derivative );
+               if ( !$this->mTempPasswordUsed ) {
+                       $reset->setOldPasswordMessage( 'oldpassword' );
+               }
+               $reset->setChangeMessage( $msg );
+               $reset->execute( null );
+       }
+
+       /**
+        * @param User $u
+        * @param bool $throttle
+        * @param string $emailTitle Message name of email title
+        * @param string $emailText Message name of email text
+        * @return Status
+        */
+       function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle',
+               $emailText = 'passwordremindertext'
+       ) {
+               global $wgNewPasswordExpiry, $wgMinimalPasswordLength;
+
+               if ( $u->getEmail() == '' ) {
+                       return Status::newFatal( 'noemail', $u->getName() );
+               }
+               $ip = $this->getRequest()->getIP();
+               if ( !$ip ) {
+                       return Status::newFatal( 'badipaddress' );
+               }
+
+               $currentUser = $this->getUser();
+               Hooks::run( 'User::mailPasswordInternal', [ &$currentUser, &$ip, &$u ] );
+
+               $np = PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
+               $u->setNewpassword( $np, $throttle );
+               $u->saveSettings();
+               $userLanguage = $u->getOption( 'language' );
+
+               $mainPage = Title::newMainPage();
+               $mainPageUrl = $mainPage->getCanonicalURL();
+
+               $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $mainPageUrl . '>',
+                       round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
+               $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
+
+               return $result;
+       }
+
+       /**
+        * Run any hooks registered for logins, then HTTP redirect to
+        * $this->mReturnTo (or Main Page if that's undefined).  Formerly we had a
+        * nice message here, but that's really not as useful as just being sent to
+        * wherever you logged in from.  It should be clear that the action was
+        * successful, given the lack of error messages plus the appearance of your
+        * name in the upper right.
+        *
+        * @private
+        */
+       function successfulLogin() {
+               # Run any hooks; display injected HTML if any, else redirect
+               $currentUser = $this->getUser();
+               $injected_html = '';
+               Hooks::run( 'UserLoginComplete', [ &$currentUser, &$injected_html ] );
+
+               if ( $injected_html !== '' ) {
+                       $this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ),
+                               'loginsuccess', $injected_html );
+               } else {
+                       $this->executeReturnTo( 'successredirect' );
+               }
+       }
+
+       /**
+        * Run any hooks registered for logins, then display a message welcoming
+        * the user.
+        *
+        * @private
+        */
+       function successfulCreation() {
+               # Run any hooks; display injected HTML
+               $currentUser = $this->getUser();
+               $injected_html = '';
+               $welcome_creation_msg = 'welcomecreation-msg';
+
+               Hooks::run( 'UserLoginComplete', [ &$currentUser, &$injected_html ] );
+
+               /**
+                * Let any extensions change what message is shown.
+                * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation
+                * @since 1.18
+                */
+               Hooks::run( 'BeforeWelcomeCreation', [ &$welcome_creation_msg, &$injected_html ] );
+
+               $this->displaySuccessfulAction(
+                       'signup',
+                       $this->msg( 'welcomeuser', $this->getUser()->getName() ),
+                       $welcome_creation_msg, $injected_html
+               );
+       }
+
+       /**
+        * Display a "successful action" page.
+        *
+        * @param string $type Condition of return to; see `executeReturnTo`
+        * @param string|Message $title Page's title
+        * @param string $msgname
+        * @param string $injected_html
+        */
+       private function displaySuccessfulAction( $type, $title, $msgname, $injected_html ) {
+               $out = $this->getOutput();
+               $out->setPageTitle( $title );
+               if ( $msgname ) {
+                       $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
+               }
+
+               $out->addHTML( $injected_html );
+
+               $this->executeReturnTo( $type );
+       }
+
+       /**
+        * Output a message that informs the user that they cannot create an account because
+        * there is a block on them or their IP which prevents account creation.  Note that
+        * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
+        * setting on blocks (bug 13611).
+        * @param Block $block The block causing this error
+        * @throws ErrorPageError
+        */
+       function userBlockedMessage( Block $block ) {
+               # Let's be nice about this, it's likely that this feature will be used
+               # for blocking large numbers of innocent people, e.g. range blocks on
+               # schools. Don't blame it on the user. There's a small chance that it
+               # really is the user's fault, i.e. the username is blocked and they
+               # haven't bothered to log out before trying to create an account to
+               # evade it, but we'll leave that to their guilty conscience to figure
+               # out.
+               $errorParams = [
+                       $block->getTarget(),
+                       $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
+                       $block->getByName()
+               ];
+
+               if ( $block->getType() === Block::TYPE_RANGE ) {
+                       $errorMessage = 'cantcreateaccount-range-text';
+                       $errorParams[] = $this->getRequest()->getIP();
+               } else {
+                       $errorMessage = 'cantcreateaccount-text';
+               }
+
+               throw new ErrorPageError(
+                       'cantcreateaccounttitle',
+                       $errorMessage,
+                       $errorParams
+               );
+       }
+
+       /**
+        * Add a "return to" link or redirect to it.
+        * Extensions can use this to reuse the "return to" logic after
+        * inject steps (such as redirection) into the login process.
+        *
+        * @param string $type One of the following:
+        *    - error: display a return to link ignoring $wgRedirectOnLogin
+        *    - signup: display a return to link using $wgRedirectOnLogin if needed
+        *    - success: display a return to link using $wgRedirectOnLogin if needed
+        *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
+        * @param string $returnTo
+        * @param array|string $returnToQuery
+        * @param bool $stickHTTPs Keep redirect link on HTTPs
+        * @since 1.22
+        */
+       public function showReturnToPage(
+               $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false
+       ) {
+               $this->mReturnTo = $returnTo;
+               $this->mReturnToQuery = $returnToQuery;
+               $this->mStickHTTPS = $stickHTTPs;
+               $this->executeReturnTo( $type );
+       }
+
+       /**
+        * Add a "return to" link or redirect to it.
+        *
+        * @param string $type One of the following:
+        *    - error: display a return to link ignoring $wgRedirectOnLogin
+        *    - signup: display a return to link using $wgRedirectOnLogin if needed
+        *    - success: display a return to link using $wgRedirectOnLogin if needed
+        *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
+        */
+       private function executeReturnTo( $type ) {
+               global $wgRedirectOnLogin, $wgSecureLogin;
+
+               if ( $type != 'error' && $wgRedirectOnLogin !== null ) {
+                       $returnTo = $wgRedirectOnLogin;
+                       $returnToQuery = [];
+               } else {
+                       $returnTo = $this->mReturnTo;
+                       $returnToQuery = wfCgiToArray( $this->mReturnToQuery );
+               }
+
+               // Allow modification of redirect behavior
+               Hooks::run( 'PostLoginRedirect', [ &$returnTo, &$returnToQuery, &$type ] );
+
+               $returnToTitle = Title::newFromText( $returnTo );
+               if ( !$returnToTitle ) {
+                       $returnToTitle = Title::newMainPage();
+               }
+
+               if ( $wgSecureLogin && !$this->mStickHTTPS ) {
+                       $options = [ 'http' ];
+                       $proto = PROTO_HTTP;
+               } elseif ( $wgSecureLogin ) {
+                       $options = [ 'https' ];
+                       $proto = PROTO_HTTPS;
+               } else {
+                       $options = [];
+                       $proto = PROTO_RELATIVE;
+               }
+
+               if ( $type == 'successredirect' ) {
+                       $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto );
+                       $this->getOutput()->redirect( $redirectUrl );
+               } else {
+                       $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options );
+               }
+       }
+
+       /**
+        * @param string $msg
+        * @param string $msgtype
+        * @throws ErrorPageError
+        * @throws Exception
+        * @throws FatalError
+        * @throws MWException
+        * @throws PermissionsError
+        * @throws ReadOnlyError
+        * @private
+        */
+       function mainLoginForm( $msg, $msgtype = 'error' ) {
+               global $wgEnableEmail, $wgEnableUserEmail;
+               global $wgHiddenPrefs, $wgLoginLanguageSelector;
+               global $wgAuth, $wgEmailConfirmToEdit;
+               global $wgSecureLogin, $wgPasswordResetRoutes;
+               global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
+
+               $titleObj = $this->getPageTitle();
+               $user = $this->getUser();
+               $out = $this->getOutput();
+
+               if ( $this->mType == 'signup' ) {
+                       // Block signup here if in readonly. Keeps user from
+                       // going through the process (filling out data, etc)
+                       // and being informed later.
+                       $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true );
+                       if ( count( $permErrors ) ) {
+                               throw new PermissionsError( 'createaccount', $permErrors );
+                       } elseif ( $user->isBlockedFromCreateAccount() ) {
+                               $this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
+
+                               return;
+                       } elseif ( wfReadOnly() ) {
+                               throw new ReadOnlyError;
+                       }
+               }
+
+               // Pre-fill username (if not creating an account, bug 44775).
+               if ( $this->mUsername == '' && $this->mType != 'signup' ) {
+                       if ( $user->isLoggedIn() ) {
+                               $this->mUsername = $user->getName();
+                       } else {
+                               $this->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
+                       }
+               }
+
+               // Generic styles and scripts for both login and signup form
+               $out->addModuleStyles( [
+                       'mediawiki.ui',
+                       'mediawiki.ui.button',
+                       'mediawiki.ui.checkbox',
+                       'mediawiki.ui.input',
+                       'mediawiki.special.userlogin.common.styles'
+               ] );
+
+               if ( $this->mType == 'signup' ) {
+                       // Additional styles and scripts for signup form
+                       $out->addModules( [
+                               'mediawiki.special.userlogin.signup.js'
+                       ] );
+                       $out->addModuleStyles( [
+                               'mediawiki.special.userlogin.signup.styles'
+                       ] );
+
+                       $template = new UsercreateTemplate( $this->getConfig() );
+
+                       // Must match number of benefits defined in messages
+                       $template->set( 'benefitCount', 3 );
+
+                       $q = 'action=submitlogin&type=signup';
+                       $linkq = 'type=login';
+               } else {
+                       // Additional styles for login form
+                       $out->addModuleStyles( [
+                               'mediawiki.special.userlogin.login.styles'
+                       ] );
+
+                       $template = new UserloginTemplate( $this->getConfig() );
+
+                       $q = 'action=submitlogin&type=login';
+                       $linkq = 'type=signup';
+               }
+
+               if ( $this->mReturnTo !== '' ) {
+                       $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
+                       if ( $this->mReturnToQuery !== '' ) {
+                               $returnto .= '&returntoquery=' .
+                                       wfUrlencode( $this->mReturnToQuery );
+                       }
+                       $q .= $returnto;
+                       $linkq .= $returnto;
+               }
+
+               # Don't show a "create account" link if the user can't.
+               if ( $this->showCreateOrLoginLink( $user ) ) {
+                       # Pass any language selection on to the mode switch link
+                       if ( $wgLoginLanguageSelector && $this->mLanguage ) {
+                               $linkq .= '&uselang=' . $this->mLanguage;
+                       }
+                       // Supply URL, login template creates the button.
+                       $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
+               } else {
+                       $template->set( 'link', '' );
+               }
+
+               $resetLink = $this->mType == 'signup'
+                       ? null
+                       : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
+
+               $template->set( 'header', '' );
+               $template->set( 'formheader', '' );
+               $template->set( 'skin', $this->getSkin() );
+               $template->set( 'name', $this->mUsername );
+               $template->set( 'password', $this->mPassword );
+               $template->set( 'retype', $this->mRetype );
+               $template->set( 'createemailset', $this->mCreateaccountMail );
+               $template->set( 'email', $this->mEmail );
+               $template->set( 'realname', $this->mRealName );
+               $template->set( 'domain', $this->mDomain );
+               $template->set( 'reason', $this->mReason );
+
+               $template->set( 'action', $titleObj->getLocalURL( $q ) );
+               $template->set( 'message', $msg );
+               $template->set( 'messagetype', $msgtype );
+               $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
+               $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
+               $template->set( 'useemail', $wgEnableEmail );
+               $template->set( 'emailrequired', $wgEmailConfirmToEdit );
+               $template->set( 'emailothers', $wgEnableUserEmail );
+               $template->set( 'canreset', $wgAuth->allowPasswordChange() );
+               $template->set( 'resetlink', $resetLink );
+               $template->set( 'canremember', $wgExtendedLoginCookieExpiration === null ?
+                       ( $wgCookieExpiration > 0 ) :
+                       ( $wgExtendedLoginCookieExpiration > 0 ) );
+               $template->set( 'usereason', $user->isLoggedIn() );
+               $template->set( 'remember', $this->mRemember );
+               $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
+               $template->set( 'stickhttps', (int)$this->mStickHTTPS );
+               $template->set( 'loggedin', $user->isLoggedIn() );
+               $template->set( 'loggedinuser', $user->getName() );
+
+               if ( $this->mType == 'signup' ) {
+                       $template->set( 'token', self::getCreateaccountToken()->toString() );
+               } else {
+                       $template->set( 'token', self::getLoginToken()->toString() );
+               }
+
+               # Prepare language selection links as needed
+               if ( $wgLoginLanguageSelector ) {
+                       $template->set( 'languages', $this->makeLanguageSelector() );
+                       if ( $this->mLanguage ) {
+                               $template->set( 'uselang', $this->mLanguage );
+                       }
+               }
+
+               $template->set( 'secureLoginUrl', $this->mSecureLoginUrl );
+               // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
+               $usingHTTPS = $this->mRequest->getProtocol() == 'https';
+               $signupendHTTPS = $this->msg( 'signupend-https' );
+               if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
+                       $template->set( 'signupend', $signupendHTTPS->parse() );
+               } else {
+                       $template->set( 'signupend', $this->msg( 'signupend' )->parse() );
+               }
+
+               // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
+               if ( $usingHTTPS ) {
+                       $template->set( 'fromhttp', $this->mFromHTTP );
+               }
+
+               // Give authentication and captcha plugins a chance to modify the form
+               $wgAuth->modifyUITemplate( $template, $this->mType );
+               if ( $this->mType == 'signup' ) {
+                       Hooks::run( 'UserCreateForm', [ &$template ] );
+               } else {
+                       Hooks::run( 'UserLoginForm', [ &$template ] );
+               }
+
+               $out->disallowUserJs(); // just in case...
+               $out->addTemplate( $template );
+       }
+
+       /**
+        * Whether the login/create account form should display a link to the
+        * other form (in addition to whatever the skin provides).
+        *
+        * @param User $user
+        * @return bool
+        */
+       private function showCreateOrLoginLink( &$user ) {
+               if ( $this->mType == 'signup' ) {
+                       return true;
+               } elseif ( $user->isAllowed( 'createaccount' ) ) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Check if a session cookie is present.
+        *
+        * This will not pick up a cookie set during _this_ request, but is meant
+        * to ensure that the client is returning the cookie which was set on a
+        * previous pass through the system.
+        *
+        * @private
+        * @return bool
+        */
+       function hasSessionCookie() {
+               global $wgDisableCookieCheck, $wgInitialSessionId;
+
+               return $wgDisableCookieCheck || (
+                       $wgInitialSessionId &&
+                       $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId
+               );
+       }
+
+       /**
+        * Get the login token from the current session
+        * @since 1.27 returns a MediaWiki\Session\Token instead of a string
+        * @return MediaWiki\Session\Token
+        */
+       public static function getLoginToken() {
+               global $wgRequest;
+               return $wgRequest->getSession()->getToken( '', 'login' );
+       }
+
+       /**
+        * Formerly randomly generated a login token that would be returned by
+        * $this->getLoginToken().
+        *
+        * Since 1.27, this is a no-op. The token is generated as necessary by
+        * $this->getLoginToken().
+        *
+        * @deprecated since 1.27
+        */
+       public static function setLoginToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+       }
+
+       /**
+        * Remove any login token attached to the current session
+        */
+       public static function clearLoginToken() {
+               global $wgRequest;
+               $wgRequest->getSession()->resetToken( 'login' );
+       }
+
+       /**
+        * Get the createaccount token from the current session
+        * @since 1.27 returns a MediaWiki\Session\Token instead of a string
+        * @return MediaWiki\Session\Token
+        */
+       public static function getCreateaccountToken() {
+               global $wgRequest;
+               return $wgRequest->getSession()->getToken( '', 'createaccount' );
+       }
+
+       /**
+        * Formerly randomly generated a createaccount token that would be returned
+        * by $this->getCreateaccountToken().
+        *
+        * Since 1.27, this is a no-op. The token is generated as necessary by
+        * $this->getCreateaccountToken().
+        *
+        * @deprecated since 1.27
+        */
+       public static function setCreateaccountToken() {
+               wfDeprecated( __METHOD__, '1.27' );
+       }
+
+       /**
+        * Remove any createaccount token attached to the current session
+        */
+       public static function clearCreateaccountToken() {
+               global $wgRequest;
+               $wgRequest->getSession()->resetToken( 'createaccount' );
+       }
+
+       /**
+        * Renew the user's session id, using strong entropy
+        */
+       private function renewSessionId() {
+               global $wgSecureLogin, $wgCookieSecure;
+               if ( $wgSecureLogin && !$this->mStickHTTPS ) {
+                       $wgCookieSecure = false;
+               }
+
+               SessionManager::getGlobalSession()->resetId();
+       }
+
+       /**
+        * @param string $type
+        * @private
+        */
+       function cookieRedirectCheck( $type ) {
+               $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
+               $query = [ 'wpCookieCheck' => $type ];
+               if ( $this->mReturnTo !== '' ) {
+                       $query['returnto'] = $this->mReturnTo;
+                       $query['returntoquery'] = $this->mReturnToQuery;
+               }
+               $check = $titleObj->getFullURL( $query );
+
+               $this->getOutput()->redirect( $check );
+       }
+
+       /**
+        * @param string $type
+        * @private
+        */
+       function onCookieRedirectCheck( $type ) {
+               if ( !$this->hasSessionCookie() ) {
+                       if ( $type == 'new' ) {
+                               $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() );
+                       } elseif ( $type == 'login' ) {
+                               $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() );
+                       } else {
+                               # shouldn't happen
+                               $this->mainLoginForm( $this->msg( 'error' )->text() );
+                       }
+               } else {
+                       $this->successfulLogin();
+               }
+       }
+
+       /**
+        * Produce a bar of links which allow the user to select another language
+        * during login/registration but retain "returnto"
+        *
+        * @return string
+        */
+       function makeLanguageSelector() {
+               $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
+               if ( $msg->isBlank() ) {
+                       return '';
+               }
+               $langs = explode( "\n", $msg->text() );
+               $links = [];
+               foreach ( $langs as $lang ) {
+                       $lang = trim( $lang, '* ' );
+                       $parts = explode( '|', $lang );
+                       if ( count( $parts ) >= 2 ) {
+                               $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
+                       }
+               }
+
+               return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
+                       $this->getLanguage()->pipeList( $links ) )->escaped() : '';
+       }
+
+       /**
+        * Create a language selector link for a particular language
+        * Links back to this page preserving type and returnto
+        *
+        * @param string $text Link text
+        * @param string $lang Language code
+        * @return string
+        */
+       function makeLanguageSelectorLink( $text, $lang ) {
+               if ( $this->getLanguage()->getCode() == $lang ) {
+                       // no link for currently used language
+                       return htmlspecialchars( $text );
+               }
+               $query = [ 'uselang' => $lang ];
+               if ( $this->mType == 'signup' ) {
+                       $query['type'] = 'signup';
+               }
+               if ( $this->mReturnTo !== '' ) {
+                       $query['returnto'] = $this->mReturnTo;
+                       $query['returntoquery'] = $this->mReturnToQuery;
+               }
+
+               $attr = [];
+               $targetLanguage = Language::factory( $lang );
+               $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
+
+               return Linker::linkKnown(
+                       $this->getPageTitle(),
+                       htmlspecialchars( $text ),
+                       $attr,
+                       $query
+               );
+       }
+
+       protected function getGroupName() {
+               return 'login';
+       }
+
+       /**
+        * Private function to check password expiration, until AuthManager comes
+        * along to handle that.
+        * @param User $user
+        * @return string|bool
+        */
+       private function checkUserPasswordExpired( User $user ) {
+               global $wgPasswordExpireGrace;
+               $dbr = wfGetDB( DB_SLAVE );
+               $ts = $dbr->selectField( 'user', 'user_password_expires', [ 'user_id' => $user->getId() ] );
+
+               $expired = false;
+               $now = wfTimestamp();
+               $expUnix = wfTimestamp( TS_UNIX, $ts );
+               if ( $ts !== null && $expUnix < $now ) {
+                       $expired = ( $expUnix + $wgPasswordExpireGrace < $now ) ? 'hard' : 'soft';
+               }
+               return $expired;
+       }
+
+       protected function getSubpagesForPrefixSearch() {
+               return [ 'signup' ];
+       }
+}
diff --git a/includes/specials/pre-authmanager/SpecialUserlogout.php b/includes/specials/pre-authmanager/SpecialUserlogout.php
new file mode 100644 (file)
index 0000000..6d6a714
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Implements Special:Userlogout
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Implements Special:Userlogout
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUserlogoutPreAuthManager extends UnlistedSpecialPage {
+       function __construct() {
+               parent::__construct( 'Userlogout' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       function execute( $par ) {
+               /**
+                * Some satellite ISPs use broken precaching schemes that log people out straight after
+                * they're logged in (bug 17790). Luckily, there's a way to detect such requests.
+                */
+               if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
+                       wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
+                       throw new HttpError( 400, $this->msg( 'suspicious-userlogout' ), $this->msg( 'loginerror' ) );
+               }
+
+               $this->setHeaders();
+               $this->outputHeader();
+
+               // Make sure it's possible to log out
+               $session = MediaWiki\Session\SessionManager::getGlobalSession();
+               if ( !$session->canSetUser() ) {
+                       throw new ErrorPageError(
+                               'cannotlogoutnow-title',
+                               'cannotlogoutnow-text',
+                               [
+                                       $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
+                               ]
+                       );
+               }
+
+               $user = $this->getUser();
+               $oldName = $user->getName();
+               $user->logout();
+
+               $loginURL = SpecialPage::getTitleFor( 'Userlogin' )->getFullURL(
+                       $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
+               $out = $this->getOutput();
+               $out->addWikiMsg( 'logouttext', $loginURL );
+
+               // Hook.
+               $injected_html = '';
+               Hooks::run( 'UserLogoutComplete', [ &$user, &$injected_html, $oldName ] );
+               $out->addHTML( $injected_html );
+
+               $out->returnToMain();
+       }
+
+       protected function getGroupName() {
+               return 'login';
+       }
+}
index 3824be1..0a5aa61 100644 (file)
@@ -20,6 +20,8 @@
  *
  * @file
  * @ingroup Templates
+ * @deprecated Will be removed when AuthManager lands.
+ *   The signup form will be generated via HTMLForm.
  */
 
 class UsercreateTemplate extends BaseTemplate {
index c2b2df6..e816b62 100644 (file)
@@ -20,6 +20,8 @@
  *
  * @file
  * @ingroup Templates
+ * @deprecated Will be removed when AuthManager lands.
+ *   The login form will be generated via HTMLForm.
  */
 
 class UserloginTemplate extends BaseTemplate {
index 0e291ed..38e9ecd 100644 (file)
@@ -181,6 +181,37 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                );
        }
 
+       /**
+        * @since 1.27
+        * @see TitleFormatter::getPrefixedDBkey()
+        * @param LinkTarget $target
+        * @return string
+        */
+       public function getPrefixedDBkey( LinkTarget $target ) {
+               $key = '';
+               if ( $target->isExternal() ) {
+                       $key .= $target->getInterwiki() . ':';
+               }
+               // Try to get a namespace name, but fallback
+               // to empty string if it doesn't exist
+               try {
+                       $nsName = $this->getNamespaceName(
+                               $target->getNamespace(),
+                               $target->getText()
+                       );
+               } catch ( InvalidArgumentException $e ) {
+                       $nsName = '';
+               }
+
+               if ( $target->getNamespace() !== 0 ) {
+                       $key .= $nsName . ':';
+               }
+
+               $key .= $target->getText();
+
+               return strtr( $key, ' ', '_' );
+       }
+
        /**
         * @see TitleFormatter::getText()
         *
index c081129..5177606 100644 (file)
@@ -68,6 +68,18 @@ interface TitleFormatter {
         */
        public function getPrefixedText( LinkTarget $title );
 
+       /**
+        * Return the title in prefixed database key form, with interwiki
+        * and namespace.
+        *
+        * @since 1.27
+        *
+        * @param LinkTarget $target
+        *
+        * @return string
+        */
+       public function getPrefixedDBkey( LinkTarget $target );
+
        /**
         * Returns the title formatted for display, with namespace and fragment.
         *
index 63c075f..597bf2f 100644 (file)
@@ -94,6 +94,15 @@ class TitleValue implements LinkTarget {
                return $this->namespace;
        }
 
+       /**
+        * @since 1.27
+        * @param int $ns
+        * @return bool
+        */
+       public function inNamespace( $ns ) {
+               return $this->namespace == $ns;
+       }
+
        /**
         * @return string
         */
diff --git a/includes/user/PasswordReset.php b/includes/user/PasswordReset.php
new file mode 100644 (file)
index 0000000..60144bb
--- /dev/null
@@ -0,0 +1,257 @@
+<?php
+/**
+ * User password reset helper for MediaWiki.
+ *
+ * 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 MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\TemporaryPasswordAuthenticationRequest;
+
+/**
+ * Helper class for the password reset functionality shared by the web UI and the API.
+ *
+ * Requires the TemporaryPasswordPrimaryAuthenticationProvider and the
+ * EmailNotificationSecondaryAuthenticationProvider (or something providing equivalent
+ * functionality) to be enabled.
+ */
+class PasswordReset {
+       /** @var Config */
+       protected $config;
+
+       /** @var AuthManager */
+       protected $authManager;
+
+       /**
+        * In-process cache for isAllowed lookups, by username. Contains pairs of StatusValue objects
+        * (for false and true value of $displayPassword, respectively).
+        * @var HashBagOStuff
+        */
+       private $permissionCache;
+
+       public function __construct( Config $config, AuthManager $authManager ) {
+               $this->config = $config;
+               $this->authManager = $authManager;
+               $this->permissionCache = new HashBagOStuff( [ 'maxKeys' => 1 ] );
+       }
+
+       /**
+        * Check if a given user has permission to use this functionality.
+        * @param User $user
+        * @param bool $displayPassword If set, also check whether the user is allowed to reset the
+        *   password of another user and see the temporary password.
+        * @return StatusValue
+        */
+       public function isAllowed( User $user, $displayPassword = false ) {
+               $statuses = $this->permissionCache->get( $user->getName() );
+               if ( $statuses ) {
+                       list ( $status, $status2 ) = $statuses;
+               } else {
+                       $resetRoutes = $this->config->get( 'PasswordResetRoutes' );
+                       $status = StatusValue::newGood();
+
+                       if ( !is_array( $resetRoutes ) ||
+                                !in_array( true, array_values( $resetRoutes ), true )
+                       ) {
+                               // Maybe password resets are disabled, or there are no allowable routes
+                               $status = StatusValue::newFatal( 'passwordreset-disabled' );
+                       } elseif (
+                               ( $providerStatus = $this->authManager->allowsAuthenticationDataChange(
+                                       new TemporaryPasswordAuthenticationRequest(), false ) )
+                               && !$providerStatus->isGood()
+                       ) {
+                               // Maybe the external auth plugin won't allow local password changes
+                               $status = StatusValue::newFatal( 'resetpass_forbidden-reason',
+                                       $providerStatus->getMessage() );
+                       } elseif ( !$this->config->get( 'EnableEmail' ) ) {
+                               // Maybe email features have been disabled
+                               $status = StatusValue::newFatal( 'passwordreset-emaildisabled' );
+                       } elseif ( !$user->isAllowed( 'editmyprivateinfo' ) ) {
+                               // Maybe not all users have permission to change private data
+                               $status = StatusValue::newFatal( 'badaccess' );
+                       } elseif ( $user->isBlocked() ) {
+                               // Maybe the user is blocked (check this here rather than relying on the parent
+                               // method as we have a more specific error message to use here
+                               $status = StatusValue::newFatal( 'blocked-mailpassword' );
+                       }
+
+                       $status2 = StatusValue::newGood();
+                       if ( !$user->isAllowed( 'passwordreset' ) ) {
+                               $status2 = StatusValue::newFatal( 'badaccess' );
+                       }
+
+                       $this->permissionCache->set( $user->getName(), [ $status, $status2 ] );
+               }
+
+               if ( !$displayPassword || !$status->isGood() ) {
+                       return $status;
+               } else {
+                       return $status2;
+               }
+       }
+
+       /**
+        * Do a password reset. Authorization is the caller's responsibility.
+        *
+        * Process the form.  At this point we know that the user passes all the criteria in
+        * userCanExecute(), and if the data array contains 'Username', etc, then Username
+        * resets are allowed.
+        * @param User $performingUser The user that does the password reset
+        * @param string $username The user whose password is reset
+        * @param string $email Alternative way to specify the user
+        * @param bool $displayPassword Whether to display the password
+        * @return StatusValue Will contain the passwords as a username => password array if the
+        *   $displayPassword flag was set
+        * @throws LogicException When the user is not allowed to perform the action
+        * @throws MWException On unexpected DB errors
+        */
+       public function execute(
+               User $performingUser, $username = null, $email = null, $displayPassword = false
+       ) {
+               if ( !$this->isAllowed( $performingUser, $displayPassword )->isGood() ) {
+                       $action = $this->isAllowed( $performingUser )->isGood() ? 'display' : 'reset';
+                       throw new LogicException( 'User ' . $performingUser->getName()
+                               . ' is not allowed to ' . $action . ' passwords' );
+               }
+
+               $resetRoutes = $this->config->get( 'PasswordResetRoutes' )
+                       + [ 'username' => false, 'email' => false ];
+               if ( $resetRoutes['username'] && $username ) {
+                       $method = 'username';
+                       $users = [ User::newFromName( $username ) ];
+               } elseif ( $resetRoutes['email'] && $email ) {
+                       if ( !Sanitizer::validateEmail( $email ) ) {
+                               return StatusValue::newFatal( 'passwordreset-invalidemail' );
+                       }
+                       $method = 'email';
+                       $users = $this->getUsersByEmail( $email );
+               } else {
+                       // The user didn't supply any data
+                       return StatusValue::newFatal( 'passwordreset-nodata' );
+               }
+
+               // Check for hooks (captcha etc), and allow them to modify the users list
+               $error = [];
+               $data = [
+                       'Username' => $username,
+                       'Email' => $email,
+                       'Capture' => $displayPassword ? '1' : null,
+               ];
+               if ( !Hooks::run( 'SpecialPasswordResetOnSubmit', [ &$users, $data, &$error ] ) ) {
+                       return StatusValue::newFatal( wfMessage( $error ) );
+               }
+
+               if ( !$users ) {
+                       if ( $method === 'email' ) {
+                               // Don't reveal whether or not an email address is in use
+                               return StatusValue::newGood( [] );
+                       } else {
+                               return StatusValue::newFatal( 'noname' );
+                       }
+               }
+
+               $firstUser = $users[0];
+
+               if ( !$firstUser instanceof User || !$firstUser->getId() ) {
+                       // Don't parse username as wikitext (bug 65501)
+                       return StatusValue::newFatal( wfMessage( 'nosuchuser', wfEscapeWikiText( $username ) ) );
+               }
+
+               // Check against the rate limiter
+               if ( $performingUser->pingLimiter( 'mailpassword' ) ) {
+                       return StatusValue::newFatal( 'actionthrottledtext' );
+               }
+
+               // All the users will have the same email address
+               if ( !$firstUser->getEmail() ) {
+                       // This won't be reachable from the email route, so safe to expose the username
+                       return StatusValue::newFatal( wfMessage( 'noemail',
+                               wfEscapeWikiText( $firstUser->getName() ) ) );
+               }
+
+               // We need to have a valid IP address for the hook, but per bug 18347, we should
+               // send the user's name if they're logged in.
+               $ip = $performingUser->getRequest()->getIP();
+               if ( !$ip ) {
+                       return StatusValue::newFatal( 'badipaddress' );
+               }
+
+               Hooks::run( 'User::mailPasswordInternal', [ &$performingUser, &$ip, &$firstUser ] );
+
+               $result = StatusValue::newGood();
+               $reqs = [];
+               foreach ( $users as $user ) {
+                       $req = TemporaryPasswordAuthenticationRequest::newRandom();
+                       $req->username = $user->getName();
+                       $req->mailpassword = true;
+                       $req->hasBackchannel = $displayPassword;
+                       $req->caller = $performingUser->getName();
+                       $status = $this->authManager->allowsAuthenticationDataChange( $req, true );
+                       if ( $status->isGood() && $status->getValue() !== 'ignored' ) {
+                               $reqs[] = $req;
+                       } elseif ( $result->isGood() ) {
+                               // only record the first error, to avoid exposing the number of users having the
+                               // same email address
+                               if ( $status->getValue() === 'ignored' ) {
+                                       $status = StatusValue::newFatal( 'passwordreset-ignored' );
+                               }
+                               $result->merge( $status );
+                       }
+               }
+
+               if ( !$result->isGood() ) {
+                       return $result;
+               }
+
+               $passwords = [];
+               foreach ( $reqs as $req ) {
+                       $this->authManager->changeAuthenticationData( $req );
+                       // TODO record mail sending errors
+                       if ( $displayPassword ) {
+                               $passwords[$req->username] = $req->password;
+                       }
+               }
+
+               return StatusValue::newGood( $passwords );
+       }
+
+       /**
+        * @param string $email
+        * @return User[]
+        * @throws MWException On unexpected database errors
+        */
+       protected function getUsersByEmail( $email ) {
+               $res = wfGetDB( DB_SLAVE )->select(
+                       'user',
+                       User::selectFields(),
+                       [ 'user_email' => $email ],
+                       __METHOD__
+               );
+
+               if ( !$res ) {
+                       // Some sort of database error, probably unreachable
+                       throw new MWException( 'Unknown database error in ' . __METHOD__ );
+               }
+
+               $users = [];
+               foreach ( $res as $row ) {
+                       $users[] = User::newFromRow( $row );
+               }
+               return $users;
+       }
+}
index 7c32c3b..ce2ac83 100644 (file)
@@ -23,6 +23,9 @@
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\SessionManager;
 use MediaWiki\Session\Token;
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\AuthenticationRequest;
 
 /**
  * String Some punctuation to prevent editing from broken text-mangling proxies.
@@ -127,6 +130,7 @@ class User implements IDBAccessObject {
                'createpage',
                'createtalk',
                'delete',
+               'deletechangetags',
                'deletedhistory',
                'deletedtext',
                'deletelogentry',
@@ -475,7 +479,7 @@ class User implements IDBAccessObject {
         */
        protected static function getInProcessCache() {
                if ( !self::$inProcessCache ) {
-                       self::$inProcessCache = new HashBagOStuff( ['maxKeys' => 10] );
+                       self::$inProcessCache = new HashBagOStuff( [ 'maxKeys' => 10 ] );
                }
                return self::$inProcessCache;
        }
@@ -498,7 +502,10 @@ class User implements IDBAccessObject {
                $data = $processCache->get( $key );
                if ( !is_array( $data ) ) {
                        $data = $cache->get( $key );
-                       if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
+                       if ( !is_array( $data )
+                               || !isset( $data['mVersion'] )
+                               || $data['mVersion'] < self::VERSION
+                       ) {
                                // Object is expired
                                return false;
                        }
@@ -674,8 +681,11 @@ class User implements IDBAccessObject {
         *  - steal: Whether to reset the account's password and email if it
         *    already exists, default false
         * @return User|null
+        * @since 1.27
         */
        public static function newSystemUser( $name, $options = [] ) {
+               global $wgDisableAuthManager;
+
                $options += [
                        'validate' => 'valid',
                        'create' => true,
@@ -687,13 +697,15 @@ class User implements IDBAccessObject {
                        return null;
                }
 
+               $fields = self::selectFields();
+               if ( $wgDisableAuthManager ) {
+                       $fields = array_merge( $fields, [ 'user_password', 'user_newpassword' ] );
+               }
+
                $dbw = wfGetDB( DB_MASTER );
                $row = $dbw->selectRow(
                        'user',
-                       array_merge(
-                               self::selectFields(),
-                               [ 'user_password', 'user_newpassword' ]
-                       ),
+                       $fields,
                        [ 'user_name' => $name ],
                        __METHOD__
                );
@@ -706,40 +718,50 @@ class User implements IDBAccessObject {
                // A user is considered to exist as a non-system user if it has a
                // password set, or a temporary password set, or an email set, or a
                // non-invalid token.
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               try {
-                       $password = $passwordFactory->newFromCiphertext( $row->user_password );
-               } catch ( PasswordError $e ) {
-                       wfDebug( 'Invalid password hash found in database.' );
-                       $password = PasswordFactory::newInvalidPassword();
-               }
-               try {
-                       $newpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
-               } catch ( PasswordError $e ) {
-                       wfDebug( 'Invalid password hash found in database.' );
-                       $newpassword = PasswordFactory::newInvalidPassword();
-               }
-               if ( !$password instanceof InvalidPassword || !$newpassword instanceof InvalidPassword
-                       || $user->mEmail || $user->mToken !== self::INVALID_TOKEN
-               ) {
+               if ( !$user->mEmail && $user->mToken === self::INVALID_TOKEN ) {
+                       if ( $wgDisableAuthManager ) {
+                               $passwordFactory = new PasswordFactory();
+                               $passwordFactory->init( RequestContext::getMain()->getConfig() );
+                               try {
+                                       $password = $passwordFactory->newFromCiphertext( $row->user_password );
+                               } catch ( PasswordError $e ) {
+                                       wfDebug( 'Invalid password hash found in database.' );
+                                       $password = PasswordFactory::newInvalidPassword();
+                               }
+                               try {
+                                       $newpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
+                               } catch ( PasswordError $e ) {
+                                       wfDebug( 'Invalid password hash found in database.' );
+                                       $newpassword = PasswordFactory::newInvalidPassword();
+                               }
+                               $canAuthenticate = !$password instanceof InvalidPassword ||
+                                       !$newpassword instanceof InvalidPassword;
+                       } else {
+                               $canAuthenticate = AuthManager::singleton()->userCanAuthenticate( $name );
+                       }
+               }
+               if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN || $canAuthenticate ) {
                        // User exists. Steal it?
                        if ( !$options['steal'] ) {
                                return null;
                        }
 
-                       $nopass = PasswordFactory::newInvalidPassword()->toString();
+                       if ( $wgDisableAuthManager ) {
+                               $nopass = PasswordFactory::newInvalidPassword()->toString();
+                               $dbw->update(
+                                       'user',
+                                       [
+                                               'user_password' => $nopass,
+                                               'user_newpassword' => $nopass,
+                                               'user_newpass_time' => null,
+                                       ],
+                                       [ 'user_id' => $user->getId() ],
+                                       __METHOD__
+                               );
+                       } else {
+                               AuthManager::singleton()->revokeAccessForUser( $name );
+                       }
 
-                       $dbw->update(
-                               'user',
-                               [
-                                       'user_password' => $nopass,
-                                       'user_newpassword' => $nopass,
-                                       'user_newpass_time' => null,
-                               ],
-                               [ 'user_id' => $user->getId() ],
-                               __METHOD__
-                       );
                        $user->invalidateEmail();
                        $user->mToken = self::INVALID_TOKEN;
                        $user->saveSettings();
@@ -1078,8 +1100,9 @@ class User implements IDBAccessObject {
                }
 
                // Reject various classes of invalid names
-               global $wgAuth;
-               $name = $wgAuth->getCanonicalName( $t->getText() );
+               $name = AuthManager::callLegacyAuthPlugin(
+                       'getCanonicalName', [ $t->getText() ], $t->getText()
+               );
 
                switch ( $validate ) {
                        case false:
@@ -1404,7 +1427,7 @@ class User implements IDBAccessObject {
         * @see $wgAutopromoteOnce
         */
        public function addAutopromoteOnceGroups( $event ) {
-               global $wgAutopromoteOnceLogInRC, $wgAuth;
+               global $wgAutopromoteOnceLogInRC;
 
                if ( wfReadOnly() || !$this->getId() ) {
                        return [];
@@ -1425,7 +1448,7 @@ class User implements IDBAccessObject {
                }
                // update groups in external authentication database
                Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
-               $wgAuth->updateExternalDBGroups( $this, $toPromote );
+               AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
 
                $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
 
@@ -2040,9 +2063,8 @@ class User implements IDBAccessObject {
                if ( $this->mLocked !== null ) {
                        return $this->mLocked;
                }
-               global $wgAuth;
-               $authUser = $wgAuth->getUserInstance( $this );
-               $this->mLocked = (bool)$authUser->isLocked();
+               $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
+               $this->mLocked = $authUser && $authUser->isLocked();
                Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
                return $this->mLocked;
        }
@@ -2058,9 +2080,8 @@ class User implements IDBAccessObject {
                }
                $this->getBlockedStatus();
                if ( !$this->mHideName ) {
-                       global $wgAuth;
-                       $authUser = $wgAuth->getUserInstance( $this );
-                       $this->mHideName = (bool)$authUser->isHidden();
+                       $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
+                       $this->mHideName = $authUser && $authUser->isHidden();
                        Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
                }
                return $this->mHideName;
@@ -2466,13 +2487,17 @@ class User implements IDBAccessObject {
         * wipes it, so the account cannot be logged in until
         * a new password is set, for instance via e-mail.
         *
-        * @deprecated since 1.27. AuthManager is coming.
+        * @deprecated since 1.27, use AuthManager instead
         * @param string $str New password to set
         * @throws PasswordError On failure
         * @return bool
         */
        public function setPassword( $str ) {
-               global $wgAuth;
+               global $wgAuth, $wgDisableAuthManager;
+
+               if ( !$wgDisableAuthManager ) {
+                       return $this->setPasswordInternal( $str );
+               }
 
                if ( $str !== null ) {
                        if ( !$wgAuth->allowPasswordChange() ) {
@@ -2489,7 +2514,6 @@ class User implements IDBAccessObject {
                        throw new PasswordError( wfMessage( 'externaldberror' )->text() );
                }
 
-               $this->setToken();
                $this->setOption( 'watchlisttoken', false );
                $this->setPasswordInternal( $str );
 
@@ -2499,16 +2523,19 @@ class User implements IDBAccessObject {
        /**
         * Set the password and reset the random token unconditionally.
         *
-        * @deprecated since 1.27. AuthManager is coming.
+        * @deprecated since 1.27, use AuthManager instead
         * @param string|null $str New password to set or null to set an invalid
         *  password hash meaning that the user will not be able to log in
         *  through the web interface.
         */
        public function setInternalPassword( $str ) {
-               global $wgAuth;
+               global $wgAuth, $wgDisableAuthManager;
+
+               if ( !$wgDisableAuthManager ) {
+                       $this->setPasswordInternal( $str );
+               }
 
                if ( $wgAuth->allowSetLocalPassword() ) {
-                       $this->setToken();
                        $this->setOption( 'watchlisttoken', false );
                        $this->setPasswordInternal( $str );
                }
@@ -2520,31 +2547,68 @@ class User implements IDBAccessObject {
         * @param string|null $str New password to set or null to set an invalid
         *  password hash meaning that the user will not be able to log in
         *  through the web interface.
+        * @return bool Success
         */
        private function setPasswordInternal( $str ) {
-               $id = self::idFromName( $this->getName(), self::READ_LATEST );
-               if ( $id == 0 ) {
-                       throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
+               global $wgDisableAuthManager;
+
+               if ( $wgDisableAuthManager ) {
+                       $id = self::idFromName( $this->getName(), self::READ_LATEST );
+                       if ( $id == 0 ) {
+                               throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
+                       }
+
+                       $passwordFactory = new PasswordFactory();
+                       $passwordFactory->init( RequestContext::getMain()->getConfig() );
+                       $dbw = wfGetDB( DB_MASTER );
+                       $dbw->update(
+                               'user',
+                               [
+                                       'user_password' => $passwordFactory->newFromPlaintext( $str )->toString(),
+                                       'user_newpassword' => PasswordFactory::newInvalidPassword()->toString(),
+                                       'user_newpass_time' => $dbw->timestampOrNull( null ),
+                               ],
+                               [
+                                       'user_id' => $id,
+                               ],
+                               __METHOD__
+                       );
+
+                       // When the main password is changed, invalidate all bot passwords too
+                       BotPassword::invalidateAllPasswordsForUser( $this->getName() );
+               } else {
+                       $manager = AuthManager::singleton();
+
+                       // If the user doesn't exist yet, fail
+                       if ( !$manager->userExists( $this->getName() ) ) {
+                               throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
+                       }
+
+                       $data = [
+                               'username' => $this->getName(),
+                               'password' => $str,
+                               'retype' => $str,
+                       ];
+                       $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
+                       $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
+                       foreach ( $reqs as $req ) {
+                               $status = $manager->allowsAuthenticationDataChange( $req );
+                               if ( !$status->isOk() ) {
+                                       \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
+                                               ->info( __METHOD__ . ': Password change rejected: ' . $status->getWikiText() );
+                                       return false;
+                               }
+                       }
+                       foreach ( $reqs as $req ) {
+                               $manager->changeAuthenticationData( $req );
+                       }
+
+                       $this->setOption( 'watchlisttoken', false );
                }
 
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->update(
-                       'user',
-                       [
-                               'user_password' => $passwordFactory->newFromPlaintext( $str )->toString(),
-                               'user_newpassword' => PasswordFactory::newInvalidPassword()->toString(),
-                               'user_newpass_time' => $dbw->timestampOrNull( null ),
-                       ],
-                       [
-                               'user_id' => $id,
-                       ],
-                       __METHOD__
-               );
+               SessionManager::singleton()->invalidateSessionsForUser( $this );
 
-               // When the main password is changed, invalidate all bot passwords too
-               BotPassword::invalidateAllPasswordsForUser( $this->getName() );
+               return true;
        }
 
        /**
@@ -2606,63 +2670,74 @@ class User implements IDBAccessObject {
        /**
         * Set the password for a password reminder or new account email
         *
-        * @deprecated since 1.27, AuthManager is coming
+        * @deprecated Removed in 1.27. Use PasswordReset instead.
         * @param string $str New password to set or null to set an invalid
         *  password hash meaning that the user will not be able to use it
         * @param bool $throttle If true, reset the throttle timestamp to the present
         */
        public function setNewpassword( $str, $throttle = true ) {
-               $id = $this->getId();
-               if ( $id == 0 ) {
-                       throw new LogicException( 'Cannot set new password for a user that is not in the database.' );
-               }
+               global $wgDisableAuthManager;
 
-               $dbw = wfGetDB( DB_MASTER );
+               if ( $wgDisableAuthManager ) {
+                       $id = $this->getId();
+                       if ( $id == 0 ) {
+                               throw new LogicException( 'Cannot set new password for a user that is not in the database.' );
+                       }
 
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               $update = [
-                       'user_newpassword' => $passwordFactory->newFromPlaintext( $str )->toString(),
-               ];
+                       $dbw = wfGetDB( DB_MASTER );
 
-               if ( $str === null ) {
-                       $update['user_newpass_time'] = null;
-               } elseif ( $throttle ) {
-                       $update['user_newpass_time'] = $dbw->timestamp();
-               }
+                       $passwordFactory = new PasswordFactory();
+                       $passwordFactory->init( RequestContext::getMain()->getConfig() );
+                       $update = [
+                               'user_newpassword' => $passwordFactory->newFromPlaintext( $str )->toString(),
+                       ];
 
-               $dbw->update( 'user', $update, [ 'user_id' => $id ], __METHOD__ );
+                       if ( $str === null ) {
+                               $update['user_newpass_time'] = null;
+                       } elseif ( $throttle ) {
+                               $update['user_newpass_time'] = $dbw->timestamp();
+                       }
+
+                       $dbw->update( 'user', $update, [ 'user_id' => $id ], __METHOD__ );
+               } else {
+                       throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
+               }
        }
 
        /**
         * Has password reminder email been sent within the last
         * $wgPasswordReminderResendTime hours?
+        * @deprecated Removed in 1.27. See above.
         * @return bool
         */
        public function isPasswordReminderThrottled() {
-               global $wgPasswordReminderResendTime;
+               global $wgPasswordReminderResendTime, $wgDisableAuthManager;
 
-               if ( !$wgPasswordReminderResendTime ) {
-                       return false;
-               }
+               if ( $wgDisableAuthManager ) {
+                       if ( !$wgPasswordReminderResendTime ) {
+                               return false;
+                       }
 
-               $this->load();
+                       $this->load();
 
-               $db = ( $this->queryFlagsUsed & self::READ_LATEST )
-                       ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
-               $newpassTime = $db->selectField(
-                       'user',
-                       'user_newpass_time',
-                       [ 'user_id' => $this->getId() ],
-                       __METHOD__
-               );
+                       $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+                               ? wfGetDB( DB_MASTER )
+                               : wfGetDB( DB_SLAVE );
+                       $newpassTime = $db->selectField(
+                               'user',
+                               'user_newpass_time',
+                               [ 'user_id' => $this->getId() ],
+                               __METHOD__
+                       );
 
-               if ( $newpassTime === null ) {
-                       return false;
+                       if ( $newpassTime === null ) {
+                               return false;
+                       }
+                       $expiry = wfTimestamp( TS_UNIX, $newpassTime ) + $wgPasswordReminderResendTime * 3600;
+                       return time() < $expiry;
+               } else {
+                       throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
                }
-               $expiry = wfTimestamp( TS_UNIX, $newpassTime ) + $wgPasswordReminderResendTime * 3600;
-               return time() < $expiry;
        }
 
        /**
@@ -3398,6 +3473,21 @@ class User implements IDBAccessObject {
                return !$this->isLoggedIn();
        }
 
+       /**
+        * @return bool Whether this user is flagged as being a bot role account
+        * @since 1.28
+        */
+       public function isBot() {
+               if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
+                       return true;
+               }
+
+               $isBot = false;
+               Hooks::run( "UserIsBot", [ $this, &$isBot ] );
+
+               return $isBot;
+       }
+
        /**
         * Check if user is allowed to access a feature / make an action
         *
@@ -3500,7 +3590,7 @@ class User implements IDBAccessObject {
         */
        public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
                if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
-                       return WatchedItemStore::getDefaultInstance()->isWatched( $this, $title );
+                       return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
                }
                return false;
        }
@@ -3514,7 +3604,7 @@ class User implements IDBAccessObject {
         */
        public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
                if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
-                       WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
+                       MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
                                $this,
                                [ $title->getSubjectPage(), $title->getTalkPage() ]
                        );
@@ -3531,8 +3621,9 @@ class User implements IDBAccessObject {
         */
        public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
                if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
-                       WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getSubjectPage() );
-                       WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getTalkPage() );
+                       $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+                       $store->removeWatch( $this, $title->getSubjectPage() );
+                       $store->removeWatch( $this, $title->getTalkPage() );
                }
                $this->invalidateCache();
        }
@@ -3601,7 +3692,7 @@ class User implements IDBAccessObject {
                        $force = 'force';
                }
 
-               WatchedItemStore::getDefaultInstance()
+               MediaWikiServices::getInstance()->getWatchedItemStore()
                        ->resetNotificationTimestamp( $this, $title, $force, $oldid );
        }
 
@@ -3768,6 +3859,7 @@ class User implements IDBAccessObject {
                if ( !$session->canSetUser() ) {
                        \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
                                ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
+                       $error = 'immutable';
                } elseif ( !$session->getUser()->equals( $this ) ) {
                        \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
                                ->warning( __METHOD__ .
@@ -3775,6 +3867,7 @@ class User implements IDBAccessObject {
                                );
                        // But we still may as well make this user object anon
                        $this->clearInstanceCache( 'defaults' );
+                       $error = 'wronguser';
                } else {
                        $this->clearInstanceCache( 'defaults' );
                        $delay = $session->delaySave();
@@ -3783,7 +3876,13 @@ class User implements IDBAccessObject {
                        $session->setUser( new User );
                        $session->set( 'wsUserID', 0 ); // Other code expects this
                        ScopedCallback::consume( $delay );
+                       $error = false;
                }
+               \MediaWiki\Logger\LoggerFactory::getInstance( 'authmanager' )->info( 'Logout', [
+                       'event' => 'logout',
+                       'successful' => $error === false,
+                       'status' => $error ?: 'success',
+               ] );
        }
 
        /**
@@ -4130,110 +4229,140 @@ class User implements IDBAccessObject {
 
        /**
         * Check to see if the given clear-text password is one of the accepted passwords
-        * @deprecated since 1.27. AuthManager is coming.
+        * @deprecated since 1.27, use AuthManager instead
         * @param string $password User password
         * @return bool True if the given password is correct, otherwise False
         */
        public function checkPassword( $password ) {
-               global $wgAuth, $wgLegacyEncoding;
+               global $wgAuth, $wgLegacyEncoding, $wgDisableAuthManager;
 
-               $this->load();
+               if ( $wgDisableAuthManager ) {
+                       $this->load();
 
-               // Some passwords will give a fatal Status, which means there is
-               // some sort of technical or security reason for this password to
-               // be completely invalid and should never be checked (e.g., T64685)
-               if ( !$this->checkPasswordValidity( $password )->isOK() ) {
-                       return false;
-               }
+                       // Some passwords will give a fatal Status, which means there is
+                       // some sort of technical or security reason for this password to
+                       // be completely invalid and should never be checked (e.g., T64685)
+                       if ( !$this->checkPasswordValidity( $password )->isOK() ) {
+                               return false;
+                       }
 
-               // Certain authentication plugins do NOT want to save
-               // domain passwords in a mysql database, so we should
-               // check this (in case $wgAuth->strict() is false).
-               if ( $wgAuth->authenticate( $this->getName(), $password ) ) {
-                       return true;
-               } elseif ( $wgAuth->strict() ) {
-                       // Auth plugin doesn't allow local authentication
-                       return false;
-               } elseif ( $wgAuth->strictUserAuth( $this->getName() ) ) {
-                       // Auth plugin doesn't allow local authentication for this user name
-                       return false;
-               }
+                       // Certain authentication plugins do NOT want to save
+                       // domain passwords in a mysql database, so we should
+                       // check this (in case $wgAuth->strict() is false).
+                       if ( $wgAuth->authenticate( $this->getName(), $password ) ) {
+                               return true;
+                       } elseif ( $wgAuth->strict() ) {
+                               // Auth plugin doesn't allow local authentication
+                               return false;
+                       } elseif ( $wgAuth->strictUserAuth( $this->getName() ) ) {
+                               // Auth plugin doesn't allow local authentication for this user name
+                               return false;
+                       }
 
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               $db = ( $this->queryFlagsUsed & self::READ_LATEST )
-                       ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       $passwordFactory = new PasswordFactory();
+                       $passwordFactory->init( RequestContext::getMain()->getConfig() );
+                       $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+                               ? wfGetDB( DB_MASTER )
+                               : wfGetDB( DB_SLAVE );
 
-               try {
-                       $mPassword = $passwordFactory->newFromCiphertext( $db->selectField(
-                               'user', 'user_password', [ 'user_id' => $this->getId() ], __METHOD__
-                       ) );
-               } catch ( PasswordError $e ) {
-                       wfDebug( 'Invalid password hash found in database.' );
-                       $mPassword = PasswordFactory::newInvalidPassword();
-               }
+                       try {
+                               $mPassword = $passwordFactory->newFromCiphertext( $db->selectField(
+                                       'user', 'user_password', [ 'user_id' => $this->getId() ], __METHOD__
+                               ) );
+                       } catch ( PasswordError $e ) {
+                               wfDebug( 'Invalid password hash found in database.' );
+                               $mPassword = PasswordFactory::newInvalidPassword();
+                       }
 
-               if ( !$mPassword->equals( $password ) ) {
-                       if ( $wgLegacyEncoding ) {
-                               // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
-                               // Check for this with iconv
-                               $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
-                               if ( $cp1252Password === $password || !$mPassword->equals( $cp1252Password ) ) {
+                       if ( !$mPassword->equals( $password ) ) {
+                               if ( $wgLegacyEncoding ) {
+                                       // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
+                                       // Check for this with iconv
+                                       $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
+                                       if ( $cp1252Password === $password || !$mPassword->equals( $cp1252Password ) ) {
+                                               return false;
+                                       }
+                               } else {
                                        return false;
                                }
-                       } else {
-                               return false;
                        }
-               }
 
-               if ( $passwordFactory->needsUpdate( $mPassword ) && !wfReadOnly() ) {
-                       $this->setPasswordInternal( $password );
-               }
+                       if ( $passwordFactory->needsUpdate( $mPassword ) && !wfReadOnly() ) {
+                               $this->setPasswordInternal( $password );
+                       }
 
-               return true;
+                       return true;
+               } else {
+                       $manager = AuthManager::singleton();
+                       $reqs = AuthenticationRequest::loadRequestsFromSubmission(
+                               $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
+                               [
+                                       'username' => $this->getName(),
+                                       'password' => $password,
+                               ]
+                       );
+                       $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
+                       switch ( $res->status ) {
+                               case AuthenticationResponse::PASS:
+                                       return true;
+                               case AuthenticationResponse::FAIL:
+                                       // Hope it's not a PreAuthenticationProvider that failed...
+                                       \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
+                                               ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
+                                       return false;
+                               default:
+                                       throw new BadMethodCallException(
+                                               'AuthManager returned a response unsupported by ' . __METHOD__
+                                       );
+                       }
+               }
        }
 
        /**
         * Check if the given clear-text password matches the temporary password
         * sent by e-mail for password reset operations.
         *
-        * @deprecated since 1.27. AuthManager is coming.
+        * @deprecated since 1.27, use AuthManager instead
         * @param string $plaintext
         * @return bool True if matches, false otherwise
         */
        public function checkTemporaryPassword( $plaintext ) {
-               global $wgNewPasswordExpiry;
+               global $wgNewPasswordExpiry, $wgDisableAuthManager;
 
-               $this->load();
+               if ( $wgDisableAuthManager ) {
+                       $this->load();
 
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               $db = ( $this->queryFlagsUsed & self::READ_LATEST )
-                       ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       $passwordFactory = new PasswordFactory();
+                       $passwordFactory->init( RequestContext::getMain()->getConfig() );
+                       $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+                               ? wfGetDB( DB_MASTER )
+                               : wfGetDB( DB_SLAVE );
 
-               $row = $db->selectRow(
-                       'user',
-                       [ 'user_newpassword', 'user_newpass_time' ],
-                       [ 'user_id' => $this->getId() ],
-                       __METHOD__
-               );
-               try {
-                       $newPassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
-               } catch ( PasswordError $e ) {
-                       wfDebug( 'Invalid password hash found in database.' );
-                       $newPassword = PasswordFactory::newInvalidPassword();
-               }
+                       $row = $db->selectRow(
+                               'user',
+                               [ 'user_newpassword', 'user_newpass_time' ],
+                               [ 'user_id' => $this->getId() ],
+                               __METHOD__
+                       );
+                       try {
+                               $newPassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
+                       } catch ( PasswordError $e ) {
+                               wfDebug( 'Invalid password hash found in database.' );
+                               $newPassword = PasswordFactory::newInvalidPassword();
+                       }
 
-               if ( $newPassword->equals( $plaintext ) ) {
-                       if ( is_null( $row->user_newpass_time ) ) {
-                               return true;
+                       if ( $newPassword->equals( $plaintext ) ) {
+                               if ( is_null( $row->user_newpass_time ) ) {
+                                       return true;
+                               }
+                               $expiry = wfTimestamp( TS_UNIX, $row->user_newpass_time ) + $wgNewPasswordExpiry;
+                               return ( time() < $expiry );
+                       } else {
+                               return false;
                        }
-                       $expiry = wfTimestamp( TS_UNIX, $row->user_newpass_time ) + $wgNewPasswordExpiry;
-                       return ( time() < $expiry );
                } else {
-                       return false;
+                       // Can't check the temporary password individually.
+                       return $this->checkPassword( $plaintext );
                }
        }
 
@@ -5089,6 +5218,7 @@ class User implements IDBAccessObject {
         * Add a newuser log entry for this user.
         * Before 1.19 the return value was always true.
         *
+        * @deprecated since 1.27, AuthManager handles logging
         * @param string|bool $action Account creation type.
         *   - String, one of the following values:
         *     - 'create' for an anonymous user creating an account for himself.
@@ -5101,14 +5231,13 @@ class User implements IDBAccessObject {
         *     - true will be converted to 'byemail'
         *     - false will be converted to 'create' if this object is the same as
         *       $wgUser and to 'create2' otherwise
-        *
         * @param string $reason User supplied reason
-        *
-        * @return int|bool True if not $wgNewUserLog; otherwise ID of log item or 0 on failure
+        * @return int|bool True if not $wgNewUserLog or not $wgDisableAuthManager;
+        *   otherwise ID of log item or 0 on failure
         */
        public function addNewUserLogEntry( $action = false, $reason = '' ) {
-               global $wgUser, $wgNewUserLog;
-               if ( empty( $wgNewUserLog ) ) {
+               global $wgUser, $wgNewUserLog, $wgDisableAuthManager;
+               if ( !$wgDisableAuthManager || empty( $wgNewUserLog ) ) {
                        return true; // disabled
                }
 
@@ -5149,6 +5278,7 @@ class User implements IDBAccessObject {
         * Used by things like CentralAuth and perhaps other authplugins.
         * Consider calling addNewUserLogEntry() directly instead.
         *
+        * @deprecated since 1.27, AuthManager handles logging
         * @return bool
         */
        public function addNewUserLogEntryAutoCreate() {
old mode 100644 (file)
new mode 100755 (executable)
index 5ff411d..6baaff0
@@ -16,6 +16,7 @@ class SearchInputWidget extends TitleInputWidget {
        protected $performSearchOnClick = true;
        protected $validateTitle = false;
        protected $highlightFirst = false;
+       protected $dataLocation = 'header';
 
        /**
         * @param array $config Configuration options
@@ -24,6 +25,8 @@ class SearchInputWidget extends TitleInputWidget {
         * @param boolean|null $config['performSearchOnClick'] If true, the script will start a search
         *  whenever a user hits a suggestion. If false, the text of the suggestion is inserted into the
         *  text field only (default: true)
+        * @param string $config['dataLocation'] Where the search input field will be
+        *  used (header or content, default: header)
         */
        public function __construct( array $config = [] ) {
                $config = array_merge( [
@@ -31,7 +34,6 @@ class SearchInputWidget extends TitleInputWidget {
                        'maxLength' => null,
                        'type' => 'search',
                        'icon' => 'search',
-                       'dataLocation' => 'content',
                ], $config );
 
                // Parent constructor
index 190f2bf..e7643b1 100644 (file)
@@ -942,14 +942,14 @@ class Language {
         * @param string $key
         * @return string
         */
-       function getMonthAbbreviation( $key ) {
+       public function getMonthAbbreviation( $key ) {
                return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
        }
 
        /**
         * @return array
         */
-       function getMonthAbbreviationsArray() {
+       public function getMonthAbbreviationsArray() {
                $monthNames = [ '' ];
                for ( $i = 1; $i < 13; $i++ ) {
                        $monthNames[] = $this->getMonthAbbreviation( $i );
@@ -961,7 +961,7 @@ class Language {
         * @param string $key
         * @return string
         */
-       function getWeekdayName( $key ) {
+       public function getWeekdayName( $key ) {
                return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
        }
 
@@ -1089,7 +1089,7 @@ class Language {
         * @throws MWException
         * @return string
         */
-       function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) {
+       public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) {
                $s = '';
                $raw = false;
                $roman = false;
@@ -2067,7 +2067,7 @@ class Language {
         *   get user timecorrection setting)
         * @return int
         */
-       function userAdjust( $ts, $tz = false ) {
+       public function userAdjust( $ts, $tz = false ) {
                global $wgUser, $wgLocalTZoffset;
 
                if ( $tz === false ) {
@@ -2214,7 +2214,7 @@ class Language {
         *   validateTimeZone() in Special:Preferences
         * @return string
         */
-       function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
+       public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
                $ts = wfTimestamp( TS_MW, $ts );
                if ( $adj ) {
                        $ts = $this->userAdjust( $ts, $timecorrection );
@@ -2233,7 +2233,7 @@ class Language {
         *   validateTimeZone() in Special:Preferences
         * @return string
         */
-       function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
+       public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
                $ts = wfTimestamp( TS_MW, $ts );
                if ( $adj ) {
                        $ts = $this->userAdjust( $ts, $timecorrection );
@@ -2253,7 +2253,7 @@ class Language {
         *   validateTimeZone() in Special:Preferences
         * @return string
         */
-       function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
+       public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
                $ts = wfTimestamp( TS_MW, $ts );
                if ( $adj ) {
                        $ts = $this->userAdjust( $ts, $timecorrection );
@@ -2559,7 +2559,7 @@ class Language {
         * @param string $key
         * @return array|null
         */
-       function getMessage( $key ) {
+       public function getMessage( $key ) {
                return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
        }
 
@@ -2576,7 +2576,7 @@ class Language {
         * @param string $string
         * @return string
         */
-       function iconv( $in, $out, $string ) {
+       public function iconv( $in, $out, $string ) {
                # This is a wrapper for iconv in all languages except esperanto,
                # which does some nasty x-conversions beforehand
 
@@ -2623,7 +2623,7 @@ class Language {
         *
         * @return string
         */
-       function ucfirst( $str ) {
+       public function ucfirst( $str ) {
                $o = ord( $str );
                if ( $o < 96 ) { // if already uppercase...
                        return $str;
@@ -2643,7 +2643,7 @@ class Language {
         *
         * @return string
         */
-       function uc( $str, $first = false ) {
+       public function uc( $str, $first = false ) {
                if ( $first ) {
                        if ( $this->isMultibyte( $str ) ) {
                                return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
index f9ebdec..b413ef9 100644 (file)
@@ -32,7 +32,7 @@ class LanguageAz extends Language {
         * @param string $string
         * @return mixed|string
         */
-       function ucfirst( $string ) {
+       public function ucfirst( $string ) {
                if ( $string[0] == 'i' ) {
                        return 'İ' . substr( $string, 1 );
                }
index 697fc93..3fec5fc 100644 (file)
@@ -59,7 +59,7 @@ class LanguageEo extends Language {
         * @param string $string Text to be converted
         * @return string
         */
-       function iconv( $in, $out, $string ) {
+       public function iconv( $in, $out, $string ) {
                if ( strcasecmp( $in, 'x' ) == 0 && strcasecmp( $out, 'utf-8' ) == 0 ) {
                        return preg_replace_callback(
                                '/([cghjsu]x?)((?:xx)*)(?!x)/i',
index 0cd0c0d..30abe25 100644 (file)
@@ -54,7 +54,7 @@ class LanguageKaa extends Language {
         *
         * @return string
         */
-       function ucfirst( $string ) {
+       public function ucfirst( $string ) {
                if ( substr( $string, 0, 1 ) === 'i' ) {
                        return 'İ' . substr( $string, 1 );
                }
index 9c6ad44..548c9a0 100644 (file)
@@ -415,7 +415,7 @@ class LanguageKk extends LanguageKk_cyrl {
         *
         * @return string
         */
-       function ucfirst( $string ) {
+       public function ucfirst( $string ) {
                if ( $string[0] == 'i' ) {
                        $variant = $this->getPreferredVariant();
                        if ( $variant == 'kk-latn' || $variant == 'kk-tr' ) {
index 353b59a..5204fb5 100644 (file)
@@ -35,7 +35,7 @@ class LanguageQqx extends Language {
         * @param string $key
         * @return string
         */
-       function getMessage( $key ) {
+       public function getMessage( $key ) {
                return "($key$*)";
        }
 }
index a4f05f8..c947341 100644 (file)
@@ -37,7 +37,7 @@ class LanguageTr extends Language {
         * @param string $string
         * @return string
         */
-       function ucfirst( $string ) {
+       public function ucfirst( $string ) {
                if ( strlen( $string ) && $string[0] == 'i' ) {
                        return 'İ' . substr( $string, 1 );
                }
index 3520298..078b068 100644 (file)
@@ -42,7 +42,7 @@ class LanguageWa extends Language {
         * @param bool $tc
         * @return string
         */
-       function date( $ts, $adj = false, $format = true, $tc = false ) {
+       public function date( $ts, $adj = false, $format = true, $tc = false ) {
                $ts = wfTimestamp( TS_MW, $ts );
                if ( $adj ) {
                        $ts = $this->userAdjust( $ts, $tc );
@@ -89,7 +89,7 @@ class LanguageWa extends Language {
         * @param bool $tc
         * @return string
         */
-       function timeanddate( $ts, $adj = false, $format = true, $tc = false ) {
+       public function timeanddate( $ts, $adj = false, $format = true, $tc = false ) {
                if ( $adj ) {
                        $ts = $this->userAdjust( $ts, $tc );
                }
index 08a40b8..e4998fc 100644 (file)
@@ -3220,6 +3220,7 @@ public static $zh2Hant = [
 '不采声' => '不采聲',
 '不采聲' => '不采聲',
 '不锈钢' => '不鏽鋼',
+'不食乾腊' => '不食乾腊',
 '不食干腊' => '不食乾腊',
 '不斗' => '不鬥',
 '丑三' => '丑三',
@@ -3273,6 +3274,7 @@ public static $zh2Hant = [
 '丰容' => '丰容',
 '丰情' => '丰情',
 '丰标' => '丰標',
+'丰標' => '丰標',
 '丰标不凡' => '丰標不凡',
 '丰標不凡' => '丰標不凡',
 '丰神' => '丰神',
@@ -3299,6 +3301,7 @@ public static $zh2Hant = [
 '么爷' => '么爺',
 '么雞' => '么雞',
 '么么小丑' => '么麼小丑',
+'么麼小丑' => '么麼小丑',
 '之一只' => '之一只',
 '之二只' => '之二只',
 '之八九只' => '之八九只',
@@ -3497,6 +3500,7 @@ public static $zh2Hant = [
 '于仁泰' => '于仁泰',
 '于仲文' => '于仲文',
 '于佳卉' => '于佳卉',
+'于來山' => '于來山',
 '于来山' => '于來山',
 '于伟国' => '于偉國',
 '于偉國' => '于偉國',
@@ -3509,17 +3513,20 @@ public static $zh2Hant = [
 '于再清' => '于再清',
 '于冕' => '于冕',
 '于冠华' => '于冠華',
+'于冠華' => '于冠華',
 '于凌奎' => '于凌奎',
 '于凌辰' => '于凌辰',
 '于勒' => '于勒',
 '于化虎' => '于化虎',
 '于占元' => '于占元',
 '于友泽' => '于友澤',
+'于友澤' => '于友澤',
 '于台烟' => '于台煙',
 '于台煙' => '于台煙',
 '于右任' => '于右任',
 '于吉' => '于吉',
 '于和伟' => '于和偉',
+'于和偉' => '于和偉',
 '于品海' => '于品海',
 '于国桢' => '于國楨',
 '于國楨' => '于國楨',
@@ -3530,6 +3537,7 @@ public static $zh2Hant = [
 '于大宝' => '于大寶',
 '于大寶' => '于大寶',
 '于天仁' => '于天仁',
+'于天龍' => '于天龍',
 '于天龙' => '于天龍',
 '于奇库杜克' => '于奇庫杜克',
 '于奇庫杜克' => '于奇庫杜克',
@@ -3543,6 +3551,7 @@ public static $zh2Hant = [
 '于家堡' => '于家堡',
 '于寘' => '于寘',
 '于宝轩' => '于寶軒',
+'于寶軒' => '于寶軒',
 '于小伟' => '于小偉',
 '于小偉' => '于小偉',
 '于小彤' => '于小彤',
@@ -3565,6 +3574,7 @@ public static $zh2Hant = [
 '于志宁' => '于志寧',
 '于志寧' => '于志寧',
 '于忠肃集' => '于忠肅集',
+'于忠肅集' => '于忠肅集',
 '于思' => '于思',
 '于慎行' => '于慎行',
 '于慧' => '于慧',
@@ -3593,6 +3603,7 @@ public static $zh2Hant = [
 '于格' => '于格',
 '于枫' => '于楓',
 '于楓' => '于楓',
+'于榮光' => '于榮光',
 '于荣光' => '于榮光',
 '于樂' => '于樂',
 '于树洁' => '于樹潔',
@@ -3644,6 +3655,7 @@ public static $zh2Hant = [
 '于西翰' => '于西翰',
 '于謙' => '于謙',
 '于谦' => '于謙',
+'于謹' => '于謹',
 '于谨' => '于謹',
 '于貝爾' => '于貝爾',
 '于贝尔' => '于貝爾',
@@ -3662,6 +3674,7 @@ public static $zh2Hant = [
 '于双戈' => '于雙戈',
 '于雙戈' => '于雙戈',
 '于云鹤' => '于雲鶴',
+'于雲鶴' => '于雲鶴',
 '于震' => '于震',
 '于震寰' => '于震寰',
 '于震环' => '于震環',
@@ -3754,9 +3767,11 @@ public static $zh2Hant = [
 '伊斯兰历' => '伊斯蘭曆',
 '伊斯兰历史' => '伊斯蘭歷史',
 '伊东怜' => '伊東怜',
+'伊東怜' => '伊東怜',
 '伊尔汗历表' => '伊爾汗曆表',
 '伊达里子' => '伊達里子',
 '伊适杰' => '伊適杰',
+'伊適杰' => '伊適杰',
 '伊里布' => '伊里布',
 '伊郁' => '伊鬱',
 '伏几' => '伏几',
@@ -3780,6 +3795,7 @@ public static $zh2Hant = [
 '余光中' => '余光中',
 '余光生' => '余光生',
 '余力为' => '余力為',
+'余力為' => '余力為',
 '余威德' => '余威德',
 '余子明' => '余子明',
 '余思敏' => '余思敏',
@@ -3804,7 +3820,6 @@ public static $zh2Hant = [
 '并吞' => '併吞',
 '并拢' => '併攏',
 '并案' => '併案',
-'并流' => '併流',
 '并火' => '併火',
 '并为一家' => '併為一家',
 '并为一体' => '併為一體',
@@ -3909,6 +3924,7 @@ public static $zh2Hant = [
 '俭仆' => '儉僕',
 '俭朴' => '儉樸',
 '俭确之教' => '儉确之教',
+'儉确之教' => '儉确之教',
 '儒略改革历' => '儒略改革曆',
 '儒略改革历史' => '儒略改革歷史',
 '儒略历' => '儒略曆',
@@ -3957,7 +3973,9 @@ public static $zh2Hant = [
 '党姓' => '党姓',
 '党家' => '党家',
 '党怀英' => '党懷英',
+'党懷英' => '党懷英',
 '党进' => '党進',
+'党進' => '党進',
 '党項' => '党項',
 '党项' => '党項',
 '内脏' => '內臟',
@@ -3990,6 +4008,8 @@ public static $zh2Hant = [
 '公仔面' => '公仔麵',
 '公仆' => '公僕',
 '公孙丑' => '公孫丑',
+'公寓里' => '公寓裡',
+'公寓里弄' => '公寓里弄',
 '公干' => '公幹',
 '公历' => '公曆',
 '公历史' => '公歷史',
@@ -4001,6 +4021,7 @@ public static $zh2Hant = [
 '六天后' => '六天後',
 '六年' => '六年',
 '六楼后座' => '六樓后座',
+'六樓后座' => '六樓后座',
 '六谷' => '六穀',
 '六扎' => '六紮',
 '六冲' => '六衝',
@@ -4042,6 +4063,7 @@ public static $zh2Hant = [
 '几椅' => '几椅',
 '几榻' => '几榻',
 '几净窗明' => '几淨窗明',
+'几淨窗明' => '几淨窗明',
 '几筵' => '几筵',
 '几面上' => '几面上',
 '凶征' => '凶徵',
@@ -4063,11 +4085,13 @@ public static $zh2Hant = [
 '分钟里' => '分鐘裡',
 '刑余' => '刑餘',
 '划一桨' => '划一槳',
+'划一槳' => '划一槳',
 '划上' => '划上',
 '划下' => '划下',
 '划不來' => '划不來',
 '划不来' => '划不來',
 '划了一会' => '划了一會',
+'划了一會' => '划了一會',
 '划來划去' => '划來划去',
 '划来划去' => '划來划去',
 '划具' => '划具',
@@ -4084,6 +4108,7 @@ public static $zh2Hant = [
 '划槳' => '划槳',
 '划水' => '划水',
 '划着独木舟' => '划着獨木舟',
+'划着獨木舟' => '划着獨木舟',
 '划着竹筏' => '划着竹筏',
 '划着船' => '划着船',
 '划算' => '划算',
@@ -4145,6 +4170,7 @@ public static $zh2Hant = [
 '刘佳怜' => '劉佳怜',
 '劉佳怜' => '劉佳怜',
 '刘芸后' => '劉芸后',
+'劉芸后' => '劉芸后',
 '力拼' => '力拚',
 '力拼众敌' => '力拼眾敵',
 '力争上游' => '力爭上遊',
@@ -4332,6 +4358,7 @@ public static $zh2Hant = [
 '叶恭弘' => '叶恭弘',
 '叶音' => '叶音',
 '叶韵' => '叶韻',
+'叶韻' => '叶韻',
 '吃板刀面' => '吃板刀麵',
 '吃碗面' => '吃碗麵',
 '吃姜' => '吃薑',
@@ -4443,8 +4470,10 @@ public static $zh2Hant = [
 '启发式' => '啟發式',
 '啷当' => '啷噹',
 '喂了一声' => '喂了一聲',
+'喂了一聲' => '喂了一聲',
 '喂喂' => '喂喂',
 '喂哟' => '喂喲',
+'喂喲' => '喂喲',
 '喂!' => '喂!',
 '喂,' => '喂,',
 '善于' => '善於',
@@ -4480,6 +4509,7 @@ public static $zh2Hant = [
 '向慕' => '嚮慕',
 '向迩' => '嚮邇',
 '严云农' => '嚴云農',
+'嚴云農' => '嚴云農',
 '严于' => '嚴於',
 '嚼谷' => '嚼穀',
 '啰啰苏苏' => '囉囉囌囌',
@@ -4552,6 +4582,7 @@ public static $zh2Hant = [
 '地志' => '地誌',
 '地丑德齐' => '地醜德齊',
 '坏于' => '坏於',
+'坏於' => '坏於',
 '坐如钟' => '坐如鐘',
 '坐台' => '坐檯',
 '坐钟' => '坐鐘',
@@ -4607,6 +4638,7 @@ public static $zh2Hant = [
 '夏游' => '夏遊',
 '外强中干' => '外強中乾',
 '外制' => '外製',
+'外面包' => '外面包',
 '多半只' => '多半只',
 '多只包括' => '多只包括',
 '多只可' => '多只可',
@@ -4825,6 +4857,7 @@ public static $zh2Hant = [
 '寺钟' => '寺鐘',
 '封后' => '封后',
 '封为后' => '封為后',
+'封為后' => '封為后',
 '封面里' => '封面裡',
 '射雕' => '射鵰',
 '专向往' => '專向往',
@@ -4838,6 +4871,7 @@ public static $zh2Hant = [
 '对准钟' => '對準鐘',
 '对准钟表' => '對準鐘錶',
 '对着干' => '對着幹',
+'對着幹' => '對着幹',
 '对华发' => '對華發',
 '对表中' => '對表中',
 '对表扬' => '對表揚',
@@ -4875,13 +4909,16 @@ public static $zh2Hant = [
 '尸子' => '尸子',
 '尸居余气' => '尸居餘氣',
 '尸弃佛' => '尸棄佛',
+'尸棄佛' => '尸棄佛',
 '尸祝' => '尸祝',
+'尸祿' => '尸祿',
 '尸禄' => '尸祿',
 '尸罗精舍' => '尸羅精舍',
 '尸羅精舍' => '尸羅精舍',
 '尸臣' => '尸臣',
 '尸谏' => '尸諫',
 '尸魂界' => '尸魂界',
+'尸鳩' => '尸鳩',
 '尸鸠' => '尸鳩',
 '局促不安' => '局促不安',
 '局里' => '局裡',
@@ -4956,6 +4993,7 @@ public static $zh2Hant = [
 '帘子' => '帘子',
 '帘布' => '帘布',
 '帝后台' => '帝后臺',
+'帝后臺' => '帝后臺',
 '师范' => '師範',
 '席卷' => '席捲',
 '带征' => '帶徵',
@@ -5117,6 +5155,7 @@ public static $zh2Hant = [
 '張杰' => '張杰',
 '张柏芝' => '張栢芝',
 '张乐于张徐' => '張樂于張徐',
+'張樂于張徐' => '張樂于張徐',
 '强制' => '強制',
 '强制作用' => '強制作用',
 '强奸' => '強姦',
@@ -5226,6 +5265,7 @@ public static $zh2Hant = [
 '心系世' => '心繫世',
 '心系中' => '心繫中',
 '心系乔' => '心繫乔',
+'心繫乔' => '心繫乔',
 '心系五' => '心繫五',
 '心系京' => '心繫京',
 '心系人' => '心繫人',
@@ -5238,6 +5278,7 @@ public static $zh2Hant = [
 '心系全' => '心繫全',
 '心系两' => '心繫兩',
 '心系农' => '心繫农',
+'心繫农' => '心繫农',
 '心系功' => '心繫功',
 '心系动' => '心繫動',
 '心系募' => '心繫募',
@@ -5420,6 +5461,7 @@ public static $zh2Hant = [
 '欲障' => '慾障',
 '忧郁' => '憂鬱',
 '凭几' => '憑几',
+'憑几' => '憑几',
 '凭吊' => '憑弔',
 '凭折' => '憑摺',
 '凭准' => '憑準',
@@ -5462,6 +5504,7 @@ public static $zh2Hant = [
 '所占卜' => '所占卜',
 '所占星' => '所占星',
 '所占算' => '所占算',
+'所有只' => '所有只',
 '所托' => '所託',
 '扁拟谷盗虫' => '扁擬穀盜蟲',
 '手塚治虫' => '手塚治虫',
@@ -5497,6 +5540,7 @@ public static $zh2Hant = [
 '扑作教刑' => '扑作教刑',
 '扑打' => '扑打',
 '扑挞' => '扑撻',
+'扑撻' => '扑撻',
 '打干哕' => '打乾噦',
 '打出吊入' => '打出弔入',
 '打卡钟' => '打卡鐘',
@@ -5757,6 +5801,7 @@ public static $zh2Hant = [
 '搏斗' => '搏鬥',
 '捣鬼吊白' => '搗鬼弔白',
 '扼肮' => '搤肮',
+'搤肮' => '搤肮',
 '扼肮拊背' => '搤肮拊背',
 '搬斗' => '搬鬥',
 '搭干铺' => '搭乾鋪',
@@ -5792,6 +5837,7 @@ public static $zh2Hant = [
 '扑咚咚' => '撲鼕鼕',
 '擀面' => '擀麵',
 '击扑' => '擊扑',
+'擊扑' => '擊扑',
 '击钟' => '擊鐘',
 '操作钟' => '操作鐘',
 '担仔面' => '擔仔麵',
@@ -5852,7 +5898,6 @@ public static $zh2Hant = [
 '数字钟' => '數字鐘',
 '数字钟表' => '數字鐘錶',
 '数罪并罚' => '數罪併罰',
-'数与虏确' => '數與虜确',
 '数只' => '數隻',
 '文丑' => '文丑',
 '文学志' => '文學誌',
@@ -5889,6 +5934,7 @@ public static $zh2Hant = [
 '旁注' => '旁註',
 '旅游' => '旅遊',
 '旋回' => '旋迴',
+'旋松' => '旋鬆',
 '族里' => '族裡',
 '日心历表' => '日心曆表',
 '日历' => '日曆',
@@ -6191,6 +6237,7 @@ public static $zh2Hant = [
 '武后' => '武后',
 '武斗' => '武鬥',
 '岁聿云暮' => '歲聿云暮',
+'歲聿云暮' => '歲聿云暮',
 '历史里' => '歷史裡',
 '归并' => '歸併',
 '归于' => '歸於',
@@ -6224,9 +6271,11 @@ public static $zh2Hant = [
 '气冲斗牛' => '氣沖斗牛',
 '气郁' => '氣鬱',
 '氤郁' => '氤鬱',
+'水并流' => '水併流',
 '水来汤里去' => '水來湯裡去',
 '水准' => '水準',
 '水无怜奈' => '水無怜奈',
+'水無怜奈' => '水無怜奈',
 '水表示' => '水表示',
 '水表面' => '水表面',
 '水里' => '水裡',
@@ -6242,9 +6291,11 @@ public static $zh2Hant = [
 '永志不忘' => '永誌不忘',
 '求知欲' => '求知慾',
 '求签' => '求籤',
+'江并流' => '江併流',
 '池里' => '池裡',
 '污蔑' => '污衊',
 '汤卤' => '汤滷',
+'汤滷' => '汤滷',
 '汲于' => '汲於',
 '决斗' => '決鬥',
 '沈淀' => '沈澱',
@@ -6290,6 +6341,7 @@ public static $zh2Hant = [
 '洒洒' => '洒洒',
 '洒淅' => '洒淅',
 '洒涤' => '洒滌',
+'洒滌' => '洒滌',
 '洒濯' => '洒濯',
 '洒然' => '洒然',
 '洒脱' => '洒脫',
@@ -6334,6 +6386,7 @@ public static $zh2Hant = [
 '涂敏恒' => '涂敏恆',
 '涂泽民' => '涂澤民',
 '涂澤民' => '涂澤民',
+'涂紹煃' => '涂紹煃',
 '涂绍煃' => '涂紹煃',
 '涂羽卿' => '涂羽卿',
 '涂謹申' => '涂謹申',
@@ -6526,6 +6579,7 @@ public static $zh2Hant = [
 '漓湘' => '灕湘',
 '漓然' => '灕然',
 '滩涂' => '灘涂',
+'灘涂' => '灘涂',
 '滩席' => '灘蓆',
 '火并非' => '火並非',
 '火并' => '火併',
@@ -6562,9 +6616,11 @@ public static $zh2Hant = [
 '照入签' => '照入籤',
 '照相干片' => '照相乾片',
 '煨干' => '煨乾',
+'煮制' => '煮製',
 '煮面' => '煮麵',
 '熊杰' => '熊杰',
 '荧郁' => '熒鬱',
+'炖制' => '燉製',
 '燎发' => '燎髮',
 '烧干' => '燒乾',
 '燕几' => '燕几',
@@ -6608,6 +6664,7 @@ public static $zh2Hant = [
 '特制' => '特製',
 '牵一发' => '牽一髮',
 '牵系' => '牽繫',
+'犖确' => '犖确',
 '荦确' => '犖确',
 '狂并潮' => '狂併潮',
 '狃于' => '狃於',
@@ -6622,6 +6679,7 @@ public static $zh2Hant = [
 '狱里' => '獄裡',
 '奖杯' => '獎盃',
 '独裁制' => '獨裁制',
+'獨裁制' => '獨裁制',
 '独辟蹊径' => '獨闢蹊徑',
 '获匪其丑' => '獲匪其醜',
 '兽欲' => '獸慾',
@@ -6814,6 +6872,7 @@ public static $zh2Hant = [
 '眼睛里' => '眼睛裡',
 '眼里' => '眼裡',
 '着眼于' => '着眼於',
+'着眼於' => '着眼於',
 '困乏' => '睏乏',
 '困了' => '睏了',
 '困倦' => '睏倦',
@@ -6863,8 +6922,10 @@ public static $zh2Hant = [
 '磨蝎' => '磨蝎',
 '磨制' => '磨製',
 '磨炼' => '磨鍊',
+'磨面' => '磨麵',
 '磬钟' => '磬鐘',
 '硗确' => '磽确',
+'磽确' => '磽确',
 '砻谷' => '礱穀',
 '示范' => '示範',
 '社里' => '社裡',
@@ -6901,7 +6962,9 @@ public static $zh2Hant = [
 '秋游' => '秋遊',
 '种丹妮' => '种丹妮',
 '种师中' => '种師中',
+'种師中' => '种師中',
 '种师道' => '种師道',
+'种師道' => '种師道',
 '种放' => '种放',
 '科尼亚克期' => '科尼亞克期',
 '科斗' => '科斗',
@@ -7068,7 +7131,6 @@ public static $zh2Hant = [
 '精准' => '精準',
 '精致' => '精緻',
 '精制' => '精製',
-'精炼' => '精鍊',
 '精辟' => '精闢',
 '精松' => '精鬆',
 '糊里糊涂' => '糊裡糊塗',
@@ -7079,6 +7141,7 @@ public static $zh2Hant = [
 '系里' => '系裡',
 '纪历' => '紀曆',
 '纪历史' => '紀歷史',
+'紅后假說' => '紅后假說',
 '红后假说' => '紅后假說',
 '红绳系足' => '紅繩繫足',
 '红钟' => '紅鐘',
@@ -7138,6 +7201,7 @@ public static $zh2Hant = [
 '经有云' => '經有云',
 '综合征' => '綜合徵',
 '绿发' => '綠髮',
+'维系统' => '維系統',
 '维系' => '維繫',
 '绾发' => '綰髮',
 '纲鉴' => '綱鑑',
@@ -7176,6 +7240,7 @@ public static $zh2Hant = [
 '总数只' => '總數只',
 '总数里' => '總數裡',
 '总裁制' => '總裁制',
+'總裁制' => '總裁制',
 '繁复' => '繁複',
 '繁钟' => '繁鐘',
 '绷扒吊拷' => '繃扒弔拷',
@@ -7200,6 +7265,7 @@ public static $zh2Hant = [
 '系于' => '繫於',
 '系于一发' => '繫於一髮',
 '系着' => '繫着',
+'繫着' => '繫着',
 '系结' => '繫結',
 '系紧' => '繫緊',
 '系绳' => '繫繩',
@@ -7262,6 +7328,7 @@ public static $zh2Hant = [
 '聊斋志异' => '聊齋志異',
 '圣人历' => '聖人曆',
 '圣后' => '聖后',
+'聖后' => '聖后',
 '圣马尔谷日' => '聖馬爾谷日',
 '聖馬爾谷日' => '聖馬爾谷日',
 '聘雇' => '聘僱',
@@ -7288,6 +7355,7 @@ public static $zh2Hant = [
 '背地里' => '背地裡',
 '胎发' => '胎髮',
 '胜肽' => '胜肽',
+'胜鍵' => '胜鍵',
 '胜键' => '胜鍵',
 '胡云' => '胡云',
 '胡子婴' => '胡子嬰',
@@ -7312,11 +7380,11 @@ public static $zh2Hant = [
 '脱发' => '脫髮',
 '脺脏' => '脺臟',
 '脾脏' => '脾臟',
-'腊之以为饵' => '腊之以為餌',
 '腊味' => '腊味',
 '腊毒' => '腊毒',
 '腊笔' => '腊筆',
 '腌臜' => '腌臢',
+'腌臢' => '腌臢',
 '肾脏' => '腎臟',
 '腐干' => '腐乾',
 '腐余' => '腐餘',
@@ -7325,7 +7393,6 @@ public static $zh2Hant = [
 '脑干' => '腦幹',
 '腰里' => '腰裡',
 '脚注' => '腳註',
-'脚炼' => '腳鍊',
 '肠脏' => '腸臟',
 '胶卷' => '膠捲',
 '膨松' => '膨鬆',
@@ -7435,6 +7502,7 @@ public static $zh2Hant = [
 '草席' => '草蓆',
 '荐居' => '荐居',
 '荐臻' => '荐臻',
+'荐饑' => '荐饑',
 '荐饥' => '荐饑',
 '荷花淀' => '荷花澱',
 '庄里' => '莊裡',
@@ -7466,7 +7534,9 @@ public static $zh2Hant = [
 '落腮胡' => '落腮鬍',
 '落发' => '落髮',
 '叶叶琴' => '葉叶琴',
+'葉叶琴' => '葉叶琴',
 '叶叶琹' => '葉叶琹',
+'葉叶琹' => '葉叶琹',
 '叶阳后' => '葉陽后',
 '葉陽后' => '葉陽后',
 '葡萄干' => '葡萄乾',
@@ -7630,6 +7700,7 @@ public static $zh2Hant = [
 '冲然' => '衝然',
 '冲盹' => '衝盹',
 '冲着' => '衝着',
+'衝着' => '衝着',
 '冲破' => '衝破',
 '冲程' => '衝程',
 '冲突' => '衝突',
@@ -7804,14 +7875,12 @@ public static $zh2Hant = [
 '言云' => '言云',
 '言大而夸' => '言大而夸',
 '言里' => '言裡',
-'言辩而确' => '言辯而确',
 '订制' => '訂製',
 '计划' => '計劃',
 '计时表' => '計時錶',
 '托了' => '託了',
 '托事' => '託事',
 '托交' => '託交',
-'托人' => '託人',
 '托付' => '託付',
 '托克逊' => '託克遜',
 '托儿' => '託兒',
@@ -7836,6 +7905,7 @@ public static $zh2Hant = [
 '托过' => '託過',
 '托里县' => '託里縣',
 '托附' => '託附',
+'許愿起經' => '許愿起經',
 '许愿起经' => '許愿起經',
 '許聖杰' => '許聖杰',
 '注上' => '註上',
@@ -7944,6 +8014,7 @@ public static $zh2Hant = [
 '谋干' => '謀幹',
 '謝杰' => '謝杰',
 '谢杰' => '謝杰',
+'謝華后' => '謝華后',
 '谢华后' => '謝華后',
 '谬采虚声' => '謬採虛聲',
 '谬赞' => '謬讚',
@@ -7994,6 +8065,7 @@ public static $zh2Hant = [
 '豔后' => '豔后',
 '象征' => '象徵',
 '贪欲' => '貪慾',
+'貴价' => '貴价',
 '贵价' => '貴价',
 '貴子里' => '貴子里',
 '贵干' => '貴幹',
@@ -8014,6 +8086,7 @@ public static $zh2Hant = [
 '赋范' => '賦范',
 '质数里' => '質數裡',
 '质朴' => '質樸',
+'賭后' => '賭后',
 '赌后' => '賭后',
 '赌台' => '賭檯',
 '赌斗' => '賭鬥',
@@ -8029,7 +8102,9 @@ public static $zh2Hant = [
 '赶制' => '趕製',
 '赶面棍' => '趕麵棍',
 '赵威后' => '趙威后',
+'趙威后' => '趙威后',
 '赵惠后' => '趙惠后',
+'趙惠后' => '趙惠后',
 '赵治勋' => '趙治勳',
 '趱干' => '趲幹',
 '足于' => '足於',
@@ -8536,6 +8611,7 @@ public static $zh2Hant = [
 '鉴识' => '鑑識',
 '鉴赏' => '鑑賞',
 '鉴于' => '鑒於',
+'長几' => '長几',
 '长几' => '長几',
 '长于' => '長於',
 '长历' => '長曆',
@@ -8569,7 +8645,6 @@ public static $zh2Hant = [
 '闯荡' => '闖蕩',
 '闯炼' => '闖鍊',
 '关系' => '關係',
-'关弓与我确' => '關弓與我确',
 '关于' => '關於',
 '辟佛' => '闢佛',
 '辟作' => '闢作',
@@ -8617,6 +8692,7 @@ public static $zh2Hant = [
 '随于' => '隨於',
 '隐占' => '隱佔',
 '隐几' => '隱几',
+'隱几' => '隱几',
 '隐于' => '隱於',
 '只字' => '隻字',
 '只影' => '隻影',
@@ -8635,6 +8711,7 @@ public static $zh2Hant = [
 '双折射' => '雙折射',
 '双折' => '雙摺',
 '双胜类' => '雙胜類',
+'雙胜類' => '雙胜類',
 '双雕' => '雙鵰',
 '杂合面儿' => '雜合麵兒',
 '杂志' => '雜誌',
@@ -8652,6 +8729,7 @@ public static $zh2Hant = [
 '难于' => '難於',
 '雨蒙蒙' => '雨濛濛',
 '雪窗萤几' => '雪窗螢几',
+'雪窗螢几' => '雪窗螢几',
 '雪里' => '雪裡',
 '雪里红' => '雪裡紅',
 '雪里蕻' => '雪裡蕻',
@@ -8767,6 +8845,7 @@ public static $zh2Hant = [
 '显示钟' => '顯示鐘',
 '显示钟表' => '顯示鐘錶',
 '风干' => '風乾',
+'風后' => '風后',
 '风后' => '風后',
 '风土志' => '風土誌',
 '风后,' => '風後,',
@@ -9006,6 +9085,7 @@ public static $zh2Hant = [
 '发胶' => '髮膠',
 '发菜' => '髮菜',
 '发蜡' => '髮蠟',
+'髮踊沖冠' => '髮踊沖冠',
 '发踊冲冠' => '髮踴沖冠',
 '发辫' => '髮辮',
 '发钗' => '髮釵',
@@ -9079,7 +9159,6 @@ public static $zh2Hant = [
 '斗劲' => '鬥勁',
 '斗勇' => '鬥勇',
 '斗胜' => '鬥勝',
-'斗口' => '鬥口',
 '斗合' => '鬥合',
 '斗嘴' => '鬥嘴',
 '斗地主' => '鬥地主',
@@ -9206,6 +9285,7 @@ public static $zh2Hant = [
 '鲜于' => '鮮于',
 '鲸须' => '鯨鬚',
 '鳥栖' => '鳥栖',
+'鳥栖市' => '鳥栖市',
 '鸟栖市' => '鳥栖市',
 '凤梨干' => '鳳梨乾',
 '鸣钟' => '鳴鐘',
@@ -9221,6 +9301,7 @@ public static $zh2Hant = [
 '鹤发' => '鶴髮',
 '鸾鉴' => '鸞鑑',
 '鹰雕' => '鹰鵰',
+'鹰鵰' => '鹰鵰',
 '咸、甜' => '鹹、甜',
 '咸味' => '鹹味',
 '咸嘴淡舌' => '鹹嘴淡舌',
@@ -9267,6 +9348,7 @@ public static $zh2Hant = [
 '麹霉' => '麴黴',
 '面人儿' => '麵人兒',
 '面包' => '麵包',
+'面团' => '麵團',
 '面坊' => '麵坊',
 '面坯儿' => '麵坯兒',
 '面塑' => '麵塑',
@@ -9283,7 +9365,6 @@ public static $zh2Hant = [
 '面筋' => '麵筋',
 '面粉' => '麵粉',
 '面糊' => '麵糊',
-'面团' => '麵糰',
 '面缸' => '麵缸',
 '面茶' => '麵茶',
 '面制品' => '麵製品',
@@ -13189,7 +13270,9 @@ public static $zh2Hans = [
 '乾上乾下' => '乾上乾下',
 '乾东' => '乾东',
 '乾東' => '乾东',
+'乾为天' => '乾为天',
 '乾為天' => '乾为天',
+'乾为阳' => '乾为阳',
 '乾為陽' => '乾为阳',
 '乾九' => '乾九',
 '乾乾' => '乾乾',
@@ -13379,6 +13462,7 @@ public static $zh2Hans = [
 '藉詞' => '借词',
 '傒倖' => '傒倖',
 '先名後姓' => '先名后姓',
+'兒宽' => '兒宽',
 '兒寬' => '兒宽',
 '六么' => '六幺',
 '蘭質薰心' => '兰质薰心',
@@ -13422,6 +13506,7 @@ public static $zh2Hans = [
 '同陞和' => '同升和',
 '名著' => '名著',
 '吳克羣' => '吴克羣',
+'吴克羣' => '吴克羣',
 '周易乾' => '周易乾',
 '諠譁' => '喧哗',
 '回覆' => '回复',
@@ -13453,6 +13538,7 @@ public static $zh2Hans = [
 '跼促' => '局促',
 '侷限' => '局限',
 '跼限' => '局限',
+'山崎闇斋' => '山崎闇斋',
 '山崎闇齋' => '山崎闇斋',
 '岳託' => '岳讬',
 '巨著' => '巨著',
@@ -13490,6 +13576,7 @@ public static $zh2Hans = [
 '么謙' => '幺谦',
 '么麼' => '幺麽',
 '么麽' => '幺麽',
+'幺麽' => '幺麽',
 '么麽小丑' => '幺麽小丑',
 '慶餘' => '庆馀',
 '康乾' => '康乾',
@@ -13556,12 +13643,14 @@ public static $zh2Hans = [
 '於崇文' => '於崇文',
 '於志賀' => '於志贺',
 '於志贺' => '於志贺',
+'於戏' => '於戏',
 '於戲' => '於戏',
 '於梨华' => '於梨华',
 '於梨華' => '於梨华',
 '於氏' => '於氏',
 '於潜' => '於潜',
 '於潛縣' => '於潜县',
+'於潜县' => '於潜县',
 '於祥玉' => '於祥玉',
 '於菟' => '於菟',
 '於賢德' => '於贤德',
@@ -13718,6 +13807,7 @@ public static $zh2Hans = [
 '論著' => '论著',
 '譯著' => '译著',
 '謝肇淛' => '谢肇淛',
+'谢肇淛' => '谢肇淛',
 '象乾' => '象乾',
 '躊躇滿志' => '踌躇滿志',
 '較著' => '较著',
@@ -14545,6 +14635,8 @@ public static $zh2TW = [
 '电线杆' => '電線桿',
 '电脑程序' => '電腦程式',
 '计算机程序' => '電腦程式',
+'电脑网络' => '電腦網路',
+'電腦網絡' => '電腦網路',
 '荷尔斯泰因' => '霍爾斯坦',
 '荷爾斯泰因' => '霍爾斯坦',
 '面包着' => '面包著',
@@ -15016,6 +15108,7 @@ public static $zh2HK = [
 '克羅埃西亞' => '克羅地亞',
 '奈洛比' => '內羅畢',
 '公布' => '公佈',
+'公寓里' => '公寓裏',
 '冒著' => '冒着',
 '冒著作' => '冒著作',
 '冒著名' => '冒著名',
@@ -18277,6 +18370,7 @@ public static $zh2CN = [
 '都卜勒' => '多普勒',
 '多明尼加' => '多米尼加',
 '大姊' => '大姐',
+'大姊姊' => '大姐姐',
 '天份' => '天分',
 '夾著' => '夹着',
 '夾著書' => '夹著书',
@@ -19315,7 +19409,6 @@ public static $zh2CN = [
 '著處' => '着处',
 '著她' => '着她',
 '著妳' => '着妳',
-'著姓' => '着姓',
 '著它' => '着它',
 '著定' => '着定',
 '著實' => '着实',
index 20047c4..0455740 100644 (file)
        "noname": "Nan ureuëng ngui nyang Droënueh peutamöng hana sah.",
        "loginsuccesstitle": "Meuhasé tamöng",
        "loginsuccess": "'''Droëneuh  jinoë ka neutamöng di {{SITENAME}} sibagoë \"$1\".'''",
-       "nosuchuser": "Hana ureuëng ngui ngön nan \"$1\".\nNan ureuëng ngui jipeubida haraih rayek.\nTulông neuparéksa keulayi neuija Droëneuh, atawa [[Special:UserLogin/signup|neudapeuta barô]].",
+       "nosuchuser": "Hana ureuëng ngui ngön nan \"$1\".\nNan ureuëng ngui jipeubida haraih rayek.\nTulông neuparéksa keulayi neuija Droëneuh, atawa [[Special:CreateAccount|neudapeuta barô]].",
        "nosuchusershort": "Hana ureuëng ngui ngön nan \"$1\".\nPréksa keulayi neu’ija Droëneuh.",
        "nouserspecified": "Neupasoë nan Droëneuh.",
        "login-userblocked": "Ureuëng ngui nyoë ka teublokir, hana idin/hanjeut tamöng.",
        "accmailtitle": "Lageuem rahsia ka meukirém",
        "newarticle": "(Barô)",
        "newarticletext": "Droëneuh ka neuseutöt peunawôt u laman nyang goh na.\nKeu neupeugöt laman nyan, neukeutik lam plôk di yup (eu [$1 laman beunantu] keu haba leubèh le).\nMeunyö droëneuh trôk keunoë hana neusaja, neuteugön tèk '''back''' bak ''browser'''droëneuh.",
-       "anontalkpagetext": "----''Nyoe nakeuh ôn marit ureueng ngui nyang hana tamöng atawa hana geungui.''\nSaweub nyan, kamoe payah meukubah alamat IP-geuh keu meuparéksa. \nAlamat IP mungkén jingui lé padum-padum droe ureueng.\nMeunyoe droeneuh ureueng nyang hana tamöng nyan, tulông [[Special:UserLogin/signup|peugöt nan ureueng ngui]] atawa [[Special:UserLogin|tamöng log]] mangat meuteugah nibak bhah nyang hana meuphôm bak uroe la'én.",
+       "anontalkpagetext": "----''Nyoe nakeuh ôn marit ureueng ngui nyang hana tamöng atawa hana geungui.''\nSaweub nyan, kamoe payah meukubah alamat IP-geuh keu meuparéksa. \nAlamat IP mungkén jingui lé padum-padum droe ureueng.\nMeunyoe droeneuh ureueng nyang hana tamöng nyan, tulông [[Special:CreateAccount|peugöt nan ureueng ngui]] atawa [[Special:UserLogin|tamöng log]] mangat meuteugah nibak bhah nyang hana meuphôm bak uroe la'én.",
        "noarticletext": "Hana naseukah jinoë lam laman nyoë.\nJi Droëneuh jeuët [[Special:Search/{{PAGENAME}}|neumita keu nan ôn nyoë]] bak ôn-ôn la’én, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} log nyang na hubôngan], atawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} neu'andam ôn nyoë]</span>.",
        "noarticletext-nopermission": "Hana asoë bak laman nyoë jinoë.\nDroëneuh jeuët [[Special:Search/{{PAGENAME}}|neumita keu nan ôn nyoë]] bak laman-laman la'én,\natawa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} neumita log nyang na meuhubông]</span>, tapi Droëneuh hana idin keu neupeugöt laman nyoë",
        "userpage-userdoesnotexist-view": "Ureueng ngui \"$1\" hana teudapeuta.",
        "allpagesprefix": "Peuleumah laman ngön harah phôn:",
        "allpages-hide-redirects": "Peusom peuninah",
        "categories": "Dapeuta kawan",
-       "special-categories-sort-count": "atôe meunurôt jumeulah",
-       "special-categories-sort-abc": "atôe meunurôt seunurat",
        "deletedcontributions": "Beuneuri nyang geusampôh",
        "deletedcontributions-title": "Beuneuri nyang geusampôh",
        "sp-deletedcontributions-contribs": "beuneuri",
index 5ef02ce..fac0302 100644 (file)
        "noname": "Ongeldige gebruikersnaam.",
        "loginsuccesstitle": "Suksesvolle aanmelding",
        "loginsuccess": "U is nou by {{SITENAME}} as \"$1\" ingeteken.",
-       "nosuchuser": "Die gebruiker \"$1\" bestaan nie.\nGebruikersname is gevoelig vir hoofletters.\nMaak seker dit is reg gespel of [[Special:UserLogin/signup|skep 'n nuwe rekening]].",
+       "nosuchuser": "Die gebruiker \"$1\" bestaan nie.\nGebruikersname is gevoelig vir hoofletters.\nMaak seker dit is reg gespel of [[Special:CreateAccount|skep 'n nuwe rekening]].",
        "nosuchusershort": "Daar is geen gebruikersnaam \"$1\" nie. Maak seker dit is reg gespel.",
        "nouserspecified": "U moet 'n gebruikersnaam spesifiseer.",
        "login-userblocked": "Hierdie gebruiker is geblokkeer.\nIntekening word verbied.",
        "accmailtext": "'n Lukrake wagwoord vir [[User talk:$1|$1]] is na $2 gestuur.\n\nDie wagwoord vir hierdie nuwe gebruiker kan op die ''[[Special:ChangePassword|verander wagwoord]]''-bladsy verander word nadat ingeteken is.",
        "newarticle": "(Nuut)",
        "newarticletext": "Hierdie bladsy bestaan nie.\nTik iets in die invoerboks hier onder om 'n nuwe bladsy te skep. Meer inligting is op die [$1 hulpbladsy] beskikbaar.\nAs u per ongeluk hier uitgekom het, gebruik u blaaier se '''terug'''-knoppie.",
-       "anontalkpagetext": "----''Hierdie is die besprekingsblad vir 'n anonieme gebruiker wat nog nie 'n gebruiker geskep het nie, of wat dit nie gebruik nie.\nDaarom moet ons sy/haar numeriese IP-adres vir identifikasie gebruik.\nSó 'n adres kan deur verskeie gebruikers gedeel word.\nIndien u 'n anonieme gebruiker is wat voel dat ontoepaslike kommentaar teen u gerig is, [[Special:UserLogin/signup|skep 'n gebruiker]] of [[Special:UserLogin|meld aan]] om verwarring met ander anonieme gebruikers te voorkom.''",
+       "anontalkpagetext": "----''Hierdie is die besprekingsblad vir 'n anonieme gebruiker wat nog nie 'n gebruiker geskep het nie, of wat dit nie gebruik nie.\nDaarom moet ons sy/haar numeriese IP-adres vir identifikasie gebruik.\nSó 'n adres kan deur verskeie gebruikers gedeel word.\nIndien u 'n anonieme gebruiker is wat voel dat ontoepaslike kommentaar teen u gerig is, [[Special:CreateAccount|skep 'n gebruiker]] of [[Special:UserLogin|meld aan]] om verwarring met ander anonieme gebruikers te voorkom.''",
        "noarticletext": "Hierdie bladsy bevat geen teks nie.\nU kan [[Special:Search/{{PAGENAME}}|vir die bladsytitel in ander bladsye soek]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} die verwante logboeke deursoek]\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} hierdie bladsy wysig]</span>.",
        "noarticletext-nopermission": "Hierdie bladsy bevat geen teks nie.\nU kan vir die term [[Special:Search/{{PAGENAME}}|in ander bladsye soek]] of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} die verwante logboeke deursoek]</span>, maar u kan nie die bladsy skep nie.",
        "missing-revision": "Die weergawe #$1 van die bladsy \"{{FULLPAGENAME}} bestaan nie.\n\nDit word meestal veroorsaak deur die volg van 'n verouderde verwysing na 'n bladsy wat verwyder is.\nMeer gegewens kan moontlik in die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} skraplogboek] gevind word.",
        "upload-form-label-infoform-description": "Beskrywing",
        "upload-form-label-usage-title": "Gebruik",
        "upload-form-label-usage-filename": "Lêernaam",
-       "foreign-structured-upload-form-label-own-work": "Dit is my eie werk",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorieë",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work": "Dit is my eie werk",
+       "upload-form-label-infoform-categories": "Kategorieë",
+       "upload-form-label-infoform-date": "Datum",
        "backend-fail-stream": "Kon nie die lêer $1 uitstroom nie.",
        "backend-fail-backup": "Kon nie 'n rugsteunkopie van die lêer $1 maak nie.",
        "backend-fail-notexists": "Die lêer $1 bestaan nie.",
index 9818253..dde3a06 100644 (file)
        "noname": "Nuk ke dhânë nofkë valide.",
        "loginsuccesstitle": "Kyçje e suksesshme",
        "loginsuccess": "'''Je kyçë në {{SITENAME}} si \"$1\".'''",
-       "nosuchuser": "Nuk ka përdorues me emnin \"$1\".\nEmnat janë senzitiv në madhësi të germës.\nKontrollo drejtshkrimin ose [[Special:UserLogin/signup|krijo llogari]].",
+       "nosuchuser": "Nuk ka përdorues me emnin \"$1\".\nEmnat janë senzitiv në madhësi të germës.\nKontrollo drejtshkrimin ose [[Special:CreateAccount|krijo llogari]].",
        "nosuchusershort": "Nuk ka përdorues me emnin \"$1\".\nKontrollo drejtshkrimin.",
        "nouserspecified": "Duhesh me dhânë nji nofkë.",
        "login-userblocked": "Ky përdorues është bllokuar. Identifikohu nuk lejohet",
        "accmailtext": "Nji fjalëkalim i krijuem rastësisht për [[User talk:$1|$1]] u dërgue në $2.\n\nFjalëkalimi për këtë llogari mundet me u ndryshue në faqen ''[[Special:ChangePassword|ndrysho fjalëkalimin]]'' mbas kyçjes.",
        "newarticle": "(I ri)",
        "newarticletext": "Ke ndjekë nji vegëz për te nji faqe që nuk ekziston.\nMe krijue kët faqe, shkruej në kutinë ma poshtë (shih [$1 faqen e ndihmës] për ma shum udhzime).\nNëse ke hy këtu gabimisht, klikoje sustën '''mbrapa''' në shfletues.",
-       "anontalkpagetext": "<em>Kjo është faqe diskutimi e një përdoruesi anonim, i cili nuk ka krijuar llogari, apo nuk e përdor atë.</em>\nPrandaj ne do të përdorim adresën numerike të IP së tij për ta identifikuar.\nAdresa IP mund të shfrytëzohet prej disa përdoruesve.\nNëse jeni përdorues anonim dhe keni përshtypjen se po ju drejtohen komente jorelevante, ju lutemi [[Special:UserLogin/signup|krijoni një llogari]] apo [[Special:UserLogin|identifikohuni]] për të ju shmangur ngatërrimev me përdorues të tjerë anonim.",
+       "anontalkpagetext": "<em>Kjo është faqe diskutimi e një përdoruesi anonim, i cili nuk ka krijuar llogari, apo nuk e përdor atë.</em>\nPrandaj ne do të përdorim adresën numerike të IP së tij për ta identifikuar.\nAdresa IP mund të shfrytëzohet prej disa përdoruesve.\nNëse jeni përdorues anonim dhe keni përshtypjen se po ju drejtohen komente jorelevante, ju lutemi [[Special:CreateAccount|krijoni një llogari]] apo [[Special:UserLogin|identifikohuni]] për të ju shmangur ngatërrimev me përdorues të tjerë anonim.",
        "noarticletext": "Momentalisht nuk ka tekst në këtë faqe.\nJu mundeni [[Special:Search/{{PAGENAME}}|me kërkue këtë titull]] në faqe tjera,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} me kërkue në regjistrat tematikisht të afërm],\napo [{{fullurl:{{FULLPAGENAME}}|action=edit}} me redaktue këtë faqe]</span>.",
        "userpage-userdoesnotexist": "Llogaria e përdoruesit \"<nowiki>$1</nowiki>\" nuk âsht regjistrue.\nJu lutemi kontrolloni nëse doni me krijue/redaktue këtë faqe.",
        "clearyourcache": "'''Shenim - Mbas ruejtjes, ka mundësi që duheni me shmângë memorizimin në cache për me i pâ ndryshimet.'''\n'''Mozilla / Firefox / Safari:''' mbani ''Shift'' tue klikue në ''Reload'', ose trusni ''Ctrl-F5'' ose ''Ctrl-R'' (''Command-R'' në Mac);\n'''Konqueror: '''klikoni ''Reload'' ose trusni ''F5'';\n'''Opera:''' fshini cachein në ''Tools → Preferences'';\n'''Internet Explorer:''' mbani ''Ctrl'' tue klikue në ''Refresh,'' ose trusni ''Ctrl-F5''.",
index 58c4a1a..65400e2 100644 (file)
        "noname": "የተወሰነው ብዕር ስም ትክክለኛ አይደለም።",
        "loginsuccesstitle": "መግባትዎ ተከናወነ!",
        "loginsuccess": "እንደ «$1» ሆነው አሁን {{SITENAME}}ን ገብተዋል።",
-       "nosuchuser": "«$1» የሚል ብዕር ስም አልተገኘም። አጻጻፉን ይመልከቱ ወይም [[Special:UserLogin/signup|አዲስ ብዕር ስም ያውጡ]]።",
+       "nosuchuser": "«$1» የሚል ብዕር ስም አልተገኘም። አጻጻፉን ይመልከቱ ወይም [[Special:CreateAccount|አዲስ ብዕር ስም ያውጡ]]።",
        "nosuchusershort": "«$1» የሚል ብዕር ስም አልተገኘም። አጻጻፉን ይመልከቱ።",
        "nouserspecified": "አንድ ብዕር ስም መጠቆም ያስፈልጋል።",
        "login-userblocked": "ተጠቃሚው አሁን የታገደ ነው። መግባት አልተፈቀደም።",
index 0af7f45..05000b1 100644 (file)
        "noname": "No ha escrito un nombre d'usuario correcto.",
        "loginsuccesstitle": "S'ha identificato correctament",
        "loginsuccess": "Ha encetato una sesión en {{SITENAME}} como \"$1\".",
-       "nosuchuser": "No bi ha garra usuario clamato \"$1\".\nOs nombres d'usuario son sensibles a las mayusclas.\nComprebe si ha escrito bien o nombre u [[Special:UserLogin/signup|creye una nueva cuenta d'usuario]].",
+       "nosuchuser": "No bi ha garra usuario clamato \"$1\".\nOs nombres d'usuario son sensibles a las mayusclas.\nComprebe si ha escrito bien o nombre u [[Special:CreateAccount|creye una nueva cuenta d'usuario]].",
        "nosuchusershort": "No bi ha garra usuario con o nombre \"$1\". Comprebe si o nombre ye bien escrito.",
        "nouserspecified": "Ha d'escribir un nombre d'usuario.",
        "login-userblocked": "Iste usuario ye bloqueyau. No se permite l'inicio de sesión.",
        "accmailtext": "S'ha ninviato a $2 una clau ta [[User talk:$1|$1]] chenerata aliatoriament.\n\nA clau ta ista nueva cuenta la puet cambiar en a pachina ''[[Special:ChangePassword|Cambiar a clau]]'' dimpués d'haber dentrato en ella.",
        "newarticle": "(Nuevo)",
        "newarticletext": "Ha siguito un vinclo ta una pachina que encara no existe.\nTa creyar a pachina, prencipie a escribir en a caixa d'abaixo (mire-se l'[$1 aduya] ta más información).\nSi ye plegau por error, punche o botón \"enta zaga\" d'o suyo navegador.",
-       "anontalkpagetext": "----''Ista ye a pachina de descusión d'un usuario anonimo que encara no ha creyato una cuenta, u no l'ha feito servir. Por ixo, hemos d'emplegar a suya adreza IP ta identificar-lo/a.\nDiferents usuarios pueden compartir una mesma adreza IP.\nSi vusté ye un usuario anonimo y creye que l'han escrito comentarios no relevants, [[Special:UserLogin/signup|creye una cuenta]] u [[Special:UserLogin/signup|identifique-se]] ta privar confusions futuras con atros usuarios anonimos.''",
+       "anontalkpagetext": "----''Ista ye a pachina de descusión d'un usuario anonimo que encara no ha creyato una cuenta, u no l'ha feito servir. Por ixo, hemos d'emplegar a suya adreza IP ta identificar-lo/a.\nDiferents usuarios pueden compartir una mesma adreza IP.\nSi vusté ye un usuario anonimo y creye que l'han escrito comentarios no relevants, [[Special:CreateAccount|creye una cuenta]] u [[Special:CreateAccount|identifique-se]] ta privar confusions futuras con atros usuarios anonimos.''",
        "noarticletext": "Por agora no bi ha garra texto en ista pachina. Puet [[Special:Search/{{PAGENAME}}|mirar o títol d'ista pachina]] en atras pachinas, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mirar os rechistros relacionatos] u [{{fullurl:{{FULLPAGENAME}}|action=edit}} escribir ista pachina]</span>.",
        "noarticletext-nopermission": "Por l'inte no i hai garra texto en ista pachina.\nPuede [[Special:Search/{{PAGENAME}}|mirar iste titol]] en atras pachinas,\nu bien <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mirar en os rechistros relacionatos]</span>, pero no tien permiso ta creyar ista pachina.",
        "userpage-userdoesnotexist": "A cuenta d'usuario \"<nowiki>$1</nowiki>\" no ye rechistrada. Piense si quiere creyar u editar ista pachina.",
index 016af24..b414d5e 100644 (file)
        "noname": "Þū nafast gewriten gengne brūcendes naman.",
        "loginsuccesstitle": "Inmeldung gesǣlde",
        "loginsuccess": "'''Þu eart nū inmeldod tō {{SITENAME}} tō \"$1\".'''",
-       "nosuchuser": "Þǣr nis nān brūcend þe hæfþ þone naman \"$1\".\nStafena micelnessa sind hefiga and ānlica on brūcendnamum.\nScēawa þīne wrītunge eft, oþþe [[Special:UserLogin/signup|sciepp nīwe reccinge]].",
+       "nosuchuser": "Þǣr nis nān brūcend þe hæfþ þone naman \"$1\".\nStafena micelnessa sind hefiga and ānlica on brūcendnamum.\nScēawa þīne wrītunge eft, oþþe [[Special:CreateAccount|sciepp nīwe reccinge]].",
        "nosuchusershort": "Þǣr nis nān brūcend mid þǣm naman \"$1\".  Scēawa þīne wrītunge.",
        "nouserspecified": "Þū scealt wrītan brūcendes naman.",
        "login-userblocked": "Þes brūcend is fortȳned. Inmeldung nis gelīfed.",
index 36cb5b2..afb78ab 100644 (file)
        "noname": "لم تحدد اسم مستخدم صحيح.",
        "loginsuccesstitle": "تم الدخول",
        "loginsuccess": "'''لقد سجلت الدخول ل{{SITENAME}} باسم \"$1\".'''",
-       "nosuchuser": "لا يوجد مستخدم بالاسم \"$1\".\nأسماء المستخدمين حساسة لحالة الحروف.\nتأكد من إملاء الاسم، أو [[Special:UserLogin/signup|قم بإنشاء حساب جديد]].",
+       "nosuchuser": "لا يوجد مستخدم بالاسم \"$1\".\nأسماء المستخدمين حساسة لحالة الحروف.\nتأكد من إملاء الاسم، أو [[Special:CreateAccount|قم بإنشاء حساب جديد]].",
        "nosuchusershort": "لا يوجد مستخدم باسم $1\".\nتأكد من إملاء الاسم.",
        "nouserspecified": "يجب عليك تحديد اسم مستخدم.",
        "login-userblocked": "هذا المستخدم ممنوع. لا يسمح بالولوج.",
        "accmailtext": "أُرسِلت كلمة سر مولدة عشوائيا ل[[User talk:$1|$1]] إلى $2. يمكن تغييرها في صفحة ''[[Special:ChangePassword|تغيير كلمة السر]]'' بعد تسجيل الدخول.",
        "newarticle": "(جديد)",
        "newarticletext": "لقد تبعت وصلة لصفحة لم يتم إنشائها بعد.\nلإنشاء هذه الصفحة ابدأ الكتابة في الصندوق بالأسفل (انظر في [$1 صفحة المساعدة] للمزيد من المعلومات).\nإذا كانت زيارتك لهذه الصفحة بالخطأ، اضغط على زر ''رجوع'' في متصفح الإنترنت لديك.",
-       "anontalkpagetext": "----''هذه صفحة نقاش لمستخدم مجهول لم يقم بإنشاء حساب بعد أو لا يستعمل ذلك الحساب.\nلذا فيجب علينا استعمال رقم الأيبي للتعرف عليه/عليها.\nمثل هذا العنوان يمكن أن يشترك فيه عدة مستخدمين.\nلو كنت مستخدما مجهولا وتشعر بأن تعليقات لا تخصك تم توجيهها إليك، من فضلك [[Special:UserLogin/signup|أنشئ حسابا]] أو [[Special:UserLogin|سجل الدخول]] لتجنب الارتباك المستقبلي مع مستخدمين مجهولين آخرين.''",
-       "noarticletext": "لا يوجد حاليا أي نص في هذه الصفحة.\nيمكنك [[Special:Search/{{PAGENAME}}|البحث عن عنوان هذه الصفحة]] في الصفحات الأخرى،\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} البحث في السجلات المتعلقة]،\nأو [{{fullurl:{{FULLPAGENAME}}|action=edit}} تعديل هذه الصفحة]</span>.",
+       "anontalkpagetext": "----''هذه صفحة نقاش لمستخدم مجهول لم يقم بإنشاء حساب بعد أو لا يستعمل ذلك الحساب.\nلذا فيجب علينا استعمال رقم الأيبي للتعرف عليه/عليها.\nمثل هذا العنوان يمكن أن يشترك فيه عدة مستخدمين.\nلو كنت مستخدما مجهولا وتشعر بأن تعليقات لا تخصك تم توجيهها إليك، من فضلك [[Special:CreateAccount|أنشئ حسابا]] أو [[Special:UserLogin|سجل الدخول]] لتجنب الارتباك المستقبلي مع مستخدمين مجهولين آخرين.''",
+       "noarticletext": "الصفحة خالية. يمكنك [[Special:Search/{{PAGENAME}}|البحث عن عنوانها]] في الصفحات الأخرى أو\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>، لكنك لست مخولاً لإنشاء هذه الصفحة.",
        "missing-revision": "المراجعة #$1 من الصفحة المسماة \"{{FULLPAGENAME}}\" غير موجودة.\n\nهذا يحدث عادة عن طريق اتباع وصلة تاريخ قديمة لصفحة تم حذفها.\nالتفاصيل يمكن إيجادها في [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سجل الحذف].",
        "userpage-userdoesnotexist": "حساب المستخدم \"<nowiki>$1</nowiki>\" غير مسجل.\nمن فضلك تأكد أنك تريد إنشاء/تعديل هذه الصفحة.",
        "upload-form-label-infoform-description": "الوصف",
        "upload-form-label-usage-title": "الاستخدام",
        "upload-form-label-usage-filename": "اسم الملف",
-       "foreign-structured-upload-form-label-own-work": "هذا عملي الخاص",
-       "foreign-structured-upload-form-label-infoform-categories": "تصنيفات",
-       "foreign-structured-upload-form-label-infoform-date": "التاريخ",
+       "upload-form-label-own-work": "هذا عملي الخاص",
+       "upload-form-label-infoform-categories": "تصنيفات",
+       "upload-form-label-infoform-date": "التاريخ",
        "backend-fail-stream": "لا يمكن عرض الملف $1.",
        "backend-fail-backup": "لا يمكن صنع نسخة أحتياطية للملف $1.",
        "backend-fail-notexists": "الملف $1 غير موجود.",
        "watchlistedit-raw-done": "قائمة مراقبتك تم تحديثها.",
        "watchlistedit-raw-added": "تمت إضافة {{PLURAL:$1||عنوان واحد|عنوانين|$1 عناوين|$1 عنوانا|$1 عنوان}}:",
        "watchlistedit-raw-removed": "تمت إزالة {{PLURAL:$1||عنوان واحد|عنوانين|$1 عناوين|$1 عنوانا|$1 عنوان}}:",
-       "watchlistedit-clear-title": "قائمة مراقبة ممسوحة",
+       "watchlistedit-clear-title": "امسح قائمة المراقبة",
        "watchlistedit-clear-legend": "امسح قائمة المراقبة",
        "watchlistedit-clear-explain": "ستحذف جميع الصفحات من قائمة مراقبتك",
        "watchlistedit-clear-titles": "العناوين:",
        "log-action-filter-delete-event": "حذف السجلات",
        "log-action-filter-delete-revision": "حذف المراجعات",
        "log-action-filter-import-interwiki": "استيراد عابر للويكي",
+       "log-action-filter-managetags-create": "إنشاء الوسوم",
+       "log-action-filter-managetags-delete": "حذف الوسوم",
        "log-action-filter-newusers-autocreate": "إنشاء آلي",
        "log-action-filter-newusers-byemail": "الإنشاء بكلمة مرور مرسلة عبر البريد الإلكتروني",
        "log-action-filter-protect-protect": "حماية",
        "log-action-filter-protect-unprotect": "رفع الحماية",
        "log-action-filter-rights-rights": "تغيير يدوي",
        "log-action-filter-upload-upload": "رفع جديد",
-       "log-action-filter-upload-overwrite": "إعادة الرفع"
+       "log-action-filter-upload-overwrite": "إعادة الرفع",
+       "authmanager-email-help": "عنوان البريد الإلكتروني",
+       "linkaccounts": "ربط الحسابات",
+       "unlinkaccounts": "إزالة ربط الحسابات"
 }
index 354c74b..44a3679 100644 (file)
        "noname": "ما مدّيتش سميّت` مستعملي مقبولة.",
        "loginsuccesstitle": "التوصال راه نجَح.",
        "loginsuccess": "<strong>راك مسجّل داخل ف {{SITENAME}} ب`السميّة \"$1\".</strong>",
-       "nosuchuser": "ما كاين حتا مستعملي ب`السميّة \"$1\".\nالسميّة تاع المستعملي راه حسّاسة ف تكسار الحروف (majuscule - minuscule).\nعاود أكّد على كيفاش كتبت الكلمات ولا [[Special:UserLogin/signup|اخلق حساب جديد]].",
+       "nosuchuser": "ما كاين حتا مستعملي ب`السميّة \"$1\".\nالسميّة تاع المستعملي راه حسّاسة ف تكسار الحروف (majuscule - minuscule).\nعاود أكّد على كيفاش كتبت الكلمات ولا [[Special:CreateAccount|اخلق حساب جديد]].",
        "nosuchusershort": "ما كاين حتا مستعملي ب` السميّة \"$1\".\nأكّد على الكتيبة تاعك.",
        "nouserspecified": "لازم لك تمدّ السميّة تاع المستعملي.",
        "login-userblocked": "هاد السميّة تاع المستعملي راهي مطرودة. تسجال` الدخول ماشي مسموح.",
        "accmailtext": "راهي انخلقت كلمت` سرّ مختارة على الزهَر لل مستعملي [[User talk:$1|$1]] و و انبعتت ل $2.\nتنجم تبدّلها فل پاجة<em>[[Special:ChangePassword|بدّل كلمت` السرّ]]</em> كي تتسجّل داخل.",
        "newarticle": "(جديد)",
        "newarticletext": "راك تبعت وصيلة لباجه لم ما تخدمتش بعد.\nباش تصنع هاذ الباجه ابدا الكتبه فالصندوق التحت (شوف في [$1  زياده باجه المساعده] لمعلومات).\nإذا كانت زيارتك لهاذ الباجه غلطه، ادرك على بوطون''ولى'' في نافيقاتور الإنترنت نتاعك.",
-       "anontalkpagetext": "----''هاذ الباجة نقاش لمستخدم مجهول ما قامش بإنشاء حساب بعد و الا ما يستعملش ذاك الحساب.\nلذا لازم علينا استعمال رقم الأيبي باش نتعرفو عليه/عليها.\nمثل هذا العنوان يمكنلو يشترك فيه مستخدمين بزاف.\nإذا كنت مستخدم مجهول وتشعر بلي التعليقات ما تخصكش وصلتلك ، من فضلك [[Special:UserLogin/signup|أصنع حساب]] ولا [[Special:UserLogin|سجل الدخول]] باش تتجنب الارتباك فالمستقبل مع مستخدمين مجهولين آخرين.''",
+       "anontalkpagetext": "----''هاذ الباجة نقاش لمستخدم مجهول ما قامش بإنشاء حساب بعد و الا ما يستعملش ذاك الحساب.\nلذا لازم علينا استعمال رقم الأيبي باش نتعرفو عليه/عليها.\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}} أصنع هاذ الباجه]</span>.",
        "missing-revision": "المراجعة #$1 من الباجة اللي سموها \"{{FULLPAGENAME}}\" ما هيش كاينة.\n\nهذا يصرا فالعادة منين نتبعو وصيلة تاريخها قديم لباجة تنحات.\nالتفاصيل يمكن نصيبوها في [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سجل المسح].",
index c996b3f..e9c1986 100644 (file)
        "noname": "Ma kṫebṫiċ ċi smiyṫ l-mosṫeĥdim mqadda.",
        "loginsuccesstitle": "dkhlti mzyan",
        "loginsuccess": "Dĥelṫi mezyan fe {{SITENAME}} be smiyṫ \"$1\".",
-       "nosuchuser": "ma kayn-ċ ċi mosṫĥdim smiṫo \"$1\".\ns-smiyyaṫ l-mosṫĥdimin raha ḫssasa lla caz (yĝni majuscule o-minuscule).\nċof l-kṫaḅa waċ hia hadik, wlla [[Special:UserLogin/signup|ṣayb ċi ḫisab jdid]].",
+       "nosuchuser": "ma kayn-ċ ċi mosṫĥdim smiṫo \"$1\".\ns-smiyyaṫ l-mosṫĥdimin raha ḫssasa lla caz (yĝni majuscule o-minuscule).\nċof l-kṫaḅa waċ hia hadik, wlla [[Special:CreateAccount|ṣayb ċi ḫisab jdid]].",
        "nosuchusershort": "ma kayn-ċ ċi mosṫĥdim smiṫo \"$1\".\nċof l-kṫaḅa waċ hia hadik.",
        "nouserspecified": "khassk tdkhl ism lmostakhdim.",
        "login-userblocked": "Had l-mosṫeĥdim meḫbos. Konnéksyon memnoĝa.",
        "accmailtext": "waḫd klmṫ s-srr mṣayba ĝċwa'iyyn ĝla ḫsab [[User talk:$1|$1]] ṫsiftaṫ l-$2.\n\nṫqdr ṫbddal klmṫ s-srr dialt had l-ḫisab j-jdid f-ṣṣfḫa ṫaĝ ''[[Special:ChangePassword|bḍḍel klmṫ s-srr]]'' mn bĝdmma ṫdĥol.",
        "newarticle": "(jdid)",
        "newarticletext": "Ṫbeĝṫi waḫed l-lyan li kayddi le waḫed ṣ-ṣefḫa li ṫṫemḫaṫ.\nBaċ ṫsayeb had ṣ-ṣefḫa, bda ṫekṫeb fe ṣ-ṣendoq li l-ṫeḫṫ (ċof ila bġiṫi [$1 ṣ-ṣefḫa de l-mosaĝada] le l-mazid de l-meĝlomaṫ).\nIla wṣelṫi hnaya ĝla ġefla, brek ĝla l-boton '''rjeĝ''' dyal n-navigaṫør internet dyalek.",
-       "anontalkpagetext": "----''hada niqaċ ṫaĝ waḫd l-mosṫĥdim anonim lli mazal ma ṣayb-ċ ċi ḫisab, wlla ma kayĥdam-ċ bih.\ndakċċi ĝlaċ raḫna ĥddamin b-ĝonwan l-IP ṛ-ṛqmi baċ nĝaṛṛfoh.\nbḫal had ĝanawin l-IP ymkn iṫċarko fiha bẓẓaf dl-mosṫĥdimin.\nina konṫi ḫṫṫa nṫa mosṫĥdim anonim o-ḫssiṫi billa wjjah lik ċi ḫadd ċi ṫĝalq ġalat,[[Special:UserLogin/signup|ṣayb ċi ḫisab]] wlla [[Special:UserLogin|dĥol]] baċ ṫqil raṣk mn ay ġalat fl-mosṫqbal.''",
+       "anontalkpagetext": "----''hada niqaċ ṫaĝ waḫd l-mosṫĥdim anonim lli mazal ma ṣayb-ċ ċi ḫisab, wlla ma kayĥdam-ċ bih.\ndakċċi ĝlaċ raḫna ĥddamin b-ĝonwan l-IP ṛ-ṛqmi baċ nĝaṛṛfoh.\nbḫal had ĝanawin l-IP ymkn iṫċarko fiha bẓẓaf dl-mosṫĥdimin.\nina konṫi ḫṫṫa nṫa mosṫĥdim anonim o-ḫssiṫi billa wjjah lik ċi ḫadd ċi ṫĝalq ġalat,[[Special:CreateAccount|ṣayb ċi ḫisab]] wlla [[Special:UserLogin|dĥol]] baċ ṫqil raṣk mn ay ġalat fl-mosṫqbal.''",
        "noarticletext": "Ma kayen fe had s-saĝa ḫṫa neṣ fe had ṣ-ṣefḫa.\nImken lek [[Special:Search/{{PAGENAME}}|ṫqelleb ĝla ṣefḫa be had l-ĝonwan]] fe ṣ-ṣefḫaṫ l-ĥrin,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ṫqelleb fe l-ĝamaliyaṫ l-mlaqyin]\nola [{{fullurl:{{FULLPAGENAME}}|action=edit}} ṫsayeb ṣ-ṣefḫa]</span>.",
        "noarticletext-nopermission": "Ma kayen fe had s-saĝa ḫṫa neṣ fe had ṣ-ṣefḫa.\nImken lek [[Special:Search/{{PAGENAME}}|ṫqelleb ĝla ṣefḫa be had l-ĝonwan]] fe ṣ-ṣefḫaṫ l-ĥrin,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ṫqelleb fe l-ĝamaliyaṫ l-mlaqyin]\nola [{{fullurl:{{FULLPAGENAME}}|action=edit}} ṫsayeb ṣ-ṣefḫa]</span>.",
        "userpage-userdoesnotexist": "ḫisab l-mosṫĥdim \"$1\" ma msjjal-ċ.\nċof waċ baġi ṫṣayb/ṫĝddal had ṣfḫa.",
index d1addb6..3138245 100644 (file)
        "noname": "انت ما حددتش اسم يوزر صحيح.",
        "loginsuccesstitle": "تم الدخول بشكل صحيح",
        "loginsuccess": "'''دخولك   {{SITENAME}} إتسجل بإسم \"$1\".'''",
-       "nosuchuser": "مافيش يوزر اسمه \"$1\".\nاسامى اليوزر بتبقى حساسه لحالة الحرف.\nاتأكد من التهجيه, او [[Special:UserLogin/signup|افتح حساب جديد]].",
+       "nosuchuser": "مافيش يوزر اسمه \"$1\".\nاسامى اليوزر بتبقى حساسه لحالة الحرف.\nاتأكد من التهجيه, او [[Special:CreateAccount|افتح حساب جديد]].",
        "nosuchusershort": "مافيش يوزر باسم $1\".\nاتاكد من تهجية الاسم.",
        "nouserspecified": "لازم تحدد اسم يوزر.",
        "login-userblocked": "اليوزر دا ممنوع من الدخول.",
        "accmailtext": "الباسورد العشوائيه اللى اتعملت لـ[[User talk:$1|$1]]  اتبعتت لـ $2.\n\nالباسورد بتاعة الحساب الجديد دا ممكن تتغير فى صفحة ''[[Special:ChangePassword|تغيير الباسورد]]''   بعد  تسجيل الدخول.",
        "newarticle": "(جديد)",
        "newarticletext": "انت وصلت لصفحه مابتدتش لسه.\nعلشان  تبتدى الصفحة ابتدى الكتابه فى الصندوق اللى تحت.\n(بص على [$1 صفحة المساعده] علشان معلومات اكتر)\nلو كانت زيارتك للصفحه دى بالغلط، دوس على زرار ''رجوع'' فى متصفح الإنترنت عندك.",
-       "anontalkpagetext": "----'' صفحة النقاش دى بتاعة يوزر مجهول لسة ما فتحش لنفسه حساب أو عنده واحد بس ما بيستعملوش.\nعلشان كدا لازم تستعمل رقم الأيبى علشان تتعرف عليه/عليها.\nالعنوان دا ممكن اكتر من واحد يكونو بيستعملوه.\nلو انت يوزر مجهول و حاسس  ان فى تعليقات بتتوجهلك مع انك مالكش دعوة بيها، من فضلك [[Special:UserLogin/signup|افتحلك حساب]] أو [[Special:UserLogin|سجل الدخول]] علشان تتجنب اللخبطة اللى ممكن تحصل فى المستقبل مع يوزرز مجهولين تانيين.''",
+       "anontalkpagetext": "----'' صفحة النقاش دى بتاعة يوزر مجهول لسة ما فتحش لنفسه حساب أو عنده واحد بس ما بيستعملوش.\nعلشان كدا لازم تستعمل رقم الأيبى علشان تتعرف عليه/عليها.\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}} تعدل الصفحه دى]</span>.",
        "userpage-userdoesnotexist": "حساب اليوزر \"<nowiki>$1</nowiki>\" مش متسجل. لو سمحت تشوف لو عايز تبتدي/تعدل الصفحة دي.",
index a57dbea..7a33b8c 100644 (file)
        "noname": "আপুনি বৈধ সদস্যনাম এটা দিয়া নাই।",
        "loginsuccesstitle": "প্ৰৱেশ অনুমোদিত হ'ল",
        "loginsuccess": "''' আপুনি {{SITENAME}}ত \"$1\" নামেৰে প্ৰৱেশ কৰিলে '''",
-       "nosuchuser": "\"$1\" নামৰ কোনো সদস্য নাই।\nসদস্য নাম আকাৰ সংবেদনশীল।\nআপোনাৰ বানানতো চাওক, বা  [[Special:UserLogin/signup|নতুন সদস্যভুক্তি কৰক]]।",
+       "nosuchuser": "\"$1\" নামৰ কোনো সদস্য নাই।\nসদস্য নাম আকাৰ সংবেদনশীল।\nআপোনাৰ বানানতো চাওক, বা  [[Special:CreateAccount|নতুন সদস্যভুক্তি কৰক]]।",
        "nosuchusershort": "\"$1\" এই নামৰ কোনো সদস্য নাই ।\nবানানতো আকৌ এবাৰ ভালদৰে চাওক ।",
        "nouserspecified": "সদস্যনাম দিয়া বাধ্যতামূলক।",
        "login-userblocked": "এই সদস্যক নিষেধ কৰা হৈছে। প্ৰৱেশ অসম্ভৱ।",
        "accmailtext": "[[User talk:$1|$1]]-ৰ কাৰণে যাদৃচ্ছিকভাৱে উৎপন্ন কৰা গুপ্তশব্দ $2লৈ পঠোৱা হ'ল । \nএই নতুন একাউন্টত প্ৰৱেশ কৰি ''[[Special:ChangePassword|গুপ্তশব্দ সলনি কৰক]]'' পৃষ্ঠাখনত শব্দতো সলনি কৰি ল’ব পাৰিব ।",
        "newarticle": "(নতুন)",
        "newarticletext": "আপুনি বিচৰা প্ৰবন্ধটো বিচাৰি পোৱা নগ'ল।\n\nইচ্ছা কৰিলে আপুনিয়েই এই প্ৰবন্ধটো লিখা আৰম্ভ কৰিব পাৰে। [$1 ইয়াত] সহায় পাব।\n\nআপুনি যদি ইয়ালৈ ভুলতে আহিছে, তেনেহলে আপোনাৰ ব্ৰাওজাৰৰ '''BACK''' বুটামত টিপা মাৰক।",
-       "anontalkpagetext": "----''এইখন আলোচনা পৃষ্ঠা বেনামী সদস্যৰ বাবে, যিয়ে নিজা একাউণ্ট  সৃষ্টি কৰা নাই বা যিয়ে সেই একাউণ্ট ব্যৱহাৰ নকৰে।\nএতেকে আমি তেখেতসকলক আই-পি ঠিকনাৰে চিনাক্ত কৰিবলৈ বাধ্য।\nসেই একেই আই-পি ঠিকনা অনেকেই ব্যৱহাৰ কৰিব পাৰে।\nআপুনি যদি এজন বেনামী সদস্য আৰু যদি আপুনি অনুভৱ কৰে যে আপোনাৰ প্ৰতি অপ্ৰাসঙ্গিক মন্তব্য কৰা হৈছে, তেনেহলে আন বেনামী সদস্যৰ পৰা পৃথক কৰিবলৈ \n[[Special:UserLogin/signup|একাউন্ট সৃষ্টি কৰক]] বা [[Special:UserLogin|প্ৰৱেশ কৰক]] ।''",
+       "anontalkpagetext": "----''এইখন আলোচনা পৃষ্ঠা বেনামী সদস্যৰ বাবে, যিয়ে নিজা একাউণ্ট  সৃষ্টি কৰা নাই বা যিয়ে সেই একাউণ্ট ব্যৱহাৰ নকৰে।\nএতেকে আমি তেখেতসকলক আই-পি ঠিকনাৰে চিনাক্ত কৰিবলৈ বাধ্য।\nসেই একেই আই-পি ঠিকনা অনেকেই ব্যৱহাৰ কৰিব পাৰে।\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}}}} সম্পৰ্কীয় অভিলেখ সন্ধান কৰিব পাৰে]</span>, কিন্তু এই পৃষ্ঠা সৃষ্টি কৰিবলৈ আপোনাৰ অনুমতি নাই।",
        "missing-revision": "\"{{FULLPAGENAME}}\" নামৰ পৃষ্ঠাৰ #$1 সংশোধনৰ অস্তিত্ব নাই।\n\nসাধাৰণতে বিলোপ কৰা এখন পৃষ্ঠাৰ পুৰণা ইতিহাস লিংক অনুসৰণ কৰিলে এনে হয়।\n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} বিলোপন ল'গ]ত অধিক তথ্য পাব।",
index c5f50c6..a8ab056 100644 (file)
@@ -29,6 +29,7 @@
        "tog-watchdefault": "Amestar les páxines y ficheros qu'edite a la mio llista de siguimientu",
        "tog-watchmoves": "Amestar les páxines y ficheros que tresllade a la mio llista de siguimientu",
        "tog-watchdeletion": "Amestar les páxines y ficheros que desanicie a la mio llista de siguimientu",
+       "tog-watchuploads": "Amestar los nuevos ficheros que xuba a la mio llista de siguimientu",
        "tog-watchrollback": "Amestar les páxines onde fici una reversión a la mio llista de siguimientu",
        "tog-minordefault": "Marcar toles ediciones como menores de mou predetermináu",
        "tog-previewontop": "Amosar previsualización enantes del cuadru d'edición",
@@ -53,7 +54,7 @@
        "tog-ccmeonemails": "Mandame copies de los correos qu'unvio a otros usuarios",
        "tog-diffonly": "Nun amosar el conteníu de la páxina embaxo de les diferencies",
        "tog-showhiddencats": "Amosar categoríes anubríes",
-       "tog-norollbackdiff": "Desaniciar les diferencies depués de facer una restauración",
+       "tog-norollbackdiff": "Nun amosar diferencies depués de facer una restauración",
        "tog-useeditwarning": "Avisame cuando salga d'una páxina d'edición con cambios ensin guardar",
        "tog-prefershttps": "Usar siempre una conexón segura en aniciando sesión",
        "underline-always": "Siempre",
        "noname": "Nun conseñasti un nome d'usuariu válidu.",
        "loginsuccesstitle": "Identificáu",
        "loginsuccess": "'''Aniciasti sesión en {{SITENAME}} como «$1».'''",
-       "nosuchuser": "Nun hai nengún usuariu col nome «$1».\nLos nomes d'usuariu distinguen mayúscules y minúscules.\nMira que tea bien escritu o [[Special:UserLogin/signup|crea una cuenta nueva]].",
+       "nosuchuser": "Nun hai nengún usuariu col nome «$1».\nLos nomes d'usuariu distinguen mayúscules y minúscules.\nMira que tea bien escritu o [[Special:CreateAccount|crea una cuenta nueva]].",
        "nosuchusershort": "Nun hai nengún usuariu col nome «$1».\nMira que tea bien escritu.",
        "nouserspecified": "Has d'especificar un nome d'usuariu.",
        "login-userblocked": "Esti usuariu ta bloquiáu. Nun se permite l'aniciu de sesión.",
        "minoredit": "Esta ye una edición menor",
        "watchthis": "Vixilar esta páxina",
        "savearticle": "Guardar la páxina",
+       "publishpage": "Publicar la páxina",
        "preview": "Vista previa",
        "showpreview": "Amosar previsualización",
        "showdiff": "Amosar cambeos",
        "accmailtext": "Unvióse a $2 una contraseña xenerada al debalu pal usuariu [[User talk:$1|$1]]. Pue camudase na páxina ''[[Special:ChangePassword|camudar contraseña]]'' depués d'aniciar sesión.",
        "newarticle": "(Nuevu)",
        "newarticletext": "Siguisti un enllaz a un artículu qu'inda nun esiste.\nPa crear la páxina, empecipia a escribir nel cuadru d'embaxo (mira la [$1 páxina d'ayuda] pa más información).\nSi llegasti equí por enquivocu, calca nel botón <strong>atrás</strong> del to restolador.",
-       "anontalkpagetext": "----\n''Esta ye la páxina d'alderique pa un usuariu anónimu qu'inda nun creó una cuenta o que nun la usa.''\nPola mor d'ello ha usase la direición numbérica IP pa identificalu/la.\nTala IP pue compartise por varios usuarios.\nSi yes un usuariu anónimu y notes qu'hai comentarios irrelevantes empobinaos pa ti, por favor [[Special:UserLogin/signup|crea una cuenta]] o [[Special:UserLogin/signup|identifícate]] pa torgar futures confusiones con otros usuarios anónimos.",
+       "anontalkpagetext": "----\n''Esta ye la páxina d'alderique pa un usuariu anónimu qu'inda nun creó una cuenta o que nun la usa.''\nPola mor d'ello ha usase la direición numbérica IP pa identificalu/la.\nTala IP pue compartise por varios usuarios.\nSi yes un usuariu anónimu y notes qu'hai comentarios irrelevantes empobinaos pa ti, por favor [[Special:CreateAccount|crea una cuenta]] o [[Special:CreateAccount|identifícate]] pa torgar futures confusiones con otros usuarios anónimos.",
        "noarticletext": "Nestos momentos nun hai testu nesta páxina.\nPuedes [[Special:Search/{{PAGENAME}}|buscar esti títulu de páxina]] n'otres páxines,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar los rexistros rellacionaos],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear esta páxina]</span>.",
        "noarticletext-nopermission": "Nestos momentos nun hai testu nesta páxina.\nPue [[Special:Search/{{PAGENAME}}|buscar esti títulu de páxina]] n'otres páxines o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar los rexistros rellacionaos]</span>, pero nun tiene permisu pa crear esta páxina.",
        "missing-revision": "La revisión #$1 de la páxina llamada \"{{FULLPAGENAME}}\" nun esiste.\n\nDe vezu la causa d'esto ye siguir un enllaz antiguu del historial a una páxina que se desanició.\nSe puen alcontrar más detalles nel [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rexistru de desanicios].",
        "userpage-userdoesnotexist": "La cuenta d'usuariu «$1» nun ta rexistrada.\nPor favor comprueba si quies crear/editar esta páxina.",
        "userpage-userdoesnotexist-view": "La cuenta d'usuariu «$1» nun ta rexistrada.",
        "blocked-notice-logextract": "Anguaño esti usuariu ta bloquiáu.\nMás abaxo ufrese la entrada del rexistru de bloqueos pa referencia:",
-       "clearyourcache": "'''Nota:''' Llueu de guardar, seique tengas que llimpiar la caché del restolador pa ver los cambeos.\n*'''Firefox / Safari:''' Caltén ''Mayús'' mentes calques en ''Recargar'', o calca ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' nun Mac)\n* '''Google Chrome:''' Calca ''Ctrl-Mayús-R'' (''⌘-Mayús-R'' nun Mac)\n* '''Internet Explorer:''' Caltén ''Ctrl'' mentes calques ''Refrescar'', o calca ''Ctrl-F5''\n* '''Opera:''' llimpia la caché en ''Ferramientes → Preferencies''",
+       "clearyourcache": "'''Nota:''' Llueu de guardar, seique tengas que llimpiar la caché del restolador pa ver los cambeos.\n*'''Firefox / Safari:''' Caltén ''Mayús'' mentes calques en ''Recargar'', o calca ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' nun Mac)\n* '''Google Chrome:''' Calca ''Ctrl-Mayús-R'' (''⌘-Mayús-R'' nun Mac)\n* '''Internet Explorer:''' Caltén ''Ctrl'' mentes calques ''Refrescar'', o calca ''Ctrl-F5''\n* '''Opera:''' Entra'n Menú → Preferencies'' (''Opera → Preferencies'' nun Mac) y d'ehí en ''Intimidá y seguridá → Llimpiar datos de navegación → Imáxenes y ficheros en caché''.",
        "usercssyoucanpreview": "'''Conseyu:''' Usa'l botón \"{{int:showpreview}}\" pa probar el CSS nuevu enantes de guardalu.",
        "userjsyoucanpreview": "'''Conseyu:''' Usa'l botón \"{{int:showpreview}}\" pa probar el JavaScript nuevu enantes de guardalu.",
        "usercsspreview": "'''Recuerda que namái ye la vista previa del CSS d'usuariu.'''\n'''¡Inda nun ta guardáu!'''",
        "right-override-export-depth": "Esportar páxines, incluyendo páxines enllazaes fasta una fondura de 5",
        "right-sendemail": "Unviar corréu a otros usuarios",
        "right-passwordreset": "Ver los correos de reestablecimientu de conseña",
-       "right-managechangetags": "Crear y desaniciar [[Special:Tags|etiquetes]] dende la base de datos",
+       "right-managechangetags": "Crear y (des)activar [[Special:Tags|etiquetes]]",
        "right-applychangetags": "Aplicar [[Special:Tags|etiquetes]] xunto colos cambios propios",
        "right-changetags": "Amestar y desaniciar [[Special:Tags|etiquetes]] arbitraries en revisiones individuales y entraes del rexistru",
+       "right-deletechangetags": "Desaniciar [[Special:Tags|etiquetes]] de la base de datos",
        "grant-generic": "Conxuntu de drechos «$1»",
        "grant-group-page-interaction": "Interactuar con páxines",
        "grant-group-file-interaction": "Interactuar con multimedia",
        "action-viewmyprivateinfo": "ver la so información privada",
        "action-editmyprivateinfo": "editar la so información privada",
        "action-editcontentmodel": "editar el modelu de conteníu d'una páxina",
-       "action-managechangetags": "crear y desaniciar etiquetes dende la base de datos",
+       "action-managechangetags": "crear y (des)activar etiquetes",
        "action-applychangetags": "aplicar etiquetes xunto colos cambios",
        "action-changetags": "amestar y desaniciar etiquetes arbitraries en revisiones individuales y entraes del rexistru",
+       "action-deletechangetags": "desaniciar etiquetes de la base de datos",
        "nchanges": "{{PLURAL:$1|un cambiu|$1 cambios}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|dende la última visita}}",
        "enhancedrc-history": "historial",
        "recentchangeslinked-page": "Nome de la páxina:",
        "recentchangeslinked-to": "Amosar los cambios de les páxines qu'enllacen en cuenta de los de la páxina dada",
        "recentchanges-page-added-to-category": "[[:$1]] amestóse a la categoría",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] y [[Special:WhatLinksHere/$1|{{PLURAL:$2|otra páxina|otres $2 páxines}}]] amestaes a la categoría",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] amestada a la categoría [[Special:WhatLinksHere/$1|esta páxina ta incluyida dientro d'otres páxines]]",
        "recentchanges-page-removed-from-category": "[[:$1]] desanicióse de la categoría",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] y [[Special:WhatLinksHere/$1|{{PLURAL:$2|otra páxina|otres $2 páxines}}]] desaniciaes de la categoría",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] desaniciada de la categoría [[Special:WhatLinksHere/$1|esta páxina ta incluyida dientro d'otres páxines]]",
        "autochange-username": "Cambiu automáticu de MediaWiki",
        "upload": "Xubir ficheru",
        "uploadbtn": "Xubir ficheru",
        "upload-form-label-infoform-description-tooltip": "Describe de mou curtiu cualquier cosa notable de la obra.\nPa una semeya, cuenta les principales coses qu'apaecen, la ocasión o'l sitiu.",
        "upload-form-label-usage-title": "Usu",
        "upload-form-label-usage-filename": "Nome del ficheru",
-       "foreign-structured-upload-form-label-own-work": "Esti ye'l mio propiu trabayu",
-       "foreign-structured-upload-form-label-infoform-categories": "Categoríes",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que xubo esti ficheru siguiendo les condiciones de serviciu y les polítiques de llicencies de {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Si nun puedes xubir esti ficheru baxo les polítiques de {{SITENAME}}, zarra esti diálogu y prueba otru métodu.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Quiciabes quieras probar tamién [[Special:Upload|la páxina predeterminada de xubíes]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Entiendo que toi xubiendo esti ficheru a un depósitu compartíu. Confirmo que toi faciéndolo cumpliendo les condiciones de serviciu y les polítiques de llicencies d'esi sitiu.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Si nun puedes xubir esti ficheru baxo les polítiques del depósitu compartíu, zarra esti diálogu y prueba otru métodu.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Tamién pué interesate usar [[Special:Upload|la páxina de carga de {{SITENAME}}]] si esti ficheru pué xubise allí baxo les sos polítiques.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Certifico que tengo los drechos d'autor d'esti ficheru, y aceuto irrevocablemente lliberalu a Wikimedia Commons baxo la llicencia [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], y aceuto les [https://wikimediafoundation.org/wiki/Terms_of_Use Condiciones d'usu].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Si nun tienes los drechos d'autor d'esti ficheru, o quieres lliberalu baxo una llicencia diferente, considera usar el [https://commons.wikimedia.org/wiki/Special:UploadWizard Asistente de carga en Commons Upload].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Tamién pué interesate usar [[Special:Upload|la páxina de carga de {{SITENAME}}]] si esti ficheru pué xubise allí baxo les sos polítiques.",
+       "upload-form-label-own-work": "Esti ye'l mio propiu trabayu",
+       "upload-form-label-infoform-categories": "Categoríes",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Confirmo que xubo esti ficheru siguiendo les condiciones de serviciu y les polítiques de llicencies de {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Si nun puedes xubir esti ficheru baxo les polítiques de {{SITENAME}}, zarra esti diálogu y prueba otru métodu.",
+       "upload-form-label-not-own-work-local-generic-local": "Quiciabes quieras probar tamién [[Special:Upload|la páxina predeterminada de xubíes]].",
+       "upload-form-label-own-work-message-generic-foreign": "Entiendo que toi xubiendo esti ficheru a un depósitu compartíu. Confirmo que toi faciéndolo cumpliendo les condiciones de serviciu y les polítiques de llicencies d'esi sitiu.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Si nun puedes xubir esti ficheru baxo les polítiques del depósitu compartíu, zarra esti diálogu y prueba otru métodu.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Tamién pué interesate usar [[Special:Upload|la páxina de carga de {{SITENAME}}]] si esti ficheru pué xubise allí baxo les sos polítiques.",
        "backend-fail-stream": "Nun se pudo tresmitir el ficheru $1.",
        "backend-fail-backup": "Nun se pudo facer copia de seguridá del ficheru $1.",
        "backend-fail-notexists": "El ficheru $1 nun esiste.",
        "changecontentmodel-success-text": "Cambióse'l tipu de conteníu de [[:$1]].",
        "changecontentmodel-cannot-convert": "El conteníu de [[:$1]] nun puede convertise a un tipu de $2.",
        "changecontentmodel-nodirectediting": "El modelu de conteníu $1 nun tien encontu pa edición direuta",
+       "changecontentmodel-emptymodels-title": "Nun hai modelos de conteníu disponibles",
+       "changecontentmodel-emptymodels-text": "El conteníu de [[:$1]] nun pue convertise a nengún tipu.",
        "log-name-contentmodel": "Rexistru de cambios del modelu de conteníu",
        "log-description-contentmodel": "Socesos rellacionaos colos modelos de conteníu d'una páxina",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|creó}} la páxina $3 usando un modelu de conteníu non predetermináu «$5»",
        "whatlinkshere-prev": "{{PLURAL:$1|anterior|anteriores $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|siguiente|siguientes $1}}",
        "whatlinkshere-links": "← enllaces",
-       "whatlinkshere-hideredirs": "$1 redireiciones",
-       "whatlinkshere-hidetrans": "$1 tresclusiones",
-       "whatlinkshere-hidelinks": "$1 enllaces",
-       "whatlinkshere-hideimages": "$1 los enllaces al ficheru",
+       "whatlinkshere-hideredirs": "Anubrir redireiciones",
+       "whatlinkshere-hidetrans": "Anubrir tresclusiones",
+       "whatlinkshere-hidelinks": "Tapecer enllaces",
+       "whatlinkshere-hideimages": "Anubrir los enllaces al ficheru",
        "whatlinkshere-filters": "Peñeres",
        "whatlinkshere-submit": "Dir",
        "autoblockid": "Autobloquiar #$1",
        "lockdbsuccesstext": "Candóse la base de datos.\n<br />Alcuérdate de [[Special:UnlockDB|descandala]] depués d'acabar el so mantenimientu.",
        "unlockdbsuccesstext": "La base de datos foi descandada.",
        "lockfilenotwritable": "L'archivu de candáu de la base de datos nun ye escribible. Pa candar o descandar la base de datos esti tien que poder ser modificáu pol sirvidor.",
+       "databaselocked": "La base de datos yá ta bloquiada.",
        "databasenotlocked": "La base de datos nun ta candada.",
        "lockedbyandtime": "(por $1 el $2 a les $3)",
        "move-page": "Treslladar $1",
        "tooltip-ca-nstab-category": "Ver la páxina de categoría",
        "tooltip-minoredit": "Marcar como una edición menor",
        "tooltip-save": "Guardar los cambios",
+       "tooltip-publish": "Publicar los cambeos",
        "tooltip-preview": "Vista previa de los cambios, ¡usa esto enantes de guardar!",
        "tooltip-diff": "Amuesa los cambios que fixisti nel testu.",
        "tooltip-compareselectedversions": "Ver les diferencies ente les dos revisiones seleicionaes d'esta páxina.",
        "confirmemail_body_set": "Dalguién, vusté posiblemente, dende la IP $1, configuró el corréu de\nla cuenta \"$2\" a esta direición de corréu en {{SITENAME}}.\n\nPa confirmar qu'esta cuenta ye suya daveres y activar les funciones\nde corréu en {{SITENAME}}, abra esti enllaz nel navegador:\n\n$3\n\nSi la cuenta *nun* ye de so, siga esti enllaz pa encaboxar\nla confirmación de les señes de corréu electrónicu:\n\n$5\n\nEsti códigu de confirmación caducará el $4.",
        "confirmemail_invalidated": "Confirmación de direición de corréu electrónicu encaboxada",
        "invalidateemail": "Encaboxar confirmación de corréu electrónicu",
+       "notificationemail_subject_changed": "Camudó la dirección de corréu electrónicu rexistrada de {{SITENAME}}",
+       "notificationemail_subject_removed": "Desanicióse la dirección de corréu electrónicu rexistrada de {{SITENAME}}",
+       "notificationemail_body_changed": "Daquién, probablemente tu, dende la dirección IP $1,\ncamudó la dirección de corréu electrónicu de la cuenta \"$2\" a \"$3\" en {{SITENAME}}.\n\nSi nun fuisti tu, comunícate darréu con un alministrador del sitiu.",
+       "notificationemail_body_removed": "Daquién, probablemente tu, dende la dirección IP $1,\ndesanició la dirección de corréu electrónicu de la cuenta \"$2\" en {{SITENAME}}.\n\nSi nun fuisti tu, comunícate darréu con un alministrador del sitiu.",
        "scarytranscludedisabled": "[La tresclusión interwiki ta desactivada]",
        "scarytranscludefailed": "[Falló la recuperación de la plantía pa $1]",
        "scarytranscludefailed-httpstatus": "[Falló la recuperación de la plantía pa $1: HTTP $2]",
        "watchlistedit-raw-done": "Anovóse la to llista de siguimientu.",
        "watchlistedit-raw-added": "{{PLURAL:$1|Añadióse un títulu|Añadiéronse $1 títulos}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|Eliminóse ún títulu|Elimináronse $1 títulos}}:",
-       "watchlistedit-clear-title": "Llimpióse la llista de siguimientu",
+       "watchlistedit-clear-title": "Llimpiar la llista de siguimientu",
        "watchlistedit-clear-legend": "Llimpiar la llista de siguimientu",
        "watchlistedit-clear-explain": "Desaniciaránse tolos títulos de la to llista de siguimientu",
        "watchlistedit-clear-titles": "Títulos:",
        "timezone-local": "Llocal",
        "duplicate-defaultsort": "Avisu: La clave d'ordenación predeterminada \"$2\" anula la clave d'ordenación anterior \"$1\".",
        "duplicate-displaytitle": "<strong>Avisu:</strong> El títulu a amosar \"$2\" anula el títulu anterior \"$1\".",
+       "restricted-displaytitle": "<strong>Atención:</strong> El títulu a amosar \"$1\" inoróse porque nun ye equivaliente al títulu real de la páxina.",
        "invalid-indicator-name": "<strong>Error:</strong> L'atributu <code>name</code> de los indicadores d'estáu de la páxina nun pue tar baleru.",
        "version": "Versión",
        "version-extensions": "Estensiones instalaes",
        "tags-delete-not-found": "La etiqueta «$1» nun esiste.",
        "tags-delete-too-many-uses": "La etiqueta «$1» aplícase a más {{PLURAL:$2|d'una revisión|de $2 revisiones}}, lo que quier dicir que nun pue desaniciase.",
        "tags-delete-warnings-after-delete": "Desanicióse la etiqueta «$1», pero {{PLURAL:$2|alcontróse'l siguiente avisu|alcontráronse los siguientes avisos}}:",
+       "tags-delete-no-permission": "Nun tienes permisu pa desaniciar etiquetes de cambiu.",
        "tags-activate-title": "Activar etiqueta",
        "tags-activate-question": "Tas a piques d'activar la etiqueta «$1».",
        "tags-activate-reason": "Motivu:",
        "feedback-useragent": "Axente d'usuariu:",
        "searchsuggest-search": "Buscar",
        "searchsuggest-containing": "que contien...",
+       "api-error-autoblocked": "La to dirección IP bloquióse automáticamente porque la usó un usuariu bloquiáu.",
        "api-error-badaccess-groups": "Nun tienes permisu pa xubir ficheros a esta wiki.",
        "api-error-badtoken": "Fallu internu: token incorreutu.",
+       "api-error-blocked": "Tas bloquiáu pa editar.",
        "api-error-copyuploaddisabled": "Xubir d'una URL ta desactivao nesti sirvidor.",
        "api-error-duplicate": "Yá hai {{PLURAL:$1|otru ficheru|otros ficheros}} nesti sitiu col mesmu conteníu.",
        "api-error-duplicate-archive": "Había {{PLURAL:$1|otru ficheru|otros ficheros}} nesti sitiu col mesmu conteníu, pero se {{PLURAL:$1|desanició|desaniciaron}}.",
        "api-error-nomodule": "Fallu internu: nun se configuró dengún módulu de xubíes.",
        "api-error-ok-but-empty": "Fallu internu: nun hai respuesta del sirvidor.",
        "api-error-overwrite": "Nun ta permitío sobroscribir un ficheru esistente.",
+       "api-error-ratelimited": "Tas tentando xubir más ficheros nun espaciu de tiempu más pequeñu del que permite esta wiki.\nTéntalo otra vuelta en dellos minutos.",
        "api-error-stashfailed": "Fallu internu: el sirvidor nun pudo guardar el ficheru temporal.",
        "api-error-publishfailed": "Fallu internu: el sirvidor nun pudo espublizar el ficheru temporal.",
        "api-error-stasherror": "Hebo un error al xubir el ficheru al almacén.",
        "sessionprovider-nocookies": "Les cookies puen tar desactivaes. Asegúrate de tener activaes les cookies y vuelve a principiar.",
        "randomrootpage": "Páxina raíz al debalu",
        "log-action-filter-block": "Tipu de bloquéu:",
+       "log-action-filter-contentmodel": "Tipu de cambéu de modelu de conteníu:",
        "log-action-filter-delete": "Tipu de desaniciu:",
+       "log-action-filter-import": "Tipu d'importación:",
+       "log-action-filter-managetags": "Tipu d'acción d'alministración d'etiquetes:",
+       "log-action-filter-move": "Tipu de movimientu:",
+       "log-action-filter-newusers": "Tipu de creación de cuenta:",
        "log-action-filter-patrol": "Tipu de patrulla:",
        "log-action-filter-protect": "Tipu de proteición:",
+       "log-action-filter-rights": "Tipu de cambéu de permisos",
+       "log-action-filter-suppress": "Tipu de supresión",
        "log-action-filter-upload": "Tipu de carga:",
        "log-action-filter-all": "Too",
        "log-action-filter-block-block": "Bloquéu",
        "log-action-filter-block-reblock": "Cambiu de bloquéu",
        "log-action-filter-block-unblock": "Desbloquéu",
+       "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-restore": "Restauración de páxines",
        "log-action-filter-delete-event": "Desaniciu de rexistros",
        "log-action-filter-delete-revision": "Desaniciu de revisión",
+       "log-action-filter-import-interwiki": "Importación ente wikis",
+       "log-action-filter-import-upload": "Importar cargando XML",
+       "log-action-filter-managetags-create": "Creación d'etiquetes",
+       "log-action-filter-managetags-delete": "Desaniciu d'etiquetes",
+       "log-action-filter-managetags-activate": "Activación d'etiquetes",
+       "log-action-filter-managetags-deactivate": "Desactivación d'etiquetes",
+       "log-action-filter-move-move": "Treslladar ensin sobreescribir les redireiciones",
+       "log-action-filter-move-move_redir": "Treslladar sobreescribiendo les redireiciones",
+       "log-action-filter-newusers-create": "Creación por usuariu anónimu",
+       "log-action-filter-newusers-create2": "Creación por usuariu rexistráu",
+       "log-action-filter-newusers-autocreate": "Creación automática",
+       "log-action-filter-newusers-byemail": "Creación cola contraseña unviada per corréu",
        "log-action-filter-patrol-patrol": "Patrulláu manual",
        "log-action-filter-patrol-autopatrol": "Patrulláu automáticu",
        "log-action-filter-protect-protect": "Proteición",
        "log-action-filter-protect-modify": "Cambiu na proteición",
        "log-action-filter-protect-unprotect": "Desproteición",
+       "log-action-filter-protect-move_prot": "Proteición treslladada",
+       "log-action-filter-rights-rights": "Cambéu manual",
+       "log-action-filter-rights-autopromote": "Cambéu automáticu",
+       "log-action-filter-suppress-event": "Supresión de rexistru",
+       "log-action-filter-suppress-revision": "Supresión de revisión",
+       "log-action-filter-suppress-delete": "Supresión de páxina",
+       "log-action-filter-suppress-block": "Supresión d'usuariu por bloquéu",
+       "log-action-filter-suppress-reblock": "Supresión d'usuariu por rebloquéu",
        "log-action-filter-upload-upload": "Nueva carga",
        "log-action-filter-upload-overwrite": "Recargar"
 }
index 71e1639..5c10f27 100644 (file)
        "noname": "Va favesikyolt gobazel.",
        "loginsuccesstitle": "Pilkomodanhara.",
        "loginsuccess": "Rin wetce « $1 » moe {{SITENAME}} til dogluyarakiraf.",
-       "nosuchuser": "« $1 » favesik me krulder.\nSutera va favesikyolt gotir eltaykoranhafa.\nAgeltal va rinaf suteks oke [[Special:UserLogin/signup|pataredura]].",
+       "nosuchuser": "« $1 » favesik me krulder.\nSutera va favesikyolt gotir eltaykoranhafa.\nAgeltal va rinaf suteks oke [[Special:CreateAccount|pataredura]].",
        "nosuchusershort": "Me tir favesik digis va « $1 » yolt. Va sutekaks ageltal.",
        "nouserspecified": "Va favesikyolt gobazel !",
        "login-userblocked": "Bat webesik tir elekayan. Dogluyara menovena.",
index 8cab3c9..2ac5aac 100644 (file)
        "noname": "आप सही सदस्यनाम नाइ दिहा गा है।",
        "loginsuccesstitle": "लॉग इन होइ गवा",
        "loginsuccess": "'''आप {{SITENAME}} में \"$1\" सदस्यनाम से लॉग इन होई {{GENDER:$1|चुके|चुकी|चुके}} हव।'''",
-       "nosuchuser": "\"$1\" नावँ कय कवनो सदस्य नाइ है।\nसदस्यनावँ में लघु औ दीर्घ अक्षरन् से फ़रक परत है।\nआपन अक्षर जाँचा जाय, या [[Special:UserLogin/signup|नवाँ खाता खोला जाय]]।",
+       "nosuchuser": "\"$1\" नावँ कय कवनो सदस्य नाइ है।\nसदस्यनावँ में लघु औ दीर्घ अक्षरन् से फ़रक परत है।\nआपन अक्षर जाँचा जाय, या [[Special:CreateAccount|नवाँ खाता खोला जाय]]।",
        "nosuchusershort": "\"$1\" नावँ कय कवनो सदस्य नाई है।\nकृपया आपन शब्द फिरसे जाँचा जाय।",
        "nouserspecified": "सदस्यनावँ देब जरुरी है।",
        "login-userblocked": "ई सदस्य प्रतिबन्धित है। सत्रारम्भ कय अनुमति नाई है।",
        "accmailtext": "[[User talk:$1|$1]] कय लिए एक यंत्र जनित गुप्त कुंजी $2 कय भेज दिहा गा है। लॉगिन करेक बाद एका '''[[Special:ChangePassword|गुप्त कुंजी बदला जाय]]'' वाला पन्नन् पे बदल सका जात है।",
        "newarticle": "(नँवा)",
        "newarticletext": "आप अईसन पन्ना पे आवा गा है जवन अभीन तक नाई बनावा है।\nपन्ना बनावेक लिये नीचे कय बौक्स में पाठ लिखा जाय। ढेर जानकारी कय लिये [$1 सहायता पन्ना] देखा जाय।\nअगर आप हिँया गलती से आवा गा हैं तव आपन ब्राउज़र कय बैक ('''back''') बटन पे क्लिक करा जाय।",
-       "anontalkpagetext": "----''ई बातचीत पन्ना उ बेनामी सदस्यन् कय खर्तीन होय जे या तव खाता नाई खोलें है या खाता कय प्रयोग नाइ करत हैं।\nइहिकै नाते वय लोगन कय पहिचान कय खर्तीन हम्मन कय वय लोगन कय आइ॰पी ठहर कय प्रयोग करेक परत है।\nआइ॰पी ठहर कयु सदस्यन् कय एक्कय होइ सकत है।\nयदि आप कवनो बेनामी सदस्य होआ जाय अव आप कय लागत है कि आप कय बारे में अप्रासंगिक टीका टिप्पणी कई गा है तव कृपया [[Special:UserLogin/signup|सदस्यता लिहा जाय]] या [[Special:UserLogin|सत्रारंभ करा जाय]] ताकि अउर बेनामी सदस्यन् में से आप कय अलग से पहिचान सका जाय।''",
+       "anontalkpagetext": "----''ई बातचीत पन्ना उ बेनामी सदस्यन् कय खर्तीन होय जे या तव खाता नाई खोलें है या खाता कय प्रयोग नाइ करत हैं।\nइहिकै नाते वय लोगन कय पहिचान कय खर्तीन हम्मन कय वय लोगन कय आइ॰पी ठहर कय प्रयोग करेक परत है।\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}}}} से सम्बन्धित लॉग खोज सका जात है] </span>लेकिन आप कय इ पन्ना बनावे कय अनुमति नाई है ।",
        "missing-revision": "\"{{FULLPAGENAME}}\" पन्ना कय अवतरण #$1 नाई है।\n\nखास कइकै ई एकठु मेटावल पन्ना कय पुरान लिंक पे क्लिक करय से होत है।\nढेर जानकारी कय खर्तिन आप [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटावे कय लॉग] देख सका जात है।",
index 5b1afd5..0ace557 100644 (file)
        "noname": "Siz mövcud olan istifadəçi adı daxil etməmisiniz.",
        "loginsuccesstitle": "Daxil oldunuz",
        "loginsuccess": "'''\"$1\" adı ilə sistemə daxil oldunuz.'''",
-       "nosuchuser": "\"$1\" adlı istifadəçi mövcud deyil.\nİstifadəçi adları hərflərin böyük və ya kiçik olmasına həssasdırlar.\nDüzgün yazdığınıza əmin olun, yaxud [[Special:UserLogin/signup|yeni hesab açın]].",
+       "nosuchuser": "\"$1\" adlı istifadəçi mövcud deyil.\nİstifadəçi adları hərflərin böyük və ya kiçik olmasına həssasdırlar.\nDüzgün yazdığınıza əmin olun, yaxud [[Special:CreateAccount|yeni hesab açın]].",
        "nosuchusershort": "\"$1\" adında istifadəçi mövcud deyil.\nDüzgün yazdığına əmin ol.",
        "nouserspecified": "İstifadəçi adı daxil etməlisiniz.",
        "login-userblocked": "Bu istifadəçi bloklanıb. Sistemə giriş üçün icazə verilmir.",
        "accmailtext": "[[User talk:$1|$1]] üçün təsadüfi yolla yaradılmış parol $2 ünvanına göndərildi.\nHesabınıza daxil olduqdan sonra, parolunuzu ''[[Special:ChangePassword|parolu dəyiş]]'' səhifəsində dəyişdirə bilərsiniz.",
        "newarticle": "(Yeni)",
        "newarticletext": "Mövcud olmayan səhifəyə olan keçidi izlədiniz. Aşağıdakı sahəyə məzmununu yazaraq bu səhifəni '''siz''' yarada bilərsiniz. (əlavə məlumat üçün [$1 kömək səhifəsinə] baxın). Əgər bu səhifəyə səhvən gəlmisinizsə sadəcə olaraq brauzerin '''geri''' düyməsinə vurun.",
-       "anontalkpagetext": "----''Bu səhifə qeydiyyatdan keçməmiş və ya daxil olmamış anonim istifadəçiyə aid müzakirə səhifəsidir.\nOna görə bu istifadəçini rəqəmlərdən ibarət IP ünvanı ilə müəyyən etmək məcburiyyətindəyik.\nBelə IP ünvan bir neçə fərd tərəfindən istifadədə ola bilər.\nƏgər siz anonim istifadəçisinizsə və bu mesajın sizə aid olmadığını düşünürsünüzsə, onda  [[Special:UserLogin/signup|qeydiyyatdan keçin]] və ya [[Special:UserLogin|daxi olun]].''",
+       "anontalkpagetext": "----''Bu səhifə qeydiyyatdan keçməmiş və ya daxil olmamış anonim istifadəçiyə aid müzakirə səhifəsidir.\nOna görə bu istifadəçini rəqəmlərdən ibarət IP ünvanı ilə müəyyən etmək məcburiyyətindəyik.\nBelə IP ünvan bir neçə fərd tərəfindən istifadədə ola bilər.\nƏgər siz anonim istifadəçisinizsə və bu mesajın sizə aid olmadığını düşünürsünüzsə, onda  [[Special:CreateAccount|qeydiyyatdan keçin]] və ya [[Special:UserLogin|daxi olun]].''",
        "noarticletext": "Hal-hazırda bu səhifə boşdur. Başqa səhifələrdə eyni adda səhifəni [[Special:Search/{{PAGENAME}}| axtara]], əlaqəli qeydlərə\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} baxa] və ya səhifəni [{{fullurl:{{FULLPAGENAME}}|action=edit}} redaktə]</span> edə bilərsiniz.",
        "noarticletext-nopermission": "Hal-hazırda bu səhifə boşdur. Başqa səhifələrdə eyni adlı səhifəni [[Special:Search/{{PAGENAME}}| axtara]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} əlaqəli qeydlərə baxa] və ya səhifəni [{{fullurl:{{FULLPAGENAME}}|action=edit}} redaktə edə bilərsiniz]</span>, lakin sizin bu məqaləni yaratmaq hüququnuz yoxdur.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" istifadəçi adı qeydiyyata alınmayıb.\nƏgər siz bu səhifəni yaratmaq/redaktə etmək istəyirsinizsə, xahiş edirik bunu yoxlayın.",
        "upload-form-label-infoform-description": "İzah",
        "upload-form-label-usage-title": "İstifadə",
        "upload-form-label-usage-filename": "Fayl adı",
-       "foreign-structured-upload-form-label-own-work": "Bu mənim öz işimdir",
-       "foreign-structured-upload-form-label-infoform-categories": "Kateqoriyalar",
-       "foreign-structured-upload-form-label-infoform-date": "Tarix",
+       "upload-form-label-own-work": "Bu mənim öz işimdir",
+       "upload-form-label-infoform-categories": "Kateqoriyalar",
+       "upload-form-label-infoform-date": "Tarix",
        "backend-fail-notexists": "\"$1\" faylı mövcud deyil",
        "backend-fail-delete": "\"$1\" faylı sililmədi.",
        "backend-fail-copy": "\"$1\" faylı \"$2\" faylına kopyalanmır.",
index 0a74b0a..40e47b4 100644 (file)
@@ -18,7 +18,8 @@
                        "Sadiqr",
                        "Mjbmr",
                        "Alp Er Tunqa",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "Ilğım"
                ]
        },
        "tog-underline": "باغلانتی‌لارین آلتینی خطله:",
        "tog-hidepatrolled": "سوْن دییشیکلیکلرده نظارتلنمیش دَییشیکلیکلری گیزلت",
        "tog-newpageshidepatrolled": "یوْخلانمیش صفحه‌لری یئنی صفحه‌لر لیستیندن گیزلت",
        "tog-extendwatchlist": "ایزله‌دیک‌لری، یالنیز یئنی‌لر اۆچون یوْخ، بۆتون دییشیک‌لیک‌لری گؤرستمک اۆچون، گنیشلندیر.",
-       "tog-usenewrc": "دَییشیک‌لیک‌لری سوْن دَییشیک‌لیک‌لر صفحه‌سینده ایزله‌دیک‌لر صفحه‌سینده گروپ‌لا (جاوااسکریپت گرک‌دیر)",
+       "tog-usenewrc": "دَییشیک‌لیک‌لری سوْن دَییشیک‌لیک‌لر صفحه‌سینده ایزله‌دیک‌لر صفحه‌سینده قروپ‌لا (جاوااسکریپت گرک‌دیر)",
        "tog-numberheadings": "باشلیق‌لاری اوْتوماتیک نۆمره‌له",
-       "tog-showtoolbar": "دَییشدیرمک آراج-چۇبوغونو گؤرست",
+       "tog-showtoolbar": "دَییشدیرمه آراج-چۇبوغونو گؤستر",
        "tog-editondblclick": "صفحه‌‌لری ایکی کیلیک‌ده دَییشدیر",
        "tog-editsectiononrightclick": "بؤلوم‌لرین دَییشدیرمه‌سینی، باشلیق‌لارین اۆستونده ساغ‌کلیک ائتمک‌‌له ایجازه وئر",
        "tog-watchcreations": "ياراتدیغیم صفحه‌‌لری و يۆکله‌دیگیم فايل‌لاری، ایزله‌دیک‌لریمه آرتیر",
        "tog-watchdefault": "دَییشدیردیگیم صفحه‌‌لری و فايل‌لاری، ایزله‌دیک‌لریمه آرتیر",
        "tog-watchmoves": "داشیدیغیم صفحه‌‌لری و فايللاری ایزله‌دیکلریمه آرتیر",
        "tog-watchdeletion": "سیلدیگیم صفحه‌‌لری و فايللاری ایزله‌دیکلریمه آرتیر",
-       "tog-watchrollback": "قایتاریلمیش صفحه لری ایزلدیکلریمه آرتیر",
-       "tog-minordefault": "دفالت اوْلاراق، بۆتون دَییشدیر‌مه‌لری کیچیک کیمی علامتله",
-       "tog-previewontop": "اؤÙ\86â\80\8cگؤسترÛ\8cØ´Û\8cØ\8c Ù\8aازÙ\85اÙ\82 Ù\82Û\87تÙ\88سÙ\88Ù\86داÙ\86 Ù\82اباÙ\82 Ú¯Ø¤Ø±Ø³Øª",
-       "tog-previewonfirst": "اÛ\8cÙ\84Ú© Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\85Ù\87â\80\8cدÙ\87 Ø§Ø¤Ù\86â\80\8cگؤسترÛ\8cØ´Û\8c Ú¯Ø¤Ø±Ø³Øª",
-       "tog-enotifwatchlistpages": "اÛ\8cزÙ\84Ù\87â\80\8cدÛ\8cÚ©Ù\84رÛ\8cÙ\85â\80\8cدÙ\87 Ø§Ù\88Ù\84اÙ\86 Ø¨Û\8cر ØµÙ\81Ø­Ù\87 Û\8cا Ù\81اÛ\8cÙ\84 Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÛ\8cÙ\84Ù\86â\80\8cدÙ\87Ø\8c Ù\85Ù\86Ù\87 Ø§Û\8cÙ\85یل گؤندر",
-       "tog-enotifusertalkpages": "دانیشیق صحیفه‌‌م دَییشدیریلنده منه ایمیل گؤندر",
-       "tog-enotifminoredits": "صحیفه‌لرده و فایل‌لاردا کیچیک دَییشیکلیکلر اولسا دا منه ایمیل گؤندر",
-       "tog-enotifrevealaddr": "منیم ایمیل آدرسیمی بیلدیریش ایمیل‌لرینده گؤستر",
-       "tog-shownumberswatching": "گؤزله‌ین ایستیفاده‌چیلرین سایینی گؤستر",
+       "tog-watchrollback": "قایتاریلمیش صفحه‌لری ایزله‌دیکلریمه آرتیر",
+       "tog-minordefault": "دیفالت اوْلاراق، بۆتون دَییشدیر‌مه‌لری کیچیک کیمی علامتله",
+       "tog-previewontop": "اؤÙ\86â\80\8cگؤسترÛ\8cØ´Û\8cØ\8c Ù\8aازÙ\85اÙ\82 Ù\82Û\87تÙ\88سÙ\88Ù\86داÙ\86 Ù\82اباÙ\82 Ú¯Ø¤Ø³ØªØ±",
+       "tog-previewonfirst": "اÛ\8cÙ\84Ú© Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\85Ù\87â\80\8cدÙ\87 Ø§Ø¤Ù\86â\80\8cگؤسترÛ\8cØ´Û\8c Ú¯Ø¤Ø³ØªØ±",
+       "tog-enotifwatchlistpages": "اÛ\8cزÙ\84Ù\87â\80\8cدÛ\8cÚ©Ù\84رÛ\8cÙ\85â\80\8cدÙ\87 Ø§Ù\88Ù\92Ù\84اÙ\86 Ø¨Û\8cر ØµÙ\81Ø­Ù\87 Ù\88 Û\8cا Ù\81اÛ\8cÙ\84 Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÛ\8cÙ\84Ù\86دÙ\87Ø\8c Ù\85Ù\86Ù\87 Ø§Û\8cÙ\85ئیل گؤندر",
+       "tog-enotifusertalkpages": "دانیشیق صفحه‌‌م دَییشدیریلنده منه ایمئیل گؤندر",
+       "tog-enotifminoredits": "صفحه‌لرده و فایل‌لاردا کیچیک دَییشیکلیکلر اوْلسا دا منه ایمئیل گؤندر",
+       "tog-enotifrevealaddr": "منیم ایمئیل آدرسیمی بیلدیریش ایمئیل‌لرینده گؤستر",
+       "tog-shownumberswatching": "گؤزله‌ین ایستیفاده‌چیلرین سایی‌سینی گؤستر",
        "tog-oldsig": "ایندی‌کی ایمضا:",
        "tog-fancysig": "ایمضانی ویکی-متن کیمی نظره آل (اوْتوماتیک باغلانتی‌سیز)",
        "tog-uselivepreview": "دیری اؤن‌گؤستریش ایشلت (تِست مرحله‌سینده)",
-       "tog-forceeditsummary": "دَییشیکلیک قیساسی بوْش قالاندا منه بیلیندیر",
+       "tog-forceeditsummary": "دَییشیکلیک قیساسی بوْش قالمیشسا منه بیلیندیر",
        "tog-watchlisthideown": "منیم دَییشیک‌لیک‌لریمی ایزله‌دیک‌لردن گیزلت",
        "tog-watchlisthidebots": "بوْت دَییشیک‌لیک‌لرینی ایزله‌دیک‌لردن گیزلت",
        "tog-watchlisthideminor": "کیچیک دَییشیک‌لیک‌لری ایزله‌دیک‌لردن گیزلت",
-       "tog-watchlisthideliu": "گیرمیش ایشلدن‌لرین دَییشیک‌لیک‌لرینی ایزله‌دیک‌لردن گیزلت",
+       "tog-watchlisthideliu": "گیریش ائتمیش ایشلدن‌لرین دَییشیک‌لیک‌لرینی ایزله‌دیک‌لردن گیزلت",
        "tog-watchlisthideanons": "تانینمامیش ایشلدن‌لرین دَییشیک‌لیک‌لرینی ایزله‌دیک‌لردن گیزلت",
        "tog-watchlisthidepatrolled": "نظارتلنمیش دَییشیکلیکلری گؤزله‌دیکلردن گیزلت",
-       "tog-ccmeonemails": "باشقا ایشلدن‌لره گؤندردیگیم ایمیل‌لرین کوْپی‌لرینی منه گؤندر",
-       "tog-diffonly": "مقایسه‌لر آلیتندا صفحه‌لرین ایچینده‌کی‌لرینی گؤرستمه",
-       "tog-showhiddencats": "Ú¯Û\8cزÙ\84Û\8c Ø¨Ø¤Ù\84Ù\85Ù\87â\80\8cÙ\84رÛ\8c Ú¯Ø¤Ø±Ø³Øª",
-       "tog-norollbackdiff": "Ù\82اÛ\8cتاراÙ\86داÙ\86 Ø³Ù\88Ù\92Ù\86را Ù\85Ù\82اÛ\8cسÙ\87 Ú¯Ø¤Ø±Ø³Øªمه",
-       "tog-useeditwarning": "دَییشدیرمه صفحه‌سیندن چیخاندا، ذخیره اولونمامیش دَییشدیرمه اوْلسا، منه تذکر وئر",
-       "tog-prefershttps": "هر زامان گیریش ائتمگه امن باغلانتی ایشلت",
+       "tog-ccmeonemails": "باشقا ایشلدن‌لره گؤندردیگیم ایمئیل‌لرین کوْپی‌لرینی منه گؤندر",
+       "tog-diffonly": "مۆقایسه‌لر آلتیندا صفحه‌نین ایچینده‌کیلرینی گؤسترمه",
+       "tog-showhiddencats": "Ú¯Û\8cزÙ\84Û\8c Ø¨Ø¤Ù\84Ù\85Ù\87â\80\8cÙ\84رÛ\8c Ú¯Ø¤Ø³ØªØ±",
+       "tog-norollbackdiff": "Ù\82اÛ\8cتاراÙ\86داÙ\86 Ø³Ù\88Ù\92Ù\86را Ù\81رÙ\82Û\8c Ú¯Ø¤Ø³ØªØ±مه",
+       "tog-useeditwarning": "دَییشدیرمه صفحه‌سیندن چیخاندا، قئید اوْلونمامیش دَییشدیرمه اوْلسا، منه بیلدیر",
+       "tog-prefershttps": "هر زامان گیریش ائتمه‌یه امنیتلی باغلانتی ایشلت",
        "underline-always": "هر زامان",
        "underline-never": "هئچ واخت",
        "underline-default": "وارساییلان قابیق یا براوزِر",
-       "editfont-style": "دَییشدیرمه قۇتوسونون فوْنتی:",
+       "editfont-style": "دَییشدیرمه قۇتوسونون فوْنتو:",
        "editfont-default": "براوزِر وارساییلانی",
        "editfont-monospace": "ثابیت آرالی فوْنت",
        "editfont-sansserif": "بۇجاق‌سیز فوْنت",
        "fri": "آینی‌گون (جۆمعه)",
        "sat": "يئل‌گونو (شنبه)",
        "january": "ژانویه",
-       "february": "فئوریه",
+       "february": "فوریه",
        "march": "مارس",
        "april": "آوریل",
        "may_long": "مئی",
        "june": "ژوئن",
-       "july": "ژولای",
+       "july": "جولای",
        "august": "آقوست",
        "september": "سپتامبر",
        "october": "اوْکتوبر",
        "november": "نوْوامبر",
-       "december": "دئساÙ\85بر",
+       "december": "دسامبر",
        "january-gen": "ژانویه",
-       "february-gen": "فئوریه",
+       "february-gen": "فوریه",
        "march-gen": "مارس",
        "april-gen": "آوریل",
        "may-gen": "مئی",
        "july-gen": "جولای",
        "august-gen": "آقوست",
        "september-gen": "سپتامبر",
-       "october-gen": "اوکتوبر",
-       "november-gen": "نووامبر",
-       "december-gen": "دئساÙ\85بر",
+       "october-gen": "اوْکتوبر",
+       "november-gen": "Ù\86Ù\88Ù\92Ù\88اÙ\85بر",
+       "december-gen": "دسامبر",
        "jan": "ژانویه",
-       "feb": "فئوریه",
+       "feb": "فوریه",
        "mar": "مارس",
        "apr": "آوریل",
        "may": "مئی",
        "jun": "ژوئن",
-       "jul": "ژولای",
+       "jul": "جولای",
        "aug": "آقوست",
        "sep": "سپتامبر",
        "oct": "اوْکتوبر",
        "nov": "نوْوامبر",
-       "dec": "دئساÙ\85بر",
+       "dec": "دسامبر",
        "january-date": "ژانویه‌نین $1-ی",
-       "february-date": "فئوریه‌نین $1-ی",
+       "february-date": "فوریه‌نین $1-ی",
        "march-date": "مارسین $1-ی",
        "april-date": "آوریلین $1-ی",
        "may-date": "مئیین $1-ی",
        "june-date": "ژوئنین $1-ی",
-       "july-date": "ژولایین $1-ی",
+       "july-date": "جولایین $1-ی",
        "august-date": "آقوستون $1-ی",
-       "september-date": "سئپتامبرین $1-ی",
+       "september-date": "سپتامبرین $1-ی",
        "october-date": "اوْکتوبرون $1-ی",
        "november-date": "نوْوامبرین $1-ی",
-       "december-date": "دئساÙ\85برÛ\8cÙ\86 $1-Û\8c",
+       "december-date": "دسامبرین $1-ی",
        "pagecategories": "{{PLURAL:$1|بؤلمه|بؤلمه‌لر}}",
        "category_header": "«$1» بؤلمه‌سینده صفحه‌لر",
        "subcategories": "آلت‌بؤلمه‌لر",
-       "category-media-header": "«$1» بؤلمه‌سین‌ده مئدیا",
-       "category-empty": "<em>بÙ\88 Ø¨Ø¤Ù\84Ù\85Ù\87â\80\8cدÙ\87 Ø§Û\8cÙ\86دÛ\8c Ù\81اÛ\8cÙ\84 Û\8cا Ù\85ئدیا یوْخدور.</em>",
+       "category-media-header": "«$1» بؤلمه‌سینده مدیا",
+       "category-empty": "<em>اÛ\8cÙ\86دÛ\8c Ø¨Û\87 Ø¨Ø¤Ù\84Ù\85Ù\87â\80\8cدÙ\87 Ù\81اÛ\8cÙ\84 Ù\88 Û\8cا Ù\85دیا یوْخدور.</em>",
        "hidden-categories": "{{PLURAL:$1|گیزلی بؤلمه|گیزلی بؤلمه‌لر}}",
        "hidden-category-category": "گیزلی بؤلمه‌لر",
        "category-subcat-count": "{{PLURAL:$2|بۇ بؤلمه‌ده تکجه آشاغیداکی آلت‌بؤلمه واردیر.|بۇ بؤلمه‌ده، جمعی $2-دن، آشاغیداکی {{PLURAL:$1|آلت‌بؤلمه|$1 آلت‌بؤلمه}} واردیر.}}",
        "viewhelppage": "یاردیم صحیفه‌سینه باخ",
        "categorypage": "بؤلمه صحیفه‌‌سینه باخ",
        "viewtalkpage": "دانیشیغا باخ",
-       "otherlanguages": "Ø¢Û\8cرÛ\8c دیل‌لرده",
+       "otherlanguages": "باشÙ\82ا دیل‌لرده",
        "redirectedfrom": "($1-دن يوْل‌لاندیریلمیش)",
        "redirectpagesub": "یوْللاندیرما صفحه‌سی",
        "redirectto": "مسیزپرین دَییشیب:",
        "currentevents": "ایندیکی حادیثه‌لر",
        "currentevents-url": "Project:ایندیکی اولایلار",
        "disclaimers": "یالانلامالار",
-       "disclaimerpage": "Project:گنل یالانلاما",
+       "disclaimerpage": "Project:گئنل یالانلاما",
        "edithelp": "ديَیشتیرمک یاردیمی",
        "helppage-top-gethelp": "کؤمک",
        "mainpage": "آنا صفحه",
        "noname": "گئچرلی ایستیفاده‌چی آدی وئرمه‌دینیز.",
        "loginsuccesstitle": "گیریش اوغورلو",
        "loginsuccess": "'''سیز ایندی {{SITENAME}} سایتینا، «$1» آدی‌له گیرمیسینیز.'''",
-       "nosuchuser": "«$1» آدلا ایستیفاده‌چی یوخدور.\nایستیفاده‌چی آدلاری، حرفلرین بؤیوک/کیچیک‌لیگینه حساس‌دیلار.\nیازدیغینیزا یئنی‌دن باخین، یوخسا [[Special:UserLogin/signup|یئنی بیر حساب آچین]].",
+       "nosuchuser": "«$1» آدلا ایستیفاده‌چی یوخدور.\nایستیفاده‌چی آدلاری، حرفلرین بؤیوک/کیچیک‌لیگینه حساس‌دیلار.\nیازدیغینیزا یئنی‌دن باخین، یوخسا [[Special:CreateAccount|یئنی بیر حساب آچین]].",
        "nosuchusershort": "\"$1\" آدلا ایستیفاده‌چی یوخدور.\nدوزگون یازدیغینیزدان آرخایین اولون.",
        "nouserspecified": "بیر ایستیفاده‌چی آدی وئرمه‌لیسینیز.",
        "login-userblocked": "بو ایستیفاده چی باغلانیب‌دیر. گیریشه ایجازه یوخدور.",
        "noemail": "«$1» ایستیفاده‌چی‌یه ایمیل آدرسی قئید اولماییب‌دیر.",
        "noemailcreate": "دوزگون بیر ایمیل آدرسی وئرمه‌لیسینیز",
        "passwordsent": "«$1»-نا قئید اولونان ایمیل آدرسینه، یئنی بیر رمز گؤندریلدی.\nاونا آلان‌دان سونرا یئنی‌دن گیرین.",
-       "blocked-mailpassword": "سیزین آی‌پی آدرسینیز دَییشیک وئرمه‌یه باغلانیب و سوءاستفاده قاباغی آلماق اوچون، رمزی یئنی‌دن اله گتیرمک ایمکانینا ایجازه‌نیز یوخدور.",
+       "blocked-mailpassword": "سیزین آی‌پی آدرسینیزین دییشدیرمه ائده بیلمه‌سی باغلانمیشدیر. سوءایستیفاده قارشی‌سینی آلماق اۆچون، رمزی یئنی‌دن اله گتیرمک ایمکانینا ایجازه‌نیز یوْخدور.",
        "eauthentsent": "سیزین سئچیلمیش ایمیل آدرسینه، دوغرولاماق اوچون بیر ایمیل گؤندریلدی.\nهر یئنی بیر ایمیل گؤندرمک‌دن اؤنجه، بو حسابین دوغرودان سیزین اولدوغونو گؤسترمک اوچون، او ایمیل‌ده‌کی ایشلری گؤرمه‌لیسینیز.",
        "throttled-mailpassword": "سون {{PLURAL:$1|ساعات|$1 ساعات}}‌دا سیزه بیر رمز یئنیله‌مه ایمیلی گؤندریلیب‌دیر.\nسوءاستفاده قاباغین آلماق اوچون، هر {{PLURAL:$1|ساعات|$1 ساعات}}‌دا یالنیز بیر رمز یئنیله‌مه ایمیلی گؤندریلر.",
        "mailerror": "ایمیل گؤندرمه خطاسی: $1",
        "createaccount-title": "{{SITENAME}} اوچون حساب یارادیلماسی",
        "createaccount-text": "بیر کس، سیزین ایمیل آدرسینیزه {{SITENAME}} ($4) سایتیندا «$2» آدی و «$3» رمزی ایله بیر حساب آچیب‌دیر. سیز گرک گیریش ائدیب و رمزینیزی ایندی دَییشدیره‌سیز.\n\nبو حساب یانلیش دوزلیب‌سه، بو مئساژا محل قویمایابیلرسیز.",
        "login-throttled": "سیزین چوخ گیریش چالیشماغینیز اولوب‌دور.\nلوطفاً یئنی‌دن چالیشماق‌دان اؤنجه بیر آز $1 دؤزون.",
-       "login-abort-generic": "سÛ\8cزÛ\8cÙ\86 Ú¯Û\8cرÛ\8cØ´Û\8cÙ\86Û\8cز Ø¨Ø§Ø´Ø§Ø±Û\8câ\80\8cسÛ\8cز Ø§Ù\88لدو - دایاندیریلدی",
+       "login-abort-generic": "سÛ\8cزÛ\8cÙ\86 Ú¯Û\8cرÛ\8cØ´Û\8cÙ\86Û\8cز Ø¨Ø§Ø¬Ø§Ø±Û\8câ\80\8cسÛ\8cز Ø§Ù\88Ù\92لدو - دایاندیریلدی",
        "login-migrated-generic": "ایستفادچی حسابینیز آختاریلمیش دیر و آدینیز داها بو ویکی ده یوخدور",
        "loginlanguagelabel": "دیل: $1",
        "suspicious-userlogout": "سیزین چیخیش ایستگینیز رد اولوندو. بو، براوزرین یا پروکسی-کَشلمه‌سینین دوزگون ایشله‌مه‌مه‌سین‌دن قایناق‌لانیر.",
        "newpassword": "یئنی رمز",
        "retypenew": "یئنی رمزی یئنی‌دن یازین:",
        "resetpass_submit": "رمز یارادین و گیریش ائدین",
-       "changepassword-success": "رÙ\85زÛ\8cÙ\86Û\8cز Ø¨Ø§Ø´Ø§Ø±Û\8câ\80\8cÙ\84ا Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\84دÛ\8c!",
+       "changepassword-success": "رÙ\85زÛ\8cÙ\86Û\8cز Ø¨Ø§Ø¬Ø§Ø±Û\8cÛ\8cÙ\84ا Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\84دÛ\8c!",
        "changepassword-throttled": "سیزین چوخ گیریش چالیشماغینیز اولوب‌دور.\nلوطفاً یئنی‌دن چالیشماق‌دان اؤنجه $1 دؤزون.",
        "botpasswords-label-create": "یارات",
        "botpasswords-label-cancel": "وازگئچ",
        "resetpass-no-info": "بو صحیفه‌نی دوغرو گؤردوگونوز اوچون سیستمه گیرمه‌لیسینیز.",
        "resetpass-submit-loggedin": "رمزی دَییشدیر",
        "resetpass-submit-cancel": "وازگئچ",
-       "resetpass-wrong-oldpass": "یانلیش گئچیجی یا ایندیکی رمز.\nاولا بیلر سیز باشاریلیق‌لا رمزینیزی دَییشمیسینیز یوخسا یئنی گئچرلی رمز ایسته‌میسینیز.",
+       "resetpass-wrong-oldpass": "یانلیش گئچیجی و یا ایندیکی رمز.\nسیز باجارییلا رمزینیزی دَییشدیریب یوخسا یئنی گئچرلی رمز ایسته‌میش اوْلدوغونوز مؤحتمل‌دیر.",
        "resetpass-recycled": "لوطفا گیریش رمزینیزی ایندیکی اولمایان بیر ایری گیریش رمزینه دَییشین",
        "resetpass-temp-emailed": "سیز بیر کدلانمیش موقت ایمیل له گیریش ائدیب سیز.\nگیریشینیزه سون وئرمک اوچون یئنی دن بیر گیریش رمزی وئرمه لی سیز:",
        "resetpass-temp-password": "گئچیجی رمز:",
        "passwordreset-emailtext-ip": "بیر کس (احتیمالاً سیز، $1 آی‌پی آدرسی‌له)، {{SITENAME}} ($4) سایتینداکی حسابینیز اوچون رمزی یئنیله‌مک ایسته‌ییب‌دیر. آشاغیداکی ایستیفاده‌چی {{PLURAL:$3|حسابی|حسابلاری}} بو ایمیل ایله ایلگی‌لی‌دیرلر:\n\n$2\n\nبو گئچیجی {{PLURAL:$3|رمز|رمزلر}}، {{PLURAL:$5|بیر گون|$5 گون}}‌ده {{PLURAL:$3|واختی|واختلاری}} قورتاراجاق‌دیر.\nسیز گرک ایندی سایتا گیریب و یئنی بیر رمز سئچه‌سینیز. باشقا آدام بو ایستَگی وئرمیش‌سه، یوخسا سیز اسکی رمزینیزی یادا گتیرمیشسینیزسه، و داها اونو چئویرمک ایسته‌میرسینیزسه، بو مئساژی سایماییب و اسکی رمزینیزی ایشلدمگه داوام ائده بیلرسینیز.",
        "passwordreset-emailtext-user": "{{SITENAME}} سایتیندا، $1 ایستیفاده‌چی، سیزین اوردا ($4) حسابینیزین رمزینی یئنیله‌مک ایستگی وئریب‌دیر. آشاغیداکی {{PLURAL:$3|ایستیفاده‌چی|ایستیفاده‌چیلر}} بو ایمیل ایله ایلگیلیدیرلر:\n\n$2\n\nبو گئچیجی {{PLURAL:$3|رمز|رمزلر}}، {{PLURAL:$5|بیر|$5گون}} سونرا واختلاری قورتاراجاق‌دیر. \nسیز گرک ایندی گیریب و بیر یئنی رمز سئچه‌سینیز. باشقا آدام بو ایستَگی وئرمیش‌سه، یوخسا سیز اسکی رمزینیزی یادا گتیرمیشسینیزسه، و داها اونو چئویرمک ایسته‌میرسینیزسه، بو مئساژی سایماییب و اسکی رمزینیزی ایشلدمگه داوام ائده بیلرسینیز.",
        "passwordreset-emailelement": "ایشلدن آدی: \n$1\n\nگئچیجی رمز: \n$2",
-       "passwordreset-emailsentemail": "بÛ\8cر Ø±Ù\85ز Û\8cئÙ\86Û\8cÙ\84Ù\87â\80\8cÙ\85Ù\87 Ø§Û\8cÙ\85Û\8cÙ\84Û\8c Ú¯Ø¤Ù\86درÛ\8cÙ\84Û\8cبâ\80\8cدیر.",
+       "passwordreset-emailsentemail": "بÛ\87 Ø§Û\8cÙ\85ئÛ\8cÙ\84 Ø¢Ø¯Ø±Ø³Û\8c Ø­Ø³Ø§Ø¨Û\8cÙ\86Û\8cزا Ø«Ø¨Øª Ø§Ù\88Ù\92Ù\84Ù\88Ù\86Ù\85Ù\88شسا٬ Ø¨Û\8cر Ø±Ù\85ز Û\8cئÙ\86Û\8cÙ\84Ù\87â\80\8cÙ\85Ù\87 Ø§Û\8cÙ\85ئÛ\8cÙ\84Û\8c Ú¯Ø¤Ù\86درÛ\8cÙ\84Ù\87â\80\8cجکدیر.",
        "passwordreset-emailsent-capture": "آشاغیدا گؤستریلن کیمی بیر رمز یئنیله‌مه ایمیلی گؤندریلیب‌دیر.",
        "passwordreset-emailerror-capture": "آشاغیدا گؤستریلن کیمی بیر رمز یئنیله‌مه ایمیلی یارادیلیب‌دیر، اما {{GENDER:$2ایستیفاده‌چی}}‌یه گؤندرمگی باشاریلی اولمادی: $1",
        "changeemail": "ایمیل آدرسینی دَییشدیر یا سیل",
        "accmailtext": "[[User talk:$1|$1]] اوچون بیر راست‌گله رمز یارادیلیب و $2-ه گؤندریلدی.\n\nبو یئنی حسابین رمزی، گیرندن سونرا <em>[[Special:ChangePassword|رمز دَییشدیرمه]]</em> صحیفه‌سیندن دَییشیله بیلر.",
        "newarticle": "(یئنی)",
        "newarticletext": "مؤوجود اوْلمايان صحیفه‌‌يه اوْلان کئچیدی ایزله‌دینیز. \nآشاغیداکی ساحه‌‌يه مظمونونو يازاراق بۇ صحیفه‌‌نی '''سیز''' يارادا بیلرسینیز. (علاوه‌‌ معلومات اۆچون [$1 کؤمک صحیفه‌‌سینه] باخین). اگر بۇ صحیفه‌‌يه سهون گلمیسینیزسه ساده‌جه اوْلاراق براوزئرین '''گئری''' دۆيمه‌سینه وۇرون.",
-       "anontalkpagetext": "''بو صحیفه قئیدیات‌دان کئچممیش و یا داخیل اولمامیش آنونیم ایستیفادچییه عایید موذاکیره صحیفه‌سی‌دیر.\nاونا گؤره بو ایستیفادچینی رقم‌لردن عبارت ایپ اونوانی ایله معین ائتمک مجبوریتیندییک.\nبئله ایپ اونوان بیر نئچه فرد طرفین‌دن ایستیفاده‌ده اولا بیلر.\nاگر سیز آنونیم ایستیفادچیسینیزسه و بو مئساژین سیزه عایید اولمادیغینی دوشونورسونوزسه، اوندا  [[Special:UserLogin/signup|قئیدیات‌دان کئچین]] و یا [[Special:UserLogin|داخی اولون]].''",
+       "anontalkpagetext": "''بو صحیفه قئیدیات‌دان کئچممیش و یا داخیل اولمامیش آنونیم ایستیفادچییه عایید موذاکیره صحیفه‌سی‌دیر.\nاونا گؤره بو ایستیفادچینی رقم‌لردن عبارت ایپ اونوانی ایله معین ائتمک مجبوریتیندییک.\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}}}} سیلمک سیاهی]‌سینده باشقا بیلگیلر اولا بیلر.",
        "previewnote": "'''بونون ساده‌جه بیر سیناق گؤستریشی اولدوغونو نظرده آلین.'''\nسیزین دییشیکلرینیز هله قئید اولونماییب!",
        "continue-editing": "دَییشدیرمه یئرینه گئت",
        "previewconflict": "بو سیناق گؤستریشی‌دیر و یادداشدا ساخلایاجاغینیز تق‌دیرده متنین دییشدیر صحیفه‌سی‌نین یوخاری حیسه‌سینده نتیجه‌نین نئجه اولاجاغینی گؤستریر.",
-       "session_fail_preview": "'''اÙ\88زر Ø§Û\8cستÛ\8cÛ\8cرÛ\8cÚ©! Ø³Û\8cزÛ\8cÙ\86 Ø¯Û\8cÛ\8cشتÛ\8cرÛ\8cÙ\86Û\8cزâ\80\8c Ø³Ø§Ø®Ù\84اÙ\86Û\8cÙ\84Ù\85ادÛ\8c. Ù\84Ø·Ù\81اÙ\8b Ø¨Û\8cر Ø¯Ø§Ù\87ا ØªÚ©Ø±Ø§Ø± Ø§Ø¦Ø¯Û\8cÙ\86. Ù¾Ø±Ù\88بÙ\84ئÙ\85 Ø­Ù\84 Ø§Ù\88Ù\84Ù\88Ù\86Ù\85اسا Ø­Ø³Ø§Ø¨Û\8cÙ\86Û\8cزداÙ\86 [[Special:UserLogout|Ú\86Û\8cØ®Û\8cÙ\86]]  Ù\88 Û\8cئÙ\86Û\8câ\80\8cدÙ\86 Ø¯Ø§Ø®Û\8cÙ\84 Ø§Ù\88لون.'''",
+       "session_fail_preview": "'''عÛ\86ذر Ø§Û\8cستÙ\87â\80\8cÛ\8cÛ\8cرÛ\8cÚ©! Ø³Û\8cزÛ\8cÙ\86 Ø¯Û\8cÛ\8cشدÛ\8cرÙ\85Ù\87â\80\8cÙ\86Û\8cز Û\8cازدÛ\8cرÛ\8cÙ\84Ù\85ادÛ\8c. \nسÛ\8cستÙ\85دÙ\86 Ú\86Û\8cØ®Ù\85Û\8cØ´ Ø§Ù\88Ù\92Ù\84دÙ\88غÙ\88Ù\86Ù\88ز Ù\85ؤحتÙ\85Ù\84â\80\8cدÛ\8cر. Ù\84Û\86Ø·Ù\81اÙ\8b Ø³Û\8cستÙ\85Ù\87 Ú¯Û\8cرÙ\85Û\8cØ´ Ø§Ù\88Ù\92Ù\84دÙ\88غÙ\88Ù\86Ù\88زداÙ\86 Ø¢Ø±Ø®Ø§Û\8cÛ\8cÙ\86 Ø§Ù\88Ù\92Ù\84Ù\88ب Ø¨Û\8cر Ø¯Ø§Ù\87ا ØªÚ©Ø±Ø§Ø± Ø§Ø¦Ø¯Û\8cÙ\86. \nÙ\85Û\86Ø´Ú©Ù\88Ù\84 Ø­Ù\84 Ø§Ù\88Ù\92Ù\84Ù\88Ù\86Ù\85اسا Ø­Ø³Ø§Ø¨Û\8cÙ\86Û\8cزداÙ\86 [[Special:UserLogout|Ú\86Û\8cØ®Û\8cب]] Û\8cئÙ\86Û\8câ\80\8cدÙ\86 Ú¯Û\8cرÛ\8cØ´ Ø§Ø¦Ø¯Û\8cÙ\86Ù¬ Ø¨Ø±Ø§Ù\88زرÛ\8cÙ\86Û\8cزÛ\8cÙ\86 Ø¨Û\87 Ø³Ø§Û\8cتا Ú©Ù\88Ú©Û\8c Ù\88ئرÙ\85Ù\87 Ø§Û\8cجازÙ\87â\80\8cسÛ\8cÙ\86Û\8c Ù\88ئردÛ\8cÚ¯Û\8cÙ\86دÙ\86 Ø¯Ù\87 Ø¢Ø±Ø®Ø§Û\8cÛ\8cÙ\86 Ø§Ù\88Ù\92لون.'''",
        "session_fail_preview_html": "'''اوزر ایستییریک! داخیل وئری‌سی‌نین ایتمه‌سین‌دن گؤره تنظیملمه‌نیزی اعمال کئچیره بیلمیجیی.' '\n\n' چونکی {{SITENAME}} سایتیندا raw هتمل تأثیرین‌دیر،تا جاوااسکریپت هوجوم‌لارینا تدبیر اولا‌راق گیزلنمیش‌دیر.'\n\n' 'گر بو حاق‌لی بیر تنظیملمه گیریش میسئ، لطفاً یئنی‌دن جهد ائدین. اگر هله چالیشمازسا، [[Special:UserLogout|خارج شوید]] یئنی‌دن ایجلاس آچماغی یوخلایین.' '",
        "token_suffix_mismatch": "'' ' ديَیشیکلیگی‌نین گئری چئوریلدی، چونکی آلیجی‌نین تنزیمله‌مه کوتوجوغونداکی دورغو ایشاره‌لرینی پوزدو. \nديَیشیکلیگی‌نین، صحیفه‌‌ متنینده پوزولماغی اؤنله‌مک اوچون گئری چئوریلدی. \nاگر پروبلئملی بیر wئب-باسئد آنونیم پروکسی خیدمتی ایستیفاده بو حادثه‌‌ بضا رئاللاشا بیلر.'' '",
        "edit_form_incomplete": "'''دییشیک‌لیک فورماسی اوچون بعضی سئروئرلره ایشلمه‌دی؛ ائتدیگینیز دییشیک‌لیک‌لر بوزولمامیشتیر، نظردن کئچیریب یئنی‌دن سینایین.'",
        "uploadstash-summary": "بو صحیفه‌‌ يوکلنمیش(و يا يوکلنمکده دیر) آمما هله ویکیده نشر اولونمامیش فايللارا چاتماغی تعمین ائدر. بو فايللار يالنیز يوکله‌يه‌نی طرفیندن گؤروله بیلر.",
        "uploadstash-clear": "مووققتی فايللاری تمیزله",
        "uploadstash-nofiles": "سیزین هئچ آمبار اولموش فایلینیز یوخدور.",
-       "uploadstash-badtoken": "Ú\86اÙ\84Û\8cØ´Ù\85اÙ\86Û\8cÙ\86 Ø­Û\8cاتا Ú©Ø¦Ú\86Û\8cرÛ\8cÙ\84Ù\85Ù\87â\80\8cسÛ\8c Ø¨Ø§Ø´Ø§Ø±Û\8cسÛ\8cز Ø§Ù\88Ù\84دÙ\88Ø\8c Ø§Ø­ØªÛ\8cÙ\85اÙ\84Ù\84ا ØªÙ\86زیمله‌مه قايدالاری زامان آشیمینا معروض قالدی. يئنیدن چالیشین.",
+       "uploadstash-badtoken": "Ú\86اÙ\84Û\8cØ´Ù\85اÙ\86Û\8cÙ\86 Ø­Û\8cاتا Ú©Ø¦Ú\86Û\8cرÛ\8cÙ\84Ù\85Ù\87â\80\8cسÛ\8c Ø¨Ø§Ø´Ø§Ø±Û\8cسÛ\8cز Ø§Ù\88Ù\84دÙ\88Ø\8c Ø§Ø­ØªÛ\8cÙ\85اÙ\84Ù\84ا ØªÙ\86ظیمله‌مه قايدالاری زامان آشیمینا معروض قالدی. يئنیدن چالیشین.",
        "uploadstash-errclear": "فايللارین سیلینمه‌سی باشاریسیز اولدو.",
        "uploadstash-refresh": "فايل سیياهیسینی يئنیله",
        "invalid-chunk-offset": "اعتبارسیز یئربه‌یئر",
        "whatlinkshere-prev": "{{PLURAL:$1|قاباقکی|قاباقکی $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|سونراکی|سونراکی $1}}",
        "whatlinkshere-links": "← باغلانتیلار",
-       "whatlinkshere-hideredirs": "یول‌لاندیرمالاری $1",
+       "whatlinkshere-hideredirs": "یول‌لاندیرمالاری گیزلت",
        "whatlinkshere-hidetrans": "علاوه‌لری $1",
        "whatlinkshere-hidelinks": "باغلانتیلاری $1",
        "whatlinkshere-hideimages": "فایل باغلانتیلارینی $1",
        "unblock": "ایستیفاده‌چی‌نین باغلانماسین گؤتور",
        "blockip": " {{GENDER:$1|ایشلدن}}ی باغلا",
        "blockip-legend": "ایستیفادچی نی باغلا",
-       "blockiptext": "آشاغی‌داکی فورمو ایستیفاده ائده‌رک مویین بیر ایپنین و یا قئیدیات‌دان کئچمیش ایستیفاده‌چی‌نین دییشیک‌لیک ائتمه‌سینی مانعه تؤره‌ده بیلرسینیز. بو یالنیز واندالیزمی قارشی‌سینی آلماق اوچون و [[{{MediaWiki:Policy-url}}|قایدا‌لارا]] اویغون اولا‌راق ائدیلمه‌لی. آشاغییا موتلق قاداغا ایله علاقه‌دار بیر شرح یازین. (نومونه:-بو-صحیفه‌لرده واندالیزم ائتمیش‌دیر).",
+       "blockiptext": "آشاغی‌داکی فورمو ایستیفاده ائده‌رک مۆعیّن بیر آی‌پی‌نین و یا قئیدیات‌دان کئچمیش ایستیفاده‌چی‌نین دییشیک‌لیک ائتمه‌سینی مانعه تؤره‌ده بیلرسینیز. بۇ یالنیز واندالیزمین قارشی‌سینی آلماق اۆچون و [[{{MediaWiki:Policy-url}}|قایدا‌لارا]] اۇیغون اوْلا‌راق ائدیلمه‌لی. آشاغی‌یا مۆطلق قاداغا ایله علاقه‌دار بیر شرح یازین. (اؤرنک:-بۇ-صفحه‌لرده واندالیزم ائتمیشدیر).",
        "ipaddressorusername": "آی-پی عونوانی و یا ایستیفاده‌چی آدی",
        "ipbexpiry": "بیتمه مدتی:",
        "ipbreason": "ندن:",
        "unblock-hideuser": "ایستیفاده‌چی آدی گیزلی اولدوغو اوچون، بی باغلامانی گؤتوره بیلمزسینیز.",
        "ipb_cant_unblock": "ختا: باغلاما آی دی سی $1 تاپیلمادی. باغلامانین گؤتورولمه‌سی مومکون‌دور.",
        "ipb_blocked_as_range": "خطا: $1 ای پی عنوانی بیرباشا مانعه و مانعه تؤرتمه آزالدیلماسینا یول وئریلمیر.\nآنجاق، بو عنوان $2 آرا‌لیغینین پارچاسی اولا‌راق مانعه تؤردیلمیش، دئکابر مانعه تؤرتمه‌سینی قال‌دیرا.",
-       "ip_range_invalid": "یانلیش ای پی",
+       "ip_range_invalid": "گئچرسیز آی‌پی آرالیغی.",
        "ip_range_toolarge": "/ $1 بلوک‌دان داها بؤیوک بازه باغلانمالارینا ایجازه وئریلمیر.",
        "proxyblocker": "پروکسی باغلییان",
        "proxyblockreason": "ای پی آدرئسینیز آچیق بیر پروکسی اولدوغو اوچون مانعه تؤردیلدی.\nخاهیش ائدیریک اینتئرنئت سئویش تعمین ایله یا دا تئکنیکی دستک ایله علاقه قورون و بو جدی تهلوکه‌سیزلیک پروبلئمین‌دن خبردار ائدین.",
        "tooltip-pt-preferences": "{{GENDER:|سیزین}} ترجیحلرینیز",
        "tooltip-pt-watchlist": "دییشمکلرینی ایزله‌دیگینیز صفحه‌لرین سیاهی‌سی",
        "tooltip-pt-mycontris": "{{GENDER:|سیزین}} چالیشمالارینیزین لیستی",
-       "tooltip-pt-login": "گیریش ائتمه‌یینیز توصیه اولونور؛ اما گرکلی دئییل",
+       "tooltip-pt-login": "گیریش ائتمه‌نیز تؤوصیه اوْلونور؛ آنجاق گرکلی دئییلدیر",
        "tooltip-pt-logout": "چیخیش",
-       "tooltip-pt-createaccount": "سیزدن دعوت اولونور ایشلدن حسابی آچیب و گیریش ائده‌سیز؛ آنجاق حساب یاراتماق ایستگه باغلی‌دیر",
+       "tooltip-pt-createaccount": "سیزی ایشلدن حسابی آچیب گیریش ائتمه‌یه چاغیریریق؛ آنجاق حساب یاراتماق ایستگینیزه باغلی‌دیر",
        "tooltip-ca-talk": "ایچینده‌کیلره گؤره دانیشیق",
        "tooltip-ca-edit": "بۇ صفحه‌‌نی دَییشدیر",
        "tooltip-ca-addsection": "یئنی بؤلوم یارات",
        "tooltip-ca-unwatch": "بو صفحه‌نی ایزله‌دیگینیز صفحه‌لردن قالدیرین",
        "tooltip-search": "{{SITENAME}}-دا آختار",
        "tooltip-search-go": "اولورسا بو آددا بیر صفحه‌یه گئت",
-       "tooltip-search-fulltext": "بو یازی اولان صفحه‌لری آختار",
+       "tooltip-search-fulltext": "بو یازینی صفحه‌لرده آختار",
        "tooltip-p-logo": "آنا صفحه‌یه باخ",
        "tooltip-n-mainpage": "آنا صفحه‌‌یه باخین",
        "tooltip-n-mainpage-description": "آنا صفحه‌یه باخ",
-       "tooltip-n-portal": "پروژه‌ یه گؤره، سیز نه ایش گوره بیلرسیز، هاردا نه‌لری تاپا بیلرسیز",
-       "tooltip-n-currentevents": "اÛ\8cÙ\86دÛ\8cÚ©Û\8c Ø§Ù\88Ù\84اÛ\8câ\80\8cÙ\84ارا Ø§Û\8cÙ\84Ú¯Û\8cÙ\84Û\8c Ø¨Û\8cÙ\84Ú¯Û\8câ\80\8cÙ\84ر ØªØ§Ù¾",
-       "tooltip-n-recentchanges": "بÙ\88 Ù\88Û\8cÚ©Û\8câ\80\8cدÙ\87 Ø³Ù\88Ù\86 Ø¯Ù\8eÛ\8cÛ\8cØ´Û\8cÚ©لرین لیستی",
+       "tooltip-n-portal": "پروژه‌‌یه گؤره، سیز نه ایش گؤره بیلرسینیز، هاردا نه‌لری تاپا بیلرسینیز",
+       "tooltip-n-currentevents": "اÛ\8cÙ\86دÛ\8cÚ©Û\8c Ø§Ù\88Ù\92Ù\84اÛ\8cÙ\84ارا Ø§Û\8cÙ\84Ú¯Û\8cÙ\84Û\8c Ø¨Û\8cÙ\84Ú¯Û\8cÙ\84ر ØªØ§Ù¾",
+       "tooltip-n-recentchanges": "بÙ\88 Ù\88Û\8cÚ©Û\8câ\80\8cدÙ\87 Ø³Ù\88Ù\92Ù\86 Ø¯Ù\8eÛ\8cÛ\8cØ´Û\8cÚ©â\80\8cÙ\84Û\8cÚ©â\80\8cلرین لیستی",
        "tooltip-n-randompage": "بیر تصادوفی صفحه گتیر",
        "tooltip-n-help": "آنلاماق یئری",
-       "tooltip-t-whatlinkshere": "بورایا باغلانان بوتون ویکی صفحه‌لرین لیستی",
-       "tooltip-t-recentchangeslinked": "بو صفحه‌دن باغلانان صفحه‌لرین سون دَییشیکلیک‌لری",
+       "tooltip-t-whatlinkshere": "بۇرا باغلانان بۆتون ویکی صفحه‌لرین لیستی",
+       "tooltip-t-recentchangeslinked": "بۇ صفحه اوْنلارا باغلانان صفحه‌لرین سوْن دَییشیکلیک‌لری",
        "tooltip-feed-rss": "بو صحیفه‌‌ اوچون آراس‌اس يايیمی",
        "tooltip-feed-atom": "بو صحیفه‌‌ اوچون آتوم يايیمی",
        "tooltip-t-contributions": "{{GENDER:$1|بۇ ایشلدنین}} وئردیگی دییشیکلر لیستی",
        "logentry-newusers-byemail": "$3 ایستیفاده‌چی حسابی، $1 ایله {{GENDER:$2|یارادیلیب}} و رمز، ایمیل ایله گؤندریلیب‌دیر",
        "logentry-newusers-autocreate": "$1 ایشلدن حسابی اوْتوماتیک {{GENDER:$2|یارادیلدی}}",
        "logentry-protect-protect": "$1 $3-ی/و  {{GENDER:$2|قوْرودو}} $4",
-       "logentry-rights-rights": "$1، $3-ین قروپ عوضولوگونو $4-دن $5-ه {{GENDER:$2|دَییشدیردی}}",
+       "logentry-rights-rights": "$1، $3-ین قروپ عۆضولوگونو $4-دن $5-ه {{GENDER:$2|دَییشدیردی}}",
        "logentry-rights-rights-legacy": "$1، $3-ین قروپ عوضولوگونو {{GENDER:$2|دَییشدیردی}}",
        "logentry-rights-autopromote": "$1-ین مقامی اوتوماتیک $4-دن $5-ه {{GENDER:$2|آرتیریلدی}}",
        "logentry-upload-upload": "$1 $3 را {{GENDER:$2|یوکلندیردی}}",
index e96e941..88e60dd 100644 (file)
        "noname": "Ғәмәлдә булған ҡатнашыусы исемен керетмәнегеҙ.",
        "loginsuccesstitle": "Танышыу уңышлы үтте",
        "loginsuccess": "Хәҙер һеҙ $1 исеме менән эшләйһегеҙ.",
-       "nosuchuser": "$1 исемле ҡулланыусы юҡ.\nҠулланыусы исеме хәреф регистрына һиҙгер.\nИсемде тикшерегеҙ йәки [[Special:UserLogin/signup|яңы иҫәп яҙыуы асығыҙ]].",
+       "nosuchuser": "$1 исемле ҡулланыусы юҡ.\nҠулланыусы исеме хәреф регистрына һиҙгер.\nИсемде тикшерегеҙ йәки [[Special:CreateAccount|яңы иҫәп яҙыуы асығыҙ]].",
        "nosuchusershort": "$1 исемле ҡулланыусы юҡ. Исемде тикшерегеҙ.",
        "nouserspecified": "Һеҙ ҡатнашыусы исемен күрһәтергә тейеш.",
        "login-userblocked": "Был ҡатнашыусыға рөхсәт юҡ.  Исеме тыйылған.",
        "accmailtext": "[[User talk:$1|$1]] өсөн осраҡлы яһалған серһүҙ $2 адресына ебәрелде.\n\nТанылғандан һуң был иҫәп яҙмаһы өсөн серһүҙҙе ''[[Special:ChangePassword|серһүҙҙе үҙгәртеү өсөн махсус биттә үҙгәртә алаһығыҙ]]''.",
        "newarticle": "(Яңы)",
        "newarticletext": "Һеҙ һылтанма буйынса әлегә яһалмаған биткә күстегеҙ.\nЯңы бит яһар өсөн аҫтағы тәҙрәгә текст керетегеҙ (тулыраҡ мәғлүмәт өсөн [$1 ярҙам битен] ҡарағыҙ).\nӘгәр был биткә яңылыш килеп эләккән булһағыҙ, браузерығыҙҙың '''артҡа''' төймәһенә баҫығыҙ.",
-       "anontalkpagetext": "----\n<em>Был фекер алышыу бите, иҫәп яҙыуы булдырмаған йәки уны ҡулланмаған аноним ҡатнашыусының бите.</em>\nШуның өсөн ҡулланыусыны таныу өсөн IP-адресы ҡулланыла.\nӘгәр һеҙ аноним ҡулланыусы булһағыҙ һәм һеҙгә ебәрелмәгән хәбәрҙәр алдым тиһәгеҙ (бер IP-адрес күп ҡулланыусы өсөн булырға мөмкин) һәм башҡа бындай аңлашылмаусанлыҡтар килеп сыҡмаһын өсөн, зинар, [[Special:UserLogin|системаға керегеҙ]] йәки [[Special:UserLogin/signup|теркәлегеҙ]].",
+       "anontalkpagetext": "----\n<em>Был фекер алышыу бите, иҫәп яҙыуы булдырмаған йәки уны ҡулланмаған аноним ҡатнашыусының бите.</em>\nШуның өсөн ҡулланыусыны таныу өсөн IP-адресы ҡулланыла.\nӘгәр һеҙ аноним ҡулланыусы булһағыҙ һәм һеҙгә ебәрелмәгән хәбәрҙәр алдым тиһәгеҙ (бер IP-адрес күп ҡулланыусы өсөн булырға мөмкин) һәм башҡа бындай аңлашылмаусанлыҡтар килеп сыҡмаһын өсөн, зинар, [[Special:UserLogin|системаға керегеҙ]] йәки [[Special:CreateAccount|теркәлегеҙ]].",
        "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}}}} журналдағы яҙмаларҙы] эҙләй алаһығыҙ, тик һеҙҙең бит яһау хоҡуғығыҙ юҡ.</span>",
        "missing-revision": "\"{{FULLPAGENAME}}\" исемле биттең $1 номерлы өлгөһө юҡ.\n\nБыл хәл, ғәҙәттә, юйылған биткә яһалған һылтанманын ваҡыты үтеүенән барлыҡҡа килә.\nТулыраҡ мәғлүмәт өсөн ҡарағыҙ: [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} юйыу яҙмалары].",
        "parser-unstrip-loop-warning": "Ябылмаған pre табылды",
        "parser-unstrip-recursion-limit": "($1) рекурсия сиге үтеп кителгән",
        "converter-manual-rule-error": "Тел әйлендереү ҡағиҙәһендә хата табылды",
-       "undo-success": "Был үҙгәртеүҙе кире алып була. Зинһар, улар һеҙҙе ҡыҙыҡһындырған үҙгәртеүҙәр булыуынан шикләнмәҫ өсөн версияларҙы сағыштырыуҙы ҡарағыҙ һәм үҙгәртеүҙәрҙе ғәмәлғә керетер өсөн «Битте һаҡларға» төймәһенә баҫығыҙ.",
+       "undo-success": "Был үҙгәртеүҙе кире алып була. Үҙгәртеүҙәр нәҡ һеҙ теләгәнсә булһа, версияларҙы сағыштырып ҡарағыҙ, ғәмәлгә индерер өсөн, «Битте һаҡларға» төймәһенә баҫығыҙ.",
        "undo-failure": "Ара үҙгәртеүҙәр тура килмәү сәбәпле төҙәтеүҙе кире алып булмай.",
        "undo-norev": "Үҙгәртеүҙе кире алып булмай, сөнки юҡ йәки юйылған.",
        "undo-nochange": "Төҙәтеү кире ҡайтарылған.",
        "grant-blockusers": "Иҫәп яҙмаларын блоклау һәм блоклауҙы асыу",
        "grant-createaccount": "Иҫәп яҙмаһын булдырырға",
        "grant-createeditmovepage": "Биттәрҙе булдырыу,мөхәррирләү һәм исемен үҙгәртеү",
-       "grant-delete": "Журналдағы биттәрҙе юйыу, төҙәтеү",
+       "grant-delete": "Журналдағы биттәрҙе, яҙмаларҙы юйыу һәм төҙәтеү",
        "grant-editinterface": "MediaWiki исеме арауығында төҙәтеү һәм ҡулланыусы CSS/JavaScript",
        "grant-editmycssjs": "CSS/JavaScript ҡулланыусы кодын төҙәтеү",
        "grant-editmyoptions": "Һеҙҙең ҡулланыусы көйләүҙәрен мөхәррирләү",
        "upload-form-label-infoform-description-tooltip": "Был әҫәр тураһында ҡыҫҡаса тасуирлама бирегеҙ. Фото өсөн - фотола нимә иң мөһиме, төшөрөлгән урынды яҙығыҙ.",
        "upload-form-label-usage-title": "Ҡулланыу",
        "upload-form-label-usage-filename": "Файл исеме",
-       "foreign-structured-upload-form-label-own-work": "Был минең эш",
-       "foreign-structured-upload-form-label-infoform-categories": "Категориялар",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
-       "foreign-structured-upload-form-label-own-work-message-local": "Тейәлгән файл  {{SITENAME}} лицензия сәйәсәтенә ярашлы икәнен раҫлайым.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "{{SITENAME}} ҡағиҙәләренә ярашлы файлды тейәй алмаһағыҙ, диалог теҙерәһен ябығыҙ ҙа тейәү !с!н башҡа ысулды һайлағыҙ.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Ошонда эшләп ҡарағыҙ[[Special:Upload|килешеү буйынса тейәү бите]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Был файлды дөйөм репозиторийға күсереүемде аңлайым. Быны ҡулланыусы килешеүе һәм лицензия сәйәсәтенә ярашлы эшләүемде раҫлайым.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "{{SITENAME}} ҡағиҙәләренә ярашлы файлды тейәй алмаһағыҙ, диалог теҙерәһен ябығыҙ ҙа тейәү өсөн башҡа ысулды һайлағыҙ.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "{{SITENAME}} талаптарына ярашлы файлы тейәп булһа,  [[Special:Upload|тейәү битен]] ҡарағыҙ.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Мин ошо файлдың авторы икәнемде раҫлайым һәм был файлды   Викискладта [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] лицензияһы аҫтынан кире алмаҫҡа урынлаштырырға ризалы! бирәм, шулай уҡ  [https://wikimediafoundation.org/wiki/ҡулланыу шарттары] менән килешәм.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Әгәр ошо файлдың авторы түгелһегеҙ һәм уны икенсе лицензия аҫтында сығарырға теләйһегеҙ икән,  [https://commons.wikimedia.org/wiki/Special:UploadWizard Викискладҡа күсереү оҫталары] мөмкинлеген файҙаланығыҙ.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "{{SITENAME}} талаптарына ярашлы файлы тейәп булһа,  [[Special:Upload|тейәү битен]] ҡарағыҙ.",
+       "upload-form-label-own-work": "Был минең эш",
+       "upload-form-label-infoform-categories": "Категориялар",
+       "upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work-message-generic-local": "Тейәлгән файл  {{SITENAME}} лицензия сәйәсәтенә ярашлы икәнен раҫлайым.",
+       "upload-form-label-not-own-work-message-generic-local": "{{SITENAME}} ҡағиҙәләренә ярашлы файлды тейәй алмаһағыҙ, диалог теҙерәһен ябығыҙ ҙа тейәү !с!н башҡа ысулды һайлағыҙ.",
+       "upload-form-label-not-own-work-local-generic-local": "Ошонда эшләп ҡарағыҙ[[Special:Upload|килешеү буйынса тейәү бите]].",
+       "upload-form-label-own-work-message-generic-foreign": "Был файлды дөйөм репозиторийға күсереүемде аңлайым. Быны ҡулланыусы килешеүе һәм лицензия сәйәсәтенә ярашлы эшләүемде раҫлайым.",
+       "upload-form-label-not-own-work-message-generic-foreign": "{{SITENAME}} ҡағиҙәләренә ярашлы файлды тейәй алмаһағыҙ, диалог теҙерәһен ябығыҙ ҙа тейәү өсөн башҡа ысулды һайлағыҙ.",
+       "upload-form-label-not-own-work-local-generic-foreign": "{{SITENAME}} талаптарына ярашлы файлы тейәп булһа,  [[Special:Upload|тейәү битен]] ҡарағыҙ.",
        "backend-fail-stream": "$1 файлын трансляциялап булмай.",
        "backend-fail-backup": "$1 файлының резерв күсермәһен эшләп булмай.",
        "backend-fail-notexists": "$1 файлы юҡ.",
        "pageinfo-not-current": "Ғәфү итегеҙ, был мәғлүмәтте иҫке версиялар өсөн күрһәтеп булмай.",
        "pageinfo-header-basic": "Төп мәғлүмәт",
        "pageinfo-header-edits": "Үҙгәртеүҙәр тарихы",
-       "pageinfo-header-restrictions": "Бите һаҡлау",
+       "pageinfo-header-restrictions": "Битте һаҡлау",
        "pageinfo-header-properties": "Биттең үҙенсәлектәре",
        "pageinfo-display-title": "Күренгән исем",
        "pageinfo-default-sort": "Ғәҙәттәге сортлау асҡысы",
        "metadata-help": "Файл, ғәҙәттә һанлы камералар йәки сканерҙар өҫтәгән мәғлүмәттәргә эйә. Әгәр файл яһалғандан һуң төҙәтелгән булһа, ҡайһы бер параметрҙар ағымдағы рәсем менән тап килмәҫкә мөмкин.",
        "metadata-expand": "Өҫтәмә мәғлүмәттәрҙе күрһәт",
        "metadata-collapse": "Өҫтәмә мәғлүмәттәрҙе йәшер",
-       "metadata-fields": "Был исемлектә һанап кителгән мета мәғлүмәт юлдары рәсем битендә күрһәтеләсәктәр, ҡалғандары иһә төрөлгән буласаҡ.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-fields": "Был исемлектә һанап кителгән метамәғлүмәт юлдары рәсем битендә күрһәтеләсәк, ҡалғандары төрөләсәк.\n* 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": "Төҫтәрҙең тәрәнлеге",
        "log-action-filter-protect-modify": "Яңынан тейәү",
        "log-action-filter-protect-unprotect": "Һаҡты алып ташлау",
        "log-action-filter-upload-upload": "Яңы күсереү",
-       "log-action-filter-upload-overwrite": "Ҡабаттан тейәү"
+       "log-action-filter-upload-overwrite": "Ҡабаттан тейәү",
+       "authmanager-email-label": "Электрон почта адресы",
+       "authmanager-realname-label": "Ысын исемегеҙ",
+       "changecredentials-submit": "Үҙгәртергә",
+       "removecredentials-submit-cancel": "Кире алырға"
 }
index d228878..06a7cdb 100644 (file)
        "noname": "شما یک معتبرین نام کاربر مشخص نه کتت.",
        "loginsuccesstitle": "ورود موفقیت آمیز",
        "loginsuccess": "''''شما الان وارد {{SITENAME}} په عنوان \"$1\".'''",
-       "nosuchuser": "هچ کاربری گون نام \"$1\" نیستن.\nکاربری نام حرفش په هور و مزنی حساس انت.\nوتی املايا چک کنیت یا [[Special:UserLogin/signup|نوکین حسابی شرکنیت]].",
+       "nosuchuser": "هچ کاربری گون نام \"$1\" نیستن.\nکاربری نام حرفش په هور و مزنی حساس انت.\nوتی املايا چک کنیت یا [[Special:CreateAccount|نوکین حسابی شرکنیت]].",
        "nosuchusershort": "هچ کاربری گون نام  \"$1\"نیستن.\nوتی املايا کنترل کنیت",
        "nouserspecified": "شما باید یک نام کاربری مشخص کنیت.",
        "login-userblocked": "ائ کابر بلاک بیتگ. لاگین مان سیستمء اجازت نه انت.",
        "accmailtext": "یک پسوردء [[User talk:$1|$1]] پر $2 راهیگ بوت. بیت آئرا چه پیجء ''[[Special:ChangePassword|پسوردء ٹگل]]'' که لاگینء درگتء پیش دارگ بیت ٹگل دئیت.",
        "newarticle": "(نوکین)",
        "newarticletext": "شما رند چه یک لینکی په یک صفحه ی که هنو نیستند اتکگیت.\nپه شر کتن صفحه، شروع کن نوشتن ته جعبه جهلی(بچار  [$1 صفحه کمک]  په گیشترین اطلاعات).\nاگر شما اشتباهی ادانیت ته وتی بروزر دکمه ''Back'' بجن.",
-       "anontalkpagetext": "----'' ای صفحه بحث انت په یک ناشناس کاربری که هنگت یک حسابی شر نه کتت یا آی ا ستفاده نه کتت. اچه ما بایدن آدرس آی پی عددی په پچاه آرگ آیی استفاده کنین.\nچوشن آدرس آی پی گون چندین کاربر استفاده بیت.\nاگه شما یک کاربر ناشناس ایت وی حس کنیت بی ربطین نظر مربوط شمی هست، لطفا [[Special:UserLogin|وارد بیت ]] یا [[Special:UserLogin/signup|حسابی شرکن]] دان چه هور بییگ گون ناسناسین کاربران پرهیز بیت.''",
+       "anontalkpagetext": "----'' ای صفحه بحث انت په یک ناشناس کاربری که هنگت یک حسابی شر نه کتت یا آی ا ستفاده نه کتت. اچه ما بایدن آدرس آی پی عددی په پچاه آرگ آیی استفاده کنین.\nچوشن آدرس آی پی گون چندین کاربر استفاده بیت.\nاگه شما یک کاربر ناشناس ایت وی حس کنیت بی ربطین نظر مربوط شمی هست، لطفا [[Special:UserLogin|وارد بیت ]] یا [[Special:CreateAccount|حسابی شرکن]] دان چه هور بییگ گون ناسناسین کاربران پرهیز بیت.''",
        "noarticletext": "هنو هچ متنی ته ای صفحه نیست.\nشما تونیت [[Special:Search/{{PAGENAME}}|گردیت په عنوان صفحه]]  ته دگه صفحات یا<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}}}} مان یکپیمیگین سیاهگان شوهاز بکن ات]</span> بلئ شما رء پر ائ پیجء اڈ کتنء اجازت نه انت.",
        "missing-revision": "ویرایش #$1 چه پیجء «{{FULLPAGENAME}}» موجود نه انت.\n\nبلکین چه نوک نکتنء بابتء هزپ بیتگ انت.\nتوان ات گیشترین جزئیات رء مان [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} هزپانی سیاهگ] ودی بکن ات.",
index a9a1d75..16a3c7a 100644 (file)
        "noname": "Ika dae tabi nakapagkaag nin sarong balidong pangaran nin paragamit.",
        "loginsuccesstitle": "Matrayumpo an saimong paglaog",
        "loginsuccess": "'''Ika ngunyan nakalaog na sa {{SITENAME}} bilang si \"$1\".'''",
-       "nosuchuser": "Dae pang paragamit na ginagamit an pangaran na \"$1\".\nAn mga ngaran nin paragamit sensitibo gayo sa tipahan.\nPakireparo kan saimong espeling, o [[Special:UserLogin/signup|Magmukna nin bagong panindog]].",
+       "nosuchuser": "Dae pang paragamit na ginagamit an pangaran na \"$1\".\nAn mga ngaran nin paragamit sensitibo gayo sa tipahan.\nPakireparo kan saimong espeling, o [[Special:CreateAccount|Magmukna nin bagong panindog]].",
        "nosuchusershort": "Mayo po tabing paragamit na an pangaran \"$1\".\nPaki-tsek an saimong espeling.",
        "nouserspecified": "Kaipuhan mong magkaag nin sarong pangaran nin paragamit.",
        "login-userblocked": "An paragamit na ini pinagkubkob. An paglaog dae pinagtutugutan.",
        "accmailtext": "An purak na pinagpuyos na pasa-taramon para ki [[User talk:$1|$1]] ipinagpadara na sa $2. Ini mapupuwedeng pagribayan sa ''[[Special:ChangePassword|change password]]'' na pahina matapos na ika nakalaog na.",
        "newarticle": "(Bàgo)",
        "newarticletext": "Ika nakapagsunod sa sarong sugpon pasiring sa sarong pahina na bako pang eksistido. Tanganing makapagmukna nin pahina, magpoon sa pagpindot sa laog nin kahon sa ibaba (hilngon an [$1 pahina nin katabangan] para sa kadugangan na impormasyon).\nKun ika napasalang nakadigde, i-klik an  '''ibalik''' na pindutan kan saimong kilyawan.",
-       "anontalkpagetext": "----''Ini iyo an pahina kan orolayan para an sarong dae bistadong paragamit na dae pa nakapagmukna nin panindog, o dae pa nakapaggamit kaini.\nKaya kami kaipong gumamit nin numerikal na IP address sa pagbisto saiya.\nAn arog kaining IP address puwedeng maikapagheras sa nagkapirang mga paragamit.\nKun ika sarong dae pa bistadong paragamit asin mati mo na igwang irelebanteng sambit na pinanungod saimo, tabi paki [[Special:UserLogin/signup|mukna nin panindog]] or [[Special:UserLogin|maglaog ka]] tanganing malikayan an pagkaribong sa pag-iriba kan iba pang mga paragamit.''",
+       "anontalkpagetext": "----''Ini iyo an pahina kan orolayan para an sarong dae bistadong paragamit na dae pa nakapagmukna nin panindog, o dae pa nakapaggamit kaini.\nKaya kami kaipong gumamit nin numerikal na IP address sa pagbisto saiya.\nAn arog kaining IP address puwedeng maikapagheras sa nagkapirang mga paragamit.\nKun ika sarong dae pa bistadong paragamit asin mati mo na igwang irelebanteng sambit na pinanungod saimo, tabi paki [[Special:CreateAccount|mukna nin panindog]] or [[Special:UserLogin|maglaog ka]] tanganing malikayan an pagkaribong sa pag-iriba kan iba pang mga paragamit.''",
        "noarticletext": "Mayo tabi sa presente nin teksto sa pahinang ini.\nIka mapuwedeng [[Special:Search/{{PAGENAME}}|maghanap para sa titulo kan pahinang ini]] sa iba pang mga pahina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa magkasurundong mga talaan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} liwaton ining pahina]</span>.",
        "noarticletext-nopermission": "Mayong sa presente nin teksto an pahinang ini.\nIka mapuwedeng [[Special:Search/{{PAGENAME}}|hanapa para kaining titulo kan pahina]] sa iba pang mga pahina,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa magkasurundong mga talaan]</span>.",
        "missing-revision": "An rebisyon #$1 kan pahina pinagngaranan na \"{{FULLPAGENAME}}\" bakong eksistido.\n\nIni pirmihan na pinagkakausa sa paagi nin pagsusunod nin luwas na petsang historiya nin kasugpunan pasiring sa sarong pahinang pinagpura na.\nAn mga detalye matatagboan sa [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} pinagpura na talaan].",
index b9da754..a50f7d8 100644 (file)
@@ -58,7 +58,7 @@
        "tog-ccmeonemails": "Адпраўляць мне копіі лістоў, якія я дасылаю іншым удзельнікам",
        "tog-diffonly": "Не паказваць зьмест старонкі пад параўнаньнем зьменаў",
        "tog-showhiddencats": "Паказваць схаваныя катэгорыі",
-       "tog-norollbackdiff": "Не паказваць зьмены пасьля выкарыстаньня функцыі адкату",
+       "tog-norollbackdiff": "Не паказваць зьмены пасьля выкананьня адкату",
        "tog-useeditwarning": "Папярэджваць мяне, калі я буду пакідаць старонку рэдагаваньня зь незахаванымі зьменамі",
        "tog-prefershttps": "Заўсёды карыстацца бясьпечным злучэньнем па ўваходзе ў сыстэму",
        "underline-always": "Заўсёды",
        "userlogin-resetpassword-link": "Забылі пароль?",
        "userlogin-helplink2": "Дапамога з уваходам у сыстэму",
        "userlogin-loggedin": "Вы ўжо ўвайшлі як {{GENDER:$1|$1}}.\nДля ўваходу пад іншым удзельнікам скарыстайцеся формай унізе.",
+       "userlogin-reauth": "Вы мусіце ўвайсьці яшчэ раз, каб пацьвердзіць, што вы — гэта {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Стварыць іншы рахунак",
        "createacct-emailrequired": "Адрас электроннай пошты",
        "createacct-emailoptional": "Адрас электроннай пошты (неабавязкова)",
        "createacct-reason-ph": "Зь якой мэтай вы ствараеце іншы рахунак",
        "createacct-submit": "Стварыць рахунак",
        "createacct-another-submit": "Стварыць рахунак",
+       "createacct-continue-submit": "Працягнуць стварэньне рахунку",
        "createacct-benefit-heading": "{{SITENAME}} створаная людзьмі, такімі як вы.",
        "createacct-benefit-body1": "{{PLURAL:$1|праўка|праўкі|правак}}",
        "createacct-benefit-body2": "{{PLURAL:$1|старонка|старонкі|старонак}}",
        "noname": "Вы пазначылі няслушнае імя ўдзельніка.",
        "loginsuccesstitle": "Увайшлі ў сыстэму",
        "loginsuccess": "<strong>Цяпер Вы ўвайшлі ў {{GRAMMAR:вінавальны|{{SITENAME}}}} як «$1».</strong>",
-       "nosuchuser": "Удзельніка «$1» не існуе.\nВялікія і малыя літары адрозьніваюцца ў імёнах удзельнікаў.\nПраверце напісаньне альбо [[Special:UserLogin/signup|стварыце новы рахунак]].",
+       "nosuchuser": "Удзельніка «$1» не існуе.\nВялікія і малыя літары адрозьніваюцца ў імёнах удзельнікаў.\nПраверце напісаньне альбо [[Special:CreateAccount|стварыце новы рахунак]].",
        "nosuchusershort": "Удзельніка зь іменем «$1» не існуе. Праверце напісаньне.",
        "nouserspecified": "Вы мусіце пазначыць імя ўдзельніка.",
        "login-userblocked": "{{GENDER:$1|Гэты ўдзельнік заблякаваны|Гэтая ўдзельніца заблякаваная}}. Уваход у сыстэму забаронены.",
        "mailmypassword": "Скінуць пароль",
        "passwordremindertitle": "Новы часовы пароль для {{GRAMMAR:родны|{{SITENAME}}}}",
        "passwordremindertext": "Нехта (магчыма Вы, з IP-адрасу $1) запытаў нас даслаць новы пароль для {{GRAMMAR:родны|{{SITENAME}}}} ($4). Для ўдзельніка «$2» быў створаны часовы пароль і ён цяпер «$3». Калі гэта была Вашая ініцыятыва, Вам трэба ўвайсьці ў сыстэму і адразу зьмяніць пароль. Тэрмін дзеяньня Вашага часовага паролю — $5 {{PLURAL:$5|дзень|дні|дзён}}.\n\nКалі гэты запыт адправіў нехта іншы, альбо Вы ўзгадалі свой пароль і ўжо не жадаеце яго зьмяніць, Вы можаце праігнараваць гэты ліст і працягваць карыстацца старым паролем.",
-       "noemail": "{{GENDER:$1|УдзелÑ\8cнÑ\96к Â«$1» Ð½Ðµ Ð¿Ð°Ð·Ð½Ð°Ñ\87Ñ\8bÑ\9e|УдзелÑ\8cнÑ\96Ñ\86а Â«$1» Ð½Ðµ Ð¿Ð°Ð·Ð½Ð°Ñ\87Ñ\8bла}} Ð½Ñ\96Ñ\8fкага Ð°Ð´Ñ\80аÑ\81Ñ\83 электроннай пошты.",
-       "noemailcreate": "Вы павінны пазначыць слушны адрас электроннай пошты",
+       "noemail": "{{GENDER:$1|УдзелÑ\8cнÑ\96к Â«$1» Ð½Ðµ Ð¿Ð°Ð·Ð½Ð°Ñ\87Ñ\8bÑ\9e|УдзелÑ\8cнÑ\96Ñ\86а Â«$1» Ð½Ðµ Ð¿Ð°Ð·Ð½Ð°Ñ\87Ñ\8bла}} Ð°Ð´Ñ\80аÑ\81 электроннай пошты.",
+       "noemailcreate": "Вы павінныя пазначыць слушны адрас электроннай пошты.",
        "passwordsent": "Новы пароль быў дасланы на адрас электроннай пошты ўдзельніка «$1».\nКалі ласка, увайдзіце ў сыстэму пасьля яго атрыманьня.",
        "blocked-mailpassword": "З Вашага IP-адрасу забароненыя рэдагаваньні. Каб пазьбегнуць злоўжываньняў, з гэтага IP-адрасу забаронена аднаўляць пароль.",
        "eauthentsent": "Пацьверджаньне было дасланае на пазначаны адрас электроннай пошты.\nУ лісьце ўтрымліваюцца інструкцыі, па выкананьні якіх Вы зможаце пацьвердзіць, што адрас сапраўды належыць Вам, і на гэты адрас будзе дасылацца пошта адсюль.",
        "accmailtext": "Створаны адвольны пароль для [[User talk:$1|$1]] быў адасланы па адрасе $2. Яго можна зьмяніць на старонцы ''[[Special:ChangePassword|зьмены паролю]]'' пасьля ўваходу.",
        "newarticle": "(Новая)",
        "newarticletext": "Вы прыйшлі па спасылцы на старонку, якая яшчэ не існуе.\nКаб стварыць яе, напішыце тэкст у полі ніжэй (глядзіце [$1 старонку дапамогі] для дадатковай інфармацыі).\nКалі Вы трапілі сюды памылкова, націсьніце кнопку «<strong>назад</strong>» у вашым браўзэры.",
-       "anontalkpagetext": "----''Гэта старонка гутарак ананімнага ўдзельніка, які яшчэ не стварыў сабе рахунак альбо не ўжывае яго. Таму мы вымушаныя ўжываць лічбавы IP-адрас дзеля ягонай ідэнтыфікацыі. Адзін IP-адрас можа выкарыстоўвацца некалькімі ўдзельнікамі. Калі Вы — ананімны ўдзельнік і лічыце, што атрымалі не прызначаныя Вам камэнтары, калі ласка, [[Special:UserLogin/signup|стварыце рахунак]] альбо [[Special:UserLogin|увайдзіце ў сыстэму]], каб у будучыні пазьбегнуць магчымай блытаніны зь іншымі ананімнымі ўдзельнікамі.''",
+       "anontalkpagetext": "----''Гэта старонка гутарак ананімнага ўдзельніка, які яшчэ не стварыў сабе рахунак альбо не ўжывае яго. Таму мы вымушаныя ўжываць лічбавы IP-адрас дзеля ягонай ідэнтыфікацыі. Адзін IP-адрас можа выкарыстоўвацца некалькімі ўдзельнікамі. Калі Вы — ананімны ўдзельнік і лічыце, што атрымалі не прызначаныя Вам камэнтары, калі ласка, [[Special:CreateAccount|стварыце рахунак]] альбо [[Special:UserLogin|увайдзіце ў сыстэму]], каб у будучыні пазьбегнуць магчымай блытаніны зь іншымі ананімнымі ўдзельнікамі.''",
        "noarticletext": "Цяпер тэкст на гэтай старонцы адсутнічае.\nВы можаце [[Special:Search/{{PAGENAME}}|пашукаць гэтую назву]] сярод іншых старонак, <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>, але ў вас няма дазволу ствараць гэтую старонку.",
        "missing-revision": "Вэрсія старонкі №$1 з назвай «{{FULLPAGENAME}}» не існуе.\n\nЗвычайна гэта здараецца з-за перахода па састарэлай спасылцы на старонку, якая была выдаленая.\nПадрабязнасьці можна знайсьці ў [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале выдаленьняў].",
        "userpage-userdoesnotexist": "Рахунак удзельніка «<nowiki>$1</nowiki>» не зарэгістраваны. Калі ласка, удакладніце, ці жадаеце Вы стварыць/рэдагаваць гэтую старонку.",
        "userpage-userdoesnotexist-view": "Рахунак «$1» ня створаны.",
        "blocked-notice-logextract": "Гэты ўдзельнік у дадзены момант заблякаваны.\nАпошні запіс з журналу блякаваньняў пададзены ніжэй для даведкі:",
-       "clearyourcache": "'''Заўвага:''' Каб пабачыць зьмены пасьля захаваньня, Вам можа спатрэбіцца ачысьціць кэш Вашага браўзэра. \n* '''Firefox / Safari:''' Трымайце ''Shift'' і націсьніце ''Reload'', ці націсьніце ''Ctrl-F5'' ці ''Ctrl-R'' (''⌘-R'' на Mac)\n* '''Google Chrome:''' Націсьніце ''Ctrl-Shift-R'' (''⌘-Shift-R'' на Mac)\n* '''Internet Explorer:''' Трымайце ''Ctrl'' і націсьніце ''Refresh'', ці націсьніце ''Ctrl-F5''\n* '''Opera:''' Ачысьціце кэш праз ''Tools → Preferences''",
+       "clearyourcache": "<strong>Заўвага:</strong> каб пабачыць зьмены пасьля захаваньня, Вам можа спатрэбіцца ачысьціць кэш Вашага браўзэра. \n* <strong>Firefox / Safari:</strong> трымайце <em>Shift</em> і націсьніце <em>Reload</em>, ці націсьніце <em>Ctrl-F5</em> ці <em>Ctrl-R</em> (<em>⌘-R</em> на Mac)\n* <strong>Google Chrome:</strong> націсьніце <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> на Mac)\n* <strong>Internet Explorer:</strong> трымайце <em>Ctrl</em> і націсьніце <em>Refresh</em>, ці націсьніце <em>Ctrl-F5</em>\n* <strong>Opera:</strong> перайдзіце ў <em>Menu → Settings</em> (<em>Opera → Preferences</em> на Mac), а потым у <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "'''Падказка:''' выкарыстоўвайце кнопку «{{int:showpreview}}», каб паспрабаваць новы код CSS перад тым як яго запісаць.",
        "userjsyoucanpreview": "'''Падказка:''' выкарыстоўвайце кнопку «{{int:showpreview}}», каб паспрабаваць новы код JavaScript перад тым, як яго запісаць.",
        "usercsspreview": "'''Памятайце, што гэта толькі папярэдні прагляд Вашага CSS. Ён яшчэ не запісаны!'''",
        "right-override-export-depth": "экспартаваньне старонак, уключаючы зьвязаныя старонкі з глыбінёй да 5",
        "right-sendemail": "адпраўка электронных лістоў іншым удзельнікам",
        "right-passwordreset": "прагляд электронных лістоў з ачысткай паролю",
-       "right-managechangetags": "ствараць і выдаляць [[Special:Tags|меткі]] з базы зьвестак",
+       "right-managechangetags": "стварэньне і (дэ)актывацыя [[Special:Tags|метак]]",
        "right-applychangetags": "дадаваць [[Special:Tags|меткі]] пры рэдагаваньні",
        "right-changetags": "дадаваць і выдаляць адвольныя [[Special:Tags|меткі]] да асобных вэрсіяў і запісаў у журнале падзеяў",
+       "right-deletechangetags": "выдаленьне [[Special:Tags|метак]] з базы зьвестак",
        "grant-generic": "Набор правоў «$1»",
        "grant-group-page-interaction": "Узаемадзеяньне з старонкамі",
        "grant-group-file-interaction": "Узаемадзеяньне з мэдыяфайламі",
        "action-viewmyprivateinfo": "прагляд вашых прыватных зьвестак",
        "action-editmyprivateinfo": "рэдагаваньне вашых прыватных зьвестак",
        "action-editcontentmodel": "рэдагаваньне мадэлі зьместу старонкі",
-       "action-managechangetags": "стварэньне і выдаленьне метак з базы зьвестак",
+       "action-managechangetags": "стварэньне і (дэ)актывацыю метак",
        "action-applychangetags": "дадаваньне метак пры рэдагаваньні",
        "action-changetags": "дадаваньне і выдаленьне адвольных метак да асобных вэрсіяў і запісаў у журнале падзеяў",
        "nchanges": "$1 {{PLURAL:$1|зьмена|зьмены|зьменаў}}",
        "upload-form-label-infoform-description-tooltip": "Коратка апішыце ўсё значнае пра гэтую працу.\nДля фота, узгадайце пра асноўныя аб’екты, выпадак ці месца.",
        "upload-form-label-usage-title": "Выкарыстаньне",
        "upload-form-label-usage-filename": "Назва файлу",
-       "foreign-structured-upload-form-label-own-work": "Гэта мая ўласная праца",
-       "foreign-structured-upload-form-label-infoform-categories": "Катэгорыі",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
-       "foreign-structured-upload-form-label-own-work-message-local": "Я пацьвярджаю, што загружаю гэты файл згодна з правіламі і ліцэнзійнай палітыкай {{GRAMMAR:родны|{{SITENAME}}}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Калі вы ня можаце загрузіць файл у адпаведнасьці з правіламі {{GRAMMAR:родны|{{SITENAME}}}}, калі ласка, закрыйце гэтае акно і паспрабуйце іншы мэтад.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Вы таксама можаце паспрабаваць [[Special:Upload|старонку загрузкі па змоўчаньні]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Я разумею, што загружаю гэты файл у агульнае сховішча. Я пацьвярджаю, што раблю гэта ў адпаведнасьці з умовамі выкарыстаньня і ліцэнзійнай палітыкай.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Калі вы ня можаце загрузіць гэты файл паводле правілаў агульнага сховішча, калі ласка, закрыйце гэты дыялёг і паспрабуйце іншы мэтад.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Вы можаце паспрабаваць скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі гэты файл можна туды загрузіць згодна з правіламі.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Я пацьвярджаю, што зьяўляюся ўласьнікам аўтарскіх правоў на гэты файл, і згодны незваротна перадаць гэты файл ў Вікісховішча на ўмовах ліцэнзіі [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], а таксама згодны з [https://wikimediafoundation.org/wiki/Terms_of_Use умовамі выкарыстаньня].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Калі вы не зьяўляецеся ўласьнікам аўтарскіх правоў на гэты файл, або вы жадаеце распаўсюджваць яго пад іншай ліцэнзіяй, можаце скарыстацца [https://commons.wikimedia.org/wiki/Special:UploadWizard Майстарам загрузкі ў Вікісховішча].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Вы таксама можаце скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі правілы сайту дазваляюць загрузку такога файлу.",
+       "upload-form-label-own-work": "Гэта мая ўласная праца",
+       "upload-form-label-infoform-categories": "Катэгорыі",
+       "upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work-message-generic-local": "Я пацьвярджаю, што загружаю гэты файл згодна з правіламі і ліцэнзійнай палітыкай {{GRAMMAR:родны|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Калі вы ня можаце загрузіць файл у адпаведнасьці з правіламі {{GRAMMAR:родны|{{SITENAME}}}}, калі ласка, закрыйце гэтае акно і паспрабуйце іншы мэтад.",
+       "upload-form-label-not-own-work-local-generic-local": "Вы таксама можаце паспрабаваць [[Special:Upload|старонку загрузкі па змоўчаньні]].",
+       "upload-form-label-own-work-message-generic-foreign": "Я разумею, што загружаю гэты файл у агульнае сховішча. Я пацьвярджаю, што раблю гэта ў адпаведнасьці з умовамі выкарыстаньня і ліцэнзійнай палітыкай.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Калі вы ня можаце загрузіць гэты файл паводле правілаў агульнага сховішча, калі ласка, закрыйце гэты дыялёг і паспрабуйце іншы мэтад.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Вы можаце паспрабаваць скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі гэты файл можна туды загрузіць згодна з правіламі.",
        "backend-fail-stream": "Немагчыма накіраваць файл $1.",
        "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файла $1.",
        "backend-fail-notexists": "Файл $1 не існуе.",
        "apisandbox-dynamic-parameters-add-placeholder": "Назва парамэтру",
        "apisandbox-dynamic-error-exists": "Парамэтар з назвай «$1» ужо існуе.",
        "apisandbox-deprecated-parameters": "Састарэлыя парамэтры",
+       "apisandbox-fetch-token": "Аўтазапаўненьне токену",
        "apisandbox-submit-invalid-fields-title": "Некаторыя палі няслушныя",
        "apisandbox-submit-invalid-fields-message": "Калі ласка, выпраўце пазначаныя палі і паспрабуйце яшчэ раз.",
        "apisandbox-results": "Вынікі",
        "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": "Значэньне гэтага поля зьяўляецца няслушным.",
        "booksources": "Крыніцы кніг",
        "booksources-search-legend": "Пошук кніг",
        "booksources-isbn": "ISBN:",
        "listgrouprights-namespaceprotection-header": "Абмежаваньні прасторы назваў",
        "listgrouprights-namespaceprotection-namespace": "Прастора назваў",
        "listgrouprights-namespaceprotection-restrictedto": "Правы, якія дазваляюць удзельніку рэдагаваць",
+       "listgrants": "Дазволы",
+       "listgrants-grant": "Дазвол",
+       "listgrants-rights": "Правы",
        "trackingcategories": "Катэгорыі, якія патрабуюць увагі",
        "trackingcategories-summary": "На гэтай старонцы пералічаныя катэгорыя, які патрабуюць увагі і якія аўтаматычна запаўняюцца праграмным забесьяпчэньнем MediaWiki. Іх назвы могуць быць зьмененыя рэдагаваньнем сыстэмных паведамленьняў у прасторы назваў {{ns:8}}.",
        "trackingcategories-msg": "Катэгорыя, якая патрабуе ўвагі",
        "changecontentmodel-title-label": "Назва старонкі",
        "changecontentmodel-model-label": "Новая мадэль зьместу",
        "changecontentmodel-reason-label": "Прычына:",
+       "changecontentmodel-submit": "Зьмяніць",
        "changecontentmodel-success-title": "Мадэль зьместу была зьмененая",
        "changecontentmodel-success-text": "Тып зьместу [[:$1]] быў зьменены.",
        "changecontentmodel-cannot-convert": "Зьмест [[:$1]] ня можа быць ператвораны ў тып $2.",
        "whatlinkshere-prev": "{{PLURAL:$1|папярэдняя|папярэднія}} $1",
        "whatlinkshere-next": "{{PLURAL:$1|наступная|наступныя}} $1",
        "whatlinkshere-links": "← спасылкі",
-       "whatlinkshere-hideredirs": "$1 перанакіраваньні",
-       "whatlinkshere-hidetrans": "$1 уключэньні",
-       "whatlinkshere-hidelinks": "$1 спасылкі",
-       "whatlinkshere-hideimages": "$1 спасылкі на выявы",
+       "whatlinkshere-hideredirs": "Схаваць перанакіраваньні",
+       "whatlinkshere-hidetrans": "Схаваць уключэньні",
+       "whatlinkshere-hidelinks": "Схаваць спасылкі",
+       "whatlinkshere-hideimages": "Схаваць спасылкі на файлы",
        "whatlinkshere-filters": "Фільтры",
        "whatlinkshere-submit": "Перайсьці",
        "autoblockid": "Аўтаматычнае блякаваньне №$1",
index a2fbb19..433f4c0 100644 (file)
        "createacct-reason-ph": "Чаму вы ствараеце іншы ўліковы запіс",
        "createacct-submit": "Стварыць уліковы запіс",
        "createacct-another-submit": "Стварыць уліковы запіс",
+       "createacct-continue-submit": "Працягнуць стварэнне ўліковага запісу",
        "createacct-benefit-heading": "{{SITENAME}} зроблены такімі ж людзьмі, як вы.",
        "createacct-benefit-body1": "{{PLURAL:$1|праўка|праўкі|правак}}",
        "createacct-benefit-body2": "{{PLURAL:$1|старонка|старонкі|старонак}}",
        "noname": "Вы не вызначылі правільнага імя ўдзельніка.",
        "loginsuccesstitle": "Паспяховы ўваход у сістэму",
        "loginsuccess": "<strong>Цяпер Вы ўвайшлі на {{SITENAME}} як \"$1\".</strong>",
-       "nosuchuser": "Няма ўдзельніка з імем \"$1\". Праверце правільнасць напісання або [[Special:UserLogin/signup|стварыце новы рахунак]]. Вялікія і малыя літары ў такіх імёнах лічацца рознымі.",
+       "nosuchuser": "Няма ўдзельніка з імем \"$1\". Праверце правільнасць напісання або [[Special:CreateAccount|стварыце новы рахунак]]. Вялікія і малыя літары ў такіх імёнах лічацца рознымі.",
        "nosuchusershort": "Удзельніка з імем \"$1\" не існуе. Праверце яго напісанне.",
        "nouserspecified": "Вы мусіце вызначыць імя ўдзельніка.",
        "login-userblocked": "Гэты карыстальнік заблакаваны. Лагін не дапускаецца.",
        "createacct-another-realname-tip": "Сапраўднае імя паведамляць неабавязкова.\nКалі вы паведаміце яго, яно будзе выкарыстоўвацца для пазначэння вашага ўкладу.",
        "pt-login": "Увайсці",
        "pt-login-button": "Увайсці",
+       "pt-login-continue-button": "Працягнуць уваход",
        "pt-createaccount": "Стварыць уліковы запіс",
        "pt-userlogout": "Выйсці",
        "php-mail-error-unknown": "Невядомая памылка ў функцыі PHP-пошты",
        "botpasswords-invalid-name": "Паказанае імя ўдзельніка не ўтрымлівае падзяляльнік паролю робата (\"$1\").",
        "botpasswords-not-exist": "Удзельнік \"$1\" не мае паролю для робата з назвай \"$2\".",
        "resetpass_forbidden": "Не дазволена мяняць паролі",
+       "resetpass_forbidden-reason": "Не дазволена мяняць паролі: $1",
        "resetpass-no-info": "Трэба ўвайсці ў сістэму, каб звяртацца да гэтай старонкі наўпрост.",
        "resetpass-submit-loggedin": "Змяніць пароль",
        "resetpass-submit-cancel": "Нічога",
        "passwordreset-emailsentusername": "Калі ёсць адрас электроннай пошты, злучаны з гэтым імем удзельніка, то будзе дасланы ліст пра скід пароля.",
        "passwordreset-emailsent-capture": "Ніжэй прыведзены адпраўлены ліст пра скід пароля.",
        "passwordreset-emailerror-capture": "Ніжэй прыведзены створаны ліст пра скід пароля, яго адпраўка не атрымалася па прычыне: $1",
+       "passwordreset-invalideamil": "Няслушны адрас электроннай пошты",
        "changeemail": "Змяніць або выдаліць адрас электроннай пошты",
        "changeemail-header": "Запоўніце гэтую форму, каб змяніць свой адрас электроннай пошты. Калі хочаце выдаліць адрас электроннай пошты, злучаны з вашым уліковым запісам, пакіньце поле новага адраса электроннай пошты пустым пры адпраўцы формы.",
        "changeemail-passwordrequired": "Вам трэба будзе ўвесці свой пароль, каб пацвердзіць гэта змяненне.",
        "minoredit": "Дробная праўка",
        "watchthis": "Назіраць за гэтай старонкай",
        "savearticle": "Запісаць",
+       "publishpage": "Апублікаваць старонку",
        "preview": "Перадпаказ",
        "showpreview": "Як будзе",
        "showdiff": "Розніца",
        "accmailtext": "На адрас $2 быў дасланы згенераваны пароль для [[User talk:$1|$1]]. Ён можа быць зменены на <em>[[Special:ChangePassword|старонцы змены пароля]]</em> пасля ўваходу ў сістэму.",
        "newarticle": "(Новы)",
        "newarticletext": "Вы перайшлі па спасылцы на старонку, якой яшчэ няма.\nКаб яе стварыць, набярыце яе тэкст у ніжэйпаказаным акне рэдагавання (падрабязнасці гл. ў [$1 даведцы]).\nКалі вы тут выпадкова, проста націсніце <strong>назад</strong> у браўзеры.",
-       "anontalkpagetext": "----''Гэта старонка размовы з ананімным удзельнікам, які або не мае свайго рахунка, або ім не карыстаўся. Таму дзеля яго ці яе ідэнтыфікацыі мы мусім выкарыстаць лічбавы IP-адрас. Такі адрас IP могуць дзяліць між сабою некалькі асоб. Калі вы ананімны ўдзельнік, і лічыце, што атрымліваеце няслушныя заўвагі,[[Special:UserLogin/signup|стварыце рахунак]] або [[Special:UserLogin|зайдзіце ў сістэму]], каб вас больш не блыталі з іншымі ананімнымі ўдзельнікамі.''",
+       "anontalkpagetext": "----\n<em>Гэта старонка размовы з ананімным удзельнікам, які або не мае свайго рахунка, або ім не карыстаўся.</em>\nТаму дзеля яго ці яе ідэнтыфікацыі мы мусім выкарыстаць лічбавы IP-адрас.\nТакі адрас IP могуць дзяліць між сабою некалькі асоб.\nКалі вы ананімны ўдзельнік, і лічыце, што атрымліваеце няслушныя заўвагі,[[Special:CreateAccount|стварыце рахунак]] або [[Special:UserLogin|зайдзіце ў сістэму]], каб вас больш не блыталі з іншымі ананімнымі ўдзельнікамі.",
        "noarticletext": "Старонка не ўтрымлівае тэксту. Вы можаце [[Special:Search/{{PAGENAME}}|пашукаць гэткую назву]] ў іншых старонках ці <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}}}} ў журналах]</span>, але вы не маеце дазволу на стварэнне гэтай старонкі.",
        "missing-revision": "Няма версіі #$1 у старонкі з назвай \"{{FULLPAGENAME}}\".\n\nЗвычайна такое здараецца, калі прайсці па састарэлай спасылцы з гісторыі на старонку, якая была сцёрта.\nПадрабязнасці можна пабачыць у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале сціранняў].",
        "right-managechangetags": "Ствараць і выдаляць [[Special:Tags|біркі]] з базы даных",
        "right-applychangetags": "Прымяняць [[Special:Tags|біркі]] са сваімі праўкамі",
        "right-changetags": "Дадаваць і выдаляць адвольныя [[Special:Tags|біркі]] да асобных версій і запісаў у журнале падзей",
+       "right-deletechangetags": "Выдаляць [[Special:Tags|біркі]] з базы даных",
        "grant-generic": "Набор дазволаў \"$1\"",
        "grant-group-page-interaction": "Узаемадзейнічаць з старонкамі",
        "grant-group-file-interaction": "Узаемадзейнічаць з медыяфайламі",
        "action-viewmyprivateinfo": "бачыць свае асабістыя звесткі",
        "action-editmyprivateinfo": "правіць свае асабістыя звесткі",
        "action-editcontentmodel": "правіць мадэль змесціва старонкі",
-       "action-managechangetags": "ствараць і выдаляць біркі з базы даных",
+       "action-managechangetags": "ствараць і (дэ)актываваць біркі",
        "action-applychangetags": "прымяняць біркі з сваімі праўкамі",
        "action-changetags": "дадаваць і выдаляць адвольныя біркі да асобных версій і запісаў у журнале падзей",
+       "action-deletechangetags": "выдаляць біркі з базы даных",
        "nchanges": "$1 {{PLURAL:$1|змена|змены|змен}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|з часу апошняга наведвання}}",
        "enhancedrc-history": "гісторыя",
        "upload-form-label-infoform-description-tooltip": "Коратка апішыце ўсё значнае пра гэту працу.\nДля фатаграфіі ўкажыце галоўныя аб'екты, нагоду або месца.",
        "upload-form-label-usage-title": "Выкарыстанне",
        "upload-form-label-usage-filename": "Назва файла",
-       "foreign-structured-upload-form-label-own-work": "Гэта мая ўласная праца",
-       "foreign-structured-upload-form-label-infoform-categories": "Катэгорыі",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
-       "foreign-structured-upload-form-label-own-work-message-local": "Я пацвярджаю, што ўкладваю гэты файл згодна з правіламі і ліцэнзійнай палітыкай {{GRAMMAR:родны|{{SITENAME}}}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Калі вы не можаце ўкладваць гэты файл згодна з правіламі пляцоўкі {{SITENAME}}, калі ласка, закрыйце гэта акно і паспрабуйце іншы метад.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Вы таксама можаце паспрабаваць [[Special:Upload|прадвызначаную старонку ўкладвання]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Я разумею, што ўкладваю гэты файл у агульнае сховішча. Я пацвярджаю, што раблю гэта ў адпаведнасці з умовамі выкарыстання і ліцэнзійнай палітыкай.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Калі вы не можаце ўкладваць гэты файл згодна з правіламі агульнага сховішча, калі ласка, закрыйце гэта акно і паспрабуйце іншы метад.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Вы таксама можаце паспрабаваць скарыстацца [[Special:Upload|старонкай укладанняў пляцоўкі {{SITENAME}}]], калі гэты файл можна укладваць туды згодна з іх палітыкай.",
+       "upload-form-label-own-work": "Гэта мая ўласная праца",
+       "upload-form-label-infoform-categories": "Катэгорыі",
+       "upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work-message-generic-local": "Я пацвярджаю, што ўкладваю гэты файл згодна з правіламі і ліцэнзійнай палітыкай {{GRAMMAR:родны|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Калі вы не можаце ўкладваць гэты файл згодна з правіламі пляцоўкі {{SITENAME}}, калі ласка, закрыйце гэта акно і паспрабуйце іншы метад.",
+       "upload-form-label-not-own-work-local-generic-local": "Вы таксама можаце паспрабаваць [[Special:Upload|прадвызначаную старонку ўкладвання]].",
+       "upload-form-label-own-work-message-generic-foreign": "Я разумею, што ўкладваю гэты файл у агульнае сховішча. Я пацвярджаю, што раблю гэта ў адпаведнасці з умовамі выкарыстання і ліцэнзійнай палітыкай.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Калі вы не можаце ўкладваць гэты файл згодна з правіламі агульнага сховішча, калі ласка, закрыйце гэта акно і паспрабуйце іншы метад.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Вы таксама можаце паспрабаваць скарыстацца [[Special:Upload|старонкай укладанняў пляцоўкі {{SITENAME}}]], калі гэты файл можна укладваць туды згодна з іх палітыкай.",
        "backend-fail-stream": "Не атрымалася трансляваць файл $1.",
        "backend-fail-backup": "Немагчыма зрабіць рэзервную копію $1.",
        "backend-fail-notexists": "Файл $1 не існуе.",
        "tags-edit-chosen-no-results": "Не знойдзена бірак, якія б адпавядалі запыту",
        "tags-edit-reason": "Прычына:",
        "tags-edit-nooldid-title": "Недапушчальная мэтавая версія",
+       "tags-edit-nooldid-text": "Вы або не пазначылі мэтавую версію для выканання гэтай функцыі, або пазначаная версія не існуе.",
+       "tags-edit-none-selected": "Калі ласка, выберыце прынамсі адну бірку для дадання ці выдалення.",
        "comparepages": "Параўнанне старонак",
        "compare-page1": "Старонка 1",
        "compare-page2": "Старонка 2",
        "compare-revision-not-exists": "Паказанай вамі версіі не існуе.",
        "dberr-problems": "Прабачце, на пляцоўцы здарыліся тэхнічныя цяжкасці.",
        "dberr-again": "Паспрабуйце перачытаць праз некалькі хвілін.",
-       "dberr-info": "(Немагчыма звязацца з серверам баз даных: $1)",
-       "dberr-info-hidden": "(Немагчыма звязацца з серверам базы звестак)",
+       "dberr-info": "(Немагчыма звязацца з базай даных: $1)",
+       "dberr-info-hidden": "(Немагчыма звязацца з базай звестак)",
        "dberr-usegoogle": "Тымчасам можна паспрабаваць пошук праз Гугл.",
        "dberr-outofdate": "Заўважце, што тамтэйшыя індэксы тутэйшага зместу могуць быць састарэлымі.",
        "dberr-cachederror": "Гэта копія старонкі, узятая з кэшу, і, магчыма, састарэлая.",
        "htmlform-cloner-create": "Дадаць яшчэ",
        "htmlform-cloner-delete": "Сцерці",
        "htmlform-cloner-required": "Неабходна хаця б адно значэнне.",
+       "htmlform-title-badnamespace": "[[:$1]] не ў прасторы назваў \"{{ns:$2}}\".",
        "htmlform-title-not-exists": "$1 не існуе.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "sqlite-has-fts": "$1 з падтрымкай поўна-тэкставага пошуку",
index b6c5cf0..849daa1 100644 (file)
@@ -33,7 +33,8 @@
                        "Matma Rex",
                        "Xð",
                        "Miroslav35232",
-                       "Ket"
+                       "Ket",
+                       "Ricordo.tenerissimo"
                ]
        },
        "tog-underline": "Подчертаване на препратките:",
        "noname": "Не указахте валидно потребителско име.",
        "loginsuccesstitle": "Успешно влизане",
        "loginsuccess": "'''Влязохте в {{SITENAME}} като „$1“.'''",
-       "nosuchuser": "Не съществува потребител с име „$1“.\nПотребителските имена са чувствителни на малки и главни букви.\nПроверете изписването или [[Special:UserLogin/signup|създайте нова сметка]].",
+       "nosuchuser": "Не съществува потребител с име „$1“.\nПотребителските имена са чувствителни на малки и главни букви.\nПроверете изписването или [[Special:CreateAccount|създайте нова сметка]].",
        "nosuchusershort": "Не съществува потребител с името „$1“. Проверете изписването.",
        "nouserspecified": "Необходимо е да се посочи потребителско име.",
        "login-userblocked": "Този потребител е блокиран. Влизането в системата не е позволено.",
        "minoredit": "Това е малка промяна",
        "watchthis": "Наблюдаване на страницата",
        "savearticle": "Съхраняване",
+       "publishpage": "Публикуване на страницата",
        "preview": "Предварителен преглед",
        "showpreview": "Предварителен преглед",
        "showdiff": "Показване на промените",
        "accmailtext": "Случайно генерирана парола за [[User talk:$1|$1]] беше изпратена на $2. Паролата може да бъде променена от страницата ''[[Special:ChangePassword|„Промяна на паролата“]]'' след влизане в системата.",
        "newarticle": "(нова)",
        "newarticletext": "Последвахте препратка към страница, която все още не съществува.\nЗа да я създадете, просто започнете да пишете в долната текстова кутия\n(вижте [$1 помощната страница] за повече информация).",
-       "anontalkpagetext": "----''Това е дискусионната страница на анонимен потребител, който все още няма регистрирана сметка или не я използва, затова се налага да използваме IP-адрес, за да го идентифицираме. Такъв адрес може да се споделя от няколко потребители.''\n\n''Ако сте анонимен потребител и мислите, че тези неуместни коментари са отправени към вас, [[Special:UserLogin/signup|регистрирайте се]] или [[Special:UserLogin|влезте в системата]], за да избегнете евентуално бъдещо объркване с други анонимни потребители.''",
-       "noarticletext": "Тази Ñ\81Ñ\82Ñ\80аниÑ\86а Ð²Ñ\81е Ð¾Ñ\89е Ð½Ðµ Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ва. Ð\9cожеÑ\82е Ð´Ð° [[Special:Search/{{PAGENAME}}|поÑ\82Ñ\8aÑ\80Ñ\81иÑ\82е Ð·Ð° Ð·Ð°Ð³Ð»Ð°Ð²Ð¸ÐµÑ\82о Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86аÑ\82а]] Ð² Ð´Ñ\80Ñ\83ги Ñ\81Ñ\82Ñ\80аниÑ\86и, Ð´Ð° <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Ð¿Ð¾Ñ\82Ñ\8aÑ\80Ñ\81иÑ\82е Ð² Ð´Ð½ÐµÐ²Ð½Ð¸Ñ\86иÑ\82е] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} да я създадете]</span>.",
+       "anontalkpagetext": "----''Това е дискусионната страница на анонимен потребител, който все още няма регистрирана сметка или не я използва, затова се налага да използваме IP-адрес, за да го идентифицираме. Такъв адрес може да се споделя от няколко потребители.''\n\n''Ако сте анонимен потребител и мислите, че тези неуместни коментари са отправени към вас, [[Special:CreateAccount|регистрирайте се]] или [[Special:UserLogin|влезте в системата]], за да избегнете евентуално бъдещо объркване с други анонимни потребители.''",
+       "noarticletext": "Ð\9fонаÑ\81Ñ\82оÑ\8fÑ\89ем Ð½Ñ\8fма Ñ\82екÑ\81Ñ\82 Ð½Ð° Ñ\82ази Ñ\81Ñ\82Ñ\80аниÑ\86а. Ð\9cожеÑ\82е Ð´Ð° [[Special:Search/{{PAGENAME}}|поÑ\82Ñ\8aÑ\80Ñ\81иÑ\82е Ð·Ð° Ð·Ð°Ð³Ð»Ð°Ð²Ð¸ÐµÑ\82о Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86аÑ\82а]] Ð² Ð´Ñ\80Ñ\83ги Ñ\81Ñ\82Ñ\80аниÑ\86и, Ð´Ð° <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Ð¿Ð¾Ñ\82Ñ\8aÑ\80Ñ\81иÑ\82е Ð² Ñ\81Ñ\8aоÑ\82веÑ\82ниÑ\82е Ð´Ð½ÐµÐ²Ð½Ð¸Ñ\86и] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} да я създадете]</span>.",
        "noarticletext-nopermission": "Текущо в тази страница няма текст.\nМожете да [[Special:Search/{{PAGENAME}}|потърсите заглавието на тази страница ]] в други страници или да <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} потърсите в съответните дневници]</span>, но нямате права да създадете тази страница.",
        "missing-revision": "Версия #$1 на страницата „{{FULLPAGENAME}}“ не съществува.\n\nТова обикновено се дължи на препратка от историята на страницата, която е била изтрита.\nПодробности могат да бъдат открити в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневника на изтриванията].",
        "userpage-userdoesnotexist": "Няма регистрирана потребителска сметка за „<nowiki>$1</nowiki>“. Изисква се потвърждение, че желаете да създадете/редактирате тази страница?",
        "revdelete-show-file-submit": "Да",
        "revdelete-selected-text": "{{PLURAL:$1|Избрана версия|Избрани версии}} от [[:$2]]:",
        "logdelete-selected": "{{PLURAL:$1|Избрано събитие|Избрани събития}}:",
+       "revdelete-text-text": "Изтритите редакции ще продължат да се виждат в историята на страницата, но части от съдържанието ще бъдат публично недостъпни.",
+       "revdelete-text-others": "Другите администратори ще продължат да имат достъп до скритото съдържание и могат да го възстановят, освен ако не бъдат наложени допълнителни ограничения.",
        "revdelete-confirm": "Необходимо е да потвърдите, че желаете да извършите действието, разбирате последствията и го правите според [[{{MediaWiki:Policy-url}}|политиката]].",
        "revdelete-suppress-text": "Премахването трябва да се използва '''само''' при следните случаи:\n* Потенциално уязвима в правно отношение информация\n* Неподходяща лична информация\n*: ''домашни адреси и телефонни номера, номера за социално осигуряване и др.''",
        "revdelete-legend": "Задаване на ограничения:",
        "upload-form-label-infoform-description-tooltip": "Накратко опишете всичко, което си струва да се каже за тази творба.\nНапример, ако е снимка, опишете основните неща, които са снимани, повода, местоположението и т.н.",
        "upload-form-label-usage-title": "Използване",
        "upload-form-label-usage-filename": "Име на файл",
-       "foreign-structured-upload-form-label-own-work": "Това е моя собствена творба",
-       "foreign-structured-upload-form-label-infoform-categories": "Категории",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
-       "foreign-structured-upload-form-label-own-work-message-local": "Потвърждавам, че качвам този файл в съответствие с правилата и лицензионната политика на сайта {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Ако не можете да заредите този файл в съответствие с правилата на сайта {{SITENAME}}, моля, затворете този прозорец и опитайте друг метод.",
+       "upload-form-label-own-work": "Това е моя собствена творба",
+       "upload-form-label-infoform-categories": "Категории",
+       "upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work-message-generic-local": "Потвърждавам, че качвам този файл в съответствие с правилата и лицензионната политика на сайта {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Ако не можете да заредите този файл в съответствие с правилата на сайта {{SITENAME}}, моля, затворете този прозорец и опитайте друг метод.",
        "backend-fail-notexists": "Файлът $1 не съществува.",
        "backend-fail-delete": "Файлът $1 не може да бъде изтрит.",
        "backend-fail-alreadyexists": "Файлът $1 вече съществува.",
        "suppress": "Премахване от публичния архив",
        "querypage-disabled": "Тази специална страница е изключена, защото затруднява производителността на уикито.",
        "apihelp-no-such-module": "Модул \"$1\" не беше намерен.",
+       "apisandbox-fullscreen": "Разшири полето",
        "apisandbox-reset": "Изчистване",
        "apisandbox-examples": "Пример",
-       "apisandbox-results": "Резултат",
+       "apisandbox-dynamic-parameters-add-placeholder": "Име на параметъра",
+       "apisandbox-results": "Резултати",
        "booksources": "Източници на книги",
        "booksources-search-legend": "Търсене на информация за книга",
        "booksources-search": "Търсене",
        "logempty": "Дневникът не съдържа записи, отговарящи на избрания критерий.",
        "log-title-wildcard": "Търсене на заглавия, започващи със",
        "showhideselectedlogentries": "Промяна на видимостта на избраните записи",
-       "checkbox-all": "Всички",
-       "checkbox-none": "Никои",
+       "checkbox-select": "Избери: $1",
+       "checkbox-all": "всички",
+       "checkbox-none": "никои",
+       "checkbox-invert": "обърни избора",
        "allpages": "Всички страници",
        "nextpage": "Следваща страница ($1)",
        "prevpage": "Предходна страница ($1)",
        "protect-othertime": "Друг срок:",
        "protect-othertime-op": "друг срок",
        "protect-existing-expiry": "Оставащо време: $2, $3",
+       "protect-existing-expiry-infinity": "Existing expiration time: безсрочно",
        "protect-otherreason": "Друга/допълнителна причина:",
        "protect-otherreason-op": "Друга причина",
        "protect-dropdown": "* Стандартни причини за защита на страници\n** Чест обект на вандализъм\n** Чест обект на спам\n** Редакторска война\n** Страница, изискваща много сървърни ресурси",
        "delete_and_move_text": "== Наложително изтриване ==\n\nЦелевата страница „[[:$1]]“ вече съществува. Искате ли да я изтриете, за да освободите място за преместването?",
        "delete_and_move_confirm": "Да, искам да изтрия тази страница.",
        "delete_and_move_reason": "Изтрита, за да се освободи място за преместване от „[[$1]]“",
-       "selfmove": "Страницата не може да бъде преместена, тъй като целевото име съвпада с първоначалното й заглавие.",
+       "selfmove": "Страницата не може да бъде преместена, тъй като целевото име съвпада с първоначалното ѝ заглавие.",
        "immobile-source-namespace": "Не могат да се местят страници в именно пространство „$1“",
        "immobile-target-namespace": "Не е възможно преместването на страници в именното пространство „$1“",
        "immobile-target-namespace-iw": "Страницата не може да бъде преместена под заглавие, оформено като междууики препратка.",
        "sqlite-no-fts": "$1 без поддръжка на пълнотекстово търсене",
        "logentry-delete-delete": "$1 {{GENDER:$2|изтри}} страницата $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|възстанови}} страницата $3",
+       "logentry-delete-revision": "$1 {{GENDER:$2|промени}} видимостта на {{PLURAL:$5|една редакция|$5 редакции}} в страница $3: $4",
+       "logentry-delete-event-legacy": "$1 {{GENDER:$2|промени}} видимостта на събитията от дневниците за страница $3",
        "logentry-delete-revision-legacy": "$1 {{GENDER:$2|промени}} видимостта на версиите на страница $3",
        "logentry-suppress-revision": "$1 тайно {{GENDER:$2|промени}} видимостта на {{PLURAL:$5|една версия|$5 версии}} на страницата $3: $4",
        "logentry-suppress-revision-legacy": "$1 тайно {{GENDER:$2|промени}} видимостта на версиите на страница $3",
index 2863646..edaf14b 100644 (file)
        "noname": "شما یک موتبرین کار زوروکی ئین نامی ئا مشخص نه کورته ئیت.",
        "loginsuccesstitle": "کامیابین لوگین",
        "loginsuccess": "'''شما انون گو «$1» ئی نا بی {{SITENAME}} ئی تا داخل بوته ایت.'''",
-       "nosuchuser": "کار زوروکئ گۆ «$1» ئی ناما موجود نه اینت.\nکار زورکئ نام گۆ گۆنڈی یا توُهی ئا هوروپ ئان هساس اینت.\nنامی املا ئا بگنیدیت، یا [[Special:UserLogin/signup|یک نوکین کار زورکی هسابئ جۆڑ بکنیت]].",
+       "nosuchuser": "کار زوروکئ گۆ «$1» ئی ناما موجود نه اینت.\nکار زورکئ نام گۆ گۆنڈی یا توُهی ئا هوروپ ئان هساس اینت.\nنامی املا ئا بگنیدیت، یا [[Special:CreateAccount|یک نوکین کار زورکی هسابئ جۆڑ بکنیت]].",
        "nosuchusershort": "هیچ کار زوروکئ بی نامئ  ''$1'' ئا وجود نداریت.\nوتئ املا ئا چیک بکنیت.",
        "nouserspecified": "باید یک کار زوروکئ نام مشخص بکنیت.",
        "login-userblocked": "ای کار زوروک بلاک بوته، په داخل بوتینا اجازه نه اینت.",
        "upload-form-label-infoform-description": "توضیحان",
        "upload-form-label-usage-title": "استفاده يی ڈول",
        "upload-form-label-usage-filename": "فایلئ نام",
-       "foreign-structured-upload-form-label-own-work": "ائ ني جیندئ کار اینت",
-       "foreign-structured-upload-form-label-infoform-categories": "تهرئان",
-       "foreign-structured-upload-form-label-infoform-date": "تاریخ",
-       "foreign-structured-upload-form-label-own-work-message-local": "من ایشیرا قبولا کنین که من ائ فایلا بُرزا کنین گۆ استفاده ئی شرایطان شه  جوازئ شینک  بوتینا و خدماتئ سیاستان به {{SITENAME}} تا.",
+       "upload-form-label-own-work": "ائ ني جیندئ کار اینت",
+       "upload-form-label-infoform-categories": "تهرئان",
+       "upload-form-label-infoform-date": "تاریخ",
+       "upload-form-label-own-work-message-generic-local": "من ایشیرا قبولا کنین که من ائ فایلا بُرزا کنین گۆ استفاده ئی شرایطان شه  جوازئ شینک  بوتینا و خدماتئ سیاستان به {{SITENAME}} تا.",
        "backend-fail-stream": "نه توانن $1 ئی فایلا دیم دهین.",
        "backend-fail-backup": "نتنوانن پُشتوانی نخسه یی په $1 فایلا جۆڑ کنن.",
        "backend-fail-notexists": " $1 ئی فایل وجود نداریت.",
        "listgrouprights-rights": "حقوق",
        "listgrouprights-helppage": "Help:گروپانئ حقوق",
        "listgrouprights-members": "(اعضائانی لڑلیست)",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "توانیت ای {{PLURAL:$2|گروپ|گروپ ئان}} ئا اضافه بکینت: $1",
        "listgrouprights-removegroup": "توانیت ای {{PLURAL:$2|گروپ|گروپ ئان}} ئه پاک بکنیت: $1",
        "listgrouprights-addgroup-all": "توانیت موچین گروپانا اضافه بکنیت",
        "whatlinkshere-prev": "{{PLURAL:$1|دیمئ|$1 دیمئ مورد}}",
        "whatlinkshere-next": "{{PLURAL:$1|پدئ|$1 پدئ مورد}}",
        "whatlinkshere-links": "→ لینک",
-       "whatlinkshere-hideredirs": "$1 تغییرمسیر",
-       "whatlinkshere-hidetrans": "$1 تراگنجانش‌هان",
-       "whatlinkshere-hidelinks": "$1 لینک",
+       "whatlinkshere-hideredirs": "تغیرمسیرانی چیهرداتین",
+       "whatlinkshere-hidetrans": "تراگنجانش‌هانی چیهر داتین",
+       "whatlinkshere-hidelinks": "لینکاني چیهر داتین",
        "whatlinkshere-hideimages": "$1 فایلی لینکان",
        "whatlinkshere-filters": "فیلتر ئان",
        "autoblockid": "#$1 ئی اوتو بلاک",
        "bydate": "شه تاریخی رُوگا",
        "sp-newimages-showfrom": "نشان‌داتین نۆکین اکسانی شه $2، $1 بئ بعد",
        "seconds": "{{PLURAL:$1|$1ثانیه| $1  ثانیه}}",
-       "minutes": "{{PLURAL:$1|دقیقه|دقیقه}}",
+       "minutes": "{{PLURAL:$1|$1 دقیقه|$1 دقیقه}}",
        "hours": "{{PLURAL:$1|ساعت|ساعت}}",
        "days": "{{PLURAL:$1|روچ|روچ}}",
        "weeks": "{{PLURAL:$1|$1 هپتگ|$1 هپتگ ئان}}",
        "logentry-delete-delete": "$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",
+       "logentry-delete-revision": "$1 ،  $3 تاکدیمئ  {{PLURAL:$5|یک نخسه|$5 نخسه}}‌ئی پیدایي‌ئا   {{GENDER:$2|تغییر دات}}: $4",
        "logentry-suppress-delete": "$1 $3 ئی تاکدیما {{GENDER:$2| سرکوب کورت}}",
        "logentry-suppress-event": "$1 پیدایی {{PLURAL:$5|یک مورد سیاه چال|$5 مورد سیاه چال}} ئا بئ $3 {{GENDER:$2|تا چیهر دات}}: $4",
-       "logentry-suppress-revision": "$1 پیدایی {{PLURAL:$5|یک نخسه|$5 نخسه}} تاکدیم $3 ئا چیهراکائی {{GENDER:$2|تغییر دات}}: $4",
+       "logentry-suppress-revision": "$1 ،  $3 تاکدیمئ  {{PLURAL:$5|یک نخسه|$5 نخسه}}‌ئی پیدایي‌ئا  چیهراکائی  {{GENDER:$2|تغییر دات}}: $4",
        "revdelete-content-hid": "محتوائانه چیهر کورت",
        "revdelete-summary-hid": "ایڈیٹ ئی خلاصه ئا چیهر کورت",
        "revdelete-uname-hid": "چیهرین کار زوروکئ نام",
index cf66e2b..fa75a26 100644 (file)
        "noname": "रउआ उपयुक्त प्रयोगकर्ता नाम नईखीं निर्दिष्ट कईले।",
        "loginsuccesstitle": "खाता प्रवेश में सफल",
        "loginsuccess": "''' \"$1\" के रुप में रउआ {{SITENAME}} में अब प्रवेश कर चुकल बानी।'''",
-       "nosuchuser": "\"$1\" नाम से कौनो प्रयोगकर्ता नईखन।\nप्रयोगकर्ता नाम संवेदनशील मामला बा।\nशब्द-वर्तनी के जाँच करीं, आ चाहे [[Special:UserLogin/signup|एगो नया खाता बनाईं]]।",
+       "nosuchuser": "\"$1\" नाम से कौनो प्रयोगकर्ता नईखन।\nप्रयोगकर्ता नाम संवेदनशील मामला बा।\nशब्द-वर्तनी के जाँच करीं, आ चाहे [[Special:CreateAccount|एगो नया खाता बनाईं]]।",
        "nosuchusershort": "ई नाम से कौनो प्रयोगकर्ता नईखन \"$1\".\nआपन शब्द-वर्तनी के जाँच करीं।",
        "nouserspecified": "रउआ एगो प्रयोगकर्ता नाम निर्दिष्ट करे के बा।",
        "login-userblocked": "ई प्रयोगकर्ता के खाता निष्क्रिय हो चुकल बा। प्रवेश के आज्ञा नईखे।",
        "accmailtext": "[[User talk:$1|$1]] खातिर एगो यंत्र जनित गुप्तशब्द $2 के भेज दिहल गइल बा। खाता में प्रवेश कइला के बाद इ '''[[Special:ChangePassword|गुप्तशब्द बदल लीं]]'' वाला पन्ना पर बदलल जा सकत बा।",
        "newarticle": "(नया)",
        "newarticletext": "रउआ एगो अइसन कड़ी के पन्ना के अनुसरण कइले बानी जवन अभी तक उपलब्ध नइखे।\nपन्ना बनावे खातिर, नीचे के बाकस में टाइप करे के शुरु करीं (ज्यादा जानकारी खातिर देखीं [$1 मदद पन्ना])।\nयदि रउआ अहिजा गलती से आ गइल बानी त, आपन ब्राउजर के '''बैक''' (Back) बटन दबाईं!",
-       "anontalkpagetext": "----''इ वार्ता पन्ना उन अनाम सदस्यन खातिर बा जिन्हन के या त खाता नइखे खोलल गइल या खाता के प्रयोग नइखन करत।\nएहि खातिर उन्हन के पहिचान खातिर हमनी के उनकर आइ॰पी पता के प्रयोग करे के पड़ेला।\nआइ॰पी पता कई सदस्यन खातिर साझा हो सकत बा।\nयदि आप एगो अनाम सदस्य बानी अउर आपके लागत बा कि आपके बारे में अप्रासंगिक टीका टिप्पणी करल गइल बा त कृपया [[Special:UserLogin/signup|सदस्यता लिहीं]] या [[Special:UserLogin|सत्रारंभ करीं]] ताकि अन्य अनाम सदस्यन में से आपके अलग से पहिचानल जा सके।''",
+       "anontalkpagetext": "----''इ वार्ता पन्ना उन अनाम सदस्यन खातिर बा जिन्हन के या त खाता नइखे खोलल गइल या खाता के प्रयोग नइखन करत।\nएहि खातिर उन्हन के पहिचान खातिर हमनी के उनकर आइ॰पी पता के प्रयोग करे के पड़ेला।\nआइ॰पी पता कई सदस्यन खातिर साझा हो सकत बा।\nयदि आप एगो अनाम सदस्य बानी अउर आपके लागत बा कि आपके बारे में अप्रासंगिक टीका टिप्पणी करल गइल बा त कृपया [[Special:CreateAccount|सदस्यता लिहीं]] या [[Special:UserLogin|सत्रारंभ करीं]] ताकि अन्य अनाम सदस्यन में से आपके अलग से पहिचानल जा सके।''",
        "noarticletext": "ए पन्ना मे अभी ले कौनों सामग्री नइखे। \nरउआ दुसरा पन्ना में [[Special:Search/{{PAGENAME}}|ए टाइटिल के खोज]] कर सकत बानीं।",
        "noarticletext-nopermission": "ए पन्ना मे अभी कौनों सामग्री नइखे।\nरउआँ दुसरा पन्ना में [[Special:Search/{{PAGENAME}}|ए टाइटिल के खोज]] कर सकत बानीं,\nया <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} या संबंधित लॉग खोज सकत बानी]</span>, बाकी रउआ के ई पन्ना बनावे के परमीशन नइखे।",
        "missing-revision": "\"{{FULLPAGENAME}}\" पन्ना के संशोधन #$1 उपलब्ध नइखे।\n\nसाधारण रुप से इ एगो हटावल गइल पन्ना के पुरान लिंक पर क्लिक कइला से होखेला।\nअधिक जानकारी खातिर आप [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटावे के लॉग] देख सकत बानी।",
index a87d235..1980adf 100644 (file)
        "noname": "Ngaran pamakai nang Pian ajuakan kada sah.",
        "loginsuccesstitle": "Kulihan babuat log",
        "loginsuccess": "'''Pian parhatan ni babuat log dalam {{SITENAME}} sawagai \"$1\".'''",
-       "nosuchuser": "Kadada pamakai bangaran \"$1\".\nNgaran pamakai adalah kasus marinci.\nLihati pulang ijaan Pian, atawa [[Special:UserLogin/signup|ulah sabuting akun hanyar]]",
+       "nosuchuser": "Kadada pamakai bangaran \"$1\".\nNgaran pamakai adalah kasus marinci.\nLihati pulang ijaan Pian, atawa [[Special:CreateAccount|ulah sabuting akun hanyar]]",
        "nosuchusershort": "Kadada pamakai bangaran \"$1\".\nLihati pulang ijaan Pian.",
        "nouserspecified": "Pian harus ma'ajuakan sabuting ngaran pamakai.",
        "login-userblocked": "Pamakai naya diblukir. Babuat log kada dibulihakan.",
        "accmailtext": "Sabuting katasunduk babarang gasan [[User talk:$1|$1]] sudah dikirim ka $2.\n\nKatasunduk gasan pamakai hanyar nangini kawa diubah pintang tungkaran ''[[Special:ChangePassword|ubah katasunduk]]'' wayah babuat log.",
        "newarticle": "(Hanyar)",
        "newarticletext": "Pian maumpati sabuah tautan ka tungkaran nang baluman ada lagi. Gasan maulah tungkaran, mulai ja mangatik pada kutak di bawah (lihati [$1 tungkaran patulung] gasan panjalasan labih). Amun Pian ka sia cagaran tasalah, klik picikan '''back''' di panjalajah web Pian.",
-       "anontalkpagetext": "----''Ngini adalah tungkaran pamandiran gasan pamakai kada bangaran nang baluman ma-ulah akun pulang, atawa  kada mamakainya. Kami tapaksa mamakai numurik alamat IP hagan maminanduinya.\nAlamat IP nangkaini kawaai dipuruk ulih babarapa pamakai.\nAmun Pian adalah pamuruk kada bangaran wan marasa kumin nang kada pas ta ka Pian, muhun [[Special:UserLogin/signup|ulah sabuah akun]] or [[Special:UserLogin|babuat log]] hagan mahindari kabingungan awan pamuruk kada bangaran lain kaina.",
+       "anontalkpagetext": "----''Ngini adalah tungkaran pamandiran gasan pamakai kada bangaran nang baluman ma-ulah akun pulang, atawa  kada mamakainya. Kami tapaksa mamakai numurik alamat IP hagan maminanduinya.\nAlamat IP nangkaini kawaai dipuruk ulih babarapa pamakai.\nAmun Pian adalah pamuruk kada bangaran wan marasa kumin nang kada pas ta ka Pian, muhun [[Special:CreateAccount|ulah sabuah akun]] or [[Special:UserLogin|babuat log]] hagan mahindari kabingungan awan pamuruk kada bangaran lain kaina.",
        "noarticletext": "Parhatan ni kadada naskah di tungkaran ngini.\nPian kawa [[Special:Search/{{PAGENAME}}|manggagai gasan judul ngini]] pintang tungkaran lain,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} manggagai log barait].</span>,\natawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} mambabak tungkaran ngini]</span>.",
        "noarticletext-nopermission": "Parhatan ni kadada naskah di tungkaran ngini.\nPian kawa [[Special:Search/{{PAGENAME}}|manggagai gasan judul ngini]] pintang tungkaran lain,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} manggagai log barait].</span>.",
        "userpage-userdoesnotexist": "Akun pamakai \"<nowiki>$1</nowiki>\" kada tadaptar.\nMuhun pariksa/ditukui amun Pian handak maulah/mambabak tungkaran ngini.",
index 55e4cf2..18d2978 100644 (file)
@@ -45,6 +45,7 @@
        "tog-watchdefault": "আমার সম্পাদিত পাতা এবং ফাইলগুলো আমার নজরতালিকায় যোগ করা হোক",
        "tog-watchmoves": "আমার স্থানান্তরিত পাতা এবং ফাইলগুলো আমার নজরতালিকায় যোগ করা হোক",
        "tog-watchdeletion": "আমার অপসারিত পাতা এবং ফাইলগুলো আমার নজর তালিকায় যোগ করা হোক",
+       "tog-watchuploads": "আমার নজরতালিকায় আমার আপলোড করা নতুন ফাইল যোগ কর",
        "tog-watchrollback": "আমার দ্বারা রোলব্যাক করা পাতা আমার নজরতালিকায় যোগ করা হোক",
        "tog-minordefault": "শুরুতেই সব সম্পাদনাকে অনুল্লেখ্য বলে চিহ্নিত করা হোক",
        "tog-previewontop": "সম্পাদনা বাক্সের আগে প্রাকদর্শন দেখানো হোক",
        "noname": "আপনি সঠিক ব্যবহারকারী নাম নির্দিষ্ট করেননি।",
        "loginsuccesstitle": "প্রবেশ করেছেন",
        "loginsuccess": "<strong>আপনি এইমাত্র \"$1\" নামে {{SITENAME}}-তে প্রবেশ করেছেন।</strong>",
-       "nosuchuser": "\"$1\" নামে কোন ব্যবহারকারী নেই।\nব্যবহারকারী নামের আকার সংবেদনশীল।\nআপনার বানান পরীক্ষা করে দেখুন, অথবা [[Special:UserLogin/signup|নতুন একটি অ্যাকাউন্ট খুলুন]]।",
+       "nosuchuser": "\"$1\" নামে কোন ব্যবহারকারী নেই।\nব্যবহারকারী নামের আকার সংবেদনশীল।\nআপনার বানান পরীক্ষা করে দেখুন, অথবা [[Special:CreateAccount|নতুন একটি অ্যাকাউন্ট খুলুন]]।",
        "nosuchusershort": "\"$1\" নামের কোন ব্যবহারকারী নেই। নামের বানান পরীক্ষা করুন।",
        "nouserspecified": "আপনাকে অবশ্যই ব্যবহারকারী নাম নির্দিষ্ট করতে হবে।",
        "login-userblocked": "এই ব্যবহারকারীকে বাধা দেওয়া হয়েছে। প্রবেশ সম্ভব নয়।",
        "minoredit": "এটি একটি অনুল্লেখ্য সম্পাদনা",
        "watchthis": "এই পাতাটি নজরে রাখুন",
        "savearticle": "সংরক্ষণ",
+       "publishpage": "পাতা প্রকাশ করুন",
        "preview": "প্রাকদর্শন",
        "showpreview": "প্রাকদর্শন",
        "showdiff": "পরিবর্তনসমূহ",
        "accmailtext": "[[User talk:$1|$1]] এর জন্য দৈব ভাবে উৎপন্ন শব্দ চাবি $2 এ পাঠানো হয়েছে।\nলগ-ইন করার পর ''[[Special:ChangePassword|পাসওয়ার্ড পরিবর্তন]]'' পাতা থেকে এটি পরিবর্তন করা যাব।",
        "newarticle": "(নতুন)",
        "newarticletext": "আপনি এমন একটি লিংক অনুসরণ করছেন, যা নেই।\nপাতাটি তৈরি করতে, নিচের বাক্সে তা টাইপ করা শুরু করুন (আরও তথ্য জানতে [$1 সহায়িকা পাতা] দেখুন)।\nআপনি যদি ভুল করে এখানে এসে থাকেন, তাহলে আপনার ব্রাউজারের '''back''' বোতাম ক্লিক করুন।",
-       "anontalkpagetext": "----''এটি একটি বেনামী ব্যবহারকারীর আলাপের পাতা, যিনি এখনও কোন অ্যাকাউন্ট তৈরি করেননি, কিংবা তিনি অ্যাকাউন্টটি ব্যবহার করছেন না।\nআমরা তাই সাংখ্যিক আইপি ঠিকানা ব্যবহার করে তাঁকে শনাক্ত করছি।\nএকাধিক ব্যবহারকারী এরকম একটি আইপি ঠিকানা ব্যবহার করতে পারেন।\nআপনি যদি একজন বেনামী ব্যবহারকারী হয়ে থাকেন এবং যদি অনুভব করেন যে আপনার প্রতি অপ্রাসঙ্গিক মন্তব্য করা হয়েছে, তাহলে অন্যান্য বেনামী ব্যবহারকারীর সাথে ভবিষ্যতে বিভ্রান্তি এড়াতে অনুগ্রহ করে [[Special:UserLogin/signup|একটি অ্যাকাউন্ট তৈরি করুন]] অথবা  [[Special:UserLogin|অ্যাকাউন্টে প্রবেশ করুন]]।''",
+       "anontalkpagetext": "----''এটি একটি বেনামী ব্যবহারকারীর আলাপের পাতা, যিনি এখনও কোন অ্যাকাউন্ট তৈরি করেননি, কিংবা তিনি অ্যাকাউন্টটি ব্যবহার করছেন না।\nআমরা তাই সাংখ্যিক আইপি ঠিকানা ব্যবহার করে তাঁকে শনাক্ত করছি।\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}}| শিরোনামটি অনুসন্ধান করতে পারেন]], অথবা <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} সম্পর্কিত লগ অনুসন্ধান করতে পারেন]</span>, কিন্তু আপনার এই পাতাটি তৈরী করার অনুমতি নেই।",
        "missing-revision": "\"{{FULLPAGENAME}}\" এর #$1তম সংস্করণটি প্রদর্শন সম্ভব নয়।\n\nসাধারণত মুছে ফেলা হয়েছে এমন পাতার মেয়াদ উত্তীর্ণ ইতিহাস পাতার লিংক ওপেন করার কারণে এটি হতে পারে। \n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} অপসারণ লগে] বিস্তারিত তথ্য জানা যাবে।",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" নামের কোন ব্যবহারকারী অ্যাকাউন্ট নিবন্ধিত হয়নি। অনুগ্রহ করে পরীক্ষা করে দেখুন আপনি এই পাতাটি সৃষ্টি/সম্পাদনা করতে চান কি না।",
        "userpage-userdoesnotexist-view": "ব্যবহারকারী অ্যাকাউন্ট \"$1\" অনিবন্ধিত।",
        "blocked-notice-logextract": "এই ব্যবহারকারী বর্তমানে ব্লক রয়েছে।\nরেফারেন্সের জন্য সাম্প্রতিক ব্লক লগ ভুক্তি নিচে দেওয়া হল:",
-       "clearyourcache": "<strong>লà¦\95à§\8dষà§\8dয à¦\95রà§\81ন:</strong> à¦¸à¦\82রà¦\95à§\8dষণà§\87র à¦ªà¦°, à¦ªà¦°à¦¿à¦¬à¦°à§\8dতনà¦\97à§\81লà§\8b à¦¦à§\87à¦\96তà§\87 à¦\86পনাà¦\95à§\87 à¦\86পনার à¦¬à§\8dরাà¦\89à¦\9cারà§\87র à¦\95à§\8dযাশà§\87 à¦ªà¦°à¦¿à¦·à§\8dà¦\95ার à¦\95রার à¦ªà§\8dরয়à§\8bà¦\9cন à¦¹à¦¤à§\87 à¦ªà¦¾à¦°à§\87।\n* <strong>ফায়ারফà¦\95à§\8dস / à¦¸à¦¾à¦«à¦¾à¦°à¦¿:</strong> <em>Shift</em> à¦§à¦°à§\87 à¦°à¦¾à¦\96া à¦\85বসà§\8dথায়<em>পà§\81নà¦\83লà§\8bড à¦\95রà§\81ন</em>-à¦\8f à¦\95à§\8dলিà¦\95 à¦\95রà§\81ন, à¦\85থবা <em>Ctrl-F5</em> à¦¬à¦¾ <em>Ctrl-R</em> (মà§\8dযাà¦\95-à¦\8f <em>â\8c\98-R</em>) à¦\9aাপà§\81ন\n* <strong>à¦\97à§\81à¦\97ল à¦\95à§\8dরà§\8bম:</strong> <em>Ctrl-Shift-R</em> (মà§\8dযাà¦\95-à¦\8f <em>â\8c\98-Shift-R</em>) à¦\9aাপà§\81ন\n* <strong>à¦\87নà§\8dà¦\9fারনà§\87à¦\9f à¦\8fà¦\95à§\8dসপà§\8dলà§\8bরার:</strong> <em>Ctrl</em> à¦§à¦°à§\87 à¦°à¦¾à¦\96া à¦\85বসà§\8dথায় <em>Refresh</em>-à¦\8f à¦\95à§\8dলিà¦\95 à¦\95রà§\81ন, à¦\85থবা <em>Ctrl-F5</em> à¦\9aাপà§\81ন\n* <strong>à¦\85পà§\87রা:</strong> <em>সরà¦\9eà§\8dà¦\9cাম â\86\92 à¦ªà¦\9bনà§\8dদসমà§\82হ</em>-à¦\8f à¦\97িয়à§\87 à¦\95à§\8dযাশà§\87 à¦ªà¦°à¦¿à¦·à§\8dà¦\95ার à¦\95রà§\87 à¦¨à¦¿à¦¨",
+       "clearyourcache": "<strong>লà¦\95à§\8dষà§\8dয à¦\95রà§\81ন:</strong> à¦¸à¦\82রà¦\95à§\8dষণà§\87র à¦ªà¦°, à¦ªà¦°à¦¿à¦¬à¦°à§\8dতনà¦\97à§\81লà§\8b à¦¦à§\87à¦\96তà§\87 à¦\86পনাà¦\95à§\87 à¦\86পনার à¦¬à§\8dরাà¦\89à¦\9cারà§\87র à¦\95à§\8dযাশà§\87 à¦ªà¦°à¦¿à¦·à§\8dà¦\95ার à¦\95রার à¦ªà§\8dরয়à§\8bà¦\9cন à¦¹à¦¤à§\87 à¦ªà¦¾à¦°à§\87।\n* <strong>ফায়ারফà¦\95à§\8dস / à¦¸à¦¾à¦«à¦¾à¦°à¦¿:</strong> <em>Shift</em> à¦§à¦°à§\87 à¦°à¦¾à¦\96া à¦\85বসà§\8dথায়<em>পà§\81নà¦\83লà§\8bড à¦\95রà§\81ন</em>-à¦\8f à¦\95à§\8dলিà¦\95 à¦\95রà§\81ন, à¦\85থবা <em>Ctrl-F5</em> à¦¬à¦¾ <em>Ctrl-R</em> (মà§\8dযাà¦\95-à¦\8f <em>â\8c\98-R</em>) à¦\9aাপà§\81ন\n* <strong>à¦\97à§\81à¦\97ল à¦\95à§\8dরà§\8bম:</strong> <em>Ctrl-Shift-R</em> (মà§\8dযাà¦\95-à¦\8f <em>â\8c\98-Shift-R</em>) à¦\9aাপà§\81ন\n* <strong>à¦\87নà§\8dà¦\9fারনà§\87à¦\9f à¦\8fà¦\95à§\8dসপà§\8dলà§\8bরার:</strong> <em>Ctrl</em> à¦§à¦°à§\87 à¦°à¦¾à¦\96া à¦\85বসà§\8dথায় <em>Refresh</em>-à¦\8f à¦\95à§\8dলিà¦\95 à¦\95রà§\81ন, à¦\85থবা <em>Ctrl-F5</em> à¦\9aাপà§\81ন\n* <strong>à¦\85পà§\87রা:</strong> <em>মà§\87নà§\81 â\86\92 à¦¬à§\8dযবসà§\8dথাপনাসমà§\82হ</em>-à¦\8f à¦¯à¦¾à¦¨ (মà§\8dযাà¦\95à§\87 <em>à¦\85পà§\87রা â\86\92 à¦ªà¦\9bনà§\8dদসমà§\82হ</em>) à¦\8fবà¦\82 à¦\8fরপর <em>à¦\97à§\8bপনà§\80য়তা à¦\93 à¦¸à§\81রà¦\95à§\8dষা â\86\92 à¦¬à§\8dরাà¦\89à¦\9cিà¦\82-à¦\8fর à¦¤à¦¥à§\8dয à¦ªà¦°à¦¿à¦·à§\8dà¦\95ার à¦\95রà§\81ন â\86\92 à¦\95à§\8dযাশà§\87 à¦\95রা à¦\9bবি à¦\93 à¦«à¦¾à¦\87লà¦\97à§\81লি</em>।",
        "usercssyoucanpreview": "'''পরামর্শ:''' \"{{int:showpreview}}\" বোতাম ব্যবহার করে সংরক্ষণের আগে আপনার নতুন CSS পরীক্ষা করুন।",
        "userjsyoucanpreview": "'''পরামর্শ:''' \"{{int:showpreview}}\" বোতাম ব্যবহার করে সংরক্ষণের আগে আপনার নতুন JavaScript পরীক্ষা করুন।",
        "usercsspreview": "'''মনে রাখবেন আপনি আপনার জন্য বরাদ্ধকৃত সিএসএস প্রাকদর্শন করছেন।\nএটা এখনও সংরক্ষণ করা হয়নি!'''",
        "continue-editing": "সম্পাদনা করুন",
        "previewconflict": "এই প্রাকদর্শনটি সম্পাদনা ক্ষেত্রের উপরের অংশটির টেক্সট সংরক্ষণ করলে যেরকম দেখাবে, তা দেখাচ্ছে।",
        "session_fail_preview": "দুঃখিত! সেশন ডাটা হারিয়ে যাওয়ার কারণে আপনার সম্পাদনাটি সংরক্ষণ করা সম্ভব হয়নি।\n\nআপনি সম্ভবত সংযোগ হারিয়েছন। <strong>দয়া করে যাচাই করুন যে আপনি এখনও প্রবেশরত রয়েছেন এবং আবার চেষ্টা করুন</strong>। যদি এটি এখনও কাজ না করে, তাহলে দয়া করে [[Special:UserLogout|অ্যাকাউন্ট থেকে প্রস্থান করুন]] এবং আবার অ্যাকাউন্টে প্রবেশ করে চেষ্টা করুন এবং এবং পরীক্ষা করুন যে আপনার ব্রাউজার এই সাইটে কুকি ব্যবহারের অনুমতি দেয়।",
-       "session_fail_preview_html": "'''দুঃখিত! সেশন উপাত্ত হারিয়ে যাওয়ার কারণে আমরা আপনার সম্পাদনাটি প্রক্রিয়া করতে পারিনি।'''\n\n''{{SITENAME}}-এ raw HTML সক্রিয় আছে বলে জাভাস্ক্রিপ্টভিত্তিক আক্রমণ থেকে প্রতিরক্ষার জন্য প্রাকদর্শনটি দেখানো হচ্ছে না।''\n\n'''যদি এটি সম্পাদনার একটি বৈধ প্রচেষ্টা হয়, তবে অনুগ্রহ করে আবার চেষ্টা করুন। যদি তারপরেও কাজ না হয়, তবে অ্যাকাউন্ট থেকে বেরিয়ে গিয়ে আবার প্রবেশ করে চেষ্টা করুন।'''",
+       "session_fail_preview_html": "দুঃখিত! সেশন উপাত্ত হারিয়ে যাওয়ার কারণে আমরা আপনার সম্পাদনাটি প্রক্রিয়া করতে পারিনি।\n\n<em>{{SITENAME}}-এ raw HTML সক্রিয় আছে বলে জাভাস্ক্রিপ্ট ভিত্তিক আক্রমণ থেকে প্রতিরক্ষার জন্য প্রাকদর্শনটি দেখানো হচ্ছে না।</em>\n\n<strong>যদি এটি সম্পাদনার একটি বৈধ প্রচেষ্টা হয়, তবে অনুগ্রহ করে আবার চেষ্টা করুন।</strong>\nযদি তারপরেও কাজ না হয়, তবে অ্যাকাউন্ট থেকে [[Special:UserLogout|বেরিয়ে গিয়ে]] আবার প্রবেশ করুন, এবং পরীক্ষা করে দেখুন যে আপনার ব্রাউজারে এই সাইট থেকে কুকি অনুমতি দেয়।",
        "token_suffix_mismatch": "'''আপনার সম্পাদনাটি প্রত্যাখ্যান করা হয়েছে, কারণ আপনার ক্লায়েন্ট প্রোগ্রামটি সম্পাদনা টেক্সটের বিরামচিহ্নগুলি গুলিয়ে ফেলেছে। পাতাটির টেক্সটে যাতে ক্ষতি না হয় সেজন্য সম্পাদনাটি প্রত্যাখ্যান করা হয়েছে। আপনি কোন ত্রুটিপূর্ণ ওয়েব-ভিত্তিক বেনামী প্রক্সি সেবা ব্যবহার করলে এরকম হতে পারে।'''",
        "edit_form_incomplete": "'''আপনার সম্পাদনার কিছু অংশ সার্ভারে পৌছায় নি; আপনার সম্পাদনা সম্পূর্ণরুপে আছে কিনা নিশ্চিত হয়ে আবার চেষ্টা করুন'''",
        "editing": "সম্পাদনা করছেন: $1",
        "undo-summary-username-hidden": "একজন লুকানো ব্যবহারকারী $1 সংশোধন পুনরায় ফিরিয়ে এনেছেন",
        "cantcreateaccounttitle": "অ্যাকাউন্ট তৈরি করা যাবে না",
        "cantcreateaccount-text": "[[User:$3|$3]] এই আইপি ঠিকানা('''$1''') থেকে অ্যাকাউন্ট সৃষ্টিতে বাধা দিয়েছেন।\n\n$3-এর দেয়া কারণ হল ''$2''",
-       "cantcreateaccount-range-text": "[[User:$3|$3]] কর্তৃক আইপি ঠিকানা <strong>$1</strong> ব্যাপ্তির মধ্য থেকে অ্যাকাউন্ট তৈরি করা অবরুদ্ধ করা হয়েছে। যাতে আপনার আইপি ঠিকানা (<strong>$4</strong>) রয়েছে। \n\n$3 কর্তৃক <em>$2</em> কারণ দেখানো হয়েছে।",
+       "cantcreateaccount-range-text": "[[User:$3|$3]] কর্তৃক আইপি ঠিকানার ব্যাপ্তি <strong>$1</strong>-এর মধ্যে অ্যাকাউন্ট তৈরি করা অবরুদ্ধ করা হয়েছে। যাতে আপনার আইপি ঠিকানাও (<strong>$4</strong>) রয়েছে। \n\n$3 কর্তৃক <em>$2</em> কারণ দেখানো হয়েছে।",
        "viewpagelogs": "এই পাতার জন্য লগগুলো দেখুন",
        "nohistory": "এই পাতার কোন সম্পাদনার ইতিহাস নেই।",
        "currentrev": "সর্বশেষ সংস্করণ",
        "recentchangeslinked-page": "পাতার নাম:",
        "recentchangeslinked-to": "প্রদত্ত পাতায় সংযুক্ত আছে এমন পাতাগুলোর পরিবর্তন দেখাও",
        "recentchanges-page-added-to-category": "বিষয়শ্রেণীতে [[:$1]] যোগ করা হয়েছে",
-       "recentchanges-page-added-to-category-bundled": "বিষয়শà§\8dরà§\87ণà§\80তà§\87 [[:$1]] à¦\93 [[Special:WhatLinksHere/$1|{{PLURAL:$2|à¦\8fà¦\95à¦\9fি à¦ªà¦¾à¦¤à¦¾|$2à¦\9fি à¦ªà¦¾à¦¤à¦¾}}]] à¦¯à§\8bà¦\97 à¦\95রা à¦¹à¦¯à¦¼à§\87à¦\9bà§\87",
+       "recentchanges-page-added-to-category-bundled": "বিষয়শà§\8dরà§\87ণà§\80তà§\87 [[:$1]] à¦¯à§\8bà¦\97 à¦¹à¦¯à¦¼à§\87à¦\9bà§\87, [[Special:WhatLinksHere/$1|à¦\8fà¦\87 à¦ªà¦¾à¦¤à¦¾à¦\9fি à¦\85নà§\8dয à¦ªà¦¾à¦¤à¦¾à¦\97à§\81লির à¦®à¦§à§\8dযà§\87 à¦\85নà§\8dতরà§\8dভà§\81à¦\95à§\8dতà¦\95à§\83ত]]",
        "recentchanges-page-removed-from-category": "বিষয়শ্রেণী থেকে [[:$1]] সরানো হয়েছে",
-       "recentchanges-page-removed-from-category-bundled": "বিষয়শ্রেণী থেকে [[:$1]] ও [[Special:WhatLinksHere/$1|{{PLURAL:$2|একটি পাতা|$2টি পাতা}}]] সরানো হয়েছে",
+       "recentchanges-page-removed-from-category-bundled": "বিষয়শ্রেণীতে [[:$1]] সরানো হয়েছে, [[Special:WhatLinksHere/$1|এই পাতাটি অন্য পাতাগুলির মধ্যে অন্তর্ভুক্তকৃত]]",
        "autochange-username": "মিডিয়াউইকি স্বয়ংক্রিয় পরিবর্তন",
        "upload": "আপলোড",
        "uploadbtn": "ফাইল আপলোড করুন",
        "upload-form-label-infoform-description": "বিবরণ",
        "upload-form-label-usage-title": "ব্যবহার",
        "upload-form-label-usage-filename": "ফাইলের নাম",
-       "foreign-structured-upload-form-label-own-work": "এটি আমার নিজের কাজ",
-       "foreign-structured-upload-form-label-infoform-categories": "বিষয়শ্রেণীসমূহ",
-       "foreign-structured-upload-form-label-infoform-date": "তারিখ",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "এছাড়াও আপনি [[Special:Upload|ডিফল্ট আপলোডের পাতা]] চেষ্টা করতে পারেন।",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "এছাড়াও আপনি [[Special:Upload|{{SITENAME}}-এর আপলোডের পাতা]] ব্যবহার করার চেষ্টা করতে পারেন, যদি এই ফাইলটি তাদের নীতিমালা অধীনে সেখানে আপলোড করা যায়।",
-       "foreign-structured-upload-form-label-own-work-message-shared": "আমি প্রত্যয়ন করছি যে আমি এই ফাইলের স্বত্তাধিকারী, এবং [https://creativecommons.org/licenses/by-sa/4.0/deed.bn ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন-শেয়ার অ্যালাইক ৪.০] লাইসেন্সের অধীনে এই ফাইলটি উইকিমিডিয়া কমন্সে অপরিবর্তনীয় প্রকাশে সম্মত হচ্ছি, এবং আমি [https://wikimediafoundation.org/wiki/Terms_of_Use ব্যবহারের শর্তাবলীর] সাথে সম্মত।",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "যদি আপনি এই ফাইলের স্বত্তাধিকারী না হন, বা আপনি একটি ভিন্ন লাইসেন্সের আওতায় প্রকাশ করতে ইচ্ছুক থাকেন, তাহলে [https://commons.wikimedia.org/wiki/Special:UploadWizard?uselang=bn কমন্স আপলোড উইজার্ড] ব্যবহার করতে বিবেচনা করুন।",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "এছাড়াও আপনি [[Special:Upload|{{SITENAME}}-এর আপলোডের পাতা]] ব্যবহার করার চেষ্টা করতে পারেন, যদি সাইটটি তাদের নীতিমালার অধীনে এই ফাইল আপলোড করার অনুমতি দেয়।",
+       "upload-form-label-own-work": "এটি আমার নিজের কাজ",
+       "upload-form-label-infoform-categories": "বিষয়শ্রেণীসমূহ",
+       "upload-form-label-infoform-date": "তারিখ",
+       "upload-form-label-not-own-work-local-generic-local": "এছাড়াও আপনি [[Special:Upload|ডিফল্ট আপলোডের পাতা]] চেষ্টা করতে পারেন।",
+       "upload-form-label-not-own-work-local-generic-foreign": "এছাড়াও আপনি [[Special:Upload|{{SITENAME}}-এর আপলোডের পাতা]] ব্যবহার করার চেষ্টা করতে পারেন, যদি এই ফাইলটি তাদের নীতিমালা অধীনে সেখানে আপলোড করা যায়।",
        "backend-fail-stream": "\"$1\" ফাইলের স্ট্রিম দেখানো যাচ্ছে না।",
        "backend-fail-backup": "\"$1\" ফাইলের ব্যাকআপ তৈরী সম্ভব নয়।",
        "backend-fail-notexists": "\"$1\" নামের কোনো ফাইল নেই।",
        "sp-contributions-logs": "লগসমূহ",
        "sp-contributions-talk": "আলোচনা",
        "sp-contributions-userrights": "ব্যবহারকারী অধিকার ব্যবস্থাপনা",
-       "sp-contributions-blocked-notice": "এই ব্যবহারকারী বর্তমানে বাধাদানকৃত অবস্থায় রয়েছেন।\nতথ্যসূত্র হিসেবে সাম্প্রতিক বাধাদান লগে ভুক্তিটি নিচে দেওয়া হলো:",
+       "sp-contributions-blocked-notice": "এই ব্যবহারকারী বর্তমানে বাধাদানকৃত অবস্থায় রয়েছেন।\nতথ্যসূত্র হিসেবে সাম্প্রতিক বাধাদান লগে ভুক্তিটি নিচে দেওয়া হলো:",
        "sp-contributions-blocked-notice-anon": "এই আইপি ঠিকানাটি বর্তমানে বাধাদানকৃত অবস্থায় রয়েছে।\nতথ্যসূত্র হিসেবে সাম্প্রতিক বাধাদান লগের ভুক্তিটি নিচে দেওয়া হলো:",
        "sp-contributions-search": "অবদানসমূহের জন্য অনুসন্ধান",
        "sp-contributions-username": "আইপি (IP) ঠিকানা অথবা ব্যবহারকারীর নাম:",
        "whatlinkshere-prev": "{{PLURAL:$1|পূর্ববর্তী|পূর্ববর্তী $1টি}}",
        "whatlinkshere-next": "{{PLURAL:$1|পরবর্তী|পরবর্তী $1টি}}",
        "whatlinkshere-links": "← সংযোগগুলি",
-       "whatlinkshere-hideredirs": "পুননির্দেশনা $1",
-       "whatlinkshere-hidetrans": "অন্তর্ভুক্তকরণ $1",
-       "whatlinkshere-hidelinks": "সংযোগ $1",
-       "whatlinkshere-hideimages": "$1 ফাইল সংযোগ",
+       "whatlinkshere-hideredirs": "পুননির্দেশনা আড়াল করো",
+       "whatlinkshere-hidetrans": "অন্তর্ভুক্তকরণ আড়াল করো",
+       "whatlinkshere-hidelinks": "সংযোগ আড়াল করো",
+       "whatlinkshere-hideimages": "ফাইল সংযোগ আড়াল করো",
        "whatlinkshere-filters": "ছাকনী",
        "whatlinkshere-submit": "চলো",
        "autoblockid": "স্বয়ংক্রিয় বাধা #$1",
        "lockdbsuccesstext": "ডাটাবেজ বন্ধ করা হয়েছে\n<br />আপনার রক্ষণাবেক্ষণ সম্পন্ন হবার পর [[Special:UnlockDB|ডাটাবেজ খুলে দিতে]] ভুলবেন না।",
        "unlockdbsuccesstext": "ডাটাবেজ খুলে দেওয়া হয়েছে।",
        "lockfilenotwritable": "ডাটাবেজ বন্ধ করার ফাইলটি লিখনযোগ্য নয়। ডাটাবেজ বন্ধ করতে বা খুলতে চাইলে ফাইলটিকে ওয়েব সার্ভার কর্তৃক লিখনযোগ্য হতে হবে।",
+       "databaselocked": "ডাটাবেসটি ইতিমধ্যেই তালাবদ্ধ।",
        "databasenotlocked": "ডাটাবেজ বন্ধ নয়।",
        "lockedbyandtime": "({{GENDER:$1|$1}} $2 এর $3 সময়ে)",
        "move-page": "$1 স্থানান্তর",
        "tooltip-ca-nstab-category": "বিষয়শ্রেণী পাতাটি দেখুন",
        "tooltip-minoredit": "এটিকে অনুল্লেখ্য সম্পাদনা হিসেবে চিহ্নিত করা হোক",
        "tooltip-save": "আপনার পরিবর্তনগুলি সংরক্ষিত হোক",
+       "tooltip-publish": "আপনার পরিবর্তন প্রকাশ করুন",
        "tooltip-preview": "অনুগ্রহ করে সংরক্ষণের আগে আপনার পরিবর্তনগুলি প্রাকদর্শন করুন!",
        "tooltip-diff": "আপনি টেক্সটে কী কী পরিবর্তন করেছেন, তা দেখানো হোক।",
        "tooltip-compareselectedversions": "এই পাতার দুইটি নির্বাচিত সংস্করণের মধ্যে তুলনা দেখুন।",
        "watchlistedit-raw-done": "আপনার নজর তালিকা হালনাগাদ করা হয়েছে।",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 শিরোনাম|$1 শিরোনামসমূহ}} যোগ করা হয়েছে:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 শিরোনাম|$1 শিরোনামসমূহ}} মুছে ফেলা হয়েছে:",
-       "watchlistedit-clear-title": "নà¦\9cরতালিà¦\95া à¦ªà¦°à¦¿à¦¸à§\8dà¦\95ার à¦\95রা à¦¹à¦¯à¦¼à§\87à¦\9bà§\87",
+       "watchlistedit-clear-title": "নà¦\9cরতালিà¦\95া à¦ªà¦°à¦¿à¦·à§\8dà¦\95ার à¦\95রà§\81ন",
        "watchlistedit-clear-legend": "নজরতালিকা পরিস্কার",
        "watchlistedit-clear-explain": "সকল শিরোনামসমূহ আপনার নজরতালিকা থেকে সরিয়ে নেয়া হয়েছে।",
        "watchlistedit-clear-titles": "শিরোনামসমূহ:",
        "logentry-protect-protect-cascade": "$1 $3 {{GENDER:$2|সুরক্ষিত করেছেন}} $4 [প্রপাতাকার]",
        "logentry-protect-modify": "$1 $3-এর জন্য সুরক্ষা স্তর {{GENDER:$2|পরিবর্তন করেছেন}} $4",
        "logentry-protect-modify-cascade": "$1 $3-এর জন্য সুরক্ষা স্তর {{GENDER:$2|পরিবর্তন করেছেন}} $4 [প্রপাতাকার]",
-       "logentry-rights-rights": "$1 ব্যবহারকারী, $3 এর দলগত সদস্যপদ $4 থেকে $5 এ {{GENDER:$2|পরিবর্তন}} করেছেন",
+       "logentry-rights-rights": "$1 ব্যবহারকারী, {{GENDER:$6|$3}}-এর দলগত সদস্যপদ $4 থেকে $5 এ {{GENDER:$2|পরিবর্তন}} করেছেন",
        "logentry-rights-rights-legacy": "$1 দলের সদস্যপদ পরিবর্তন করেছেন {{GENDER:$2|changed}} এর জন্য $3",
        "logentry-rights-autopromote": "$1 স্বয়ংক্রিয়ভাবে $4 থেকে $5-এ {{GENDER:$2|উন্নীত}} হয়েছেন",
        "logentry-upload-upload": "$1 $3 {{GENDER:$2|আপলোড করেছেন}}",
        "randomrootpage": "অজানা মূল পাতা",
        "log-action-filter-block": "বাধাদানের ধরন:",
        "log-action-filter-delete": "অপসারণের ধরন:",
+       "log-action-filter-import": "আমদানির ধরন:",
        "log-action-filter-patrol": "টহলের ধরন:",
        "log-action-filter-protect": "সুরক্ষার ধরন:",
        "log-action-filter-upload": "আপলোডের ধরন:",
        "log-action-filter-protect-protect": "সুরক্ষা",
        "log-action-filter-protect-modify": "সুরক্ষা পরিমার্জন",
        "log-action-filter-protect-unprotect": "অসুরক্ষা",
+       "log-action-filter-rights-autopromote": "স্বয়ংক্রিয় পরিবর্তন",
        "log-action-filter-upload-upload": "নতুন আপলোড",
        "log-action-filter-upload-overwrite": "পুনঃআপলোড"
 }
index 299b757..a3c739a 100644 (file)
        "noname": "তি চুম্পা আতাকুরার নাঙহান না লেপ করিসত।",
        "loginsuccesstitle": "লগইনহান চুমিল",
        "loginsuccess": "'''এরে {{SITENAME}}ত তি \"$1\" হিসাবে না হমাসত।'''",
-       "nosuchuser": "এরে \"$1\" নাঙর কোন আতাকুরা নেই।\nআতাকুরার নাঙ কেইস সংবেদনশীল।\nতর বানানহান খিয়াল কর, নাইলে [[Special:UserLogin/signup|আরাক নুৱা একাউন্ট]]আহান হঙকর।",
+       "nosuchuser": "এরে \"$1\" নাঙর কোন আতাকুরা নেই।\nআতাকুরার নাঙ কেইস সংবেদনশীল।\nতর বানানহান খিয়াল কর, নাইলে [[Special:CreateAccount|আরাক নুৱা একাউন্ট]]আহান হঙকর।",
        "nosuchusershort": "এরে \"$1\" নাঙর কোন আতাকুরা নেই।\nতর বানানহান খিয়াল কর।",
        "nouserspecified": "তি আতাকুরার নাঙ আহান থনা লাগতই।",
        "login-userblocked": "আতাকুরাগরে থেপকরানি অসে। লগইন করে নারবে।",
        "accmailtext": "[[User talk:$1|$1]]র কা খন্তাচাবি(password) $2-রাঙ দিয়াপেঠুৱাদেনা ইল।\nলগ-ইন করানির পিসে তর নুৱা অ্যাকাউন্টর কা ''[[Special:ChangePassword|খন্তাচাবি সিলকরানি]]''র পাতাত্ব সালকরানি একরতই।",
        "newarticle": "(নুৱা)",
        "newarticletext": "তি বিসারার মিলাপ অহান নেয়সে।\nতি চেইলে তলর বক্সগত বিষয়হানর বারে খানি ইকরিয়া ইতুকরে পারর বারো নিবন্ধহান অকরে পারর (আরাকউ হারপানিরকা [$1 পাঙলাক পাতা] চা) পারর।\nযদি হারনাপেয়া এহাত আহিয়া থার অতা ইলে ব্রাউজারর ব্যাক গুতমগত ক্লিক করিয়া আগর পাতাত আল পারর।",
-       "anontalkpagetext": "----''এহান অচিনা অতার য়্যারির পাতাহান, যেগই কোন অ্যাকাউন্ট না খুলিসে, নাইলে ব্যবহার নাকরের।\nঅহানে আমি আইপি ঠিকানা (IP Address)ল অগরে দেখাদেরাঙতা।\nআক্কুসে এসাদে আকহান আইপি ব্যবহার করতে পারে।\nঅহানে তি নিশ্চকে এরে আইপি এহাত্ত উইকিপিডিয়াত হমিয়া কোন হবানেই য়্যারী দেখর, অহান তরে নিঙকরিয়া নাউ ইতে পারে। অহানে হাবিত্ত হবা অর, তি যদি [[Special:UserLogin|লগ-ইন]] করর, নাইলে [[Special:UserLogin/signup|নুৱা একাউন্ট খুলর]] অতা ইলে এসাদে ভুল বুঝাবুঝি নাইব।''",
+       "anontalkpagetext": "----''এহান অচিনা অতার য়্যারির পাতাহান, যেগই কোন অ্যাকাউন্ট না খুলিসে, নাইলে ব্যবহার নাকরের।\nঅহানে আমি আইপি ঠিকানা (IP Address)ল অগরে দেখাদেরাঙতা।\nআক্কুসে এসাদে আকহান আইপি ব্যবহার করতে পারে।\nঅহানে তি নিশ্চকে এরে আইপি এহাত্ত উইকিপিডিয়াত হমিয়া কোন হবানেই য়্যারী দেখর, অহান তরে নিঙকরিয়া নাউ ইতে পারে। অহানে হাবিত্ত হবা অর, তি যদি [[Special:UserLogin|লগ-ইন]] করর, নাইলে [[Special:CreateAccount|নুৱা একাউন্ট খুলর]] অতা ইলে এসাদে ভুল বুঝাবুঝি নাইব।''",
        "noarticletext": "এপাগা এরে পাতাত কোন লেখা নেই। তি মনেইলে হের পাতাহান [[Special:Search/{{PAGENAME}}|এরে চিঙনাঙল বিসারা পারর]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} এহানর বারে লগ বিসারা পারর], নাইলে [{{fullurl:{{FULLPAGENAME}}|action=edit}} এরে পাতা এহান পতা পারর।]",
        "noarticletext-nopermission": "এপাগাউ কোন মেয়েক নেই পাতা এহাত।\nতি [[Special:Search/{{PAGENAME}}|এরে পাতার চিঙনাঙ এহানল বিসারা]] হের পাতা অতাত,\nনাইলে <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} মিল আসে লগ অতাত বিসারা]</span>।",
        "userpage-userdoesnotexist": "আতাকুরা \"<nowiki>$1</nowiki>\"র অ্যাকাউন্টহান না হঙিসে।\nতি পাতা এহান হঙকরানি/পতানি চারাতানা কিতা খালকরিয়া চা।",
index 03a0f88..ae01322 100644 (file)
@@ -24,6 +24,7 @@
        "tog-hideminor": "Kuzhat ar c'hemmoù nevez dister",
        "tog-hidepatrolled": "Kuzhat ar c'hemmoù evezhiet e-touez ar c'hemmoù diwezhañ",
        "tog-newpageshidepatrolled": "Kuzhat ar pajennoù evezhiet diouzh roll ar pajennoù nevez",
+       "tog-hidecategorization": "Kuzhat rummatadur ar pajennoù",
        "tog-extendwatchlist": "Astenn ar roll evezhiañ a-benn diskouez an holl gemmoù ha neket ar re ziwezhañ hepken.",
        "tog-usenewrc": "Diskouez ar c'hemmoù nevez en ur feson kempennoc'h",
        "tog-numberheadings": "Niverenniñ emgefre an titloù",
        "noname": "N'hoc'h eus lakaet anv implijer ebet.",
        "loginsuccesstitle": "Kevreet oc'h.",
        "loginsuccess": "'''Kevreet oc'h bremañ ouzh {{SITENAME}} evel \"$1\".'''",
-       "nosuchuser": "N'eus ket eus an implijer \"$1\".\nKizidik eo anv an implijer ouzh ar pennlizherennoù\nGwiriit eo bet skrivet mat an anv ganeoc'h pe [[Special:UserLogin/signup|krouit ur gont nevez]].",
+       "nosuchuser": "N'eus ket eus an implijer \"$1\".\nKizidik eo anv an implijer ouzh ar pennlizherennoù\nGwiriit eo bet skrivet mat an anv ganeoc'h pe [[Special:CreateAccount|krouit ur gont nevez]].",
        "nosuchusershort": "N'eus perzhiad ebet gantañ an anv « $1 ». Gwiriit ar reizhskrivadur.",
        "nouserspecified": "Ret eo deoc'h spisaat un anv implijer.",
        "login-userblocked": "Stanket eo an implijer-mañ. N'eo ket aotret da gevreañ.",
        "accmailtext": "Kaset ez eus bet ur ger-tremen dargouezhek evit [[User talk:$1|$1]] da $2. Cheñchet e c'hall ar ger-tremen evit ar gont nevez-mañ bezañ war ar bajenn ''[[Special:ChangePassword|cheñch ger-tremen]]'', ur wezh kevreet.",
        "newarticle": "(Nevez)",
        "newarticletext": "Heuliet hoc'h eus ul liamm a gas d'ur bajenn n'eo ket bet savet evit c'hoazh.\nA-benn krouiñ ar bajenn-se, krogit da skrivañ er prenestr skridaozañ dindan (gwelet ar [$1 bajenn skoazell] evit gouzout hiroc'h).\nM'emaoc'h en em gavet amañ dre fazi, klikit war bouton '''kent''' ho merdeer evit mont war ho kiz.",
-       "anontalkpagetext": "---- ''Homañ eo ar bajenn gaozeal evit un implijer(ez) dizanv n'eus ket krouet kont ebet evit c'hoazh pe na implij ket anezhi.\nSetu perak e rankomp ober gant ar chomlec'h IP niverel evit anavezout anezhañ/i.\nGallout a ra ur chomlec'h a seurt-se bezañ rannet etre meur a implijer(ez).\nMa'z oc'h un implijer(ez) dizanv ha ma stadit ez eus bet kaset deoc'h kemennadennoù na sellont ket ouzhoc'h, gallout a rit [[Special:UserLogin/signup|krouiñ ur gont]]pe [[Special:UserLogin|kevreañ]] kuit a vagañ muioc'h a gemmesk gant implijerien dizanv all.",
+       "anontalkpagetext": "---- ''Homañ eo ar bajenn gaozeal evit un implijer(ez) dizanv n'eus ket krouet kont ebet evit c'hoazh pe na implij ket anezhi.\nSetu perak e rankomp ober gant ar chomlec'h IP niverel evit anavezout anezhañ/i.\nGallout a ra ur chomlec'h a seurt-se bezañ rannet etre meur a implijer(ez).\nMa'z oc'h un implijer(ez) dizanv ha ma stadit ez eus bet kaset deoc'h kemennadennoù na sellont ket ouzhoc'h, gallout a rit [[Special:CreateAccount|krouiñ ur gont]]pe [[Special:UserLogin|kevreañ]] kuit a vagañ muioc'h a gemmesk gant implijerien dizanv all.",
        "noarticletext": "N'eus tamm skrid ebet war ar bajenn-mañ evit poent.\nGallout a rit [[Special:Search/{{PAGENAME}}|klask an titl anezhi]] e pajennoù all,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} klask en oberiadennoù liammet], pe [{{fullurl:{{FULLPAGENAME}}|action=edit}} krouiñ ar bajenn]</span>.",
        "noarticletext-nopermission": "N'eus, evit ar mare, tamm testenn ebet war ar bajenn-mañ.\nGallout a rit [[Special:Search/{{PAGENAME}}|klask titl ar bajenn-mañ]] war pajennoù all,\npe <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} klask er marilhoù kar]</span>, met n'oc'h ket aotreet da grouiñ ar bajenn-mañ.",
        "missing-revision": "N'eus ket eus adwel niv. $1 eus ar bajenn anvet « {{FULLPAGENAME}} ».\n\nC'hoarvezout a ra peurliesañ pa vez heuliet ul liamm istorel dispredet war-zu ur bajenn zo bet dilamet.\nGallout a reot kavout muioc'h a vunudoù e [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} renabl an dilamadurioù].",
        "rows": "Linennoù :",
        "columns": "Bannoù",
        "searchresultshead": "Klask",
-       "stub-threshold": "Bevenn uhelañ evit al <a href=\"#\" class=\"stub\">liammoù war-du an danvez pennadoù</a> (okted) :",
+       "stub-threshold": "Bevenn uhelañ evit al liammoù war-du an danvez pennadoù ($1) :",
        "stub-threshold-disabled": "Diweredekaet",
        "recentchangesdays": "Niver a zevezhioù da ziskouez er c'hemmoù diwezhañ :",
        "recentchangesdays-max": "D'ar muiañ $1 {{PLURAL:$1|deiz|deiz}}",
        "prefs-emailconfirm-label": "Kadarnaat ar postel :",
        "youremail": "Postel :",
        "username": "{{GENDER:$1|Anv implijer|Anv implijerez}}:",
-       "prefs-memberingroups": "{{GENDER:$2|Ezel}} eus {{PLURAL:$1|ar strollad|ar strolladoù}} :",
+       "prefs-memberingroups": "{{GENDER:$2|Ezel}} eus {{PLURAL:$1|ar strollad|ar strolladoù}}:",
        "prefs-registration": "Deiziad enskrivañ :",
        "yourrealname": "Anv gwir*",
        "yourlanguage": "Yezh an etrefas&nbsp;",
        "upload-form-label-infoform-description": "Deskrivadur",
        "upload-form-label-usage-title": "Implij",
        "upload-form-label-usage-filename": "Anv ar restr",
-       "foreign-structured-upload-form-label-infoform-categories": "Rummadoù",
-       "foreign-structured-upload-form-label-infoform-date": "Deiziad",
+       "upload-form-label-infoform-categories": "Rummadoù",
+       "upload-form-label-infoform-date": "Deiziad",
        "backend-fail-stream": "Dibosupl eo lenn ar restr $1.",
        "backend-fail-backup": "Dibosupl enrollañ ar restr $1.",
        "backend-fail-notexists": "N'eus ket eus ar restr $1.",
        "delete-confirm": "Diverkañ \"$1\"",
        "delete-legend": "Diverkañ",
        "historywarning": "<strong>Diwallit :</strong> Emaoc'h war-nes diverkañ ur bajenn dezhi un istor gant {{PLURAL:$1|adweladenn}} :",
+       "historyaction-submit": "Diskouez",
        "confirmdeletetext": "War-nes diverkañ da viken ur bajenn pe ur skeudenn eus ar bank roadennoù emaoc'h. Diverket e vo ivez an holl stummoù kozh stag outi.\nKadarnait, mar plij, eo mat an dra-se hoc'h eus c'hoant da ober, e komprenit mat an heuliadoù, hag e rit se diouzh ar [[{{MediaWiki:Policy-url}}]].",
        "actioncomplete": "Diverkadenn kaset da benn",
        "actionfailed": "Ober c'hwitet",
index 7d3500a..e4ed30f 100644 (file)
        "noname": "Niste izabrali ispravno korisničko ime.",
        "loginsuccesstitle": "Prijavljen",
        "loginsuccess": "'''Sad ste prijavljeni na {{SITENAME}} kao \"$1\".'''",
-       "nosuchuser": "Ne postoji korisnik s imenom \"$1\".\nKorisnička imena razlikuju velika i mala slova.\nProvjerite Vaš unos ili [[Special:UserLogin/signup|napravite novi korisnički račun]].",
+       "nosuchuser": "Ne postoji korisnik s imenom \"$1\".\nKorisnička imena razlikuju velika i mala slova.\nProvjerite Vaš unos ili [[Special:CreateAccount|napravite novi korisnički račun]].",
        "nosuchusershort": "Ne postoji korisnik s imenom \"$1\".\nProvjerite jeste li dobro ukucali.",
        "nouserspecified": "Morate izabrati korisničko ime.",
        "login-userblocked": "Ovaj korisnik je blokiran. Prijava nije dopuštena.",
        "accmailtext": "Nasumično odabrana šifra za [[User talk:$1|$1]] je poslata na adresu $2.\n\nŠifra/lozinka za ovaj novi račun može biti promijenjena na stranici ''[[Special:ChangePassword|izmjene šifre]]'' nakon prijave.",
        "newarticle": "(Novi)",
        "newarticletext": "Došli ste na stranicu koja još nema sadržaja.\n*Ako želite unijeti sadržaj, počnite tipkati u prozor ispod ovog teksta.\n*Ako Vam treba pomoć, idite na [$1 stranicu za pomoć].\n*Ako ste ovamo dospjeli slučajno, kliknite na dugme \"Nazad\" (''Back'') u Vašem internetskom pregledniku.",
-       "anontalkpagetext": "----''Ovo je stranica za razgovor za anonimnog korisnika koji još nije napravio nalog ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo identifikovali njega ili nju.\nTakvu adresu može dijeliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene nebitne primjedbe, molimo Vas da [[Special:UserLogin/signup|napravite nalog]] ili se [[Special:UserLogin|prijavite]] da biste izbjegli buduću zabunu sa ostalim anonimnim korisnicima.''",
+       "anontalkpagetext": "----''Ovo je stranica za razgovor za anonimnog korisnika koji još nije napravio nalog ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo identifikovali njega ili nju.\nTakvu adresu može dijeliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene nebitne primjedbe, molimo Vas da [[Special:CreateAccount|napravite nalog]] ili se [[Special:UserLogin|prijavite]] da biste izbjegli buduću zabunu sa ostalim anonimnim korisnicima.''",
        "noarticletext": "Na ovoj stranici trenutno nema teksta.\nMožete [[Special:Search/{{PAGENAME}}|tražiti naslov ove stranice]] na drugim stranicama,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tražiti u povezanim zapisnicima] ili [{{fullurl:{{FULLPAGENAME}}|action=edit}} napraviti ovu stranicu]</span>.",
        "noarticletext-nopermission": "Trenutno nema teksta na ovoj stranici.\nMožete [[Special:Search/{{PAGENAME}}|tražiti ovaj naslov stranice]] na drugim stranicama ili <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane zapisnike]</span>, ali nemate dozvolu da napravite ovu stranicu.",
        "missing-revision": "Uređivanje broj $1 na stranici \"{{FULLPAGENAME}}\" ne postoji.\n\nOvo se obično dešava kad pratite zastarjelu vezu na stranicu koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protokolu brisanja].",
        "upload-form-label-infoform-description": "Opis",
        "upload-form-label-usage-title": "Korištenje",
        "upload-form-label-usage-filename": "Ime datoteke",
-       "foreign-structured-upload-form-label-own-work": "Ovo je moje djelo",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorije",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Potvrđujem da postavljam ovu datoteku u skladu s uslovima korištenja i pravilima o licenciranju na {{GRAMMAR:dativ|{{SITENAME}}}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Ako niste u stanju postaviti ovu datoteku pod pravilima {{GRAMMAR:genitiv|{{SITENAME}}}}, molimo zatvorite ovaj prozor i pokušajte drugom metodom.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Također možete pokušati [[Special:Upload|na standardnoj stranici za postavljanje]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Shvatam da postavljam ovu datoteku na zajedničko spremište. Potvrđujem da to činim u skladu s uslovima korištenja i ovdašnjim pravilima licenciranja.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Ako niste u stanju postaviti ovu datoteku pod pravilima zajedničkog skladišta, molimo zatvorite ovaj prozor i pokušajte drugom metodom.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Možete također pokušati koristeći  [[Special:Upload|stranicu za postavljanje na  {{GRAMMAR:dativ|{{SITENAME}}}}]], ako se ova datoteka može postaviti pod tamošnjim pravilima.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Potvrđujem da posjedujem autorska prava za ovu datoteku i slažem se da ću je neopozivo postaviti na Wikimedia Commons pod licencom [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], te se slažem s [https://wikimediafoundation.org/wiki/Terms_of_Use uslovima korištenja].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Ako ne posjedujete autorska prava za ovu datoteku ili je želite postaviti pod drugom licencom, imajte na umu da možete koristiti [https://commons.wikimedia.org/wiki/Special:UploadWizard čarobnjak za postavljanje datoteka na Commonsu].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Također možete koristiti [[Special:Upload|stranicu za postavljanje datoteka na {{GRAMMAR:dativ|{{SITENAME}}}}]] ako pravila te stranice dozvoljavaju postavljanje ove datoteke.",
+       "upload-form-label-own-work": "Ovo je moje djelo",
+       "upload-form-label-infoform-categories": "Kategorije",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Potvrđujem da postavljam ovu datoteku u skladu s uslovima korištenja i pravilima o licenciranju na {{GRAMMAR:dativ|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Ako niste u stanju postaviti ovu datoteku pod pravilima {{GRAMMAR:genitiv|{{SITENAME}}}}, molimo zatvorite ovaj prozor i pokušajte drugom metodom.",
+       "upload-form-label-not-own-work-local-generic-local": "Također možete pokušati [[Special:Upload|na standardnoj stranici za postavljanje]].",
+       "upload-form-label-own-work-message-generic-foreign": "Shvatam da postavljam ovu datoteku na zajedničko spremište. Potvrđujem da to činim u skladu s uslovima korištenja i ovdašnjim pravilima licenciranja.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Ako niste u stanju postaviti ovu datoteku pod pravilima zajedničkog skladišta, molimo zatvorite ovaj prozor i pokušajte drugom metodom.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Možete također pokušati koristeći  [[Special:Upload|stranicu za postavljanje na  {{GRAMMAR:dativ|{{SITENAME}}}}]], ako se ova datoteka može postaviti pod tamošnjim pravilima.",
        "backend-fail-stream": "Ne mogu emitirati datoteku $1.",
        "backend-fail-backup": "Ne može sigurnosno kopirati datoteku $1.",
        "backend-fail-notexists": "Datoteka $1 ne postoji.",
index 44634f2..31ccf1d 100644 (file)
@@ -97,7 +97,7 @@
        "tog-ccmeonemails": "Envia’m còpies dels missatges que enviï als altres usuaris",
        "tog-diffonly": "Amaga el contingut de la pàgina davall de la taula de diferències",
        "tog-showhiddencats": "Mostra les categories ocultes",
-       "tog-norollbackdiff": "Omet la pàgina de diferències després de realitzar una reversió",
+       "tog-norollbackdiff": "No mostris les diferències després de realitzar una reversió",
        "tog-useeditwarning": "Avisa'm quan surti d'una pàgina d'edició amb canvis sense desar",
        "tog-prefershttps": "Utilitza sempre una connexió segura en iniciar una sessió",
        "underline-always": "Sempre",
        "noname": "No heu especificat un nom vàlid d'usuari.",
        "loginsuccesstitle": "Sessió iniciada",
        "loginsuccess": "Heu iniciat la sessió a {{SITENAME}} com a «$1».",
-       "nosuchuser": "No hi ha cap usuari anomenat «$1».\nReviseu-ne l'ortografia (recordeu que es distingeixen les majúscules i minúscules), o [[Special:UserLogin/signup|creeu un compte d'usuari nou]].",
+       "nosuchuser": "No hi ha cap usuari anomenat «$1».\nReviseu-ne l'ortografia (recordeu que es distingeixen les majúscules i minúscules), o [[Special:CreateAccount|creeu un compte d'usuari nou]].",
        "nosuchusershort": "No hi ha cap usuari anomenat «$1». Comproveu que ho hàgiu escrit correctament.",
        "nouserspecified": "Heu d'especificar un nom d'usuari.",
        "login-userblocked": "Aquest usuari està bloquejat. Inici de sessió no permès.",
        "botpasswords-created-title": "S'ha creat la contrasenya del bot",
        "botpasswords-created-body": "S'ha creat la contrasenya per al bot «$1» de l'usuari «$2».",
        "botpasswords-updated-title": "Contrasenya de bot actualitzada",
+       "botpasswords-not-exist": "L'usuari «$1» no té una contrasenya de bot anomenada «$2».",
        "resetpass_forbidden": "No poden canviar-se les contrasenyes",
        "resetpass-no-info": "Heu d'estar registrats en un compte per a poder accedir directament a aquesta pàgina.",
        "resetpass-submit-loggedin": "Canvia la contrasenya",
        "minoredit": "Aquesta és una modificació menor",
        "watchthis": "Vigila aquesta pàgina",
        "savearticle": "Desa la pàgina",
+       "publishpage": "Publica la pàgina",
        "preview": "Previsualització",
        "showpreview": "Mostra una previsualització",
        "showdiff": "Mostra els canvis",
        "accmailtext": "S'ha enviat una contrasenya aleatòria a $2 per a {{GENDER:$1|l'usuari|la usuària}} [[User talk:$1|$1]].\n\nLa contrasenya per aquest nou compte pot ser canviada a la pàgina de ''[[Special:ChangePassword|canvi de contrasenya]]'' un cop connectat.",
        "newarticle": "(Nou)",
        "newarticletext": "Heu seguit un enllaç a una pàgina que encara no existeix.\nPer a crear-la, comenceu a escriure en l'espai de sota\n(vegeu l'[$1 ajuda] per a més informació).\nSi sou ací per error, simplement cliqueu al botó «Enrere» del vostre navegador.",
-       "anontalkpagetext": "----''Aquesta és la pàgina de discussió d'un usuari anònim que encara no ha creat un compte o que no fa servir el seu nom registrat. Per tant, hem de fer servir la seua adreça IP numèrica per a identificar-lo. Una adreça IP pot ser compartida per molts usuaris. Si sou un usuari anònim, i trobeu que us han adreçat comentaris inoportuns, si us plau, [[Special:UserLogin/signup|creeu-vos un compte]], o [[Special:UserLogin|entreu en el vostre compte]] si ja en teniu un, per a evitar futures confusions amb altres usuaris anònims.''",
+       "anontalkpagetext": "----''Aquesta és la pàgina de discussió d'un usuari anònim que encara no ha creat un compte o que no fa servir el seu nom registrat. Per tant, hem de fer servir la seua adreça IP numèrica per a identificar-lo. Una adreça IP pot ser compartida per molts usuaris. Si sou un usuari anònim, i trobeu que us han adreçat comentaris inoportuns, si us plau, [[Special:CreateAccount|creeu-vos un compte]], o [[Special:UserLogin|entreu en el vostre compte]] si ja en teniu un, per a evitar futures confusions amb altres usuaris anònims.''",
        "noarticletext": "Actualment no hi ha text en aquesta pàgina.\nPodeu [[Special:Search/{{PAGENAME}}|cercar aquest títol]] en altres pàgines,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercar en els registres]\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear-la ara]</span>.",
        "noarticletext-nopermission": "Actualment no hi ha text en aquesta pàgina.\nPodeu [[Special:Search/{{PAGENAME}}|cercar aquest títol]] en altres pàgines o bé <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercar en els registres relacionats]</span>, però no teniu permisos per crear la pàgina.",
        "missing-revision": "La revisió núm. $1 de la pàgina anomenada «{{FULLPAGENAME}}» no existeix.\n\nNormalment això ocorre quan seguiu un enllaç d’historial desactualitzat que apunta a una pàgina que s’ha suprimit.\nPodeu trobar detalls en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registre de supressions].",
        "currentrev-asof": "Revisió de $1",
        "revisionasof": "Revisió del $1",
        "revision-info": "La revisió el $1 per {{GENDER:$6|$2}}$7",
-       "previousrevision": "←Versió més antiga",
-       "nextrevision": "Versió més nova→",
+       "previousrevision": "← Versió més antiga",
+       "nextrevision": "Versió més nova →",
        "currentrevisionlink": "Versió actual",
        "cur": "act",
        "next": "seg",
        "upload-form-label-infoform-description": "Descripció",
        "upload-form-label-usage-title": "Ús",
        "upload-form-label-usage-filename": "Nom del fitxer",
-       "foreign-structured-upload-form-label-own-work": "Això és el meu propi treball",
-       "foreign-structured-upload-form-label-infoform-categories": "Categories",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "També podeu provar [[Special:Upload|la pàgina de càrrega per defecte]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Entenc que esteu carregant el fitxer en un repositori compartit. Confirmo que ho estic fent seguint les condicions d'ús i les polítiques de llicenciament que s'hi apliquen.",
+       "upload-form-label-own-work": "Això és el meu propi treball",
+       "upload-form-label-infoform-categories": "Categories",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-not-own-work-local-generic-local": "També podeu provar [[Special:Upload|la pàgina de càrrega per defecte]].",
+       "upload-form-label-own-work-message-generic-foreign": "Entenc que esteu carregant el fitxer en un repositori compartit. Confirmo que ho estic fent seguint les condicions d'ús i les polítiques de llicenciament que s'hi apliquen.",
        "backend-fail-stream": "No s'ha pogut transmetre el fitxer $1.",
        "backend-fail-backup": "No s'ha pogut fer una còpia de seguretat del fitxer $1.",
        "backend-fail-notexists": "El fitxer $1 no existeix.",
        "whatlinkshere-prev": "{{PLURAL:$1|anterior|anteriors $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|següent|següents $1}}",
        "whatlinkshere-links": "← enllaços",
-       "whatlinkshere-hideredirs": "$1 redireccions",
-       "whatlinkshere-hidetrans": "$1 inclusions",
-       "whatlinkshere-hidelinks": "$1 enllaços",
+       "whatlinkshere-hideredirs": "Amaga les redireccions",
+       "whatlinkshere-hidetrans": "Amagar transclusions",
+       "whatlinkshere-hidelinks": "Amagar enllaços",
        "whatlinkshere-hideimages": "$1 enllaços de fitxers",
        "whatlinkshere-filters": "Filtres",
        "whatlinkshere-submit": "Vés-hi",
        "exif-compression-6": "JPEG (antic)",
        "exif-copyrighted-true": "Sotmesa a drets d'autor",
        "exif-copyrighted-false": "No s'ha definit l'estat de copyright",
+       "exif-photometricinterpretation-1": "Negra i blanc (negre és 0)",
        "exif-unknowndate": "Data desconeguda",
        "exif-orientation-1": "Normal",
        "exif-orientation-2": "Invertit horitzontalment",
        "confirmemail_body_set": "Algú, probablement vós, des de l'adreça IP $1, \nha establert aquesta adreça de correu electrònic com la del compte «$2» del lloc {{SITENAME}}. \n\nPer confirmar que aquest compte realment us pertany i reactivar \nles facilitats de correu electrònic a {{SITENAME}}, cal que obriu al navegador aquest enllaç:\n\n$3\n\nSi el compte *no* us pertany, cancel·leu l'adreça de correu electrònic seguint aquest enllaç: \n\n$5\n\nAquest codi de confirmació caducarà el $4.",
        "confirmemail_invalidated": "Confirmació d'adreça electrònica cancel·lada",
        "invalidateemail": "Cancel·lació d'adreça electrònica",
+       "notificationemail_subject_changed": "L'adreça registrada de {{SITENAME}} ha canviat",
+       "notificationemail_subject_removed": "L'adreça registrada de {{SITENAME}} ha estat suprimida",
        "scarytranscludedisabled": "[S'ha inhabilitat la transclusió interwiki]",
        "scarytranscludefailed": "[Ha fallat la recuperació de la plantilla per a $1]",
        "scarytranscludefailed-httpstatus": "[Ha fallat la recuperació de la plantilla per a $1: HTTP $2]",
index cf650ea..df8d3a4 100644 (file)
        "noname": "汝未指定蜀萆合法其用戶名。",
        "loginsuccesstitle": "躒入成功",
        "loginsuccess": "'''汝現在已經「$1」其成功躒入{{SITENAME}}了。'''",
-       "nosuchuser": "無總款其用戶名「$1」。\n用户名是大小写敏感其。\n检查汝其拼写,或者覷蜀覷[[Special:UserLogin/signup|開新賬戶]]。",
+       "nosuchuser": "無總款其用戶名「$1」。\n用户名是大小写敏感其。\n检查汝其拼写,或者覷蜀覷[[Special:CreateAccount|開新賬戶]]。",
        "nosuchusershort": "無總款其用戶名「$1」。\n檢查汝其拼寫。",
        "nouserspecified": "汝著指定蜀萆用戶名。",
        "login-userblocked": "茲隻用戶已經乞封鎖去了。登錄是𣍐允許其。",
        "accmailtext": "共[[User talk:$1|$1]]用戶隨機生成其密碼已經發遘$2了。汝登錄以後會使敆[[Special:ChangePassword|修改密碼]]頁面修改茲蜀萆密碼。",
        "newarticle": "(新)",
        "newarticletext": "汝已經跟鏈接跟遘無存在其頁面了。\n卜想創建頁面,敆下底其框框𡅏拍字(覷蜀覷[$1 幫助頁面]有無更更価其幫助)。\n如果汝是無注意來遘茲蜀萆頁面,篤囇汝其瀏覽器上其「返回」按鈕。",
-       "anontalkpagetext": "<em>茲是未登錄其用戶討論頁面。</em>\n故此儂家著使數字IP來確定伊。\n總款其IP地址會乞雅価用戶共享。\n如果蜀隻未登錄其用戶見覺無關係其評論指向汝,起動[[Special:UserLogin/signup|開賬戶]]或者[[Special:UserLogin|登錄]]來避免以後共其它未登錄其用戶混蜀堆。",
+       "anontalkpagetext": "<em>茲是未登錄其用戶討論頁面。</em>\n故此儂家著使數字IP來確定伊。\n總款其IP地址會乞雅価用戶共享。\n如果蜀隻未登錄其用戶見覺無關係其評論指向汝,起動[[Special:CreateAccount|開賬戶]]或者[[Special:UserLogin|登錄]]來避免以後共其它未登錄其用戶混蜀堆。",
        "noarticletext": "現在敆茲蜀頁𡅏無文字。汝會使敆其它其頁面𡅏[[Special:Search/{{PAGENAME}}|討蜀討茲蜀萆標題]],<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 討相關其記錄],或者[{{fullurl:{{FULLPAGENAME}}|action=edit}}編輯茲蜀頁]</span>。",
        "clearyourcache": "'''注意:'''保存以後,汝可能固著刷新汝其瀏覽器緩存來看遘變化。\n* '''火狐/Safari:'''擪下''Shift''篤蜀篤''重新載入'',或者擪蜀擪''Ctrl+F5''或者''Ctrl+R'' (''⌘-R''敆Mac懸頂)\n* '''Google Chrome:'''擪''Ctrl+Shift+R''(敆Mac𡅏使''⌘-Shift-R'')\n* '''Internet Explorer:'''擪''Ctrl''其時候篤蜀篤''刷新'',或者擪''Ctrl+F5''\n* '''Opera:'''敆''工具→首選項''𡅏清除緩存",
        "note": "<strong>注意:</strong>",
index e00c4a5..0a4fdf3 100644 (file)
        "noname": "Ахьа декъашхочун цӀе лартӀахь язйина яц.",
        "loginsuccesstitle": "Хьан пароль тӀеэца, марша догӀила Википеди чу!",
        "loginsuccess": "Хlинца ахьа болх бó оцу цlарца $1.",
-       "nosuchuser": "Иштта $1 цӀе йолуш декъашхочун дӀаяздар дац.\nДекъашхой цӀерш хаалуш ю дӀаяздарца элпаш.\nНийса юьй хьажа цӀе я [[Special:UserLogin/signup|дӀаяздар кхолла керла]].",
+       "nosuchuser": "Иштта $1 цӀе йолуш декъашхочун дӀаяздар дац.\nДекъашхой цӀерш хаалуш ю дӀаяздарца элпаш.\nНийса юьй хьажа цӀе я [[Special:CreateAccount|дӀаяздар кхолла керла]].",
        "nosuchusershort": "Ишта «$1» цӀе йолу декъашхо вац/яц. Хьажа цӀе нийса язйина юй.",
        "nouserspecified": "Ахьа декъашхочун цӀе язъян езаш ю.",
        "login-userblocked": "ХӀара декъашхо блоктоьхна ву/ю. Системин чувала/яла магийна дац.",
        "accmailtext": "Пароль декъашхочуьнан [[User talk:$1|$1]], йина ша шех хитта делла чу элпашах, дӀаяхийтина хьокху $2 адрес тӀе.\n\nЧуваьлла/яла чул тӀехьа , кху гӀирса чохь шуьга хийцалур ю ''[[Special:ChangePassword|шай пароль]]''.",
        "newarticle": "(Kерла)",
        "newarticletext": "ХӀара агӀо хӀинца йоцаш ю.\nНагахь иза кхолла лаахь, хӀотта де лахо гуш долу корехь йоза (мадарра хьажа. [$1 гӀон агӀон чу]).\nНагахь гӀалат даьлла цахууш кхечехь кхузе, атта тӀе тӀаӀа йе '''юха йоккхуриг''' хьай браузеран тӀехь.",
-       "anontalkpagetext": "----''ХӀара дийцаре агӀо къайлаха декъашхочуьна ю, хӀинца дӀаяздар доцу, я лелош воцуш/йоцуш.\nЦундела иза вовзийта/йовзийта лелош ду терахьца IP-адрес.\nИза терахь долу меттиг хила мега кхечу декъашхойчух терра.\nНагахь хьо къайлах волу декъашхо валахь хьайна хаам кхаьчна аьлла хеташн, хьуна хьажийна доцуш, дехар до, кхолла хьай меттиг кху чохь[[Special:UserLogin/signup|дӀаяздар кхоллар]] я [[Special:UserLogin|системин чугӀо]],",
+       "anontalkpagetext": "----''ХӀара дийцаре агӀо къайлаха декъашхочуьна ю, хӀинца дӀаяздар доцу, я лелош воцуш/йоцуш.\nЦундела иза вовзийта/йовзийта лелош ду терахьца IP-адрес.\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}}}} лаха оцуьнах терадерг кхечу тептаршкахь].</span>",
        "missing-revision": "АгӀона «{{FULLPAGENAME}}» верси $1 яц.\n\nИшта хуьйла ширелла дӀаяьккхина агӀонан хьажораган дихьа делча.\nМа-дара хила мега [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дӀайаьхарш йолу тептар] чохь.",
        "upload-form-label-infoform-description": "Цуьнах лаьцна",
        "upload-form-label-usage-title": "Лелор",
        "upload-form-label-usage-filename": "файлан цӀе",
-       "foreign-structured-upload-form-label-own-work": "ХӀара сан долара болх бу",
-       "foreign-structured-upload-form-label-infoform-categories": "Категореш",
-       "foreign-structured-upload-form-label-infoform-date": "Терахь",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "ХӀара файл {{SITENAME}} сайтан бакъонашца чуйоккхила делахь, хьайн таро ю [[Special:Upload|хӀара агӀо]] лелаян.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Аса со хӀокху файлан авторан бакъонаш долахь ерг хилар бакъдо, цундела хӀара файл [https://creativecommons.org/licenses/by-sa/4.0/deed.ru Creative Commons Attribution-ShareAlike 4.0] лицензица Викигуламан чуяккха бакъо ю, цул совнах [https://wikimediafoundation.org/wiki/ хӀокху хьолаца лело] а мега.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "ХӀокху файлан авторан бакъонаш хьай яцахь, я хьайна кхечу лицензица яржо лууш делахь хьажа [https://commons.wikimedia.org/wiki/Special:UploadWizard Викигуламера чуяхаран говзанча] лелон тароне.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "ХӀара файл {{SITENAME}} сайтан бакъонашца чуйоккхила делахь, хьайн таро ю [[Special:Upload|хӀара агӀо]] лелаян.",
+       "upload-form-label-own-work": "ХӀара сан долара болх бу",
+       "upload-form-label-infoform-categories": "Категореш",
+       "upload-form-label-infoform-date": "Терахь",
+       "upload-form-label-not-own-work-local-generic-foreign": "ХӀара файл {{SITENAME}} сайтан бакъонашца чуйоккхила делахь, хьайн таро ю [[Special:Upload|хӀара агӀо]] лелаян.",
        "backend-fail-stream": "ДӀаяккха цатарло файл «$1».",
        "backend-fail-backup": "Таро яц файлан $1 тӀаьхьалонан копиян.",
        "backend-fail-notexists": "Файл $1 яц.",
index 17e3f08..31f3cf0 100644 (file)
        "noname": "Wala ikaw mag-specify og valid nga user name.",
        "loginsuccesstitle": "Malamposon ang pagpaila",
        "loginsuccess": "'''Nailhan ka na sa {{SITENAME}} isip \"$1\".'''",
-       "nosuchuser": "Walay gumagamit nga may pangalang \"$1\".\nCase sensitive ang mga user name.\nI-tsek ang imong espeling, o [[Special:UserLogin/signup|paghimo og bag-ong akawnt]].",
+       "nosuchuser": "Walay gumagamit nga may pangalang \"$1\".\nCase sensitive ang mga user name.\nI-tsek ang imong espeling, o [[Special:CreateAccount|paghimo og bag-ong akawnt]].",
        "nosuchusershort": "Walay gumagamit nga may pangalang \"$1\".\nI-tsek ang imong espeling.",
        "nouserspecified": "Kinahanglan mag-specify ka og username.",
        "wrongpassword": "Sayop nga pasword ang naentra.\nPalihog sulayi'g usab.",
        "allpagessubmit": "Sige",
        "categories": "Mga kategoriya",
        "categoriespagetext": "Ang mosunod nga mga kategoriya adunay sulod nga panid o medya.\n[[Special:UnusedCategories|Unused categories]] are not shown here.\nAlso see [[Special:WantedCategories|wanted categories]].",
-       "special-categories-sort-abc": "han-aya nga paalpabetikal",
        "linksearch": "Mga sumpay sa gawas",
        "listgrouprights-members": "(talaan sa mga miyembro)",
        "emailuser": "I-email kaning gumagamit",
index 696fffe..6abe392 100644 (file)
        "noname": "ناوی بەکارهێنەرییەکی گۆنجاوت دیاری نەکردووه.",
        "loginsuccesstitle": "چوویە ناوەوە",
        "loginsuccess": "'''ئێستا بە ناوی «$1»ەوە لە {{SITENAME}} چوویتەتەژوورەوە.'''",
-       "nosuchuser": "بەکارھێنەرێک بە ناوی «$1» نیە.\nناوی بەکارھێنەر بە گەورە و بچووک بوونی پیتەکان ھەستیارە.\nڕێنووسەکەت چاولێکەرەوە، یان [[Special:UserLogin/signup|ھەژمارێکی نوێ دروست بکە]].",
+       "nosuchuser": "بەکارھێنەرێک بە ناوی «$1» نیە.\nناوی بەکارھێنەر بە گەورە و بچووک بوونی پیتەکان ھەستیارە.\nڕێنووسەکەت چاولێکەرەوە، یان [[Special:CreateAccount|ھەژمارێکی نوێ دروست بکە]].",
        "nosuchusershort": "بەکارھێنەرێک بە ناوی «$1»ەوە نیە.\nبە نووسراوەکەتدا بچۆرەوە.",
        "nouserspecified": "دەبێت ناوێکی بەکارھێنەری دەستنیشان بکەیت.",
        "login-userblocked": "ئەم بەکارهێنەرە بڵۆک کراوە. چوونەژوورەوە ڕێگەپێنەدراوە.",
        "accmailtext": "تێپەڕوشەیەک کە بە هەڕەمەکی بۆ [[User talk:$1|$1]] دروست کرا، نێررا بۆ $2. دەتوانیت لە پەڕەی <em>[[Special:ChangePassword|گۆڕینی تێپەڕوشەدا]]</em> لە کاتی چوونەژوورەوەدا بیگۆڕی.",
        "newarticle": "(نوێ)",
        "newarticletext": "بە دوای بەستەری پەڕەیەک کەوتووی کە ھێشتا دروست نەکراوە.\nبۆ دروست کردنی پەڕەکە، لە چوارچێوەکەی خوارەوە دەست بکە بە تایپ کردن. (بۆ زانیاری زورتر\n[$1 یارمەتی] ببینە).\nئەگەر بە ھەڵەوە ھاتویتە ئێرە، لە سەر دوگمەی '''back'''ی وێبگەڕەکەت کلیک بکە.",
-       "anontalkpagetext": "----''ئەمە لاپەڕەی وتووێژە بۆ بەکارهێنەرێکی نەناسراوە کە هێشتا هەژمارەی درووست‌نەکردووه یان کەڵکی‌ لێ وەرناگرێ .\nلەبەر ئەوە مەجبوورین ئای‌پی ئەدرەسەکی ژمارەیی بۆ ناساندنی بەکار بێنین.\nئای‌پی ئەدرەسی وا لەوانەیه لە لایەن چەندین بەکارهێنەروە بەکاربێت.\nئەگەر تۆ بەکارهێنەرێکی نەناسراوی و هەست ئەکەی ئەم لێدوانە پەیوەندی بە تۆوە نیە تکایە [[Special:UserLogin/signup|ھەژمارێکی نوێ دروست بکە]] یان [[Special:UserLogin|بچۆ ژوورەوە]] لەبەر ئەوەی لەداهاتوودا دەگەڵ بەکارهێنەرانی‌ نەناسراوی دی تێکەڵ نەکرێیت. ''",
+       "anontalkpagetext": "----''ئەمە لاپەڕەی وتووێژە بۆ بەکارهێنەرێکی نەناسراوە کە هێشتا هەژمارەی درووست‌نەکردووه یان کەڵکی‌ لێ وەرناگرێ .\nلەبەر ئەوە مەجبوورین ئای‌پی ئەدرەسەکی ژمارەیی بۆ ناساندنی بەکار بێنین.\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}}|بۆ ئەم ناوە بگەڕێی]]، یان <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} لە لۆگە پەیوەندیدارەکاندا بگەڕێی]</span>، بەڵام ناتوانی ئەم پەڕەیە دروست بکەی.",
        "userpage-userdoesnotexist": "ھەژماری بەکارھێنەریی «$1» تۆمار نەکراوە.\nتکایە دڵنیا ببەرەوە ئەگەر دەتھەوێت ئەم پەڕەیە دروست یان دەستکاری بکەیت.",
index 089bfc3..bfa212d 100644 (file)
        "noname": "Догъру къулланыджы адыны кирсетмединъиз.",
        "loginsuccesstitle": "Кириш япылды",
        "loginsuccess": "'''$1 адынен {{SITENAME}} сайтында чалышып оласынъыз.'''",
-       "nosuchuser": "«$1» адлы къулланыджы ёкъ.\nКъулланыджы адларында буюк ве кичик арифлер арасында фаркъ бар.\nДогъру язгъанынъызны тешкеринъиз я да [[Special:UserLogin/signup|янъы къулланыджы эсабыны ачынъыз]].",
+       "nosuchuser": "«$1» адлы къулланыджы ёкъ.\nКъулланыджы адларында буюк ве кичик арифлер арасында фаркъ бар.\nДогъру язгъанынъызны тешкеринъиз я да [[Special:CreateAccount|янъы къулланыджы эсабыны ачынъыз]].",
        "nosuchusershort": "«$1» адлы къулланыджы тапыламады. Адынъызны догъру язгъанынъыздан эмин олунъыз.",
        "nouserspecified": "Къулланыджы адыны кирсетмек керексинъиз.",
        "login-userblocked": "Бу къулланыджы блок этильген. Кирмеге рухсет этильмей.",
        "accmailtext": "[[User talk:$1|$1]] ичюн тесадуфий ишаретлерден яратылгъан пароль $2 адресине ёлланды.\n\nБу янъы эсап ичюн пароль, кириш япкъандан сонъ ''[[Special:ChangePassword|парольни денъиштир]]'' болюгинде денъиштирилип олур.",
        "newarticle": "(Янъы)",
        "newarticletext": "Сиз бу багълантынен шимдилик ёкъ олгъан саифеге авуштынъыз. Янъы бир саифе яратмакъ ичюн ашагъыда булунгъан пенджереге метин язынъыз (тафсилятлы малюмат алмакъ ичюн [$1 ярдым саифесине] бакъынъыз). Бу саифеге тесадюфен авушкъан олсанъыз, браузеринъиздеки '''кери''' дёгмесине басынъыз.",
-       "anontalkpagetext": "----''Бу музакере саифеси шимдилик къайд олунмагъан я да отурымыны ачмагъан адсыз (аноним) къулланыджыгъа менсюптир. Идентификация ичюн IP адрес ишлетиле. Бир IP адресинден бир къач къулланыджы файдаланып ола.\nЭгер сиз аноним къулланыджы олсанъыз ве сизге кельген беянатларны янълыштан кельгенини беллесенъиз, лютфен, артыкъ бунынъ киби къарышыкълыкъ олмасын деп [[Special:UserLogin/signup|къайд олунъыз]] я да [[Special:UserLogin|отурым ачынъыз]].''",
+       "anontalkpagetext": "----''Бу музакере саифеси шимдилик къайд олунмагъан я да отурымыны ачмагъан адсыз (аноним) къулланыджыгъа менсюптир. Идентификация ичюн IP адрес ишлетиле. Бир IP адресинден бир къач къулланыджы файдаланып ола.\nЭгер сиз аноним къулланыджы олсанъыз ве сизге кельген беянатларны янълыштан кельгенини беллесенъиз, лютфен, артыкъ бунынъ киби къарышыкълыкъ олмасын деп [[Special:CreateAccount|къайд олунъыз]] я да [[Special:UserLogin|отурым ачынъыз]].''",
        "noarticletext": "Бу саифе шимди боштыр. Бу серлеваны башкъа саифелерде [[Special:Search/{{PAGENAME}}|къыдырып оласынъыз]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} багълы журнал къайдларыны къыдырып оласынъыз] я да бу саифени озюнъиз [{{fullurl:{{FULLPAGENAME}}|action=edit}} язып оласынъыз]</span>.",
        "noarticletext-nopermission": "Бу саифе шимди боштыр. Бу серлеваны башкъа саифелерде [[Special:Search/{{PAGENAME}}|къыдыра биле]] я да <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} багълы журналларны козьден кечире билесинъиз]</span>.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" адлы къулланыджы ёкътыр. Тамам бу саифени денъиштирмеге истегенинъизни тешкеринъиз.",
        "allpages-bad-ns": "{{SITENAME}} сайтында «$1» исим фезасы ёкътыр.",
        "categories": "Саифе категориялары",
        "categoriespagetext": "Ашагъыдаки {{PLURAL:$1|1=категорияда|категорияларда}} саифе я да медиа файллар бар.\n[[Special:UnusedCategories|Къулланылмагъан категориялар]] мында косьтерильмеген.\nАйрыджа [[Special:WantedCategories|талап этильген категорияларнынъ джедвелине]] де бакъынъыз.",
-       "special-categories-sort-count": "сайыларына коре сырала",
-       "special-categories-sort-abc": "элифбе сырасынен сырала",
        "linksearch": "Тыш багълантылар",
        "linksearch-pat": "Къыдырув ичюн шаблон:",
        "linksearch-ns": "Исим фезасы:",
        "watchlisttools-raw": "Козетюв джедвелини адий метин оларакъ денъиштир",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|музакере]])",
        "version": "Версия",
-       "fileduplicatesearch-legend": "Дубликатны къыдыр",
        "fileduplicatesearch-filename": "Файл ады:",
        "fileduplicatesearch-submit": "Къыдыр",
        "fileduplicatesearch-info": "$1 × $2 пиксел<br />Файл буюклиги: $3<br />MIME чешити: $4",
index ffdd83c..fc114e5 100644 (file)
        "noname": "Doğru qullanıcı adını kirsetmediñiz.",
        "loginsuccesstitle": "Kiriş yapıldı",
        "loginsuccess": "'''$1 adınen {{SITENAME}} saytında çalışıp olasıñız.'''",
-       "nosuchuser": "\"$1\" adlı qullanıcı yoq.\nQullanıcı adlarında büyük ve kiçik arifler arasında farq bar.\nDoğru yazğanıñıznı teşkeriñiz ya da [[Special:UserLogin/signup|yañı qullanıcı esabını açıñız]].",
+       "nosuchuser": "\"$1\" adlı qullanıcı yoq.\nQullanıcı adlarında büyük ve kiçik arifler arasında farq bar.\nDoğru yazğanıñıznı teşkeriñiz ya da [[Special:CreateAccount|yañı qullanıcı esabını açıñız]].",
        "nosuchusershort": "\"$1\" adlı qullanıcı tapılamadı. Adıñıznı doğru yazğanıñızdan emin oluñız.",
        "nouserspecified": "Qullanıcı adını kirsetmek kereksiñiz.",
        "login-userblocked": "Bu qullanıcı blok etilgen. Kirmege ruhset etilmey.",
        "accmailtext": "[[User talk:$1|$1]] içün tesadufiy işaretlerden yaratılğan parol $2 adresine yollandı.\n\nBu yañı esap içün parol, kiriş yapqandan soñ ''[[Special:ChangePassword|parolni deñiştir]]'' bölüginde deñiştirilip olur.",
        "newarticle": "(Yañı)",
        "newarticletext": "Siz bu bağlantınen şimdilik yoq olğan saifege avuştıñız. Yañı bir saife yaratmaq içün aşağıda bulunğan pencerege metin yazıñız (tafsilâtlı malümat almaq içün [$1 yardım saifesine] baqıñız). Bu saifege tesadüfen avuşqan olsañız, brauzeriñizdeki '''keri''' dögmesine basıñız.",
-       "anontalkpagetext": "----''Bu muzakere saifesi şimdilik qayd olunmağan ya da oturımını açmağan adsız (anonim) qullanıcığa mensüptir. İdentifikatsiya içün IP adres işletile.\nBir IP adresinden bir qaç qullanıcı faydalanıp ola.\nEger siz anonim qullanıcı olsañız ve sizge kelgen beyanatlarnı yañlıştan kelgenini belleseñiz, lütfen, artıq bunıñ kibi qarışıqlıq olmasın dep [[Special:UserLogin/signup|qayd oluñız]] ya da [[Special:UserLogin|oturım açıñız]].''",
+       "anontalkpagetext": "----''Bu muzakere saifesi şimdilik qayd olunmağan ya da oturımını açmağan adsız (anonim) qullanıcığa mensüptir. İdentifikatsiya içün IP adres işletile.\nBir IP adresinden bir qaç qullanıcı faydalanıp ola.\nEger siz anonim qullanıcı olsañız ve sizge kelgen beyanatlarnı yañlıştan kelgenini belleseñiz, lütfen, artıq bunıñ kibi qarışıqlıq olmasın dep [[Special:CreateAccount|qayd oluñız]] ya da [[Special:UserLogin|oturım açıñız]].''",
        "noarticletext": "Bu saife şimdi boştır. Bu serlevanı başqa saifelerde [[Special:Search/{{PAGENAME}}|qıdırıp olasıñız]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} bağlı jurnal qaydlarını qıdırıp olasıñız] ya da bu saifeni özüñiz [{{fullurl:{{FULLPAGENAME}}|action=edit}} yazıp olasıñız]</span>.",
        "noarticletext-nopermission": "Bu saife şimdi boştır. Bu serlevanı başqa saifelerde [[Special:Search/{{PAGENAME}}|qıdıra bile]] ya da <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} bağlı jurnallarnı közden keçire bilesiñiz]</span>.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" adlı qullanıcı yoqtır. Tamam bu saifeni deñiştirmege istegeniñizni teşkeriñiz.",
        "allpages-bad-ns": "{{SITENAME}} saytında \"$1\" isim fezası yoqtır.",
        "categories": "Saife kategoriyaları",
        "categoriespagetext": "Aşağıdaki {{PLURAL:$1|kategoriyada|kategoriyalarda}} saife ya da media fayllar bar.\n[[Special:UnusedCategories|Qullanılmağan kategoriyalar]] mında kösterilmegen.\nAyrıca [[Special:WantedCategories|talap etilgen kategoriyalarnıñ cedveline]] de baqıñız.",
-       "special-categories-sort-count": "sayılarına köre sırala",
-       "special-categories-sort-abc": "elifbe sırasınen sırala",
        "linksearch": "Tış bağlantılar",
        "linksearch-pat": "Qıdıruv içün şablon:",
        "linksearch-ns": "İsim fezası:",
        "watchlisttools-raw": "Közetüv cedvelini adiy metin olaraq deñiştir",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|muzakere]])",
        "version": "Versiya",
-       "fileduplicatesearch-legend": "Dublikatnı qıdır",
        "fileduplicatesearch-filename": "Fayl adı:",
        "fileduplicatesearch-submit": "Qıdır",
        "fileduplicatesearch-info": "$1 × $2 piksel<br />Fayl büyükligi: $3<br />MIME çeşiti: $4",
index 89ce77e..4ae6c23 100644 (file)
        "password-change-forbidden": "Na této wiki nemůžete měnit hesla.",
        "externaldberror": "Buď nastala chyba externí autentizační databáze, nebo nemáte dovoleno měnit svůj externí účet.",
        "login": "Přihlaste se",
+       "login-security": "Ověřte svou identitu",
        "nav-login-createaccount": "Přihlášení / vytvoření účtu",
        "userlogin": "Přihlášení / vytvoření účtu",
        "userloginnocreate": "Přihlášení",
        "userlogin-resetpassword-link": "Zapomněli jste heslo?",
        "userlogin-helplink2": "Nápověda k přihlašování",
        "userlogin-loggedin": "Již jste {{GENDER:$1|přihlášen|přihlášena}} jako $1.\nPomocí formuláře níže se můžete přihlásit jako jiný uživatel.",
+       "userlogin-reauth": "Abyste {{GENDER:$1|prokázal|prokázala|prokázali}}, že jste $1, musíte se znovu přihlásit.",
        "userlogin-createanother": "Vytvořit jiný účet",
        "createacct-emailrequired": "E-mailová adresa",
        "createacct-emailoptional": "E-mailová adresa (nepovinné)",
        "nocookiesnew": "Uživatelský účet byl vytvořen, ale nejste přihlášeni. {{SITENAME}} používá cookies k přihlášení uživatelů. Vy máte cookies vypnuty. Prosím, zapněte je a poté se přihlaste svým novým uživatelským jménem a heslem.",
        "nocookieslogin": "{{SITENAME}} používá cookies k přihlášení uživatelů. Vy máte cookies vypnuty. Prosím zapněte je a zkuste znovu.",
        "nocookiesfornew": "Uživatelský účet nebyl založen, neboť jsme nebyli schopni potvrdit jeho původ.\nUjistěte se, že máte povoleny cookies, obnovte tuto stránku a zkuste to znovu.",
+       "createacct-loginerror": "Účet byl úspěšně vytvořen, ale nemohli jste být automaticky přihlášeni. Pokračujte prosím na [[Special:UserLogin|ruční přihlášení]].",
        "noname": "{{GENDER:|Nezadal|Nezadala|Nezadali}} jste platné uživatelské jméno.",
        "loginsuccesstitle": "Přihlášení bylo úspěšné",
        "loginsuccess": "<strong>Nyní jste na {{grammar:6sg|{{SITENAME}}}} {{GENDER:$1|přihlášen jako uživatel|přihlášena jako uživatelka}} „$1“.</strong>",
-       "nosuchuser": "Neexistuje uživatel se jménem „$1“. U uživatelských jmen se rozlišují malá/velká písmena. Zkontrolujte zápis, nebo si [[Special:UserLogin/signup|vytvořte nový účet]].",
+       "nosuchuser": "Neexistuje uživatel se jménem „$1“.\nU uživatelských jmen se rozlišují malá/velká písmena.\nZkontrolujte zápis, nebo si [[Special:CreateAccount|vytvořte nový účet]].",
        "nosuchusershort": "Neexistuje uživatel se jménem „$1“. Zkontrolujte zápis.",
        "nouserspecified": "Musíte zadat uživatelské jméno.",
        "login-userblocked": "{{GENDER:$1|Tento uživatel je zablokován|Tato uživatelka je zablokována}}. Přihlášení není dovoleno.",
        "createacct-another-realname-tip": "Skutečné jméno je nepovinné.\nPokud se ho rozhodnete uvést, bude použito pro označení autorství vaší práce.",
        "pt-login": "Přihlášení",
        "pt-login-button": "Přihlásit se",
+       "pt-login-continue-button": "Pokračovat v přihlášení",
        "pt-createaccount": "Vytvoření účtu",
        "pt-userlogout": "Odhlásit se",
        "php-mail-error-unknown": "Neznámá chyba v PHP funkci mail()",
        "changepassword-success": "Vaše heslo bylo změněno!",
        "changepassword-throttled": "Provedli jste příliš mnoho pokusů o přihlášení.\nČekejte prosím $1 a zkuste to znovu.",
        "botpasswords": "Hesla pro boty",
-       "botpasswords-summary": "<em>Hesla pro boty</em> umožňují přistupovat k uživatelskému účtu prostřednictví API bez použití hlavních přihlašovacích údajů účtu. Uživatelská oprávnění dostupná po přihlášení pomocí hesla pro boty mohou být omezena.\n\nPokud nevíte, k čemu byste to {{GENDER:|chtěl|chtěla|chtěli}} použít, pravděpodobně byste to používat {{GENDER:|neměl|neměla|neměli}}. Nikdo by vás nikdy neměl žádat, abyste si zde vygenerovali heslo a dali mu ho.",
+       "botpasswords-summary": "<em>Hesla pro boty</em> umožňují přistupovat k uživatelskému účtu prostřednictví API bez použití hlavních přihlašovacích údajů účtu. Uživatelská oprávnění dostupná po přihlášení pomocí hesla pro boty mohou být omezena.\n\nPokud nevíte, k čemu byste to {{GENDER:|chtěl|chtěla|chtěli}} použít, pravděpodobně byste to používat {{GENDER:|neměl|neměla|neměli}}. Nikdo by vás nikdy neměl žádat, abyste si zde {{GENDER:|vygeneroval heslo a dal|vygenerovala heslo a dala|vygenerovali heslo a dali}} mu ho.",
        "botpasswords-disabled": "Hesla pro boty jsou zakázána.",
        "botpasswords-no-central-id": "Abyste {{GENDER:|mohl|mohla|mohl(a)}} použít hesla pro boty, musíte být {{GENDER:|přihlášen|přihlášena|přihlášen(a)}} k centrálnímu účtu.",
        "botpasswords-existing": "Stávající hesla pro boty",
        "botpasswords-invalid-name": "Uvedené uživatelské jméno neobsahuje oddělovač hesel pro boty („$1“).",
        "botpasswords-not-exist": "Uživatel „$1“ nemá heslo pro bota nazvaného „$2“.",
        "resetpass_forbidden": "Hesla nelze změnit.",
+       "resetpass_forbidden-reason": "Hesla nelze změnit: $1",
        "resetpass-no-info": "K této stránce mají přímý přístup jen přihlášení uživatelé.",
        "resetpass-submit-loggedin": "Změnit heslo",
        "resetpass-submit-cancel": "Storno",
        "accmailtext": "Náhodně vygenerované heslo pro uživatele [[User talk:$1|$1]] bylo odesláno na $2. Po přihlášení ho bude možno změnit na [[Special:ChangePassword|stránce pro změnu hesla]].",
        "newarticle": "(Nový)",
        "newarticletext": "Následovali jste odkaz na stránku, která dosud neexistuje.\nPokud ji chcete vytvořit, začněte psát do rámečku níže (více informací najdete v [$1 nápovědě]).\nPokud jste zde omylem, stiskněte ve svém prohlížeči tlačítko <strong>Zpět</strong>.",
-       "anontalkpagetext": "----\n<em>Toto je diskusní stránka anonymního uživatele, který si dosud nevytvořil účet nebo ho nepoužívá.</em>\nK&nbsp;jeho identifikaci proto musíme používat číselnou IP adresu. Takovou IP adresu může sdílet několik uživatelů. Pokud jste anonymní uživatel a&nbsp;máte za to, že jsou vám adresovány irelevantní komentáře, prosíme, [[Special:UserLogin/signup|vytvořte si účet]] nebo [[Special:UserLogin|se přihlaste]], vyhnete se tím budoucí záměně s&nbsp;jinými anonymními uživateli.",
+       "anontalkpagetext": "----\n<em>Toto je diskusní stránka anonymního uživatele, který si dosud nevytvořil účet nebo ho nepoužívá.</em>\nK&nbsp;jeho identifikaci proto musíme používat číselnou IP adresu. Takovou IP adresu může sdílet několik uživatelů. Pokud jste anonymní uživatel a&nbsp;máte za to, že jsou vám adresovány irelevantní komentáře, prosíme, [[Special:CreateAccount|vytvořte si účet]] nebo [[Special:UserLogin|se přihlaste]], vyhnete se tím budoucí záměně s&nbsp;jinými anonymními uživateli.",
        "noarticletext": "Tato stránka zatím neobsahuje žádný text.\nMůžete [[Special:Search/{{PAGENAME}}|zkusit tento název vyhledat]] na jiných stránkách, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} prohlédnout si příslušné protokolovací záznamy] nebo [{{fullurl:{{FULLPAGENAME}}|action=edit}} tuto stránku založit]</span>.",
        "noarticletext-nopermission": "Tato stránka zatím neobsahuje žádný text.\nMůžete [[Special:Search/{{PAGENAME}}|zkusit tento název vyhledat]] na jiných stránkách nebo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} si prohlédnout příslušné protokolovací záznamy]</span>, ale na založení této stránky nemáte oprávnění.",
        "missing-revision": "Revize #$1 stránky s názvem „{{FULLPAGENAME}}“ neexistuje.\n\nToto je obvykle způsobeno tím, že jste následovali zastaralý odkaz na historickou verzi stránky, jež byla smazána.\nPodrobnosti mohou být uvedeny v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} knize smazaných stránek].",
        "userpage-userdoesnotexist": "Uživatelský účet „$1“ není zaregistrován.\nZkontrolujte, zda skutečně chcete tuto stránku vytvořit či editovat.",
        "userpage-userdoesnotexist-view": "Uživatelský účet „$1“ není zaregistrován.",
        "blocked-notice-logextract": "{{GENDER:$1|Tento uživatel|Tato uživatelka}} je momentálně {{GENDER:$1|zablokován|zablokována}}.\nZde je pro přehled zobrazen nejnovější záznam z knihy zablokování:",
-       "clearyourcache": "<strong>Poznámka:</strong> Po uložení musíte smazat cache vašeho prohlížeče, jinak změny neuvidíte.\n* <strong>Firefox / Safari:</strong> Při kliknutí na <em>Aktualizovat</em> držte <em>Shift</em> nebo stiskněte <em>Ctrl-F5</em> nebo <em>Ctrl-R</em> (na Macu <em>⌘-R</em>)\n* <strong>Google Chrome:</strong> Stiskněte <em>Ctrl-Shift-R</em> (na Macu <em>⌘-Shift-R</em>)\n* <strong>Internet Explorer:</strong> Při kliknutí na <em>Aktualizovat</em> držte <em>Ctrl</em> nebo stiskněte <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Smažte obsah cache v menu <em>Nástroje → Nastavení</em>",
+       "clearyourcache": "<strong>Poznámka:</strong> Po uložení musíte smazat cache vašeho prohlížeče, jinak změny neuvidíte.\n* <strong>Firefox / Safari:</strong> Při kliknutí na <em>Aktualizovat</em> držte <em>Shift</em> nebo stiskněte <em>Ctrl-F5</em> nebo <em>Ctrl-R</em> (na Macu <em>⌘-R</em>)\n* <strong>Google Chrome:</strong> Stiskněte <em>Ctrl-Shift-R</em> (na Macu <em>⌘-Shift-R</em>)\n* <strong>Internet Explorer:</strong> Při kliknutí na <em>Aktualizovat</em> držte <em>Ctrl</em> nebo stiskněte <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Jděte do <em>Menu → Nastavení</em> (na Macu <em>Opera → Nastavení</em>) a tam pak <em>Soukromí & bezpečnost → Vymazat údaje o prohlížení → Obrázky a soubory z cache</em>",
        "usercssyoucanpreview": "<strong>Tip:</strong> Použijte tlačítko „{{int:showpreview}}“ k testování vašeho nového CSS před uložením.",
        "userjsyoucanpreview": "<strong>Tip:</strong> Použijte tlačítko „{{int:showpreview}}“ k testování vašeho nového JavaScriptu před uložením.",
        "usercsspreview": "<strong>Pamatujte, že si prohlížíte jen náhled vašeho uživatelského CSS, jelikož dosud nebyl uložen!</strong>",
        "right-override-export-depth": "Exportovat stránky včetně odkazovaných stránek až do hloubky 5",
        "right-sendemail": "Odesílání e-mailů ostatním uživatelům",
        "right-passwordreset": "Prohlížení e-mailů pro znovunastavení hesla",
-       "right-managechangetags": "Vytváření [[Special:Tags|značek]] a jejich mazání z databáze",
+       "right-managechangetags": "Vytváření a (de)aktivace [[Special:Tags|značek]]",
        "right-applychangetags": "Přidávání [[Special:Tags|značek]] k vlastním změnám",
        "right-changetags": "Přidávání libovolných [[Special:Tags|značek]] na jednotlivé revize a protokolovací záznamy a jejich odebírání",
+       "right-deletechangetags": "Mazání [[Special:Tags|značek]] z databáze",
        "grant-generic": "Balíček oprávnění „$1“",
        "grant-group-page-interaction": "Interakce se stránkami",
        "grant-group-file-interaction": "Interakce se soubory",
        "action-viewmyprivateinfo": "prohlížet si své soukromé údaje",
        "action-editmyprivateinfo": "změnit své soukromé údaje",
        "action-editcontentmodel": "editovat model obsahu stránky",
-       "action-managechangetags": "vytvářet a mazat značky z databáze",
+       "action-managechangetags": "vytvářet a (de)aktivovat značky",
        "action-applychangetags": "přidávat značky k vlastním změnám",
        "action-changetags": "přidávat libovolné značky na jednotlivé revize a protokolovací záznamy a odebírat je",
+       "action-deletechangetags": "mazat značky z databáze",
        "nchanges": "$1 {{PLURAL:$1|změna|změny|změn}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od poslední návštěvy}}",
        "enhancedrc-history": "historie",
        "upload-form-label-infoform-description-tooltip": "Stručně popište všechny důležité informace o díle.\nU fotografií zmiňte hlavní zobrazené objekty, příležitost, při které fotografie vznikla, nebo místo.",
        "upload-form-label-usage-title": "Použití",
        "upload-form-label-usage-filename": "Jméno souboru",
-       "foreign-structured-upload-form-label-own-work": "Je to mé vlastní dílo",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorie",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Potvrzuji, že tento soubor načítám v souladu s podmínkami užití a licenčními pravidly na {{grammar:6sg|{{SITENAME}}}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Pokud nemůžete tento soubor načíst v souladu s pravidly {{grammar:2sg|{{SITENAME}}}}, zavřete prosím tento dialog a zkuste jiný způsob.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Také můžete zkusit [[Special:Upload|standardní stránku pro načítání souborů]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Chápu, že soubor načítám na sdílené úložiště. Potvrzuji, že tak činím v souladu s tamními podmínkami užití a licenčními pravidly.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Pokud nemůžete tento soubor načíst v souladu s pravidly sdíleného úložiště, zavřete prosím tento dialog a zkuste jiný způsob.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Také můžete zkusit [[Special:Upload|stránku pro načítání souborů na {{grammar:6sg|{{SITENAME}}}}]], pokud tam lze soubor podle tamních pravidel načíst.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Potvrzuji, že jsem držitelem autorských práv k tomuto souboru a neodvolatelně souhlasím s jeho zveřejněním na Wikimedia Commons pod licencí [https://creativecommons.org/licenses/by-sa/4.0/deed.cs Creative Commons Uveďte původ – Zachovejte licenci 4.0] a souhlasím s [https://wikimediafoundation.org/wiki/Terms_of_Use/cs Podmínkami užití].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Pokud nejste držiteli autorských práv k tomuto souboru nebo si ho přejete zveřejnit pod jinou licencí, zvažte použití [https://commons.wikimedia.org/wiki/Special:UploadWizard Průvodce načítáním souborů na Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Také můžete zkusit [[Special:Upload|stránku pro načítání souborů na {{grammar:6sg|{{SITENAME}}}}]], pokud tamní pravidla načtení tohoto souboru dovolují.",
+       "upload-form-label-own-work": "Je to mé vlastní dílo",
+       "upload-form-label-infoform-categories": "Kategorie",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Potvrzuji, že tento soubor načítám v souladu s podmínkami užití a licenčními pravidly na {{grammar:6sg|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Pokud nemůžete tento soubor načíst v souladu s pravidly {{grammar:2sg|{{SITENAME}}}}, zavřete prosím tento dialog a zkuste jiný způsob.",
+       "upload-form-label-not-own-work-local-generic-local": "Také můžete zkusit [[Special:Upload|standardní stránku pro načítání souborů]].",
+       "upload-form-label-own-work-message-generic-foreign": "Chápu, že soubor načítám na sdílené úložiště. Potvrzuji, že tak činím v souladu s tamními podmínkami užití a licenčními pravidly.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Pokud nemůžete tento soubor načíst v souladu s pravidly sdíleného úložiště, zavřete prosím tento dialog a zkuste jiný způsob.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Také můžete zkusit [[Special:Upload|stránku pro načítání souborů na {{grammar:6sg|{{SITENAME}}}}]], pokud tam lze soubor podle tamních pravidel načíst.",
        "backend-fail-stream": "Soubor $1 nelze streamovat.",
        "backend-fail-backup": "Soubor $1 nelze zazálohovat.",
        "backend-fail-notexists": "Soubor $1 neexistuje.",
        "changecontentmodel-success-text": "Model obsahu stránky [[:$1]] byl změněn.",
        "changecontentmodel-cannot-convert": "Obsah stránky [[:$1]] nelze zkonvertovat na typ $2.",
        "changecontentmodel-nodirectediting": "Model obsahu $1 nepodporuje přímou editaci",
+       "changecontentmodel-emptymodels-title": "Nejsou k dispozici žádné modely obsahu",
+       "changecontentmodel-emptymodels-text": "Obsah stránky [[:$1]] nelze zkonvertovat na žádný typ.",
        "log-name-contentmodel": "Kniha změny modelů obsahu",
        "log-description-contentmodel": "Události týkající se modelů obsahu stránek",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|založil|založila}} stránku $3 za použití nestandardního modelu obsahu „$5“",
        "whatlinkshere-prev": "{{PLURAL:$1|předchozí|předchozí $1|předchozích $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|následující|následující $1|následujících $1}}",
        "whatlinkshere-links": "← odkazy",
-       "whatlinkshere-hideredirs": "$1 přesměrování",
-       "whatlinkshere-hidetrans": "$1 vložení",
-       "whatlinkshere-hidelinks": "$1 odkazy",
-       "whatlinkshere-hideimages": "$1 vložení souboru",
+       "whatlinkshere-hideredirs": "Skrýt přesměrování",
+       "whatlinkshere-hidetrans": "Skrýt vložení",
+       "whatlinkshere-hidelinks": "Skrýt odkazy",
+       "whatlinkshere-hideimages": "Skrýt vložení souboru",
        "whatlinkshere-filters": "Filtry",
        "whatlinkshere-submit": "Přejít",
        "autoblockid": "Autoblok #$1",
        "lockdbsuccesstext": "Databáze {{grammar:2sg|{{SITENAME}}}} byla úspěšně uzamčena.\n<br />Nezapomeňte ji po dokončení údržby [[Special:UnlockDB|odemknout]].",
        "unlockdbsuccesstext": "Databáze {{grammar:2sg|{{SITENAME}}}} je odemčena.",
        "lockfilenotwritable": "Do souboru zámku databáze nelze zapisovat. Pro zamčení či odemčení databáze musí mít webový server právo zápisu do tohoto souboru.",
+       "databaselocked": "Databáze je již zamčená.",
        "databasenotlocked": "Databáze není uzamčena.",
        "lockedbyandtime": "({{gender:$1|zamkl|zamkla|zamkl}} $1 $2 v $3)",
        "move-page": "Přesunout „$1“",
        "timezone-local": "místní čas",
        "duplicate-defaultsort": "Upozornění: Implicitní klíč řazení (DEFAULTSORTKEY) „$2“ přepisuje dříve nastavenou hodnotu „$1“.",
        "duplicate-displaytitle": "<strong>Upozornění:</strong> Předchozí zobrazovaný název „$1“ je nahrazen zobrazovaným názvem „$2“.",
+       "restricted-displaytitle": "<strong>Upozornění:</strong> Zobrazovaný název „$1“ byl ignorován, neboť není ekvivalentní skutečnému názvu stránky.",
        "invalid-indicator-name": "<strong>Chyba:</strong> Atribut <code>name</code> indikátoru stavu stránky nesmí být prázdný.",
        "version": "Verze",
        "version-extensions": "Nainstalovaná rozšíření",
        "tags-delete-not-found": "Značka „$1“ neexistuje.",
        "tags-delete-too-many-uses": "Značkou „$1“ {{PLURAL:$2|je označena více než $2 revize|jsou označeny více než $2 revize|je označeno více než $2 revizí}}, což znamená, že ji nelze smazat.",
        "tags-delete-warnings-after-delete": "Značka „$1“ byla smazána, ale {{PLURAL:$2|bylo zjištěno|byla zjištěna}} následující varování:",
+       "tags-delete-no-permission": "Nemáte oprávnění mazat značky pro změny.",
        "tags-activate-title": "Aktivovat značku",
        "tags-activate-question": "Chystáte se aktivovat značku „$1“.",
        "tags-activate-reason": "Důvod:",
        "feedback-useragent": "Uživatelský agent:",
        "searchsuggest-search": "Hledat",
        "searchsuggest-containing": "obsahující…",
+       "api-error-autoblocked": "Vaše IP adresa byla automaticky zablokována, protože ji používal zablokovaný uživatel.",
        "api-error-badaccess-groups": "Nemáte povoleno nahrávat soubory na tuto wiki.",
        "api-error-badtoken": "Vnitřní chyba: špatný token.",
+       "api-error-blocked": "Byla vám zablokována možnost editace.",
        "api-error-copyuploaddisabled": "Načítání z URL je na tomto severu zakázáno.",
        "api-error-duplicate": "Na této wiki již {{PLURAL:$1|existuje jiný soubor|existují jiné soubory}} se shodným obsahem.",
        "api-error-duplicate-archive": "{{PLURAL:$1|Soubor|Soubory}} se stejným obsahem již zde dříve {{PLURAL:$1|byl|byly}}, ale {{PLURAL:$1|byl smazán|byly smazány}}.",
        "api-error-nomodule": "Interní chyba: není nastaven načítací modul.",
        "api-error-ok-but-empty": "Interní chyba: ze serveru nepřišla odpověď.",
        "api-error-overwrite": "Není dovoleno přepsat existující soubor.",
+       "api-error-ratelimited": "Pokoušíte se načíst v krátkém časovém rozmezí načíst více souborů, než je na této wiki dovoleno.\nZkuste to znovu za několik minut.",
        "api-error-stashfailed": "Vnitřní chyba: Serveru se nepodařilo uložit dočasný soubor.",
        "api-error-publishfailed": "Vnitřní chyba: Serveru se nepodařilo zveřejnit dočasný soubor.",
        "api-error-stasherror": "Při načítání souboru do skrýše došlo k chybě.",
index aca1e80..99eac43 100644 (file)
        "loginerror": "Fela logòwaniô",
        "loginsuccesstitle": "ùdałé logòwanié",
        "loginsuccess": "Të jes wlogòwóny do {{SITENAME}} jakno \"$1\".",
-       "nosuchuser": "Nie dô brëkòwnika ò mionie \"$1\".\nSprôwdzë pisënk abò [[Special:UserLogin/signup|ùsôdzë nowé kònto]].",
+       "nosuchuser": "Nie dô brëkòwnika ò mionie \"$1\".\nSprôwdzë pisënk abò [[Special:CreateAccount|ùsôdzë nowé kònto]].",
        "nouserspecified": "Mùszisz pòdac miono brëkòwnika.",
        "wrongpassword": "Lëchô parola.\nSpróbùjë znowa.",
        "wrongpasswordempty": "Wpisónô parola je pùstô\nSpróbùjë znowa.",
        "accmailtext": "Przëtrôfkòwò wëgenerowónô parola dlô [[User talk:$1|$1]] òsta wësłónô do $2.\n\nParolã dlô negò nowégò kònta mòże zmienic pò wlogòwaniu na starnie ''[[Special:ChangePassword|zjinaka parolë]]''.",
        "newarticle": "(Nowi)",
        "newarticletext": "Môsz przëszłi z lënkù do starnë jaka jesz nie òbstoji.\nBë ùsôdzëc artikel, naczni pisac w kastce niżi (òb. [$1 starnã pòmòcë]\ndlô wicy wëdowiédzë).\nJeżlë jes të tuwò bez zmiłkã, le klëkni w swòjim przezérnikù knąpã '''nazôd'''.",
-       "anontalkpagetext": "----''To je starna dyskùsëji anonimòwiégò brëkòwnika, chtëren nie ùsôdzëł jesz swòjegò kòntae, abò gò nie brëkùje.\nAbë gò rozpòznac, ùżëwómë adresów IP.\nTakô adresa IP, mòże bëc równak brëkòwónô przez wiele lëdzy.\nJeżlë jes anonimòwim brëkòwnikã ë ùwôżôsz, że ne wiadła nie są do ce sczerowóne, tedë [[Special:UserLogin/signup|ùsôdzë nowé kònto]] abò [[Special:UserLogin|wlogùjë sã]], bë niechac niezrozmeiniô z jinyma anonimòwima brëkòwnikama.''",
+       "anontalkpagetext": "----''To je starna dyskùsëji anonimòwiégò brëkòwnika, chtëren nie ùsôdzëł jesz swòjegò kòntae, abò gò nie brëkùje.\nAbë gò rozpòznac, ùżëwómë adresów IP.\nTakô adresa IP, mòże bëc równak brëkòwónô przez wiele lëdzy.\nJeżlë jes anonimòwim brëkòwnikã ë ùwôżôsz, że ne wiadła nie są do ce sczerowóne, tedë [[Special:CreateAccount|ùsôdzë nowé kònto]] abò [[Special:UserLogin|wlogùjë sã]], bë niechac niezrozmeiniô z jinyma anonimòwima brëkòwnikama.''",
        "noarticletext": "Felëje starna ò tim titlu.\nMòżesz [[Special:Search/{{PAGENAME}}|szëkac za {{PAGENAME}} na jinych starnach]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} szëkac w logù] abò [{{fullurl:{{FULLPAGENAME}}|action=edit}} ùsôdzëc nã starnã]</span>",
        "clearyourcache": "'''Bôczë: Pò zapisanim, mòże bãdzesz mùszôł òminąc pamiãc przezérnika bë òbaczëc zmianë.'''\n'''Mozilla / Firefox / Safari:''' przëtrzëmôj ''Shift'' òbczas klëkaniô na ''Zladëjë znowa'', abò wcësni ''Ctrl-F5'' abò ''Ctrl-R'' (''Command-R'' na kòmpùtrach Mac);\n'''Konqueror:''': klëkni na knąpã ''Zladëjë znowa'', abò wcësni ''F5'';\n'''Opera:''' wëczëszczë pòdrãczną pamiãc w ''Tools→Preferences'';\n'''Internet Explorer:'''przëtrzëmôj ''Ctrl'' òbczas klëkaniô na ''Zladëjë znowa'', abò wcësni ''Ctrl-F5''.",
        "updated": "(Zaktualnioné)",
        "uploadwarning": "Òstrzega ò wladënkù",
        "savefile": "Zapiszë lôpk",
        "uploaddisabled": "Przeprôszómë! Mòżlëwòta wladënkù lopków na nen serwer òsta wëłączonô.",
-       "upload-success-subj": "Wladënk darzëł sã",
        "listfiles": "Lësta òbrôzków",
        "listfiles_user": "Brëkòwnik",
        "file-anchor-link": "Òbrôzk",
        "linksearch": "Bùtnowé lënczi",
        "listgrouprights-members": "(lësta nôlëżników karna)",
        "emailuser": "Wëslë e-maila do negò brëkòwnika",
-       "emailpage": "Sélajë e-mail do brëkòwnika",
        "defemailsubject": "E-mail òd {{SITENAME}}",
        "noemailtitle": "Felënk email-adresë",
        "emailfrom": "Òd:",
        "move-page-legend": "Przeniesë starnã",
        "movepagetext": "Z pòmòcą ùiższegò fòrmùlôra zjinaczisz miono starnë, przenosząc równoczasno ji historëjã.\nPòd stôrim titlã bãdze ùsôdzonô przeczérowùjącô starna.\nMòżesz aùtomatno zaktualniac przeczérowania wskazëwôjące titel przed zjinaką.\nJeżlë nie wëbiérzesz ti òptacëji, ùgwësni sã pò przenieseniu starnë, czë nie òstałé ùsôdzoné [[Special:DoubleRedirects|dëbeltné]] abò [[Special:BrokenRedirects|zerwóné przeczérowania]].\nJes òdpòwiedzalny za to, abë lënczi dali robiłë tam dze mają.\n\nStarna '''ni''' bãdze przeniosłô, jeżlë starna ò nowim mionie ju je, chòba że je òna pùstô abò je przeczérowaniém ë mô pùstą historëjã edicëji.\nTo òznôczô, że lëchą òperacëjã zjinaczi miona mòże doprowôdzëc bezpieczno nazôd, zjinaczając nowé miono starnë nawczasniészą, ë że ni mòże nadpisac stranë chtërną ju dô.\n\n'''BÔCZËNK!'''\nTo mòże bëc drasticznô abò nieprzewidëwólnô zjinaka w przëtrôfkù pòpùlarnych starnów.\nÙgwësni sã co do skùtków ti òperacëji, niglë to zrobisz.",
        "movepagetalktext": "Sparłãczonô starna diskùsëji, jeżlë ju je, to bãdze przeniosłô aùtomatno, chòba że:\n*niepùstô starna diskùsëji ju je z nowim mionã\n*rëmniész nacéchòwanié z niższegò pòla wëbiérkù\n\nW taczich przëtrôfkach zamkłosc diskùsëji mòże przeniesc blós rãczno.",
-       "movearticle": "Przeniesë artikel",
        "newtitle": "Nowi titel:",
        "move-watch": "Ùzérôj tã starnã",
        "movepagebtn": "Przeniesë starnã",
        "movelogpage": "Przeniosłé",
        "movereason": "Przëczëna:",
        "revertmove": "copnij",
-       "delete_and_move": "Rëmôj ë przeniesë",
        "delete_and_move_confirm": "Jo, rëmôj ną starnã",
        "export": "Ekspòrt starnów",
        "allmessages": "Wszëtczé systemòwé ògłosë",
        "specialpages": "Specjalné starnë",
        "revdelete-restricted": "nastôwi ògrańczenia dlô sprôwników",
        "revdelete-unrestricted": "rëmôj ògrańczenia dlô sprôwników",
-       "revdelete-summary": "òpisënk zjinaczi"
+       "revdelete-summary": "òpisënk zjinaczi",
+       "special-characters-group-ipa": "IPA",
+       "special-characters-group-symbols": "Céchë",
+       "special-characters-group-greek": "Grecczi",
+       "special-characters-group-cyrillic": "Cërylica",
+       "special-characters-group-arabic": "Arabsczi",
+       "special-characters-group-hebrew": "Hebrajsczi"
 }
index 454c0b1..43b294c 100644 (file)
@@ -24,7 +24,7 @@
        "tog-hideminor": "Cuddio golygiadau bychain yn rhestr y newidiadau diweddar",
        "tog-hidepatrolled": "Cuddio golygiadau sydd wedi derbyn ymweliad patrôl rhag y rhestr newidiadau diweddar",
        "tog-newpageshidepatrolled": "Cuddio tudalennau a batroliwyd o'r rhestr y tudalennau newydd",
-       "tog-hidecategorization": "Cuddiwych y categoriau",
+       "tog-hidecategorization": "Cuddiwch y categoriau",
        "tog-extendwatchlist": "Ehangu'r rhestr wylio i ddangos pob golygiad yn hytrach na'r diweddaraf yn unig",
        "tog-usenewrc": "Grwpio'r newidiadau bob yn ddalen yn y 'newidiadau diweddar' a'r 'rhestr wylio'",
        "tog-numberheadings": "Rhifo penawdau'n awtomatig",
@@ -55,7 +55,7 @@
        "tog-watchlistreloadautomatically": "Ail-lwyther y Rhestr wylio yn otomatigpan newider ffiltr (angen JavaScript)",
        "tog-watchlisthideanons": "Cuddio golygiadau gan ddefnyddwyr anhysbys rhag y rhestr wylio",
        "tog-watchlisthidepatrolled": "Cuddio golygiadau sydd wedi derbyn ymweliad patrôl rhag y rhestr wylio",
-       "tog-watchlisthidecategorization": "Cuddiwych y categoriau",
+       "tog-watchlisthidecategorization": "Cuddiwch y categoriau",
        "tog-ccmeonemails": "Anfon copi ataf pan anfonaf e-bost at ddefnyddiwr arall",
        "tog-diffonly": "Peidio â dangos cynnwys y dudalen islaw'r gymhariaeth ar dudalennau cymharu",
        "tog-showhiddencats": "Dangos categorïau cuddiedig",
        "noname": "Dydych chi ddim wedi cynnig enw defnyddiwr dilys.",
        "loginsuccesstitle": "Llwyddodd y mewngofnodi",
        "loginsuccess": "'''Yr ydych wedi mewngofnodi i {{SITENAME}} fel \"$1\".'''",
-       "nosuchuser": "Does dim defnyddiwr o'r enw \"$1\".\nMae'r rhaglen yn gwahaniaethu rhwng llythrennau bach a mawr.\nSicrhewch eich bod chi wedi sillafu'r enw'n gywir, neu [[Special:UserLogin/signup|crëwch gyfrif newydd]].",
+       "nosuchuser": "Does dim defnyddiwr o'r enw \"$1\".\nMae'r rhaglen yn gwahaniaethu rhwng llythrennau bach a mawr.\nSicrhewch eich bod chi wedi sillafu'r enw'n gywir, neu [[Special:CreateAccount|crëwch gyfrif newydd]].",
        "nosuchusershort": "Does dim defnyddiwr o'r enw \"$1\". Gwiriwch eich sillafu.",
        "nouserspecified": "Mae'n rhaid nodi enw defnyddiwr.",
        "login-userblocked": "Mae'r defnyddiwr hwn wedi ei flocio. Ni ellir mewngofnodi.",
        "accmailtext": "Anfonwyd cyfrinair a grewyd ar hap ar gyfer [[User talk:$1|$1]] at $2. Gellir newid y cyfrinair hwn ar y dudalen ''[[Special:ChangePassword|newid cyfrinair]]'', wrth fewngofnodi.",
        "newarticle": "(Newydd)",
        "newarticletext": "Rydych chi wedi dilyn cysylltiad i dudalen sydd heb gael ei chreu eto.\nI greu'r dudalen, dechreuwch deipio yn y blwch isod (gweler y [$1 dudalen gymorth] am fwy o wybodaeth).\nOs daethoch yma ar ddamwain, cliciwch botwm '''n&ocirc;l''' y porwr.",
-       "anontalkpagetext": "----''Dyma dudalen sgwrs ar gyfer defnyddiwr anhysbys sydd heb greu cyfrif eto, neu nad yw'n ei ddefnyddio. Felly mae'n rhaid inni ddefnyddio'r cyfeiriad IP i'w (h)adnabod. Mae cyfeiriadau IP yn gallu cael eu rhannu rhwng nifer o ddefnyddwyr. Os ydych chi'n ddefnyddiwr anhysbys ac yn teimlo'ch bod wedi derbyn sylwadau amherthnasol, [[Special:UserLogin/signup|crëwch gyfrif]] neu [[Special:UserLogin|mewngofnodwch]] i osgoi cael eich drysu gyda defnyddwyr anhysbys eraill o hyn ymlaen.''",
+       "anontalkpagetext": "----''Dyma dudalen sgwrs ar gyfer defnyddiwr anhysbys sydd heb greu cyfrif eto, neu nad yw'n ei ddefnyddio. Felly mae'n rhaid inni ddefnyddio'r cyfeiriad IP i'w (h)adnabod. Mae cyfeiriadau IP yn gallu cael eu rhannu rhwng nifer o ddefnyddwyr. Os ydych chi'n ddefnyddiwr anhysbys ac yn teimlo'ch bod wedi derbyn sylwadau amherthnasol, [[Special:CreateAccount|crëwch gyfrif]] neu [[Special:UserLogin|mewngofnodwch]] i osgoi cael eich drysu gyda defnyddwyr anhysbys eraill o hyn ymlaen.''",
        "noarticletext": "Mae'r dudalen hon yn wag ar hyn o bryd.\nGallwch [[Special:Search/{{PAGENAME}}|chwilio am y teitl hwn]] ar ddalennau eraill, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chwilio drwy'r logiau perthnasol], neu [{{fullurl:{{FULLPAGENAME}}|action=edit}} fe allwch greu'r ddalen hon]</span>.",
        "noarticletext-nopermission": "Mae'r dudalen hon yn wag ar hyn o bryd.\nGallwch [[Special:Search/{{PAGENAME}}|chwilio am y teitl hwn]] ar dudalennau eraill, neu gallwch <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chwilio drwy'r logiau perthnasol]</span>, ond nid yw'r gallu gennych i ddechrau'r dudalen o'r dechrau cyntaf.",
        "missing-revision": "Nid yw'r diwygiad #$1 o'r dudalen \"{{FULLPAGENAME}}\" ar gael.\n\nFel arfer, fe ddigwydd hyn wrth ddilyn hen gyswllt i dudalen sydd wedi ei dileu.\nGallwch weld y manylion yn y [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} lòg dileu].",
        "rcshowhidemine": "$1 fy ngolygiadau",
        "rcshowhidemine-show": "Dangoser",
        "rcshowhidemine-hide": "Cuddier",
-       "rcshowhidecategorization": "Categorieiddio tudalen $1",
+       "rcshowhidecategorization": "Categoreiddio tudalen $1",
        "rcshowhidecategorization-show": "Dangos",
        "rcshowhidecategorization-hide": "Cuddio",
        "rclinks": "Dangos y $1 newid diweddaraf yn ystod y(r) $2 diwrnod diwethaf<br />$3",
        "upload-form-label-infoform-description": "Disgrifiad",
        "upload-form-label-usage-title": "Defnydd",
        "upload-form-label-usage-filename": "Enw'r ffeil",
-       "foreign-structured-upload-form-label-own-work": "Dyma fy ngwaith fy hun",
-       "foreign-structured-upload-form-label-infoform-categories": "Categoriau",
-       "foreign-structured-upload-form-label-infoform-date": "Dyddiad",
-       "foreign-structured-upload-form-label-own-work-message-local": "Rwy'n cadarnhau fy mod yn uwchlwytho'r ffeil yma gan ddilyn amodau a pholisiau trwyddedu {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Efallai y carwch hefyd roi gynnig ar [[Special:Upload|y ddalen uwchlwytho diofyn]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Rwy'n cadarnhau fy mod yn uwchlwytho'r ffeil yma i fan sy'n cael ei rannu gan nifer. Rwy'n cadarnhau hefyd y gwnaf hyn gan ddilyn yr amodau a'r polisiau trwyddedu sydd yno.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Cadarnhaf mai fi yw perchennog hawlfraint y ffeil hon, a chytunaf  yn ddi-droi'n ôl i ryddhau'r ffeil hon i Gomin Wicimedia dan drwydded [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], a chytunaf gyda'r[https://wikimediafoundation.org/wiki/Terms_of_Use Amodau Defnyddio].",
+       "upload-form-label-own-work": "Dyma fy ngwaith fy hun",
+       "upload-form-label-infoform-categories": "Categoriau",
+       "upload-form-label-infoform-date": "Dyddiad",
+       "upload-form-label-own-work-message-generic-local": "Rwy'n cadarnhau fy mod yn uwchlwytho'r ffeil yma gan ddilyn amodau a pholisiau trwyddedu {{SITENAME}}.",
+       "upload-form-label-not-own-work-local-generic-local": "Efallai y carwch hefyd roi gynnig ar [[Special:Upload|y ddalen uwchlwytho diofyn]].",
+       "upload-form-label-own-work-message-generic-foreign": "Rwy'n cadarnhau fy mod yn uwchlwytho'r ffeil yma i fan sy'n cael ei rannu gan nifer. Rwy'n cadarnhau hefyd y gwnaf hyn gan ddilyn yr amodau a'r polisiau trwyddedu sydd yno.",
        "backend-fail-stream": "Wedi methu ffrydio'r ffeil $1.",
        "backend-fail-backup": "Wedi methu gwneud copi wrth gefn o'r ffeil $1.",
        "backend-fail-notexists": "Nid yw'r ffeil $1 ar gael.",
index 2a80b92..7a43083 100644 (file)
        "noname": "Du har ikke angivet et gyldigt brugernavn.",
        "loginsuccesstitle": "Du er nu logget på",
        "loginsuccess": "'''Du er nu logget på {{SITENAME}} som \"$1\".'''",
-       "nosuchuser": "Der er ingen bruger med navnet \"$1\".\nDer skelnes mellem store og bogstaver i brugernavne.\nKontrollér stavemåden, eller [[Special:UserLogin/signup|opret en ny konto]].",
+       "nosuchuser": "Der er ingen bruger med navnet \"$1\".\nDer skelnes mellem store og bogstaver i brugernavne.\nKontrollér stavemåden, eller [[Special:CreateAccount|opret en ny konto]].",
        "nosuchusershort": "Der er ingen bruger ved navn \"$1\". Tjek din stavning.",
        "nouserspecified": "Angiv venligst et brugernavn.",
        "login-userblocked": "Denne bruger er blokeret. Det er ikke tilladt at logge på.",
        "accmailtext": "En tilfældigt dannet adgangskode for [[User talk:$1|$1]] er sendt til $2. Den kan ændres på siden ''[[Special:ChangePassword|skift adgangskode]]'', når du logger på.",
        "newarticle": "(Ny)",
        "newarticletext": "Du har fulgt en henvisning til en side som endnu ikke findes.\nFor at oprette siden skal du begynde at skrive i boksen nedenfor\n(se [$1 hjælpesiden] for yderligere information).\nHvis du er her ved en fejl, så tryk på din browsers '''tilbage'''-knap.",
-       "anontalkpagetext": "---- ''Dette er en diskussionsside for en anonym bruger, der ikke har oprettet en konto endnu eller ikke bruger den.\nVi er derfor nødt til at bruge den numeriske IP-adresse til at identificere ham eller hende.\nEn IP-adresse kan være delt mellem flere brugere.\nHvis du er en anonym bruger og synes, at du har fået irrelevante kommentarer på sådan en side, så vær venlig at [[Special:UserLogin/signup|oprette en brugerkonto]] og [[Special:UserLogin|logge på]], så vi undgår fremtidige forvekslinger med andre anonyme brugere.''",
+       "anontalkpagetext": "---- ''Dette er en diskussionsside for en anonym bruger, der ikke har oprettet en konto endnu eller ikke bruger den.\nVi er derfor nødt til at bruge den numeriske IP-adresse til at identificere ham eller hende.\nEn IP-adresse kan være delt mellem flere brugere.\nHvis du er en anonym bruger og synes, at du har fået irrelevante kommentarer på sådan en side, så vær venlig at [[Special:CreateAccount|oprette en brugerkonto]] og [[Special:UserLogin|logge på]], så vi undgår fremtidige forvekslinger med andre anonyme brugere.''",
        "noarticletext": "Der er i øjeblikket ikke nogen tekst på denne side.\nDu kan [[Special:Search/{{PAGENAME}}|søge efter sidenavnet]] på andre sider,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søge i relaterede logger]\neller [{{fullurl:{{FULLPAGENAME}}|action=edit}} oprette siden]</span>.",
        "noarticletext-nopermission": "Der er i øjeblikket ikke nogen tekst på denne side.\nDu kan [[Special:Search/{{PAGENAME}}|søge efter sidenavnet]] på andre sider,\neller <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søge i relaterede loglister]</span>,\nmen du har ikke tilladelse til at oprette denne side.",
        "missing-revision": "Revision #$1 af siden med navnet \"{{FULLPAGENAME}}\" eksisterer ikke.\n\nDette skyldes normalt at et forældet historik-link er fulgt til en side der er slettet.\nDetaljer kan findes i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sletningsloggen].",
        "recentchanges-legend-heading": "<strong>Forklaring:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (se også [[Special:NewPages|listen over nye sider]])",
        "recentchanges-legend-plusminus": "(''±123'')",
+       "recentchanges-submit": "Vis",
        "rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
        "rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
        "rcshowhideminor": "$1 mindre ændringer",
        "upload-form-label-infoform-name": "Navn",
        "upload-form-label-infoform-description": "Beskrivelse",
        "upload-form-label-usage-filename": "Filnavn",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorier",
-       "foreign-structured-upload-form-label-infoform-date": "Dato",
-       "foreign-structured-upload-form-label-own-work-message-local": "Jeg bekræfter at jeg uploader filen i overenstemmelse med betingelser for brug og licenseringspoltikken på {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Hvis du ikke kan uploade filen under politikerne på {{SITENAME}}, skal du lukke dialogboksen og prøve en anden metode.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Du kan også vælge at prøve [[Special:Upload|den almindelige uploadside]].",
+       "upload-form-label-infoform-categories": "Kategorier",
+       "upload-form-label-infoform-date": "Dato",
+       "upload-form-label-own-work-message-generic-local": "Jeg bekræfter at jeg uploader filen i overenstemmelse med betingelser for brug og licenseringspoltikken på {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Hvis du ikke kan uploade filen under politikerne på {{SITENAME}}, skal du lukke dialogboksen og prøve en anden metode.",
+       "upload-form-label-not-own-work-local-generic-local": "Du kan også vælge at prøve [[Special:Upload|den almindelige uploadside]].",
        "backend-fail-stream": "Kunne ikke streame filen $1.",
        "backend-fail-backup": "Kunne ikke lave sikkerhedskopi af filen $1.",
        "backend-fail-notexists": "Filen $1 findes ikke.",
        "apisandbox": "API-sandkassen",
        "apisandbox-api-disabled": "API er deaktiveret på dette websted.",
        "apisandbox-intro": "Brug denne side til at eksperimentere med '''MediaWiki web service API'''.\nVi henviser til [//www.mediawiki.org/wiki/API:Main_page dokumentationen af API] for yderligere oplysninger om brug af API.  Eksempel: [//www.mediawiki.org/wiki/API#A_simple_example få indholdet af en forside]. Vælg en handling at se flere eksempler.\n\nBemærk, at selv om dette er en sandkasse, vil handlinger du udfører på denne side redigere wikien.",
+       "apisandbox-unfullscreen": "Vis side",
        "apisandbox-submit": "Lav forespørgsel",
        "apisandbox-reset": "Ryd",
        "apisandbox-examples": "Eksempler",
        "specialloguserlabel": "Udført af:",
        "speciallogtitlelabel": "Mål (titel eller {{ns:user}}:brugernavn for bruger):",
        "log": "Loglister",
+       "logeventslist-submit": "Vis",
        "all-logs-page": "Alle offentlige logger",
        "alllogstext": "Samlet visning af alle loggene på {{SITENAME}}.\nDu kan afgrænse visningen ved at vælge en logtype, brugernavn eller påvirket side. Der skelnes mellem små og store bogstaver for både bruger- og sidenavne.",
        "logempty": "Intet passende fundet.",
        "cachedspecial-viewing-cached-ts": "Du ser en hengemt version af denne side, som måske ikke er helt aktuel.",
        "cachedspecial-refresh-now": "Vis seneste.",
        "categories": "Kategorier",
+       "categories-submit": "Vis",
        "categoriespagetext": "Følgende {{PLURAL:$1|kategori|kategorier}} indeholder sider eller media.\n[[Special:UnusedCategories|Ubrugte kategorier]] vises ikke her.\nSe også [[Special:WantedCategories|ønskede kategorier]].",
        "categoriesfrom": "Vis kategorier startende med:",
        "deletedcontributions": "Slettede brugerbidrag",
        "unwatchthispage": "Fjern overvågning",
        "notanarticle": "Ikke en artikel",
        "notvisiblerev": "Versionen er blevet slettet",
-       "watchlist-details": "Du har {{PLURAL:$1|side|sider}} på din overvågningsliste (uden at medregne diskussionssider).",
+       "watchlist-details": "Du har {{PLURAL:$1|$1 side|$1 sider}} på din overvågningsliste (uden at medregne diskussionssider).",
        "wlheader-enotif": "E-mail-underretning er slået til.",
        "wlheader-showupdated": "Sider, der er ændret siden dit sidste besøg, er vist med '''fed skrift'''.",
        "wlnote": "Nedenfor ses {{PLURAL:$1|den seneste ændring|de seneste <strong>$1</strong> ændringer}} i {{PLURAL:$2|den sidste time|de sidste <strong>$2</strong> timer}} op til den $3 kl. $4.",
        "wlshowlast": "Vis de seneste $1 timer $2 dage",
        "watchlist-hide": "Skjul",
+       "watchlist-submit": "Vis",
        "wlshowtime": "Vis seneste:",
        "wlshowhideminor": "mindre ændringer",
        "wlshowhidebots": "robotter",
        "delete-confirm": "Slet \"$1\"",
        "delete-legend": "Slet",
        "historywarning": "<strong>Advarsel:</strong> Siden du er ved at slette har en historie med $1 {{PLURAL:$1|version|versioner}}:",
+       "historyaction-submit": "Vis",
        "confirmdeletetext": "Du er ved at slette en side sammen med hele dens tilhørende historik.\nBekræft venligst at du virkelig vil gøre dette, at du forstår konsekvenserne, og at du gør det i overensstemmelse med [[{{MediaWiki:Policy-url}}|retningslinjerne]].",
        "actioncomplete": "Gennemført",
        "actionfailed": "Handlingen mislykkedes",
        "whatlinkshere-prev": "{{PLURAL:$1|forrige|forrige $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|næste|næste $1}}",
        "whatlinkshere-links": "← henvisninger",
-       "whatlinkshere-hideredirs": "$1 omdirigeringer",
-       "whatlinkshere-hidetrans": "$1 inkluderinger",
-       "whatlinkshere-hidelinks": "$1 henvisninger",
+       "whatlinkshere-hideredirs": "Skjul omdirigeringer",
+       "whatlinkshere-hidetrans": "Skjul inkluderinger",
+       "whatlinkshere-hidelinks": "Skjul henvisninger",
        "whatlinkshere-hideimages": "$1 filhenvisninger",
        "whatlinkshere-filters": "Filtre",
        "autoblockid": "Autoblock #$1",
        "revdelete-restricted": "tilføjede begrænsninger for administratorer",
        "revdelete-unrestricted": "fjernede begrænsninger for administratorer",
        "logentry-block-block": "$1 {{GENDER:$2|blokerede}} {{GENDER:$4|$3}} med en udløbstid på $5 $6",
+       "logentry-block-unblock": "$1 {{GENDER:$2|ophævede blokering af}} {{GENDER:$4|$3}}",
        "logentry-block-reblock": "$1 {{GENDER:$2|ændrede}} blokeringsindstillinger for {{GENDER:$4|$3}} med en udløbstid på $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|ændrede}} blokeringsindstillinger for {{GENDER:$4|$3}} med en udløbstid på $5 $6",
        "logentry-move-move": "$1 {{GENDER:$2|flyttede}} siden $3 til $4",
        "mw-widgets-titleinput-description-new-page": "side eksisterer ikke endnu",
        "mw-widgets-titleinput-description-redirect": "omdiriger til $1",
        "api-error-blacklisted": "Vælg venligst en anden, beskrivende titel.",
-       "randomrootpage": "Tilfældig stamside"
+       "randomrootpage": "Tilfældig stamside",
+       "log-action-filter-block": "Blokeringstype:",
+       "log-action-filter-move": "Flyttetype:",
+       "log-action-filter-patrol": "Patruljeringstype:",
+       "log-action-filter-protect": "Beskyttelsestype:",
+       "log-action-filter-block-block": "Blokering",
+       "log-action-filter-block-reblock": "Ændring af blokering",
+       "log-action-filter-delete-delete": "Sidesletning",
+       "log-action-filter-delete-restore": "Sidegendannelse",
+       "log-action-filter-delete-event": "Logsletning",
+       "log-action-filter-delete-revision": "Revisionssletning",
+       "log-action-filter-move-move": "Flytning uden overskrivelse af omdirigeringer",
+       "log-action-filter-move-move_redir": "Flytning med overskrivelse af omdirigeringer",
+       "log-action-filter-patrol-patrol": "Manuel patruljering",
+       "log-action-filter-patrol-autopatrol": "Automatisk patruljering",
+       "log-action-filter-protect-protect": "Beskyttelse",
+       "log-action-filter-protect-modify": "Ændring af beskyttelse",
+       "log-action-filter-protect-unprotect": "Fjernede beskyttelse",
+       "log-action-filter-protect-move_prot": "Flyttede beskyttelse"
 }
index 2275389..91580b4 100644 (file)
        "tog-ccmeonemails": "Schicke mir Kopien der E-Mails, die ich anderen Benutzern sende",
        "tog-diffonly": "Beim Versionsvergleich nur die Unterschiede und nicht die vollständige Seite anzeigen",
        "tog-showhiddencats": "Versteckte Kategorien anzeigen",
-       "tog-norollbackdiff": "Unterschied nach dem Zurücksetzen unterdrücken",
+       "tog-norollbackdiff": "Unterschied nach dem Zurücksetzen nicht anzeigen",
        "tog-useeditwarning": "Warnen, sofern eine zur Bearbeitung geöffnete Seite verlassen wird, die nicht gespeicherte Änderungen enthält",
        "tog-prefershttps": "Wenn angemeldet, immer eine sichere Verbindung benutzen.",
        "underline-always": "immer",
        "category-empty": "''Diese Kategorie enthält zurzeit keine Seiten oder Medien.''",
        "hidden-categories": "{{PLURAL:$1|Versteckte Kategorie|Versteckte Kategorien}}",
        "hidden-category-category": "Versteckte Kategorien",
-       "category-subcat-count": "{{PLURAL:$2|Diese Kategorie enthält folgende Unterkategorie:|{{PLURAL:$1|Folgende Unterkategorie ist eine von insgesamt $2 Unterkategorien in dieser Kategorie:|Es werden $1 von insgesamt $2 Unterkategorien in dieser Kategorie angezeigt:}}}}",
+       "category-subcat-count": "{{PLURAL:$2|Diese Kategorie enthält folgende Unterkategorie:|{{PLURAL:$1|Folgende Unterkategorie ist eine von insgesamt $2 Unterkategorien in dieser Kategorie:|Es werden $1 von insgesamt $2 Unterkategorien in dieser Kategorie angezeigt.}}}}",
        "category-subcat-count-limited": "Diese Kategorie enthält folgende {{PLURAL:$1|Unterkategorie|$1 Unterkategorien}}:",
        "category-article-count": "{{PLURAL:$2|Diese Kategorie enthält folgende Seite:|{{PLURAL:$1|Folgende Seite ist eine von insgesamt $2 Seiten in dieser Kategorie:|Es werden $1 von insgesamt $2 Seiten in dieser Kategorie angezeigt.}}}}",
        "category-article-count-limited": "Folgende {{PLURAL:$1|Seite ist|$1 Seiten sind}} in dieser Kategorie enthalten:",
        "databaseerror-query": "Abfrage: $1",
        "databaseerror-function": "Funktion: $1",
        "databaseerror-error": "Fehler: $1",
-       "transaction-duration-limit-exceeded": "Um eine hohe Nachbildungsverzögerung zu vermeiden, wurde diese Transaktion abgebrochen, da die Schreibdauer ($1) die Grenze von {{PLURAL:$2|einer Sekunde|$2 Sekunden}} überschritten hat. Falls du viele Objekte auf einmal änderst, versuche stattdessen, mehrere kleine Operationen auszuführen.",
+       "transaction-duration-limit-exceeded": "Um eine große Verzögerung in der Datenreplikation zu vermeiden, wurde diese Transaktion abgebrochen. Die Schreibdauer ($1) hat die Grenze von {{PLURAL:$2|einer Sekunde|$2 Sekunden}} überschritten. Falls du viele Objekte auf einmal änderst, versuche stattdessen, die Änderungen auf mehrere Operationen aufzuteilen.",
        "laggedslavemode": "<strong>Achtung:</strong> Die angezeigte Seite könnte unter Umständen nicht die letzten Bearbeitungen enthalten.",
        "readonly": "Datenbank gesperrt",
        "enterlockreason": "Bitte gib einen Grund ein, warum die Datenbank gesperrt werden soll und eine Abschätzung über die Dauer der Sperrung",
        "password-change-forbidden": "Du kannst auf diesem Wiki keine Passwörter ändern.",
        "externaldberror": "Entweder liegt ein Fehler bei der externen Authentifizierung vor oder du darfst dein externes Benutzerkonto nicht aktualisieren.",
        "login": "Anmelden",
+       "login-security": "Verifiziere deine Identität",
        "nav-login-createaccount": "Anmelden / Benutzerkonto erstellen",
        "userlogin": "Anmelden / Benutzerkonto anlegen",
        "userloginnocreate": "Anmelden",
        "userlogin-resetpassword-link": "Passwort vergessen?",
        "userlogin-helplink2": "Hilfe beim Anmelden",
        "userlogin-loggedin": "Du bist bereits als {{GENDER:$1|$1}} angemeldet.\nBenutze das unten stehende Formular, um dich unter einem anderen Benutzernamen anzumelden.",
+       "userlogin-reauth": "Du musst dich erneut anmelden, um zu verifizieren, dass du {{GENDER:$1|$1}} bist.",
        "userlogin-createanother": "Ein weiteres Benutzerkonto erstellen",
        "createacct-emailrequired": "E-Mail-Adresse",
        "createacct-emailoptional": "E-Mail-Adresse (optional)",
        "createacct-email-ph": "Gib deine E-Mail-Adresse ein",
        "createacct-another-email-ph": "E-Mail-Adresse",
        "createaccountmail": "Ein temporäres Zufallspasswort verwenden und an die angegebene E-Mail-Adresse versenden",
+       "createaccountmail-help": "Kann verwendet werden, um für eine andere Person ein Benutzerkonto zu erstellen, ohne das Passwort zu erfahren.",
        "createacct-realname": "Bürgerlicher Name (optional)",
        "createaccountreason": "Grund:",
        "createacct-reason": "Begründung",
        "createacct-reason-ph": "Warum erstellst du ein anderes Benutzerkonto?",
+       "createacct-reason-help": "Im Neuanmeldungs-Logbuch angezeigte Nachricht",
        "createacct-submit": "Benutzerkonto erstellen",
        "createacct-another-submit": "Benutzerkonto erstellen",
+       "createacct-continue-submit": "Benutzerkontenerstellung fortfahren",
+       "createacct-another-continue-submit": "Benutzerkontenerstellung fortfahren",
        "createacct-benefit-heading": "{{SITENAME}} wird von Menschen wie dir geschaffen.",
        "createacct-benefit-body1": "{{PLURAL:$1|Bearbeitung|Bearbeitungen}}",
        "createacct-benefit-body2": "{{PLURAL:$1|Seite|Seiten}}",
        "nocookiesnew": "Der Benutzerzugang wurde erstellt, aber du bist nicht angemeldet. {{SITENAME}} benötigt für diese Funktion Cookies, bitte aktiviere diese und melde dich dann mit deinem neuen Benutzernamen und dem zugehörigen Passwort an.",
        "nocookieslogin": "{{SITENAME}} benutzt Cookies zur Anmeldung der Benutzer. Du hast Cookies deaktiviert, bitte aktiviere diese und versuche es erneut.",
        "nocookiesfornew": "Das Benutzerkonto wurde nicht erstellt, da die Datenherkunft nicht ermittelt werden konnte.\nBitte stelle sicher, dass du Cookies aktiviert hast. Lade diese Seite danach erneut und versuche es noch einmal.",
+       "createacct-loginerror": "Das Benutzerkonto wurde erfolgreich erstellt, aber du konntest nicht automatisch angemeldet werden. Bitte fahre mit der [[Special:UserLogin|manuellen Anmeldung]] fort.",
        "noname": "Du musst einen gültigen Benutzernamen angeben.",
        "loginsuccesstitle": "Angemeldet",
        "loginsuccess": "<strong>Du bist jetzt als „$1“ bei {{SITENAME}} angemeldet.</strong>",
-       "nosuchuser": "Der Benutzername „$1“ existiert nicht.\nÜberprüfe die Schreibweise (Groß-/Kleinschreibung beachten) oder [[Special:UserLogin/signup|lege ein neues Benutzerkonto an]].",
+       "nosuchuser": "Der Benutzername „$1“ existiert nicht.\nÜberprüfe die Schreibweise (Groß-/Kleinschreibung beachten) oder [[Special:CreateAccount|lege ein neues Benutzerkonto an]].",
        "nosuchusershort": "Der Benutzername „$1“ ist nicht vorhanden. Bitte überprüfe die Schreibweise.",
        "nouserspecified": "Bitte gib einen Benutzernamen an.",
        "login-userblocked": "{{GENDER:$1|Dieser Benutzer|Diese Benutzerin}} ist gesperrt. Die Anmeldung ist nicht erlaubt.",
        "createacct-another-realname-tip": "Der bürgerliche Name ist optional.\nWenn du ihn angibst, wird er für die Zuordnung der Beiträge verwendet.",
        "pt-login": "Anmelden",
        "pt-login-button": "Anmelden",
+       "pt-login-continue-button": "Anmeldung fortfahren",
        "pt-createaccount": "Benutzerkonto erstellen",
        "pt-userlogout": "Abmelden",
        "php-mail-error-unknown": "Unbekannter Fehler in der PHP-Funktion mail().",
        "botpasswords-invalid-name": "Der angegebene Benutzername enthält keinen Botpassworttrenner („$1“).",
        "botpasswords-not-exist": "Der Benutzer „$1“ hat kein Botpasswort mit dem Namen „$2“.",
        "resetpass_forbidden": "Das Passwort kann nicht geändert werden.",
+       "resetpass_forbidden-reason": "Die Passwörter können nicht geändert werden: $1",
        "resetpass-no-info": "Du musst dich anmelden, um auf diese Seite direkt zuzugreifen.",
        "resetpass-submit-loggedin": "Passwort ändern",
        "resetpass-submit-cancel": "Abbrechen",
        "passwordreset-emailsentusername": "Falls es eine E-Mail-Adresse gibt, die mit diesem Benutzernamen verknüpft ist, wird eine Passwort-Zurücksetzungs-E-Mail versandt.",
        "passwordreset-emailsent-capture": "Eine Passwortzurücksetzungs-E-Mail wurde versandt, die unten angezeigt wird.",
        "passwordreset-emailerror-capture": "Die unten angezeigte Passwortzurücksetzungs-E-Mail wurde generiert, allerdings ist der Versand an {{GENDER:$2|den Benutzer|die Benutzerin}} gescheitert: $1",
+       "passwordreset-emailsent-capture2": "Die Passwort-Zurücksetzungs-{{PLURAL:$1|E-Mail wurde|E-Mails wurden}} versandt. {{PLURAL:$1|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird unten angezeigt.",
+       "passwordreset-emailerror-capture2": "Das Senden der E-Mail an {{GENDER:$2|den Benutzer|die Benutzerin}} ist fehlgeschlagen: $1 {{PLURAL:$3|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird unten angezeigt.",
+       "passwordreset-nocaller": "Es muss ein Rufer angegeben werden",
+       "passwordreset-nosuchcaller": "Rufer ist nicht vorhanden: $1",
+       "passwordreset-ignored": "Die Passwortzurücksetzung konnte nicht verarbeitet werden. Vielleicht wurde kein Dienstanbieter konfiguriert?",
+       "passwordreset-invalideamil": "Ungültige E-Mail-Adresse",
+       "passwordreset-nodata": "Weder ein Benutzername noch eine E-Mail-Adresse wurde angegeben",
        "changeemail": "E-Mail-Adresse ändern oder entfernen",
        "changeemail-header": "Fülle dieses Formular vollständig aus, um deine E-Mail-Adresse zu ändern. Falls du die Zuweisung einer E-Mail-Adresse zu deinem Benutzerkonto entfernen möchtest, lasse beim Übermitteln des Formulars das Feld für die neue E-Mail-Adresse leer.",
        "changeemail-passwordrequired": "Du musst dein Passwort eingeben, um diese Änderung zu bestätigen.",
        "blankarticle": "<strong>Warnung:</strong> Die Seite, die du erstellst, ist leer.\nWenn du erneut auf „{{int:savearticle}}“ klickst, wird die Seite ohne Inhalt erstellt.",
        "anoneditwarning": "<strong>Warnung:</strong> Du bist nicht angemeldet. Deine IP-Adresse wird öffentlich sichtbar, falls du Bearbeitungen durchführst. Sofern du dich <strong>[$1 anmeldest]</strong> oder <strong>[$2 ein Benutzerkonto erstellst]</strong>, werden deine Bearbeitungen zusammen mit anderen Beiträgen deinem Benutzernamen zugeordnet.",
        "anonpreviewwarning": "''Du bist nicht angemeldet. Beim Speichern wird deine IP-Adresse in der Versionsgeschichte aufgezeichnet.''",
-       "missingsummary": "'''Hinweis:''' Du hast keine Zusammenfassung angegeben. Wenn du erneut auf „{{int:savearticle}}“ klickst, wird deine Änderung ohne Zusammenfassung übernommen.",
+       "missingsummary": "<strong>Hinweis:</strong> Du hast keine Zusammenfassung angegeben. Wenn du erneut auf „{{int:savearticle}}“ klickst, wird deine Änderung ohne Zusammenfassung übernommen.",
        "selfredirect": "<strong>Warnung:</strong> Du leitest auf diese Seite selbst weiter.\nDu hast vermutlich das falsche Weiterleitungsziel angegeben oder du bearbeitest die falsche Seite.\nWenn du erneut auf „{{int:savearticle}}“ klickst, wird die Weiterleitung dennoch erstellt.",
        "missingcommenttext": "Bitte gib unten einen Kommentar ein.",
        "missingcommentheader": "<strong>Achtung:</strong> Du hast keinen Betreff eingegeben. Wenn du erneut auf „{{int:savearticle}}“ klickst, wird deine Bearbeitung ohne Überschrift gespeichert.",
        "accmailtext": "Ein zufällig generiertes Passwort für [[User talk:$1|$1]] wurde an $2 versandt. Es kann auf der Seite ''[[Special:ChangePassword|Passwort ändern]]'' nach der Anmeldung geändert werden.",
        "newarticle": "(Neu)",
        "newarticletext": "Du bist einem Link zu einer Seite gefolgt, die nicht vorhanden ist.\nUm diese Seite anzulegen, trage deinen Text in das untenstehende Bearbeitungsfeld ein (weitere Informationen auf der [$1 Hilfeseite]).\nSofern du fälschlicherweise hier bist, klicke auf die Schaltfläche '''Zurück''' deines Browsers.",
-       "anontalkpagetext": "----''Diese Seite dient dazu, einem nicht angemeldeten Benutzer Nachrichten zu hinterlassen. Es wird seine IP-Adresse zur Identifizierung verwendet. IP-Adressen können von mehreren Benutzern gemeinsam verwendet werden. Wenn du mit den Kommentaren auf dieser Seite nichts anfangen kannst, richten sie sich vermutlich an einen früheren Inhaber deiner IP-Adresse und du kannst sie ignorieren. Du kannst dir auch ein [[Special:UserLogin/signup|Benutzerkonto erstellen]] oder dich [[Special:UserLogin|anmelden]], um künftig Verwechslungen mit anderen anonymen Benutzern zu vermeiden.''",
+       "anontalkpagetext": "----\n<em>Diese Seite dient dazu, einem nicht angemeldeten Benutzer Nachrichten zu hinterlassen.</em>\nEs wird seine IP-Adresse zur Identifizierung verwendet.\nIP-Adressen können von mehreren Benutzern gemeinsam verwendet werden.\nWenn du mit den Kommentaren auf dieser Seite nichts anfangen kannst, richten sie sich vermutlich an einen früheren Inhaber deiner IP-Adresse und du kannst sie ignorieren.\nDu kannst dir auch ein [[Special:CreateAccount|Benutzerkonto erstellen]] oder dich [[Special:UserLogin|anmelden]], um künftig Verwechslungen mit anderen anonymen Benutzern zu vermeiden.",
        "noarticletext": "Diese Seite enthält momentan noch keinen Text.\nDu kannst sie <span class=\"plainlinks\">[{{fullurl:{{FULLPAGENAME}}|action=edit}} erstellen]</span>,\nihren Titel auf anderen Seiten [[Special:Search/{{PAGENAME}}|suchen]]\noder die zugehörigen <span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbücher betrachten]</span>.",
        "noarticletext-nopermission": "Diese Seite enthält momentan noch keinen Text und du bist auch nicht dazu berechtigt, diese Seite zu erstellen.\nDu kannst ihren Titel auf anderen Seiten [[Special:Search/{{PAGENAME}}|suchen]] oder die zugehörigen <span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbücher betrachten].</span>",
        "missing-revision": "Die Version $1 der Seite namens „{{FULLPAGENAME}}“ ist nicht vorhanden.\n\nDieser Fehler wird normalerweise von einem veralteten Link zur Versionsgeschichte einer Seite verursacht, die zwischenzeitlich gelöscht wurde.\nEinzelheiten sind im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lösch-Logbuch] einsehbar.",
        "editpage-cannot-use-custom-model": "Das Inhaltsmodell dieser Seite kann nicht geändert werden.",
        "longpageerror": "'''Fehler: Der Text, den du zu speichern versuchst, ist {{PLURAL:$1|ein Kilobyte|$1 Kilobyte}} groß. Dies ist größer als das erlaubte Maximum von {{PLURAL:$2|ein Kilobyte|$2 Kilobyte}}.'''\nEr kann nicht gespeichert werden.",
        "readonlywarning": "<strong>Achtung: Die Datenbank wurde für Wartungsarbeiten gesperrt, so dass deine Änderungen derzeit nicht gespeichert werden können.\nSichere den Text bitte lokal auf deinem Computer und versuche zu einem späteren Zeitpunkt, die Änderungen zu übertragen.</strong>\n\nGrund für die Sperre: $1",
-       "protectedpagewarning": "'''Achtung: Diese Seite wurde geschützt. Nur Benutzer mit Administratorrechten können die Seite bearbeiten.'''\nZur Information folgt der aktuelle Logbucheintrag:",
+       "protectedpagewarning": "<strong>Achtung: Diese Seite wurde geschützt. Nur Benutzer mit Administratorrechten können die Seite bearbeiten.</strong>\nZur Information folgt der aktuelle Logbucheintrag:",
        "semiprotectedpagewarning": "'''Halbsperrung:''' Die Seite wurde so geschützt, dass nur registrierte Benutzer diese ändern können.\nZur Information folgt der aktuelle Logbucheintrag:",
        "cascadeprotectedwarning": "<strong>Achtung:</strong> Diese Seite wurde so geschützt, dass sie nur durch Benutzer mit Administratorrechten bearbeitet werden kann. Sie ist in die {{PLURAL:$1|folgende Seite|folgenden Seiten}} eingebunden, die mittels der Kaskadensperroption geschützt {{PLURAL:$1|ist|sind}}:",
        "titleprotectedwarning": "'''Achtung: Die Seitenerstellung wurde so geschützt, dass nur Benutzer mit [[Special:ListGroupRights|speziellen Rechten]] diese Seite erstellen können.'''\nZur Information folgt der aktuelle Logbucheintrag:",
        "permissionserrorstext": "Du bist nicht berechtigt, die Aktion auszuführen. {{PLURAL:$1|Grund|Gründe}}:",
        "permissionserrorstext-withaction": "Du bist aus {{PLURAL:$1|dem folgenden Grund|den folgenden Gründen}} nicht berechtigt, $2:",
        "contentmodelediterror": "Du kannst diese Version nicht bearbeiten, da das Inhaltsmodell <code>$1</code> vom aktuellen Inhaltsmodell der Seite <code>$2</code> abweicht.",
-       "recreate-moveddeleted-warn": "'''Achtung: Du erstellst eine Seite, die bereits früher gelöscht wurde.'''\n\nBitte prüfe sorgfältig, ob die erneute Seitenerstellung den Richtlinien entspricht.\nZu deiner Information folgt das Lösch- und Verschiebungs-Logbuch mit der Begründung für die vorhergehende Löschung:",
+       "recreate-moveddeleted-warn": "<strong>Achtung: Du erstellst eine Seite, die bereits früher gelöscht wurde.</strong>\n\nBitte prüfe sorgfältig, ob die erneute Seitenerstellung den Richtlinien entspricht.\nZu deiner Information folgt das Lösch- und Verschiebungs-Logbuch mit der Begründung für die vorhergehende Löschung:",
        "moveddeleted-notice": "Diese Seite wurde gelöscht. Zur Information folgt das Lösch- und Verschiebungs-Logbuch dieser Seite.",
        "moveddeleted-notice-recent": "Leider wurde diese Seite kürzlich gelöscht (innerhalb der letzten 24 Stunden).\nZur Information wird das Lösch- und Verschiebungs-Logbuch für die Seite unten angezeigt.",
        "log-fulllog": "Alle Logbucheinträge ansehen",
        "right-override-export-depth": "Exportiere Seiten einschließlich verlinkter Seiten bis zu einer Tiefe von 5",
        "right-sendemail": "E-Mails an andere Benutzer senden",
        "right-passwordreset": "Passwort eines Benutzers zurücksetzen und die dazu verschickte E-Mail einsehen",
-       "right-managechangetags": "[[Special:Tags|Markierungen]] erstellen und aus der Datenbank löschen",
+       "right-managechangetags": "[[Special:Tags|Markierungen]] erstellen und (de)aktivieren",
        "right-applychangetags": "[[Special:Tags|Markierungen]] zusammen mit den Änderungen anwenden",
        "right-changetags": "Beliebige [[Special:Tags|Markierungen]] zu einzelnen Versionen und Logbucheinträgen hinzufügen und entfernen",
+       "right-deletechangetags": "[[Special:Tags|Markierungen]] aus der Datenbank löschen",
        "grant-generic": "Rechtegruppe „$1“",
        "grant-group-page-interaction": "Mit Seiten interagieren",
        "grant-group-file-interaction": "Mit Medien interagieren",
        "action-viewmyprivateinfo": "deine privaten Informationen einzusehen",
        "action-editmyprivateinfo": "deine privaten Informationen zu bearbeiten",
        "action-editcontentmodel": "das Inhaltsmodell einer Seite zu bearbeiten",
-       "action-managechangetags": "Markierungen zu erstellen und aus der Datenbank zu löschen",
+       "action-managechangetags": "Markierungen zu erstellen und zu (de)aktivieren",
        "action-applychangetags": "Markierungen zusammen mit deinen Änderungen anzuwenden",
        "action-changetags": "beliebige Markierungen zu einzelnen Versionen und Logbucheinträgen hinzuzufügen und zu entfernen",
+       "action-deletechangetags": "Markierungen aus der Datenbank zu löschen",
        "nchanges": "$1 {{PLURAL:$1|Änderung|Änderungen}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|seit dem letzten Besuch}}",
        "enhancedrc-history": "Versionsgeschichte",
        "upload-form-label-infoform-description-tooltip": "Beschreibe kurz alles bedeutende über das Werk.\nErwähne für ein Foto die abgebildeten hauptsächlichen Dinge, das Ereignis oder den Ort.",
        "upload-form-label-usage-title": "Verwendung",
        "upload-form-label-usage-filename": "Dateiname",
-       "foreign-structured-upload-form-label-own-work": "Dies ist mein eigenes Werk",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorien",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Ich bestätige, dass ich diese Datei gemäß den Nutzungsbedingungen und Lizenzrichtlinien von {{SITENAME}} hochlade.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Wenn du diese Datei nicht unter den Richtlinien von {{SITENAME}} hochladen kannst, schließe bitte diesen Dialog und versuche eine andere Methode.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Du kannst auch [[Special:Upload|die Standard-Hochladeseite]] ausprobieren.",
-       "foreign-structured-upload-form-label-own-work-message-default": "Ich verstehe, dass ich diese Datei auf ein gemeinsames Repositorium hochlade. Ich bestätige, dass ich dies gemäß den dortigen Nutzungs- und Lizenzbedingungen tue.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Falls du diese Datei nicht unter den Bedingungen des gemeinsamen Repositoriums hochladen kannst, schließe bitte diesen Dialog und versuche eine andere Methode.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Du kannst auch [[Special:Upload|die Hochladeseite auf {{SITENAME}}]] ausprobieren, falls diese Datei dort unter ihren Richtlinien hochgeladen werden kann.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Ich bestätige, dass ich das Urheberrecht für diese Datei besitze und stimme unwiderruflich der Veröffentlichung dieser Datei auf Wikimedia Commons unter der Lizenz [https://creativecommons.org/licenses/by-sa/4.0/deed.de „Creative Commons Namensnennung – Weitergabe unter gleichen Bedingungen 4.0 International“] sowie den [https://wikimediafoundation.org/wiki/Terms_of_Use/de Nutzungsbedingungen] zu.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Falls du nicht das Urheberrecht für diese Datei besitzt oder du diese Datei unter einer anderen Lizenz veröffentlichen möchtest, ziehe [https://commons.wikimedia.org/wiki/Special:UploadWizard den Hochladeassistenten auf Wikimedia Commons] in Erwägung.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Du kannst auch [[Special:Upload|die Hochladeseite auf {{SITENAME}}]] ausprobieren, falls die Website das Hochladen dieser Datei unter ihren Richtlinien erlaubt.",
+       "upload-form-label-own-work": "Dies ist mein eigenes Werk",
+       "upload-form-label-infoform-categories": "Kategorien",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Ich bestätige, dass ich diese Datei gemäß den Nutzungsbedingungen und Lizenzrichtlinien von {{SITENAME}} hochlade.",
+       "upload-form-label-not-own-work-message-generic-local": "Wenn du diese Datei nicht unter den Richtlinien von {{SITENAME}} hochladen kannst, schließe bitte diesen Dialog und versuche eine andere Methode.",
+       "upload-form-label-not-own-work-local-generic-local": "Du kannst auch [[Special:Upload|die Standard-Hochladeseite]] ausprobieren.",
+       "upload-form-label-own-work-message-generic-foreign": "Ich verstehe, dass ich diese Datei auf ein gemeinsames Repositorium hochlade. Ich bestätige, dass ich dies gemäß den dortigen Nutzungs- und Lizenzbedingungen tue.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Falls du diese Datei nicht unter den Bedingungen des gemeinsamen Repositoriums hochladen kannst, schließe bitte diesen Dialog und versuche eine andere Methode.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Du kannst auch [[Special:Upload|die Hochladeseite auf {{SITENAME}}]] ausprobieren, falls diese Datei dort unter ihren Richtlinien hochgeladen werden kann.",
        "backend-fail-stream": "Die Datei $1 konnte nicht übertragen werden.",
        "backend-fail-backup": "Die Datei $1 konnte nicht gesichert werden.",
        "backend-fail-notexists": "Die Datei $1 ist nicht vorhanden.",
        "changecontentmodel-success-text": "Der Inhaltstyp von [[:$1]] wurde geändert.",
        "changecontentmodel-cannot-convert": "Der Inhalt von [[:$1]] kann nicht zum Typ $2 konvertiert werden.",
        "changecontentmodel-nodirectediting": "Das Inhaltsmodell „$1“ unterstützt keine direkten Bearbeitungen",
+       "changecontentmodel-emptymodels-title": "Keine Inhaltsmodelle verfügbar",
+       "changecontentmodel-emptymodels-text": "Der Inhalt auf [[:$1]] kann zu keinem Typ konvertiert werden.",
        "log-name-contentmodel": "Inhaltsmodell-Änderungs-Logbuch",
        "log-description-contentmodel": "Ereignisse bezüglich den Inhaltsmodellen einer Seite",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|erstellte}} die Seite $3 mit einem Nicht-Standard-Inhaltsmodell „$5“",
        "whatlinkshere-prev": "{{PLURAL:$1|vorheriger|vorherige $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|nächster|nächste $1}}",
        "whatlinkshere-links": "← Links",
-       "whatlinkshere-hideredirs": "Weiterleitungen $1",
-       "whatlinkshere-hidetrans": "Vorlageneinbindungen $1",
-       "whatlinkshere-hidelinks": "Links $1",
-       "whatlinkshere-hideimages": "Dateilinks $1",
+       "whatlinkshere-hideredirs": "Weiterleitungen ausblenden",
+       "whatlinkshere-hidetrans": "Vorlageneinbindungen ausblenden",
+       "whatlinkshere-hidelinks": "Links ausblenden",
+       "whatlinkshere-hideimages": "Dateilinks ausblenden",
        "whatlinkshere-filters": "Filter",
        "whatlinkshere-submit": "Los",
        "autoblockid": "Automatische Sperrung #$1",
        "lockdbsuccesstext": "Die {{SITENAME}}-Datenbank wurde gesperrt.<br />Bitte gib die Datenbank [[Special:UnlockDB|wieder frei]], sobald die Wartung abgeschlossen ist.",
        "unlockdbsuccesstext": "Die {{SITENAME}}-Datenbank wurde freigegeben.",
        "lockfilenotwritable": "Die Datenbank-Sperrdatei ist nicht beschreibbar. Zum Sperren oder Freigeben der Datenbank muss diese für den Webserver beschreibbar sein.",
+       "databaselocked": "Die Datenbank ist bereits gesperrt.",
        "databasenotlocked": "Die Datenbank ist nicht gesperrt.",
        "lockedbyandtime": "(von $1 am $2 um $3 Uhr)",
        "move-page": "Verschieben von „$1“",
        "movepagetext": "Mit untenstehendem Formular kannst du eine Seite umbenennen, indem du sie mitsamt allen Versionen auf einen neuen Titel verschiebst.\nDer alte Titel wird danach zum neuen weiterleiten.\nDu kannst Weiterleitungen, die auf den Originaltitel verlinken, automatisch korrigieren lassen.\nStelle sicher, dass du im Anschluss alle [[Special:DoubleRedirects|doppelten]] oder [[Special:BrokenRedirects|defekten Weiterleitungen]] überprüfst.\nDu bist dafür verantwortlich, dass Links weiterhin auf das korrekte Ziel verweisen.\n\nDie Seite wird <strong>nicht</strong> verschoben, sofern es bereits eine Seite mit dem vorgesehenen Titel gibt, es sei denn, letztere ist eine Weiterleitung ohne Versionsgeschichte.\nDies bedeutet, dass du die Umbenennung rückgängig machen kannst, sofern du einen Fehler gemacht hast. Du kannst hingegen keine existierende Seite überschreiben.\n\n<strong>Hinweis:</strong>\nDie Verschiebung kann weitreichende und unerwartete Folgen für häufig besuchte Seiten haben.\nDu solltest daher die Konsequenzen verstanden haben, bevor du jetzt fortfährst.",
        "movepagetext-noredirectfixer": "Mit untenstehendem Formular kannst du eine Seite umbenennen, indem du sie mitsamt allen Versionen auf einen neuen Titel verschiebst.\nDer alte Titel wird danach zum neuen weiterleiten.\nStelle sicher, dass du im Anschluss alle [[Special:DoubleRedirects|doppelten]] oder [[Special:BrokenRedirects|defekten Weiterleitungen]] überprüfst.\nDu bist dafür verantwortlich, dass Links weiterhin auf das korrekte Ziel verweisen.\n\nDie Seite wird <strong>nicht</strong> verschoben, sofern es bereits eine Seite mit dem vorgesehenen Titel gibt, es sei denn, diese ist eine Weiterleitung ohne Versionsgeschichte.\nDies bedeutet, dass du die Umbenennung rückgängig machen kannst, sofern du einen Fehler gemacht hast. Du kannst hingegen keine existierende Seite überschreiben.\n\n<strong>Hinweis:</strong>\nDie Verschiebung kann weitreichende und unerwartete Folgen für häufig besuchte Seiten haben.\nDu solltest daher die Konsequenzen verstanden haben, bevor du jetzt fortfährst.",
        "movepagetalktext": "Falls du dieses Kästchen aktivierst, wird die dazugehörige Diskussionsseite automatisch auf den neuen Titel verschoben, sofern nicht bereits eine nicht-leere Diskussionsseite dort vorhanden ist.\n\nIn diesem Fall musst du die Seite manuell verschieben oder zusammenführen, falls erforderlich.",
-       "moveuserpage-warning": "'''Warnung:''' Du bist dabei, eine Benutzerseite zu verschieben. Bitte bedenke, dass dadurch nur die Benutzerseite verschoben, '''nicht''' aber der Benutzer umbenannt wird.",
+       "moveuserpage-warning": "<strong>Warnung:</strong> Du bist dabei, eine Benutzerseite zu verschieben. Bitte bedenke, dass dadurch nur die Benutzerseite verschoben, <em>nicht</em> aber der Benutzer umbenannt wird.",
        "movecategorypage-warning": "<strong>Warnung:</strong> Du bist gerade dabei, eine Kategorieseite zu verschieben. Bitte sei dir bewusst, dass nur die Seite verschoben wird. Alle der alten Kategorie zugeordneten Seiten werden <em>nicht</em> neu kategorisiert.",
        "movenologintext": "Du musst ein registrierter Benutzer und [[Special:UserLogin|angemeldet]] sein, um eine Seite zu verschieben.",
        "movenotallowed": "Du hast nicht die erforderliche Berechtigung, um Seiten verschieben zu können.",
        "imageinvalidfilename": "Der Ziel-Dateiname ist ungültig",
        "fix-double-redirects": "Nach dem Verschieben alle Weiterleitungen auf die Ursprungsseite bereinigen",
        "move-leave-redirect": "Weiterleitung erstellen",
-       "protectedpagemovewarning": "'''Warnung:''' Diese Seite wurde so geschützt, dass sie nur von Benutzern mit Administratorenrechten verschoben werden kann.\nZur Information folgt der aktuelle Logbucheintrag:",
+       "protectedpagemovewarning": "<strong>Warnung:</strong> Diese Seite wurde so geschützt, dass sie nur von Benutzern mit Administratorenrechten verschoben werden kann.\nZur Information folgt der aktuelle Logbucheintrag:",
        "semiprotectedpagemovewarning": "'''Hinweis:''' Diese Seite wurde so geschützt, dass sie nur von angemeldeten Benutzern verschoben werden kann.\nZur Information folgt der aktuelle Logbucheintrag:",
        "move-over-sharedrepo": "[[:$1]] existiert in einem gemeinsam genutzten Repositorium. Das Verschieben einer Datei zu diesem Titel überschreibt die gemeinsam genutzte Datei.",
        "file-exists-sharedrepo": "Der gewählte Dateiname wird bereits in einem gemeinsam genutzten Repositorium verwendet.\nBitte wähle einen anderen Namen.",
        "filedelete-archive-read-only": "Das Archiv-Verzeichnis „$1“ ist für den Webserver nicht beschreibbar.",
        "previousdiff": "← Zum vorherigen Versionsunterschied",
        "nextdiff": "Zum nächsten Versionsunterschied →",
-       "mediawarning": "'''Warnung:''' Dieser Dateityp kann böswilligen Programmcode enthalten.\nDurch das Herunterladen und Öffnen der Datei kann dein Computer beschädigt werden.",
+       "mediawarning": "<strong>Warnung:</strong> Dieser Dateityp kann böswilligen Programmcode enthalten.\nDurch das Herunterladen und Öffnen der Datei kann dein Computer beschädigt werden.",
        "imagemaxsize": "Maximale Bildgröße:<br />''(für Dateibeschreibungsseiten)''",
        "thumbsize": "Standardgröße der Vorschaubilder:",
        "widthheightpage": "$1 × $2, {{PLURAL:$3|1 Seite|$3 Seiten}}",
        "size-megabytes": "$1 MB",
        "size-gigabytes": "$1 GB",
        "lag-warn-normal": "Bearbeitungen der letzten {{PLURAL:$1|Sekunde|$1 Sekunden}} werden in dieser Liste noch nicht angezeigt.",
-       "lag-warn-high": "Auf Grund hoher Datenbankauslastung werden die Bearbeitungen der letzten {{PLURAL:$1|Sekunde|$1 Sekunden}} noch nicht in dieser Liste angezeigt.",
+       "lag-warn-high": "Aufgrund hoher Datenbankauslastung werden die Bearbeitungen der letzten {{PLURAL:$1|Sekunde|$1 Sekunden}} noch nicht in dieser Liste angezeigt.",
        "watchlistedit-normal-title": "Beobachtungsliste bearbeiten",
        "watchlistedit-normal-legend": "Einträge von der Beobachtungsliste entfernen",
        "watchlistedit-normal-explain": "Dies sind die Einträge deiner Beobachtungsliste. Um Einträge zu entfernen, markiere die Kästchen neben den Einträgen und klicke am Ende der Seite auf „{{int:Watchlistedit-normal-submit}}“. Du kannst deine Beobachtungsliste auch im [[Special:EditWatchlist/raw|Listenformat bearbeiten]].",
        "timezone-local": "Lokal",
        "duplicate-defaultsort": "Achtung: Der Sortierungsschlüssel „$2“ überschreibt den vorher verwendeten Schlüssel „$1“.",
        "duplicate-displaytitle": "<strong>Warnung:</strong> Der Anzeigetitel „$2“ überschreibt den früheren Anzeigetitel „$1“.",
+       "restricted-displaytitle": "<strong>Warnung:</strong> Der Anzeigetitel „$1“ wurde ignoriert, da er nicht mit dem tatsächlichen Seitentitel gleichwertig ist.",
        "invalid-indicator-name": "<strong>Fehler:</strong> Das Attribut <code>name</code> des Seitenstatusindikators darf nicht leer sein.",
        "version": "Version",
        "version-extensions": "Installierte Erweiterungen",
        "tags-delete-not-found": "Die Markierung „$1“ ist nicht vorhanden.",
        "tags-delete-too-many-uses": "Die Markierung „$1“ wird bei mehr als {{PLURAL:$2|einer Version|$2 Versionen}} verwendet und kann deshalb nicht gelöscht werden.",
        "tags-delete-warnings-after-delete": "Die Markierung „$1“ wurde gelöscht, aber die {{PLURAL:$2|folgende Warnung ist|folgenden Warnungen sind}} aufgetreten:",
+       "tags-delete-no-permission": "Du hast keine Berechtigung, Änderungsmarkierungen zu löschen.",
        "tags-activate-title": "Markierung aktivieren",
        "tags-activate-question": "Du bist dabei, die Markierung „$1“ zu aktivieren.",
        "tags-activate-reason": "Grund:",
        "feedback-useragent": "User Agent:",
        "searchsuggest-search": "Suchen",
        "searchsuggest-containing": "enthält …",
+       "api-error-autoblocked": "Deine IP-Adresse wurde automatisch gesperrt, da sie von einem gesperrten Benutzer verwendet wurde.",
        "api-error-badaccess-groups": "Du hast nicht die Berechtigung Dateien in dieses Wiki hochzuladen.",
        "api-error-badtoken": "Interner Fehler: Der Token ist fehlerhaft.",
+       "api-error-blocked": "Du wurdest für das Bearbeiten gesperrt.",
        "api-error-copyuploaddisabled": "Das Hochladen via URL wurde auf diesem Server deaktiviert.",
        "api-error-duplicate": "Es gibt im Wiki bereits {{PLURAL:$1|eine andere Datei|mehrere andere Dateien}} gleichen Inhalts.",
        "api-error-duplicate-archive": "Es {{PLURAL:$1|war bereits eine andere Datei|waren bereits andere Dateien}} gleichen Inhalts vorhanden. Sie {{PLURAL:$1|wurde|wurden}} allerdings gelöscht.",
        "api-error-nomodule": "Interner Fehler: Es wurde kein Modul zum Hochladen festgelegt.",
        "api-error-ok-but-empty": "Interner Fehler: Der Server reagiert nicht.",
        "api-error-overwrite": "Das Überschreiben einer vorhandenen Datei ist nicht erlaubt.",
+       "api-error-ratelimited": "Du versuchst, mehr Dateien in kurzer Zeit hochzuladen, als dieses Wiki erlaubt.\nBitte versuche es in einigen Minuten erneut.",
        "api-error-stashfailed": "Interner Fehler: Der Server konnte keine temporäre Datei speichern.",
        "api-error-publishfailed": "Interner Fehler: Der Server konnte die temporäre Datei nicht veröffentlichen.",
        "api-error-stasherror": "Beim Hochladen der Datei gab es einen Fehler.",
        "log-action-filter-suppress-block": "Benutzerunterdrückung durch Sperre",
        "log-action-filter-suppress-reblock": "Benutzerunterdrückung durch Neusperre",
        "log-action-filter-upload-upload": "Neue Hochladung",
-       "log-action-filter-upload-overwrite": "Wiederhochladung"
+       "log-action-filter-upload-overwrite": "Wiederhochladung",
+       "authmanager-authn-not-in-progress": "Die Authentifizierung ist nicht im Gang oder es sind Sitzungsdaten verloren gegangen. Bitte beginne von vorn.",
+       "authmanager-authn-no-primary": "Die angegebenen Anmeldeinformationen konnten nicht überprüft werden.",
+       "authmanager-authn-no-local-user": "Die angegebenen Anmeldeinformationen sind mit keinem Benutzer auf diesem Wiki verknüpft.",
+       "authmanager-authn-no-local-user-link": "Die angegebenen Anmeldeinformationen sind gültig, aber sind mit keinem Benutzer auf diesem Wiki verknüpft. Melde dich auf andere Weise an oder erstelle ein neues Benutzerkonto und du wirst die Möglichkeit haben, deine früheren Anmeldeinformationen mit diesem Konto zu verknüpfen.",
+       "authmanager-authn-autocreate-failed": "Die automatische Erstellung des lokalen Benutzerkontos ist fehlgeschlagen: $1",
+       "authmanager-change-not-supported": "Die angegebenen Anmeldeinformationen konnten nicht geändert werden, da sie von nichts genutzt werden würden.",
+       "authmanager-create-disabled": "Die Benutzerkontenerstellung ist deaktiviert.",
+       "authmanager-create-from-login": "Um dein Benutzerkonto zu erstellen, fülle bitte die unten stehenden Felder aus.",
+       "authmanager-create-not-in-progress": "Die Benutzerkontenerstellung ist nicht im Gang oder es sind Sitzungsdaten verloren gegangen. Bitte beginne von vorn.",
+       "authmanager-create-no-primary": "Die angegebenen Anmeldeinformationen konnten nicht für die Benutzerkontenerstellung verwendet werden.",
+       "authmanager-link-no-primary": "Die angegebenen Anmeldeinformationen konnten nicht für die Benutzerkontenverknüpfung verwendet werden.",
+       "authmanager-link-not-in-progress": "Die Benutzerkontenverknüpfung ist nicht im Gang oder es sind Sitzungsdaten verloren gegangen. Bitte beginne von vorn.",
+       "authmanager-authplugin-setpass-failed-title": "Passwortänderung fehlgeschlagen",
+       "authmanager-authplugin-setpass-failed-message": "Das Authentifizierungs-Plugin hat die Passwortänderung abgelehnt.",
+       "authmanager-authplugin-create-fail": "Das Authentifizierungs-Plugin hat die Benutzerkontenerstellung abgelehnt.",
+       "authmanager-authplugin-setpass-denied": "Das Authentifizierungs-Plugin erlaubt keine Passwortänderungen.",
+       "authmanager-authplugin-setpass-bad-domain": "Ungültige Domain.",
+       "authmanager-autocreate-noperm": "Die automatische Benutzerkontenerstellung ist nicht erlaubt.",
+       "authmanager-autocreate-exception": "Die automatische Benutzerkontenerstellung ist aufgrund früherer Fehler vorübergehend deaktiviert.",
+       "authmanager-userdoesnotexist": "Das Benutzerkonto „$1“ ist nicht registriert.",
+       "authmanager-userlogin-remembermypassword-help": "Ob das Passwort länger als die Sitzungslänge behalten werden soll.",
+       "authmanager-username-help": "Benutzername für die Authentifizierung.",
+       "authmanager-password-help": "Passwort für die Authentifizierung.",
+       "authmanager-domain-help": "Domain für die externe Authentifizierung.",
+       "authmanager-retype-help": "Passwort erneut zur Bestätigung eingeben.",
+       "authmanager-email-label": "E-Mail",
+       "authmanager-email-help": "E-Mail-Adresse",
+       "authmanager-realname-label": "Bürgerlicher Name",
+       "authmanager-realname-help": "Bürgerlicher Name des Benutzers",
+       "authmanager-provider-password": "Passwortbasierte Authentifizierung",
+       "authmanager-provider-password-domain": "Passwort- und domainbasierte Authentifizierung",
+       "authmanager-provider-temporarypassword": "Temporäres Passwort",
+       "authprovider-confirmlink-message": "Basierend auf deinen letzten Anmeldeversuchen können die folgenden Benutzerkonten mit deinem Wiki-Benutzerkonto verknüpft werden. Das Verknüpfen ermöglicht die Anmeldung über diese Konten. Bitte wähle das Benutzerkonto aus, das verknüpft werden soll.",
+       "authprovider-confirmlink-request-label": "Benutzerkonten, die verknüpft werden sollen",
+       "authprovider-confirmlink-success-line": "$1: Erfolgreich verknüpft.",
+       "authprovider-confirmlink-failed": "Die Benutzerkontenverknüpfung war nicht vollständig erfolgreich: $1",
+       "authprovider-confirmlink-ok-help": "Nach der Anzeige von Verknüpfungsfehlermeldungen fortfahren.",
+       "authprovider-resetpass-skip-label": "Überspringen",
+       "authprovider-resetpass-skip-help": "Das Zurücksetzen des Passworts überspringen.",
+       "authform-nosession-login": "Die Authentifizierung war erfolgreich, aber dein Browser kann sich deine Anmeldung nicht „merken“.\n\n$1",
+       "authform-nosession-signup": "Das Benutzerkonto wurde erstellt, aber dein Browser kann sich deine Anmeldung nicht „merken“.\n\n$1",
+       "authform-newtoken": "Fehlender Token. $1",
+       "authform-notoken": "Fehlender Token",
+       "authform-wrongtoken": "Falscher Token",
+       "specialpage-securitylevel-not-allowed-title": "Nicht erlaubt",
+       "specialpage-securitylevel-not-allowed": "Leider bist du nicht berechtigt, diese Seite zu benutzen, da deine Identität nicht verifiziert werden konnte.",
+       "authpage-cannot-login": "Anmeldung konnte nicht gestartet werden.",
+       "authpage-cannot-login-continue": "Anmeldung konnte nicht fortgeführt werden. Vielleicht liegt bei deiner Sitzung eine Zeitüberschreitung vor.",
+       "authpage-cannot-create": "Benutzerkontenerstellung konnte nicht gestartet werden.",
+       "authpage-cannot-create-continue": "Die Benutzerkontenerstellung konnte nicht fortgeführt werden. Vielleicht liegt bei deiner Sitzung eine Zeitüberschreitung vor.",
+       "authpage-cannot-link": "Die Benutzerkontenverknüpfung konnte nicht gestartet werden.",
+       "authpage-cannot-link-continue": "Die Benutzerkontenverknüpfung konnte nicht fortgeführt werden. Vielleicht liegt bei deiner Sitzung eine Zeitüberschreitung vor.",
+       "cannotauth-not-allowed-title": "Zugriff verweigert",
+       "cannotauth-not-allowed": "Du bist nicht berechtigt, diese Seite zu verwenden.",
+       "changecredentials": "Anmeldeinformationen ändern",
+       "changecredentials-submit": "Ändern",
+       "changecredentials-submit-cancel": "Abbrechen",
+       "changecredentials-invalidsubpage": "$1 ist kein gültiger Typ für Anmeldeinformationen.",
+       "changecredentials-success": "Deine Anmeldeinformationen wurden geändert.",
+       "removecredentials": "Anmeldeinformationen entfernen",
+       "removecredentials-submit": "Entfernen",
+       "removecredentials-submit-cancel": "Abbrechen",
+       "removecredentials-invalidsubpage": "$1 ist kein gültiger Typ für Anmeldeinformationen.",
+       "removecredentials-success": "Deine Anmeldeinformationen wurden entfernt.",
+       "credentialsform-provider": "Typ der Anmeldeinformationen:",
+       "credentialsform-account": "Benutzerkontenname:",
+       "cannotlink-no-provider-title": "Es gibt keine verknüpfbaren Benutzerkonten",
+       "cannotlink-no-provider": "Es gibt keine verknüpfbaren Benutzerkonten.",
+       "linkaccounts": "Benutzerkonten verknüpfen",
+       "linkaccounts-success-text": "Das Benutzerkonto wurde verknüpft.",
+       "linkaccounts-submit": "Benutzerkonten verknüpfen",
+       "unlinkaccounts": "Benutzerkonten trennen",
+       "unlinkaccounts-success": "Das Benutzerkonto wurde getrennt."
 }
index 54f842a..9ff372c 100644 (file)
@@ -22,7 +22,8 @@
                        "아라",
                        "Calak",
                        "Macofe",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "Kumkumuk"
                ]
        },
        "tog-underline": "Bınê gırey de xete bance:",
        "hidden-category-category": "Kategoriyê nımıtey",
        "category-subcat-count": "{{PLURAL:$2|Na kategoriya de $1 bınkategoriyay estê.|$2 kategoriyan ra $1 bınkategoriyay asenê.}}",
        "category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|ena kategoriya bınêne esta|enê $1 kategoriyê bınêni estê}}.",
-       "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Na kategoriye de $2 tenan ra, {{PLURAL:$1|ena pele esta|$1 peli}}, ena kategoriye miyan derê}}",
+       "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pela na kategoriye dera|$1 enê peli na kategoriye derê.}}}}",
        "category-article-count-limited": "{{PLURAL:$1|Pela cêrêne|$1 Pelê cêrêni}} na kategoriye derê.",
        "category-file-count": "<noinclude>{{PLURAL:$2|Na kategoriye tenya dosyayanê cêrênan muhtewa kena.}}</noinclude>\n*Na kategoriye de $2 dosyayan ra {{PLURAL:$1|yew dosya tenêka esta| $1 dosyey asenê}}.",
        "category-file-count-limited": "{{PLURAL:$1|Dosya cêrêne|$1 Dosyê cêrêni}} na kategoriye derê.",
        "search": "Cı geyre",
        "searchbutton": "Cı geyre",
        "go": "Şo",
-       "searcharticle": "Şo",
+       "searcharticle": "So",
        "history": "Tarixê pele",
        "history_short": "Tarix",
        "updatedmarker": "cıkewtena mına peyêne ra dıme biyo rocane",
-       "printableversion": "Asayışê çapkerdışi",
+       "printableversion": "Asaena çapkerdene",
        "permalink": "Gıreyo jûqere",
        "print": "Çap ke",
        "view": "Bıvêne",
        "unprotectthispage": "Starkerdışe ena peler bıvurne",
        "newpage": "Pela newiye",
        "talkpage": "Ena pele sero werêne",
-       "talkpagelinktext": "Werênayış",
+       "talkpagelinktext": "werênayış",
        "specialpage": "Pela xısusiye",
        "personaltools": "Hacetê şexsiy",
        "articlepage": "Pela zerreki bıvêne",
        "lastmodifiedat": "Ena pele tewr peyên roca $2, $1 de biya rocaniye.",
        "viewcount": "Ena pele {{PLURAL:$1|rae|$1 rey}} vêniya.",
        "protectedpage": "Pela pawıtiye",
-       "jumpto": "Şo:",
+       "jumpto": "Şo be:",
        "jumptonavigation": "Pusula",
        "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",
        "pool-errorunknown": "Xeta nêzanıtiye",
        "poolcounter-usage-error": "Xırab karyayış:$1",
        "aboutsite": "Heqa {{SITENAME}} de",
-       "aboutpage": "Project:Heqa {{SITENAME}} de",
+       "aboutpage": "Project:Heqa",
        "copyright": "Zerrekacı $1 bındı not biya.",
        "copyrightpage": "{{ns:project}}:Heqa telifi",
        "currentevents": "Veng û vac",
        "mainpage": "Pela Seri",
        "mainpage-description": "Pela seri",
        "policy-url": "Project:Terzê hereketi",
-       "portal": "Portalê cemaeti",
-       "portal-url": "Project:Portalê cemaeti",
-       "privacy": "Madeyê dızdiye",
+       "portal": "Portalê kome",
+       "portal-url": "Project:Portalê kome",
+       "privacy": "Madê dızdêni",
        "privacypage": "Project:Xısusiyetê nımtışi",
        "badaccess": "Xeta mısadey",
        "badaccess-group0": "Heqa şıma çıniya, karo ke şıma waşt, bıkerê.",
        "toc": "Sernameyê meselan",
        "showtoc": "bımocne",
        "hidetoc": "bınımne",
-       "collapsible-collapse": "Kılm ke",
+       "collapsible-collapse": "Teng ke",
        "collapsible-expand": "Hera ke",
        "confirmable-confirm": "{{GENDER:$1|Şıma }} do emeli?",
        "confirmable-yes": "Eya",
        "feed-invalid": "Qeydey cıresnayışê  beğşi nêvêreno.",
        "feed-unavailable": "Cıresnayışê şebekey çıniyê",
        "site-rss-feed": "$1 Cıresnayışê RSSi",
-       "site-atom-feed": "$1 Cıresnayışê atomi",
+       "site-atom-feed": "$1 Wari kerdrna Atomi",
        "page-rss-feed": "\"$1\" Cıresnayışê RSSi",
        "page-atom-feed": "\"$1\" Cıresnayışê atomi",
        "feed-atom": "Atom",
        "noname": "Yew nameyo maqbul bınuse.",
        "loginsuccesstitle": "Hesab abıya",
        "loginsuccess": "'''{{SITENAME}} dı name dê \"$1\" şıma hesab akerdo.'''",
-       "nosuchuser": "Ebe namey \"$1\"i yew karber çıniyo.\nNuştışê namanê karberan de herfa pil u qıce rê diqet kerên.\nNuştışê xo qonrol kerên, ya zi [[Special:UserLogin/signup|yew hesabo newe akerên]].",
+       "nosuchuser": "\"$1\" ya yew namey karberi çıniyo.\nNuştışê namanê karberan de herfa pil u qıce rê diqet kerên.\nNuştışê xo qontrol kerên, ya zi [[Special:CreateAccount|yew hesabo newe akerê]].",
        "nosuchusershort": "No \"$1\" name de yew ten çino. Kontrolê nuştışi bıkere.",
        "nouserspecified": "Şıma gani yew name bıde.",
        "login-userblocked": "No karber/na karbere blokekerdeyo/blokekerdiya. Cıkewtışi rê musade çıniyo.",
        "wrongpassword": "Parola ğeleta. Rêna / fına bıcerrebne .",
        "wrongpasswordempty": "Parola tola, venga. tekrar bınuse.",
        "passwordtooshort": "Paroley gani tewr senık be {{PLURAL:$1|1 karakter|$1 karakteran}} derg bê.",
-       "passwordtoolong": "Dergeya parolay en cêrek ra do {{PLURAL:$1|1 karakter|$1 karakter}} bo",
+       "passwordtoolong": "Paroleyi be {{PLURAL:$1|1 karakter|$1 karakteran}} ra derg nêbenê.",
        "password-name-match": "Parola u nameyê şıma gani zeypê (seypê) nêbo.",
        "password-login-forbidden": "Nameyê nê karberi û gurenayışê parola biyo qedeğen.",
        "mailmypassword": "Parola reset ke",
        "resetpass_submit": "Parola eyar kere u newe ra dekewe",
        "changepassword-success": "Parola şıma be serkewtış vuriye!",
        "changepassword-throttled": "Şıma zaf ronıştış akerdış ke.Kerem ke verdi dekewten $1 bıpawe.",
-       "botpasswords-label-appid": "Bot name:",
+       "botpasswords-label-appid": "Nameyê boti:",
        "botpasswords-label-create": "Vıraze",
        "botpasswords-label-update": "Rocane ke",
        "botpasswords-label-cancel": "Bıtexelne",
        "botpasswords-label-delete": "Bestere",
-       "botpasswords-label-resetpassword": "Parola reset ke",
-       "botpasswords-label-grants-column": "Dayen",
+       "botpasswords-label-resetpassword": "Parola raçarne",
+       "botpasswords-label-grants-column": "Daye",
        "resetpass_forbidden": "parolayi nêvuryayi",
        "resetpass-no-info": "şıma gani hesab akere u hona bıeşke bırese cı",
        "resetpass-submit-loggedin": "Parola bıvurne",
        "accmailtext": "[[User talk:$1|$1]] parolayo ke raşt ameyo şırawiyo na adres $2.\n\nQey na hesabê newe parola, cıkewtış dıma şıma eşkeni na qısım de ''[[Special:ChangePassword|parola bıvurn]]'' bıvurni.",
        "newarticle": "(Newe)",
        "newarticletext": "To yew gıre tıkna be ra yew pela ke hewna çıniya.\nSeba afernayışê pele ra, qutiya metnê cêrêni bıgurene (seba melumati qaytê [$1 pela peşti] ke).\nEke be ğeletine ameya tiya, wa gocega <strong>peyser</strong>i programê xo de bıtıkne.",
-       "anontalkpagetext": "----''No pel, pel o karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres şuxulneni û ney IP adresan herkes eşkeno bıvino. Eke şıma qayil niye ina bo xo ri [[Special:UserLogin/signup|yew hesab bıvıraze]] veyaxut [[Special:UserLogin|hesab akere]].''",
+       "anontalkpagetext": "----''No pel, pel o karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres şuxulneni û ney IP adresan herkes eşkeno bıvino. Eke şıma qayil niye ina bo xo ri [[Special:CreateAccount|yew hesab bıvıraze]] veyaxut [[Special:UserLogin|hesab akere]].''",
        "noarticletext": "Ena pele de hewna theba çıniyo.\nTı şenay zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|seba sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.",
        "noarticletext-nopermission": "Ena pele de hewna theba çıniyo.\nTı şenay zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|seba sernameyê na pele cı geyre]], ya zi <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre]</span>, ema destur çıniyo ke na pele vırazê.",
        "missing-revision": "Rewizyonê name dê pela da #$1 \"{{FULLPAGENAME}}\" dı çıniyo.\n\nNo normal de tarix dê pelanê besterneyan dı ena xırabin asena.\nDetayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.",
        "userinvalidcssjstitle": "'''Teme:''' Mewzuyê \"$1\" çıniyo.\nDosyanê be namey .css u .js'i de herfa werdiye bıgurêne, mesela herında {{ns:user}}:Foo/Vector.css'i de {{ns:user}}:Foo/vector.css bınuse.",
        "updated": "(Rozeneya)",
        "note": "'''Not:'''",
-       "previewnote": "'''Şıma bızanê ke ena yew verqayta.'''\nVurnayışê şıma hona qeyd nêbiyo!",
+       "previewnote": "'''Şıma bızanê ke eno yew verqayto.'''\nVurnayışê şıma hewna qeyd nêbiyê!",
        "continue-editing": "Şo herunda vurnayışi",
        "previewconflict": "No seyrkerdışê verqaydi serê qutiyê nuşte tezim kerdış de yo, eke şıma qayile vurnayişê maddeyi seyino bıvini, no mocneno şıma.",
        "session_fail_preview": "Ma ef kere. Vindibiyayişê tayê datay ra a kerdışê hesabê şıma de ma vurnayişê şıma qayd nêkerd. Newe ra tesel (cereb) bıkere. Eke no qayde zi nêbo, [[Special:UserLogout|hesabê xo bıqefelne]] u newera a kere.",
        "undo-summary-username-hidden": "Rewizyona veri $1'i hewada",
        "cantcreateaccounttitle": "Nêşenay hesab rakerê",
        "cantcreateaccount-text": "Hesabvıraştışê na IP adrese ('''$1''') terefê [[User:$3|$3]] kılit biyo.\n\nSebebo ke terefê $3 ra diyao ''$2''",
-       "viewpagelogs": "Heqdê na perer qeydan bıvin",
+       "viewpagelogs": "Seba na pele rê qeydan bımocne",
        "nohistory": "Verê vurnayışanê na pele çıniyo.",
        "currentrev": "Çımraviyarnayışo rocane",
        "currentrev-asof": "Revizyonanê peniyan, tarixê $1",
        "prefs-help-signature": "Peran de vatenana de vatışi\"<nowiki>~~~~</nowiki>\" ya do imza bé, no bahdo beno çerğé imza u wahdey zemani",
        "badsig": "Îmzayê tu raşt niyo.\nEtiketê HTMLî kontrol bike.",
        "badsiglength": "İmzaya şıma zaf derga.\nA gani be $1 {{PLURAL:$1|karakter|karakteran}} ra zêde mebo.",
-       "yourgender": "Cınsiyeta şıma?",
+       "yourgender": "Şeklê xitabi?",
        "gender-unknown": "Ez detay nivana",
        "gender-male": "Perané wiki camérd deyne ezo vırnena",
        "gender-female": "Perané wiki cıni deyne eza vırnena",
        "enhancedrc-history": "tarix",
        "recentchanges": "Vurnayışê peyêni",
        "recentchanges-legend": "Tercihê vurnayışanê peyênan",
-       "recentchanges-summary": "Wiki sero vurnayışanê peyênan ena pele de teqib kerê.",
+       "recentchanges-summary": "Ena pele de wiki sero vurnayışanê peyênan teqib ke.",
        "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 yew pela newiye vıraziye",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|Lista pelanê neweyan]] zi bıvêne)",
        "recentchanges-legend-plusminus": "''(±123)''",
        "rcnotefrom": "Cêr de <strong>$2</strong> ra nata vurnayışiyê asenê (tewr vêşi <strong>$1</strong> asenê).",
-       "rclistfrom": "$3 u seate $2 ra tepiya vurnayışanê neweyan bımocne",
-       "rcshowhideminor": "vurnayışanê werdiyan $1",
+       "rclistfrom": "$3 $2 ra tepiya vurnayışanê neweyan bımocne",
+       "rcshowhideminor": "vurnayışê werdiyi $1",
        "rcshowhideminor-show": "Bımocne",
        "rcshowhideminor-hide": "Bınımne",
-       "rcshowhidebots": "Vurnayışanê botan $1",
+       "rcshowhidebots": "botan $1",
        "rcshowhidebots-show": "Bımocne",
        "rcshowhidebots-hide": "Bınımne",
-       "rcshowhideliu": "Qeydbıyayê karberan $1",
+       "rcshowhideliu": "karberê qeydbiyayeyi $1",
        "rcshowhideliu-show": "Bımocne",
        "rcshowhideliu-hide": "Bınımne",
-       "rcshowhideanons": "Bêname karberan $1",
+       "rcshowhideanons": "karberê bênameyi $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ê êdê mı $1",
+       "rcshowhidemine": "vurnayışanê mı $1",
        "rcshowhidemine-show": "Bımocne",
        "rcshowhidemine-hide": "Bınımne",
-       "rcshowhidecategorization": "kategorizasyona perer $1",
+       "rcshowhidecategorization": "kategorizasyonê pele $1",
        "rcshowhidecategorization-show": "Bımocne",
        "rcshowhidecategorization-hide": "Bınımne",
-       "rclinks": "Peyên $2 rocan de $1 vurnayışan bımocne <br />$3",
+       "rclinks": "Peyniya $2 rocan de $1 vurnayışan bımocne <br />$3",
        "diff": "ferq",
        "hist": "verên",
        "hide": "Bınımne",
        "uploadbtn": "Dosya bar ke",
        "reuploaddesc": "Barkerdışi iptal ke u peyser şo formê barkerdışi",
        "upload-tryagain": "Deskripyonê dosyayî ke vurîya ey qeyd bike",
-       "uploadnologin": "Şıma cıkewtış nêvıraşt o",
+       "uploadnologin": "Şıma cıkewtış nêvıraşto",
        "uploadnologintext": "Ti şeni $1 dosya bar bikere.",
        "upload_directory_missing": "Direktorê dosyayê ($1)î biyo vînî u webserver de nieşkeno viraziye.",
        "upload_directory_read_only": "Direktorê dosyayê ($1)î webserver de nieşkeno binuse.",
        "upload-form-label-infoform-description": "Şınasnayış",
        "upload-form-label-usage-title": "Gurenayış",
        "upload-form-label-usage-filename": "Nameyê dosya",
-       "foreign-structured-upload-form-label-own-work": "No karê mıno",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategoriyi",
-       "foreign-structured-upload-form-label-infoform-date": "Tarix",
+       "upload-form-label-own-work": "No karê mıno",
+       "upload-form-label-infoform-categories": "Kategoriyi",
+       "upload-form-label-infoform-date": "Tarix",
        "backend-fail-stream": "$1 nê vırazeyê",
        "backend-fail-backup": "$1 nê wendeyê",
        "backend-fail-notexists": "Dosyaya $1 çıniya.",
        "randomredirect-nopages": "Cayê nameyê \"$1\" de serşıkıtışi çıniyê.",
        "statistics": "İstatistiki",
        "statistics-header-pages": "İstatistikê pele",
-       "statistics-header-edits": "Îstatistikê vurnayîşî",
+       "statistics-header-edits": "İstatistikê vurnayışan",
        "statistics-header-users": "İstatistikê karberi",
-       "statistics-header-hooks": "Îstatistiksê binî",
+       "statistics-header-hooks": "Yewbina istatistiki",
        "statistics-articles": "Pelê zerreki",
        "statistics-pages": "Peli",
        "statistics-pages-desc": "Pelanê hemî ke wîkî de estê, pelanê mineqeşeyî, redireksiyon ucb... dehil o.",
        "statistics-files": "Dosyayê bar biye",
        "statistics-edits": "{{SITENAME}} saz kerdış ra hetana newke amora vırnayışan",
        "statistics-edits-average": "Her pele sero nısbi vurnayış",
-       "statistics-users": "Qeyd biye [[Special:ListUsers|karberî]]",
+       "statistics-users": "[[Special:ListUsers|Karber]]ê qeydıni",
        "statistics-users-active": "Karberê aktifi",
        "statistics-users-active-desc": "{{PLURAL:$1|roco peyin de|$1 roco peyin de}} karber ê ke kar kerdê.",
        "pageswithprop": "Peli be yew xısusiyetê pele",
        "listgrouprights-group": "Grube",
        "listgrouprights-rights": "Heqqî",
        "listgrouprights-helppage": "Help:Heqqanê gruban",
-       "listgrouprights-members": "(listey ezayan)",
+       "listgrouprights-members": "[lista ezayan]",
        "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
        "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "{{PLURAL:$2|Grube|Gruban}} cı kerê: $1",
        "watchlist-details": "{{PLURAL:$1|$1 pele|$1 peleyan}} listeyê seyr-kerdışi şıma dı, peleyanê vurnayışi dahil niyo.",
        "wlheader-enotif": "E-mail xeber dayiş abiyo.",
        "wlheader-showupdated": "ziyaretê şıma ye peyini de vuryayişê peli pê '''nuşteyo qalıni''' mocyayo.",
-       "wlnote": "$3 seate u $4 deqa dıma {{PLURAL:$2|ju seate dı|'''$2''' ju seate dı}} {{PLURAL:$1|vurnayışe peyeni|vurnayışe '''$1''' peyeni}} cêrdeyê",
+       "wlnote": "$3 saete $4 ra dıme {{PLURAL:$2|yew saete de|'''$2''' saetan de}} {{PLURAL:$1|vurnayışo peyên|vurnayışê '''$1''' peyêni}} cêrderê.",
        "wlshowlast": "Peyni de vurnayışan ra  $1 seata u $2 roca  bımocnê",
        "watchlist-hide": "Bınımne",
        "wlshowtime": "Peyênan bımocne:",
-       "wlshowhideminor": "vurnayışanê werdiyan",
+       "wlshowhideminor": "vurnayışê werdiyi",
        "wlshowhidebots": "boti",
        "wlshowhideliu": "karberê qeydıni",
        "wlshowhideanons": "karberê anonimi",
        "wlshowhidepatr": "vurnayışê pawıteyi",
        "wlshowhidemine": "vurnayışê mı",
-       "wlshowhidecategorization": "Kategorizasyona perer",
+       "wlshowhidecategorization": "kategorizasyonê pele",
        "watchlist-options": "Tercihê liste da seyri",
        "watching": "Seyr ke...",
        "unwatching": "Seyr meke...",
        "actioncomplete": "Kar bi temam",
        "actionfailed": "kar nêbı",
        "deletedtext": "\"$1\" biya wedariya.\nQe qeydê wedarnayışi, $2 bevinin.",
-       "dellogpage": "Qeydê esternayışi",
+       "dellogpage": "Qeydê esterıtışi",
        "dellogpagetext": "listeya cêrıni heme qaydê hewn a kerdeyan o.",
-       "deletionlog": "Qeydê esternayışi",
+       "deletionlog": "qeydê esterıtışi",
        "reverted": "revizyono verin tepiya anciyayo",
        "deletecomment": "Sebeb:",
        "deleteotherreason": "Sebebo bin:",
        "changecontentmodel-title-label": "Sernameyê pele",
        "changecontentmodel-model-label": "Modelê zerrekiyo newe",
        "changecontentmodel-reason-label": "Sebeb:",
-       "log-name-contentmodel": "Qeydé vurnayıÅ\9fan de modela zerreki",
+       "log-name-contentmodel": "Qeydê vurnayıÅ\9fanê modelê zerreki",
        "protectlogpage": "Qeydê staryayan",
        "protectlogtext": "Şıma vurnayişê gırewtışê/wedarnayışê pawıtişi vinenê.\nQey malumato ziyede [[Special:ProtectedPages|Peleyê ke star biye]] bewni rê êna .",
        "protectedarticle": "\"[[$1]]\" kılit biyo",
        "undelete": "Peleyê ke besterneyayê enê bımocnê",
        "undeletepage": "bıewn revizyonê peli yê hewn a şiyayeyan u tepiya biyar",
        "undeletepagetitle": "'''pelo [[:$1|$1]] cêrın, wayirê revizyonê hewn a şiyayeyan o'''.",
-       "viewdeletedpage": "Bıewni perandê besternayan",
+       "viewdeletedpage": "Pelanê esteriyayeyan bımocne",
        "undeletepagetext": "{{PLURAL:$1|pelo|$1 pelo}} cerın hewn a şiyo labele hema zi arşiv de yo u tepiya geriyeno.\nArşiv daimi pak beno.",
        "undelete-fieldset-title": "revizyonan tepiya bar ker",
        "undeleteextrahelp": "Qey ardışê pel u verê pelani tuşê '''tepiya biya!'''yi bıtıknê. qey ciya ciya ardışê verê pelani zi qutiye tesdiqi nişane kerê u tuşê '''tepiya biya!'''yi bıtıknê '''''{{int:undeletebtn}}'''''.. qey hewn a kerdışê qutiya tesdiqan u qey sıfır kerdışê cayê sebebani zi tuşê '''agêr caverd/aça ker'''i bıtıknê '''''{{int:undeletebtn}}'''''..",
        "sp-contributions-newbies": "Tenya iştıraqanê karberanê neweyan bımocne",
        "sp-contributions-newbies-sub": "Qe hesebê newe",
        "sp-contributions-newbies-title": "Îştîrakê karberî ser hesabê neweyî",
-       "sp-contributions-blocklog": "Qeydê meni",
+       "sp-contributions-blocklog": "qeydê kılitbiyayeyi",
        "sp-contributions-deleted": "iştırakê karberi esterdi",
        "sp-contributions-uploads": "barkerdey",
        "sp-contributions-logs": "qeydi",
        "whatlinkshere-prev": "{{PLURAL:$1|veror|veror $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|verni|verni $1}}",
        "whatlinkshere-links": "← gırey",
-       "whatlinkshere-hideredirs": "Hetenayışê $1",
-       "whatlinkshere-hidetrans": "Açarnayışê $1",
-       "whatlinkshere-hidelinks": "Greyê $1",
+       "whatlinkshere-hideredirs": "Hetenayışan bınımne",
+       "whatlinkshere-hidetrans": "Gırêyanê açarnayışan bınımne",
+       "whatlinkshere-hidelinks": "Gırêyan bınımne",
        "whatlinkshere-hideimages": "Gıreyê dosya $1",
        "whatlinkshere-filters": "Avrêci",
        "autoblockid": "Otomatik vındarnayış #$1",
        "contribslink": "iştıraqi",
        "emaillink": "e-poste bırışe",
        "autoblocker": "Şıma otomatikmen kılit biy, çıke adresa şımaya ''IP''y terefê \"[[User:$1|$1]]\" gureniyena.\nSebebê kılitbiyayışê $1'i \"$2\"o",
-       "blocklogpage": "Qeydê meni",
+       "blocklogpage": "Qeydê astengi",
        "blocklog-showlog": "verniyê no/na karberi cıwa ver geriyayo/ya.",
        "blocklog-showsuppresslog": "verniyê no/na karberi cıwa ver geriyayo/ya.",
        "blocklogentry": "[[$1]] biyo bloqe, sebeb: $3, hetana $2 do bıramo.",
        "import-options-wrong": "{{PLURAL:$2|Weçenego|Weçenego}} xerpiyaye: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "Sernuştey ena pela reçey cı raverde niyo.",
        "import-rootpage-nosubpage": "Qan de bınnaman reçe de \"$1\" re mısade nedano.",
-       "importlogpage": "Qeydé raverdayıÅ\9fi",
+       "importlogpage": "Qeydê ragozi",
        "importlogpagetext": "wiki yo ke nişane biyo tera kırıştışê zerredayişi nêbeno.",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|çımraviyarnayış|çımraviyarnayışi}}",
        "import-logentry-interwiki-detail": "$2 ra $1 {{PLURAL:$1|çımraviyarnayış|çımraviyarnayışi}}",
        "tooltip-ca-move": "Ena pele bere",
        "tooltip-ca-watch": "Ena pele lista xoya seyrkerdışi ke",
        "tooltip-ca-unwatch": "Ena pele lista xoya seyrkerdışi ra vece",
-       "tooltip-search": "{{SITENAME}} miyan de bıvin",
+       "tooltip-search": "{{SITENAME}} de bıvin",
        "tooltip-search-go": "Ebe nê namey tami şo yew pela ke esta",
-       "tooltip-search-fulltext": "Nê  metni peran dı cı geyre",
-       "tooltip-p-logo": "Şo pela seri",
+       "tooltip-search-fulltext": "Pela miyan dı bı geyr ena metin",
+       "tooltip-p-logo": "pela seri ziyaret ke",
        "tooltip-n-mainpage": "Şo pela seri",
        "tooltip-n-mainpage-description": "Şo pela seri",
        "tooltip-n-portal": "Heqa proceyi de, çı şenay bıkerê, çı koti vêniyeno",
        "tooltip-feed-atom": "Qe ena pele atom feed",
        "tooltip-t-contributions": "Yew lista iştırakanê {{GENDER:$1|nê karberi}}",
        "tooltip-t-emailuser": "Ena karber ri yew email bışırav",
-       "tooltip-t-upload": "Dosya bar ke",
+       "tooltip-t-upload": "Dosyey bar ke",
        "tooltip-t-specialpages": "Yew lista pelanê xasanê pêroyinan",
        "tooltip-t-print": "Hewl versiyona ploğnayışa na perer",
        "tooltip-t-permalink": "Gırêyo daimi be ena versiyonê pele",
        "patrol-log-page": "Qeydé çımsernayoğan",
        "patrol-log-header": "Ena listeyê logi revizyonê devriyeyi mocneno.",
        "log-show-hide-patrol": "Qeydé Çımsernayoğan $1",
-       "log-show-hide-tag": "$1 qeydé etiketi",
+       "log-show-hide-tag": "$1 qeydê etiketi",
        "deletedrevision": "Veriyono kihan $1 wederna",
        "filedeleteerror-short": "Wedarnayişê dosya de ğelati esto: $1",
        "filedeleteerror-long": "Eka dosya wedarnayişi de ğeleti biyê:\n\n$1",
        "exif-urgency-low": "($1) Kemiyo",
        "exif-urgency-high": "( $1 ) Vêşiyo",
        "exif-urgency-other": "Sıftê  şınasiya karberi ($1)",
-       "namespacesall": "pêro",
+       "namespacesall": "pêron",
        "monthsall": "pêro",
        "confirmemail": "Adresê e-posta tesdiq ker",
        "confirmemail_noemail": "Yew emaîlê tu raştîyê çin o ke [[Special:Preferences|tercihê karberî]] ayar bike.",
        "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": "Pelê xısusiyi",
+       "specialpages": "Pelê xasi",
        "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",
        "logentry-rights-rights-legacy": "$1 qandê $3 rê ezayiya grube {{GENDER:$2|vuriye}}",
        "logentry-rights-autopromote": "$1 otomatikmen $4 ra $5 {{GENDER:$2|terfi bi}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|bar kerd}} $3",
-       "log-name-managetags": "Qeydé idareyê etiketi",
-       "log-name-tag": "Qeydé etiketi",
+       "log-name-managetags": "Qeydê idareyê etiketi",
+       "log-name-tag": "Qeydê etiketi",
        "rightsnone": "(çıniyo)",
        "revdelete-summary": "kılmvatışê vuriyayişi",
        "feedback-adding": "Pela rê peyxeberdar defêno...",
index f11718e..0cbc2e8 100644 (file)
        "noname": "Njejsy žedno płaśece wužywarske mě zapódał.",
        "loginsuccesstitle": "Pśizjawjenje wuspěšne",
        "loginsuccess": "'''Sy něnto ako „$1” w {{GRAMMAR:lokatiw|{{SITENAME}}}} pśizjawjony.'''",
-       "nosuchuser": "Wužywaŕ z mjenim „$1“ njeeksistěrujo. Wužywarske mjenja źiwaju na wjelikopisanje.\nPśeglěduj pšawopis abo [[Special:UserLogin/signup|załož nowe konto]].",
+       "nosuchuser": "Wužywaŕ z mjenim „$1“ njeeksistěrujo. Wužywarske mjenja źiwaju na wjelikopisanje.\nPśeglěduj pšawopis abo [[Special:CreateAccount|załož nowe konto]].",
        "nosuchusershort": "Wužywarske mě „$1“ njeeksistěrujo. Pśeglěduj pšawopis.",
        "nouserspecified": "Pšosym pódaj wužywarske mě.",
        "login-userblocked": "Toś ten wužywaŕ jo blokěrowany. Pśizjawjenje njejo dowólone.",
        "accmailtext": "Pśipadnje napórane gronidło za [[User talk:$1|$1]] jo se pósłało k $2. Dajo se na boku ''[[Special:ChangePassword|Gronidło změniś]]'' pśi pśizjawjenju změniś.",
        "newarticle": "(Nowy nastawk)",
        "newarticletext": "Sy slědował wótkaz na bok, kótaryž hyšći njeeksistěrujo.\nAby bok napórał, zapiš do kašćika dołojce (glědaj [$1 bok pomocy] za dalšne informacije). Jolic sy zamólnje how, klikni na tłocašk '''Slědk''' w swójom wobglědowaku.",
-       "anontalkpagetext": "---- ''Toś jo diskusijny bok za anonymnego wužywarja, kótaryž njejo dotychměst žedno wužywarske konto załožył abo swójo konto njewužywa. Togodla dejmy numerisku IP-adresu wužywaś, aby jogo/ju identificěrowali. Taka IP-adresa dajo se wót wšakich wužywarjow wužywaś. Jolic sy anonymny wužywaŕ a se mysliš, až su se njerelewantne komentary na tebje měrili, [[Special:UserLogin/signup|załož konto]] abo [[Special:UserLogin|pśizjaw se]], aby se w pśichoźe zmuśenje z drugimi anonymnymi wužywarjami wobinuł.''",
+       "anontalkpagetext": "---- ''Toś jo diskusijny bok za anonymnego wužywarja, kótaryž njejo dotychměst žedno wužywarske konto załožył abo swójo konto njewužywa. Togodla dejmy numerisku IP-adresu wužywaś, aby jogo/ju identificěrowali. Taka IP-adresa dajo se wót wšakich wužywarjow wužywaś. Jolic sy anonymny wužywaŕ a se mysliš, až su se njerelewantne komentary na tebje měrili, [[Special:CreateAccount|załož konto]] abo [[Special:UserLogin|pśizjaw se]], aby se w pśichoźe zmuśenje z drugimi anonymnymi wužywarjami wobinuł.''",
        "noarticletext": "Dotychměst toś ten bok hyšći njewopśimujo žeden tekst. Móžoš w drugich bokach [[Special:Search/{{PAGENAME}}|titel togo boka pytaś]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} wótpowědne protokole pytaś] abo [{{fullurl:{{FULLPAGENAME}}|action=edit}} toś ten bok wobźěłaś]</span>.",
        "noarticletext-nopermission": "Tuchylu njejo žeden tekst na toś tom boku.\nMóžoš [[Special:Search/{{PAGENAME}}|toś ten bokowy titel]] na drugich bokach pytaś\nabo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} wótpowědne protokole pytaś]</span>, ale njamaš pšawo, toś ten bok napóraś.",
        "missing-revision": "Wersija #$1 boka z mjenim \"{{FULLPAGENAME}}\" njeeksistěrujo.\n\nPśicyna jo zwětšego zestarjony wótkaz w historiji k bokoju, kótaryž jo se wulašował.\nDrobnostki móžoš w  [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protokolu wulašowanjow] namakaś.",
index 1b9381e..b79dcda 100644 (file)
        "noname": "Awu nu pinosuang ngaran momomoguno di pasagaon.",
        "loginsuccesstitle": "Nokosuang log.",
        "loginsuccess": "'''Nokosuang log ko noh id {{SITENAME}} sobaagi do \"$1\".'''",
-       "nosuchuser": "Ingaa momoguno pinungaranan \"$1\".\nNgaran momoguno nopo nga kowoyo pimato.\nIntaai ijaannu, toi [[Special:UserLogin/signup|pomonsoi do takaun kawawagu]].",
+       "nosuchuser": "Ingaa momoguno pinungaranan \"$1\".\nNgaran momoguno nopo nga kowoyo pimato.\nIntaai ijaannu, toi [[Special:CreateAccount|pomonsoi do takaun kawawagu]].",
        "nosuchusershort": "Ingaa momoguno kingaran  \"$1\".\nIntaai ijaannu.",
        "nouserspecified": "Maai posuango ngaran momoguno.",
        "login-userblocked": "Nantaban momoguno diti. Oduhan do sumuang log.",
        "accmailtext": "Kaatalib somporimonsoi montok [[User talk:$1|$1]] nakaatod no hilo $2.\n\nKaatalib montok akaun wagu milo do alanan hilo id bolikon ''[[Special:ChangePassword|pangalanan kaatalib]]'' soira do kosuang log.",
        "newarticle": "(Wagu)",
        "newarticletext": "Nakatanudko di noputan kumaa di bolikon di awu po nokosuang.\nMomonsoi ko nopo do bolikon diti, timpuun do manaip id kutak siriba (intaai [$1 bolikon ponokodung] montok lobi gumu kointalangan).\nNung nakaansau ko do hiti, tonsoko ot butang '''gumuli''' id pogigihumnu.",
-       "anontalkpagetext": "----''Iti nopo nga bolikon pogibabarasan montok di momomoguno poinlisok it awu poh nokopomonsoi do takaun, toi it awu momoguno dilo.\nMantad dilo, asansagan yahai do poposurat numbur kinoyonon IP do mongintutun dau.\nKinoyonon IP pogialasan do pipiro momomoguno.\nNung momomoguno koh do ingaa ngaran om karamit koh do komin di awu kapadan, mangai [[Special:UserLogin/signup|wonsoyo takaun]] toi [[Special:UserLogin|sumuang log]] do magalai kounsuhan di momomoguno suai di tingaa ngaran.''",
+       "anontalkpagetext": "----''Iti nopo nga bolikon pogibabarasan montok di momomoguno poinlisok it awu poh nokopomonsoi do takaun, toi it awu momoguno dilo.\nMantad dilo, asansagan yahai do poposurat numbur kinoyonon IP do mongintutun dau.\nKinoyonon IP pogialasan do pipiro momomoguno.\nNung momomoguno koh do ingaa ngaran om karamit koh do komin di awu kapadan, mangai [[Special:CreateAccount|wonsoyo takaun]] toi [[Special:UserLogin|sumuang log]] do magalai kounsuhan di momomoguno suai di tingaa ngaran.''",
        "noarticletext": "Maso po do ingaa tik id bolikon diti.\nMilo ko nogi do [[Special:Search/{{PAGENAME}}|mogihum do tuluhon bolikon diti]] id bolikon suai,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mogihum kokomoi log-log],\ntoi [{{fullurl:{{FULLPAGENAME}}|action=edit}} mongidit bolikon diti]</span>.",
        "noarticletext-nopermission": "Maso po do ingaa tik id bolikon diti.\nMilo ko nogi do [[Special:Search/{{PAGENAME}}|mogihum do tuluhon bolikon diti]] id bolikon suai, toi\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mogihum kokomoi log-log] </span>, nga ingaa kapasagaannu do momonsoi do bolikon diti.",
        "userpage-userdoesnotexist": "Akaun momomoguno \"<nowiki>$1</nowiki>\" awu nokorijisto.\nMangai intangai nung mumang ko pomonsoi/mongidit do bolikon diti.",
        "allpages-bad-ns": "{{SITENAME}} aiso pinungaranan do \"$1\"",
        "categories": "Kakatogori",
        "categoriesfrom": "Pokitono kakatogori tinimpuun do:",
-       "special-categories-sort-abc": "uludo mompimato",
        "deletedcontributions": "Pugaso pinototoluod do momoguno",
        "deletedcontributions-title": "Pugaso pinototoluod do momoguno",
        "sp-deletedcontributions-contribs": "Totoluod",
index 51104de..269afc3 100644 (file)
@@ -13,6 +13,7 @@
        "tog-hideminor": "अहिलका मामूली सम्पादनलाई लुकाउन्या",
        "tog-hidepatrolled": "गस्ती(patrolled)सम्पादनलाई लुकाउन्या",
        "tog-newpageshidepatrolled": "गस्ती गरिया पानानलाई नयाँ पाना  सूचीबठेई लुकाउन्या",
+       "tog-hidecategorization": "पृष्ठहरुको श्रेणीकरण हटाया",
        "tog-extendwatchlist": "निगरानी सूचीलाई सबै परिवर्तन धेकुन्या गरी बढुन्या , ऐईलका बाहेक",
        "tog-usenewrc": "पानाका अहिलका  परिवर्तन र अवलोकन सूचीका आधारमी सामूहिक परिवर्तनहरू",
        "tog-numberheadings": "शीर्षकहरूलाई स्वत:अङ्कित गर",
@@ -23,6 +24,7 @@
        "tog-watchdefault": "मुइले सम्पादन गरयाको पाना र फाइल ध्यान सूचीमाई थप्दया",
        "tog-watchmoves": "मुइले सार्या पानाहरू र फाइलहरूलाई ध्यान सूचीमी थप्द्या",
        "tog-watchdeletion": "मुइले हटायाका पानाहरू र चित्रहरूलाई ध्यान सूचीमी थप्द्या",
+       "tog-watchuploads": "नयाँ फाईल थप|मेरा अवलोकसुचिमी अपलोड गर",
        "tog-watchrollback": "मुइले रोलब्याक गर्याका पानाहरू मेरो ध्यानसूचीमी थप्द्या ।",
        "tog-minordefault": "सबै सम्पादनहरूलाई पहिल्लई निर्धारित रुपमी सामान्य चिनो लगाउन्या",
        "tog-previewontop": "सम्पादन बाकस अगि पहिलाकोरूप देखाउन्या",
        "morenotlisted": "यो सूची पूरा नाइँ हो ।",
        "mypage": "पानो",
        "mytalk": "मेरी कुरडी",
-       "anontalk": "यà¥\88 à¤\86à¤\87. à¤ªà¥\80. à¤\95ा à¤¬à¤¾à¤°à¥\87मà¥\80 à¤\95à¥\81रडà¥\80 à¤\97र",
+       "anontalk": "à¤\95à¥\81रडà¥\80",
        "navigation": "खोज",
        "and": "&#32;र",
        "qbfind": "तम जाण",
        "versionrequiredtext": "ये पाना प्रयोग गर्नका लागि MediaWiki $1 संस्करण चाहिन्छ ।\nहेर  [[Special:Version|version page]]",
        "ok": "भयो",
        "retrievedfrom": " \"$1\" बठे निकालिया",
-       "youhavenewmessages": "तमखी लेखा($2)मी $1 छ।",
+       "youhavenewmessages": "तमखी लेखा($2)मी $1 छ ।",
        "youhavenewmessagesfromusers": "तमखी लेखा {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्तान}}($2)बठे$1",
        "youhavenewmessagesmanyusers": "तमलाई धेरै प्रयोगकर्ताहरू($2) बठे $1 छ ।",
        "newmessageslinkplural": "{{PLURAL:$1|एक नौलो रैबार|999=नौला रैबारहरू}}",
        "nstab-template": "ढाँचा",
        "nstab-help": "सहायता पानो",
        "nstab-category": "श्रेणी",
+       "mainpage-nstab": "मुख्य पानो",
        "nosuchaction": "यसो काम हैन",
        "nosuchactiontext": "URL ले खुलाएको काम मान्य छैन ।\nतमीले URL गलत टाइपगरेका हौ , वा गलत लिंकक पछाडी लागेका हुनसक्देहौ ।\nयो {{SITENAME}}ले सफ्टवेयरमी भयाको गल्ति देखायाको लै हुनसक्छ ।",
        "nosuchspecialpage": "तसो विशेष पानो छैन",
        "viewsource-title": " $1 को स्रोत हेर",
        "actionthrottled": "कार्य रोकिईयो",
        "actionthrottledtext": "स्पामको रोकथामको लागि , तमीलाई यो कार्य नापै समयमी मैथै पटक गद्दाबठे सिमित गरियाको छ, र तमीले आफ्नो सिमा पार गरिसक्याछौ ।\nकृपया केही मिनेट पछि पुन: प्रयास गर  ।",
+       "viewsourcetext": "तम ये पृष्ठको स्रोत हेद्दु सकुन्छौ और उईको नक्कल उताद्दु सकुन्छौ |",
        "viewyourtext": "यै पानामी रह्याका '''तमरा सम्पादनहरू''' हेद्द या प्रतिलिपी गद्द सक्द्या हौ :",
        "editinginterface": "<strong>चेतावनी:</strong> तमी यै पानालाई सम्पादन गद्द लाग्याछौ, जनले सफ्टवेयरको लागि \nइन्टरफेस सामग्रीहरू प्रदान गरन्छ।\nयै पानामी गरियाको परिवर्तनले यै विकिमी अरु प्रयोगकर्तानको इन्टरफेसको प्रदर्शनमी प्रभाव पडन्छ ।",
        "namespaceprotected": "तमलाई '''$1'''  नेमस्पेसमी रह्याका पानाहरू सम्पादन गद्या अनुमति छैन ।",
        "nologin": "तमरो खाता छैन? $1।",
        "nologinlink": "नयाँ खाता खोल",
        "createaccount": "खाता खोल",
+       "gotaccount": "तमरो खाता छनोई छ? $1।",
        "gotaccountlink": "प्रवेश",
        "userlogin-resetlink": "प्रवेश सम्बन्धी विवरणहरू बिसरया भयो?",
        "userlogin-resetpassword-link": "पासवर्ड भुलिगया?",
        "resetpass_submit": "पासवर्ड व्यवस्थित गरी र प्रवेशगर्ने",
        "changepassword-success": "तमरो पासवर्ड सफलतापूर्वक परिवर्तन भयो!",
        "changepassword-throttled": "तमले अलै भौत फेर प्रवेशका निम्ति प्रयास गरया छौ।\nकृपया $1 थोक्कै जागी मात्र प्रयास गर।",
+       "botpasswords-label-grants-column": "प्रदान भयो",
+       "botpasswords-created-title": "बोट को पासवर्ड बन्यो",
+       "botpasswords-deleted-title": "बोट को पासवर्ड मेटियो",
        "resetpass_forbidden": "पासवर्ड परिवर्तन गर्न नाइँमिल्लो",
+       "resetpass_forbidden-reason": "पासवर्ड परिवर्तन गद्दु नाइँमिल्लो:$1",
        "resetpass-no-info": "ये पाना सिधाई हेद्दाई तमले प्रवेश गद्दु पडून्छ ।",
        "resetpass-submit-loggedin": "पासवर्ड परिवर्तन गर",
        "resetpass-submit-cancel": "रद्द",
        "accmailtitle": "पासवर्ड पठाइयो",
        "newarticle": "(नयाँ)",
        "newarticletext": "तमले अहिलसम्म नभयाका पानाको लिंङ्क पहिल्यायाका छौ ।\nयो पानो बनौनाखी तल्तिरको कोष्ठमी टाइप गरि । (और जाण्णाखीलेखा [$1 help page] हेर )।\nताखाइ सुधिसार आइपुग्या हौ भण्या, ब्राउजरको  '''back''' बटन थिचिहाल ।",
-       "anontalkpagetext": "----''यो कुरडी पानो अज्ञात प्रयोगकर्ताको हो जनले अहिलसम्म खाता बनायाकै छैन, अथवा जनले यै पानाको उपयोग गर्दैन।\nयस कारण हामीले उनलाई उनरो आइ पी (IP) ठेगानाले चिन्न सकन्छौ। \nयस्तो आइ पी (IP) ठेगाना धेरै प्रयोगकर्तानको साझा हुनसकन्छ ।\nयदि तमी अज्ञात प्रयोगकर्ता हौ र तमलाई अचाहिँदो टिप्पणी भयाको अनुभव गद्दा छौ भण्या भविष्यमी अन्य अज्ञात प्रयोगकर्तासँगको भ्रमबाट बाँच्न कृपया [[Special:UserLogin/signup|खाता खोल]] अथवा [[Special:UserLogin|प्रवेश गर]] ''",
+       "anontalkpagetext": "----''यो कुरडी पानो अज्ञात प्रयोगकर्ताको हो जनले अहिलसम्म खाता बनायाकै छैन, अथवा जनले यै पानाको उपयोग गर्दैन।\nयस कारण हामीले उनलाई उनरो आइ पी (IP) ठेगानाले चिन्न सकन्छौ। \nयस्तो आइ पी (IP) ठेगाना धेरै प्रयोगकर्तानको साझा हुनसकन्छ ।\nयदि तमी अज्ञात प्रयोगकर्ता हौ र तमलाई अचाहिँदो टिप्पणी भयाको अनुभव गद्दा छौ भण्या भविष्यमी अन्य अज्ञात प्रयोगकर्तासँगको भ्रमबाट बाँच्न कृपया [[Special:CreateAccount|खाता खोल]] अथवा [[Special:UserLogin|प्रवेश गर]] ''",
        "noarticletext": "यै लेखमी अहिल क्यै पन पाठ नाइथी  ।\nतमले और पृष्ठमी\n[[Special:Search/{{PAGENAME}}|यस पृष्ठको शीर्षककी लेखा खोज]] गद्द सकन्छौ ।\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} पाना सम्बन्धित ढड्डामी खोज],\nवा [{{fullurl:{{FULLPAGENAME}}|action=edit}}  यै पानालाई सम्पादन गद्या]</span>.",
        "noarticletext-nopermission": "यै लेखमी अहिल केइ पन पाठ नाइथी  ।\nतमले और पानामी\n[[Special:Search/{{PAGENAME}}|यै पानाको शीर्षककी लेखा खोज]] गद्द सकन्छौ ।\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} पाना सम्बन्धित ढड्डामी खोज्न],\nवा [{{fullurl:{{FULLPAGENAME}}|action=edit}}  यै पानालाई सम्पादन गद्द] सकन्छौ</span>.",
        "userinvalidcssjstitle": "<strong>चेतावनी:</strong> यहाँ कोइपनि \"$1\" नामको खोल नाइथिन् ।\nप्रचलित .css तथा .js पानाहरूले निम्नपद शीर्षक प्रयोग गद्दान्, जस्तै {{ns:user}}:Foo/Vector.css को सट्टामी {{ns:user}}:Foo/vector.css",
index 388ad61..adb3dab 100644 (file)
        "noname": "Al nòm utèint scrét an n'é mìa vâlid.",
        "loginsuccesstitle": "Ingrès fât.",
        "loginsuccess": "''' T'é stê coleghê al terminêl {{SITENAME}} cun al nòm utèint '''$1'''.",
-       "nosuchuser": "An n'é mìa registrê nisûn utèint cun al nòm \"$1\". I nòm utèin în sensébil al lètri grândi. Veréfica al nòm scrét o [[Special:UserLogin/signup|fà un nōv ingrès]].",
+       "nosuchuser": "An n'é mìa registrê nisûn utèint cun al nòm \"$1\". I nòm utèin în sensébil al lètri grândi. Veréfica al nòm scrét o [[Special:CreateAccount|fà un nōv ingrès]].",
        "nosuchusershort": "An gh'é mìa registrê un utèint ciamê ''$1''. Veréfica al nòm scrét.",
        "nouserspecified": "L'é necesâri precişêr un nòm utèint.",
        "login-userblocked": "Cl'utèinsa ché l'é bluchêda. An n'é pusébil fêr l'ingrès.",
        "accmailtext": "'Na cêva 'd ingrés l'è stêda fâta a chêş per [[User talk:$1|$1]] e l'è stêda spidîda a $2. Cla cêva 'd ingrès ché la pōl èser cambiêda int la pàgina per ''[[Special:ChangePassword|cambiêr la cêva 'd ingrès]]'' subét dôp avèir fât l'ingrès.",
        "newarticle": "(Nōv)",
        "newarticletext": "Al colegamèint apèina fât al cumbîna cun 'na pàgina ch' an n'é mìa incòra stêda fâta. S'ét vō fêr la pàgina adès, l'é asê cumincêr a scréver al tèst int la caşèla ché sòt (per vedèr infurmasiòun pió precîşi guêrda la [$1 pàgina 'd ajót]). Se al colegamèint  l'é stê avêrt per erōr, l'é asê clichêr al pulsânt \"Indrē\" dal tó navigadōr.",
-       "anontalkpagetext": "----'' Còsta l'è la pàgina 'd discusiòun ed 'n utèint sèinsa nòm, ch' an n' à mìa incòra fât 'n' utèinsa o in tót al manēri an n'è mìa drē druvêrla. Per arcgnòsrel l'è dòunca necesâri druvê al só indirés IP. J indirés IP a pōlen èser spartî cun êter utèint. Se t'è un utèint sèinsa nòm e 't pèins che i cumèint in cla pàgina ché an riguêrden mìa tè, [[Special:UserLogin/signup|fa 'n' utèinsa nōva]] o [[Special:UserLogin|vîn dèinter cun còla ch' ét gh'ê bèle]] per schivşêr, in futûr,  'd èser cunfûş cun 'd j êter utèint sèinsa nòm.''",
+       "anontalkpagetext": "----'' Còsta l'è la pàgina 'd discusiòun ed 'n utèint sèinsa nòm, ch' an n' à mìa incòra fât 'n' utèinsa o in tót al manēri an n'è mìa drē druvêrla. Per arcgnòsrel l'è dòunca necesâri druvê al só indirés IP. J indirés IP a pōlen èser spartî cun êter utèint. Se t'è un utèint sèinsa nòm e 't pèins che i cumèint in cla pàgina ché an riguêrden mìa tè, [[Special:CreateAccount|fa 'n' utèinsa nōva]] o [[Special:UserLogin|vîn dèinter cun còla ch' ét gh'ê bèle]] per schivşêr, in futûr,  'd èser cunfûş cun 'd j êter utèint sèinsa nòm.''",
        "noarticletext": "In cól mumèint ché la pàgina serchêda l'é vōda. L'é pusébil [[Special:Search/{{PAGENAME}}|serchêr sté tétol]] int al j êtri pàgini dal sît, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchêr int i regéster coleghê] opór  [{{fullurl:{{FULLPAGENAME}}|action=edit}} mudifichêr la pàgina adèsa]</span>.",
        "noarticletext-nopermission": "In cól mumèint ché la pàgina serchêda l'é vōda. L'é pusébil [[Special:Search/{{PAGENAME}}|serchêr sté tétol]] int al j êtri pàgini dal sît o<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchêr int i regéster coleghê] <span>, mó an 't gh'ê mìa al permès ed fêr cla pàgina ché.",
        "missing-revision": "La revişiòun #$1 'd la pagina \"{{FULLPAGENAME}}\" l' an gh'è mìa. Còst, ed sôlit, a sucēd mèint'r as va drē a 'n colegamèint a 'na pàgina scanşlêda, in 'na stòria, di lavōr fât, mìa arnuvêda. I particulêr a 's pōlen catêr int al [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} regéster dal scanşladûri].",
index 5b96993..2500688 100644 (file)
@@ -46,7 +46,8 @@
                        "Giorgos456",
                        "SucreRouge",
                        "Gts-tg",
-                       "Nemo bis"
+                       "Nemo bis",
+                       "Αντιγόνη"
                ]
        },
        "tog-underline": "Υπογράμμιση συνδέσμων:",
        "site-atom-feed": "$1 ροή Atom",
        "page-rss-feed": "Ροή RSS «$1»",
        "page-atom-feed": "Ροή Atom «$1»",
-       "feed-atom": "Άτομο",
+       "feed-atom": "Atom",
        "red-link-title": "$1 (η σελίδα δεν υπάρχει)",
        "sort-descending": "Φθίνουσα ταξινόμηση",
        "sort-ascending": "Αύξουσα ταξινόμηση",
        "noname": "Το όνομα χρήστη που έχετε καθορίσει δεν είναι έγκυρο.",
        "loginsuccesstitle": "Επιτυχής σύνδεση",
        "loginsuccess": "Είστε συνδεδεμένος(-η) στο {{SITENAME}} ως \"$1\".",
-       "nosuchuser": "Δεν υπάρχει χρήστης με το όνομα \"$1\".\nΤα ονόματα χρηστών είναι ευαίσθητα σε κεφαλαιογράμματη και μικρογράμματη γραφή.\nΕλέγξτε την ορθογραφία ή [[Special:UserLogin/signup|δημιουργήστε ένα νέο λογαριασμό]].",
+       "nosuchuser": "Δεν υπάρχει χρήστης με το όνομα \"$1\".\nΤα ονόματα χρηστών είναι ευαίσθητα σε κεφαλαιογράμματη και μικρογράμματη γραφή.\nΕλέγξτε την ορθογραφία ή [[Special:CreateAccount|δημιουργήστε ένα νέο λογαριασμό]].",
        "nosuchusershort": "Δεν υπάρχει χρήστης με το όνομα \"$1\". Παρακαλούμε ελέγξτε την ορθογραφία.",
        "nouserspecified": "Πρέπει να ορίσετε ένα όνομα χρήστη.",
        "login-userblocked": "Αυτός ο χρήστης έχει αποκλειστεί. Δεν επιτρέπεται σύνδεση.",
        "accmailtext": "Ένας τυχαία παρηγμένος κωδικός για {{GENDER:$1|τον|την}} [[User talk:$1|$1]] έχει σταλεί στο $2.\n\nΜπορεί να αλλαχθεί από την σελίδα ''[[Special:ChangePassword|αλλαγή κωδικού]]'' μετά τη σύνδεση.",
        "newarticle": "(Νέο)",
        "newarticletext": "Ακολουθήσατε ένα σύνδεσμο προς μια σελίδα που δεν υπάρχει ακόμα. \nΓια να δημιουργήσετε τη σελίδα, αρχίστε να πληκτρολογείτε στο παρακάτω πλαίσιο (δείτε τη [$1 σελίδα βοήθειας] για περισσότερες πληροφορίες).\nΑν έχετε βρεθεί εδώ κατά λάθος, πατήστε το κουμπί '''πίσω''' στον περιηγητή σας.",
-       "anontalkpagetext": "----''Αυτή η σελίδα συζήτησης προορίζεται για ανώνυμο χρήστη που δεν έχει δημιουργήσει ακόμα λογαριασμό ή που δεν τον χρησιμοποιεί. Έτσι για την ταυτοποίηση ενός ανώνυμου χρήστη χρησιμοποιείται η διεύθυνση IP του. Είναι όμως πιθανόν η διεύθυνση αυτή να είναι κοινή για πολλούς διαφορετικούς χρήστες.  Αν είστε ανώνυμος χρήστης και νομίζετε ότι άσχετα σχόλια απευθύνθηκαν σε σας, παρακαλούμε να [[Special:UserLogin/signup|δημιουργήσετε ένα λογαριασμό]] ή να  [[Special:UserLogin|συνδεθείτε]] για να αποφεύγεται η μελλοντική σύγχυση με άλλους ανώνυμους χρήστες.''",
+       "anontalkpagetext": "----''Αυτή η σελίδα συζήτησης προορίζεται για ανώνυμο χρήστη που δεν έχει δημιουργήσει ακόμα λογαριασμό ή που δεν τον χρησιμοποιεί. Έτσι για την ταυτοποίηση ενός ανώνυμου χρήστη χρησιμοποιείται η διεύθυνση IP του. Είναι όμως πιθανόν η διεύθυνση αυτή να είναι κοινή για πολλούς διαφορετικούς χρήστες.  Αν είστε ανώνυμος χρήστης και νομίζετε ότι άσχετα σχόλια απευθύνθηκαν σε σας, παρακαλούμε να [[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}}|αναζητήσετε αυτόν τον τίτλο σελίδας]] σε άλλες σελίδες, ή <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} να ψάξετε στις σχετικές καταγραφές]</span>, αλλά δεν έχετε την άδεια να δημιουργήσετε αυτή τη σελίδα.",
        "missing-revision": "Δεν υπάρχει αναθεώρηση με αριθμό $1 για τη σελίδα με όνομα «{{FULLPAGENAME}}».\n\nΑυτό συνήθως προκαλείται από παλιό σύνδεσμο ιστορικού προς σελίδα που έχει διαγραφεί.\nΛεπτομέρειες θα βρείτε στο [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ημερολόγιο καταγραφής διαγραφών].",
        "upload-form-label-infoform-description": "Περιγραφή",
        "upload-form-label-usage-title": "Χρήση",
        "upload-form-label-usage-filename": "Όνομα αρχείου",
-       "foreign-structured-upload-form-label-own-work": "Αυτό είναι το δικό μου έργο",
-       "foreign-structured-upload-form-label-infoform-categories": "Κατηγορίες",
-       "foreign-structured-upload-form-label-infoform-date": "Ημερομηνία",
-       "foreign-structured-upload-form-label-own-work-message-local": "Επιβεβαιώνω ότι επιφορτώνω  αυτό το αρχείο κατά τους όρους της υπηρεσίας και πολιτικές αδειοδότησης για τον ιστότοπο {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Εάν δεν είστε σε θέση να ανεβάσετε αυτό το αρχείο στο πλαίσιο των πολιτικών της  {{SITENAME}}, παρακαλώ κλείστε αυτό το παράθυρο διαλόγου και να επιχειρήσετε μια άλλη μέθοδος.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Επίσης, μπορεί να θέλετε να δοκιμάσετε [[Special:Upload|την προεπιλεγμένη σελίδα επιφόρτωσης]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Καταλαβαίνω ότι είμαι φόρτωμα αυτό το αρχείο σε ένα κοινόχρηστο αρχείο. Επιβεβαιώνω ότι είμαι τόσο ακόλουθες τους όρους της υπηρεσίας και πολιτικές αδειοδότησης.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Εάν δεν είστε σε θέση να ανεβάσετε αυτό το αρχείο στο πλαίσιο των πολιτικών της shared repository, παρακαλώ κλείστε αυτό το παράθυρο διαλόγου και να επιχειρήσετε μια άλλη μέθοδος.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Επίσης, μπορεί να θέλετε να δοκιμάσετε χρησιμοποιώντας το [[Special:Upload|τη σελίδα ανεβάσματος για το {{SITENAME}}]], αν αυτό το αρχείο μπορεί να φορτωθεί κάτω σύμφωνα με τις πολιτικές τους.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Δηλώνω ότι κατέχω τα πνευματικά δικαιώματα για αυτό το αρχείο, και συμφωνώ αμετάκλητα στην απελευθέρωση  αυτού του  αρχείου στο Wikimedia Commons με άδεια  [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], και συμφωνώ με την [https://wikimediafoundation.org/wiki/Terms_of_Use Όρους Χρήσης].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Αν δεν κατέχει τα πνευματικά δικαιώματα για αυτό το αρχείο, ή επιθυμείτε να το δημοσιεύσετε υπό μια διαφορετική άδεια χρήσης, μπορείτε να χρησιμοποιήσετε τον [https://commons.wikimedia.org/wiki/Special:UploadWizard Οδηγό Ανεβάσματος των Wikimedia Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Επίσης, μπορεί να θέλετε να δοκιμάσετε να χρησιμοποιήσετε  το [[Special:Upload|τη σελίδα ανεβάσματος για το {{SITENAME}}]], αν αυτό το αρχείο μπορεί να φορτωθεί σύμφωνα με  τις πολιτικές τους.",
+       "upload-form-label-own-work": "Αυτό είναι το δικό μου έργο",
+       "upload-form-label-infoform-categories": "Κατηγορίες",
+       "upload-form-label-infoform-date": "Ημερομηνία",
+       "upload-form-label-own-work-message-generic-local": "Επιβεβαιώνω ότι επιφορτώνω  αυτό το αρχείο κατά τους όρους της υπηρεσίας και πολιτικές αδειοδότησης για τον ιστότοπο {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Εάν δεν είστε σε θέση να ανεβάσετε αυτό το αρχείο στο πλαίσιο των πολιτικών της  {{SITENAME}}, παρακαλώ κλείστε αυτό το παράθυρο διαλόγου και να επιχειρήσετε μια άλλη μέθοδος.",
+       "upload-form-label-not-own-work-local-generic-local": "Επίσης, μπορεί να θέλετε να δοκιμάσετε [[Special:Upload|την προεπιλεγμένη σελίδα επιφόρτωσης]].",
+       "upload-form-label-own-work-message-generic-foreign": "Καταλαβαίνω ότι είμαι φόρτωμα αυτό το αρχείο σε ένα κοινόχρηστο αρχείο. Επιβεβαιώνω ότι είμαι τόσο ακόλουθες τους όρους της υπηρεσίας και πολιτικές αδειοδότησης.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Εάν δεν είστε σε θέση να ανεβάσετε αυτό το αρχείο στο πλαίσιο των πολιτικών της shared repository, παρακαλώ κλείστε αυτό το παράθυρο διαλόγου και να επιχειρήσετε μια άλλη μέθοδος.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Επίσης, μπορεί να θέλετε να δοκιμάσετε χρησιμοποιώντας το [[Special:Upload|τη σελίδα ανεβάσματος για το {{SITENAME}}]], αν αυτό το αρχείο μπορεί να φορτωθεί κάτω σύμφωνα με τις πολιτικές τους.",
        "backend-fail-stream": "Αδύνατη η μετάδοση του αρχείου $1.",
        "backend-fail-backup": "Αδύνατη η δημιουργία αντίγραφου ασφαλείας του αρχείου $1.",
        "backend-fail-notexists": "Το αρχείο $1 δεν υπάρχει.",
        "wantedtemplates": "Ζητούμενα πρότυπα",
        "mostlinked": "Σελίδες με τους περισσότερους συνδέσμους προς αυτές",
        "mostlinkedcategories": "Κατηγορίες με τους περισσότερους συνδέσμους προς αυτές",
-       "mostlinkedtemplates": "ΣελίδεÏ\82 Ï\80οÏ\85 Î­Ï\87οÏ\85ν ÎµÎ½Ï\83Ï\89μαÏ\84Ï\89θεί Ï\80εÏ\81ιÏ\83Ï\83Ï\8cÏ\84εÏ\81ο Î±Ï\80Ï\8c Ï\8cλεÏ\82 Ï\84ιÏ\82 Î¬Î»Î»ες",
+       "mostlinkedtemplates": "ΠεÏ\81ιÏ\83Ï\83Ï\8cÏ\84εÏ\81ο ÎµÎ½Ï\83Ï\89μαÏ\84Ï\89μένεÏ\82 Ï\83ελίδες",
        "mostcategories": "Σελίδες με τις περισσότερες κατηγορίες",
        "mostimages": "Αρχεία με τους περισσότερους συνδέσμους προς αυτά",
        "mostinterwikis": "Σελίδες με τους περισσότερους διαγλωσσικούς συνδέσμους",
        "apisandbox-dynamic-parameters-add-placeholder": "Ονομασία παραμέτρου",
        "apisandbox-dynamic-error-exists": "Η παράμετρος με την ονομασία \"$1\" υπάρχει ήδη",
        "apisandbox-submit-invalid-fields-title": "Κάποια από τα πεδία δεν είναι έγκυρα",
-       "apisandbox-submit-invalid-fields-message": "ΠαÏ\81ακαλÏ\8e Î´Î¹Î¿Ï\81θÏ\8eÏ\83Ï\84ε Ï\84α Ï\83ημειÏ\89μένα Ï\80εδία ÎºÎ±Î¹ Ï\80Ï\81οÏ\83Ï\80αθείστε ξανά.",
+       "apisandbox-submit-invalid-fields-message": "ΠαÏ\81ακαλÏ\8e Î´Î¹Î¿Ï\81θÏ\8eÏ\83Ï\84ε Ï\84α Ï\83ημειÏ\89μένα Ï\80εδία ÎºÎ±Î¹ Ï\80Ï\81οÏ\83Ï\80αθήστε ξανά.",
        "apisandbox-results": "Αποτελέσματα",
        "apisandbox-sending-request": "Αποστολή αιτήματος API...",
        "apisandbox-loading-results": "Λήψη αποτελεσμάτων API...",
        "whatlinkshere-prev": "{{PLURAL:$1|προηγούμενη|προηγούμενες $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|επόμενη|επόμενες $1}}",
        "whatlinkshere-links": "← σύνδεσμοι",
-       "whatlinkshere-hideredirs": "$1 ανακατευθύνσεων",
-       "whatlinkshere-hidetrans": "$1 ενσωματώσεων",
-       "whatlinkshere-hidelinks": "$1 συνδέσμων",
+       "whatlinkshere-hideredirs": "Απόκρυψη ανακατευθύνσεων",
+       "whatlinkshere-hidetrans": "Απόκρυψη ενσωματώσεων",
+       "whatlinkshere-hidelinks": "Απόκρυψη συνδέσμων",
        "whatlinkshere-hideimages": "$1 σύνδεσμοι αρχείων",
        "whatlinkshere-filters": "Φίλτρα",
        "whatlinkshere-submit": "Μετάβαση",
        "logentry-protect-protect": "$1 {{GENDER:$2|προστατευμένος|προστατευμένη}} $3 $4",
        "logentry-protect-modify": "$1 {{GENDER:$2|άλλαξε}} επίπεδο προστασίας για $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|άλλαξε}} επίπεδο προστασίας για $3 $4 [διαδοχική]",
-       "logentry-rights-rights": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ιδιότητα μέλους ομάδας για {{GENDER:$3|τον|την}} $3 από $4 σε $5",
+       "logentry-rights-rights": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ιδιότητα μέλους ομάδας για {{GENDER:$6|τον|την}} $3 από $4 σε $5",
        "logentry-rights-rights-legacy": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ιδιότητα μέλους ομάδας {{GENDER:$1|του|της}} $3",
        "logentry-rights-autopromote": "$1 {{GENDER:$2|προωθήθηκε}} αυτόματα από το $4 στο $5",
        "logentry-upload-upload": "{{GENDER:$2|Ο|Η}} $1 ανέβασε το $3",
index d76825c..8b9fefe 100644 (file)
@@ -43,7 +43,7 @@
        "tog-ccmeonemails": "Send me copies of emails I send to other users",
        "tog-diffonly": "Do not show page content below diffs",
        "tog-showhiddencats": "Show hidden categories",
-       "tog-norollbackdiff": "Omit diff after performing a rollback",
+       "tog-norollbackdiff": "Don't show diff after performing a rollback",
        "tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
        "tog-prefershttps": "Always use a secure connection when logged in",
        "underline-always": "Always",
        "password-change-forbidden": "You cannot change passwords on this wiki.",
        "externaldberror": "There was either an authentication database error or you are not allowed to update your external account.",
        "login": "Log in",
+       "login-security": "Verify your identity",
        "nav-login-createaccount": "Log in / create account",
        "loginprompt": "",
        "userlogin": "Log in / create account",
        "helplogin-url": "https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Logging_in",
        "userlogin-helplink2": "Help with logging in",
        "userlogin-loggedin": "You are already logged in as {{GENDER:$1|$1}}.\nUse the form below to log in as another user.",
+       "userlogin-reauth": "You must log in again to verify that you are {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Create another account",
        "createacct-emailrequired": "Email address",
        "createacct-emailoptional": "Email address (optional)",
        "createacct-email-ph": "Enter your email address",
        "createacct-another-email-ph": "Enter email address",
        "createaccountmail": "Use a temporary random password and send it to the specified email address",
+       "createaccountmail-help": "Can be used to create account for another person without learning the password.",
        "createacct-realname": "Real name (optional)",
        "createaccountreason": "Reason:",
        "createacct-reason": "Reason",
        "createacct-reason-ph": "Why you are creating another account",
+       "createacct-reason-help": "Message shown in the account creation log",
        "createacct-imgcaptcha-help": "",
        "createacct-submit": "Create your account",
        "createacct-another-submit": "Create account",
+       "createacct-continue-submit": "Continue account creation",
+       "createacct-another-continue-submit": "Continue account creation",
        "createacct-benefit-heading": "{{SITENAME}} is made by people like you.",
        "createacct-benefit-icon1": "icon-edits",
        "createacct-benefit-head1": "{{NUMBEROFEDITS}}",
        "nocookieslogin": "{{SITENAME}} uses cookies to log in users.\nYou have cookies disabled.\nPlease enable them and try again.",
        "nocookiesfornew": "The user account was not created, as we could not confirm its source.\nEnsure you have cookies enabled, reload this page and try again.",
        "nocookiesforlogin": "{{int:nocookieslogin}}",
+       "createacct-loginerror": "The account was successfully created but you could not be logged in automatically. Please proceed to [[Special:UserLogin|manual login]].",
        "noname": "You have not specified a valid username.",
        "loginsuccesstitle": "Logged in",
        "loginsuccess": "<strong>You are now logged in to {{SITENAME}} as \"$1\".</strong>",
-       "nosuchuser": "There is no user by the name \"$1\".\nUsernames are case sensitive.\nCheck your spelling, or [[Special:UserLogin/signup|create a new account]].",
+       "nosuchuser": "There is no user by the name \"$1\".\nUsernames are case sensitive.\nCheck your spelling, or [[Special:CreateAccount|create a new account]].",
        "nosuchusershort": "There is no user by the name \"$1\".\nCheck your spelling.",
        "nouserspecified": "You have to specify a username.",
        "login-userblocked": "This user is blocked. Login not allowed.",
        "createacct-another-realname-tip": "Real name is optional.\nIf you choose to provide it, this will be used for giving the user attribution for their work.",
        "pt-login": "Log in",
        "pt-login-button": "Log in",
+       "pt-login-continue-button": "Continue login",
        "pt-createaccount": "Create account",
        "pt-userlogout": "Log out",
        "pear-mail-error": "$1",
        "botpasswords-invalid-name": "The username specified does not contain the bot password separator (\"$1\").",
        "botpasswords-not-exist": "User \"$1\" does not have a bot password named \"$2\".",
        "resetpass_forbidden": "Passwords cannot be changed",
+       "resetpass_forbidden-reason": "Passwords cannot be changed: $1",
        "resetpass-no-info": "You must be logged in to access this page directly.",
        "resetpass-submit-loggedin": "Change password",
        "resetpass-submit-cancel": "Cancel",
        "passwordreset-emailsentusername": "If there is an email address associated with this username, then a password reset email will be sent.",
        "passwordreset-emailsent-capture": "A password reset email has been sent, which is shown below.",
        "passwordreset-emailerror-capture": "A password reset email was generated, which is shown below, but sending it to the {{GENDER:$2|user}} failed: $1",
+       "passwordreset-emailsent-capture2": "The password reset {{PLURAL:$1|email has|emails have}} been sent. The {{PLURAL:$1|username and password|list of usernames and passwords}} is shown below.",
+       "passwordreset-emailerror-capture2": "Emailing the {{GENDER:$2|user}} failed: $1 The {{PLURAL:$3|username and password|list of usernames and passwords}} is shown below.",
+       "passwordreset-nocaller": "A caller must be provided",
+       "passwordreset-nosuchcaller": "Caller does not exist: $1",
+       "passwordreset-ignored": "The password reset was not handled. Maybe no provider was configured?",
+       "passwordreset-invalideamil": "Invalid email address",
+       "passwordreset-nodata": "Neither a username nor an email address was supplied",
        "changeemail": "Change or remove email address",
        "changeemail-summary": "",
        "changeemail-header": "Complete this form to change your email address. If you would like to remove the association of any email address from your account, leave the new email address blank when submitting the form.",
        "image_tip": "Embedded file",
        "media_sample": "Example.ogg",
        "media_tip": "File link",
+       "sig-text": "--$1",
        "sig_tip": "Your signature with timestamp",
        "hr_tip": "Horizontal line (use sparingly)",
        "summary": "Summary:",
        "newarticletext": "You have followed a link to a page that does not exist yet.\nTo create the page, start typing in the box below (see the [$1 help page] for more info).\nIf you are here by mistake, click your browser's <strong>back</strong> button.",
        "newarticletextanon": "{{int:newarticletext|$1}}",
        "talkpagetext": "<!-- MediaWiki:talkpagetext -->",
-       "anontalkpagetext": "----\n<em>This is the discussion page for an anonymous user who has not created an account yet, or who does not use it.</em>\nWe therefore have to use the numerical IP address to identify him/her.\nSuch an IP address can be shared by several users.\nIf you are an anonymous user and feel that irrelevant comments have been directed at you, please [[Special:UserLogin/signup|create an account]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous users.",
+       "anontalkpagetext": "----\n<em>This is the discussion page for an anonymous user who has not created an account yet, or who does not use it.</em>\nWe therefore have to use the numerical IP address to identify him/her.\nSuch an IP address can be shared by several users.\nIf you are an anonymous user and feel that irrelevant comments have been directed at you, please [[Special:CreateAccount|create an account]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous users.",
        "noarticletext": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} create this page]</span>.",
        "noarticletext-nopermission": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages, or <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>, but you do not have permission to create this page.",
        "noarticletextanon": "{{int:noarticletext}}",
        "right-override-export-depth": "Export pages including linked pages up to a depth of 5",
        "right-sendemail": "Send email to other users",
        "right-passwordreset": "View password reset emails",
-       "right-managechangetags": "Create and delete [[Special:Tags|tags]] from the database",
+       "right-managechangetags": "Create and (de)activate [[Special:Tags|tags]]",
        "right-applychangetags": "Apply [[Special:Tags|tags]] along with one's changes",
        "right-changetags": "Add and remove arbitrary [[Special:Tags|tags]] on individual revisions and log entries",
+       "right-deletechangetags": "Delete [[Special:Tags|tags]] from the database",
        "grant-generic": "\"$1\" rights bundle",
        "grant-group-page-interaction": "Interact with pages",
        "grant-group-file-interaction": "Interact with media",
        "action-viewmyprivateinfo": "view your private information",
        "action-editmyprivateinfo": "edit your private information",
        "action-editcontentmodel": "edit the content model of a page",
-       "action-managechangetags": "create and delete tags from the database",
+       "action-managechangetags": "create and (de)activate tags",
        "action-applychangetags": "apply tags along with your changes",
        "action-changetags": "add and remove arbitrary tags on individual revisions and log entries",
+       "action-deletechangetags": "delete tags from the database",
        "nchanges": "$1 {{PLURAL:$1|change|changes}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|since last visit}}",
        "enhancedrc-history": "history",
        "upload-form-label-infoform-description-tooltip": "Briefly describe everything notable about the work.\nFor a photo, mention the main things that are depicted, the occasion, or the place.",
        "upload-form-label-usage-title": "Usage",
        "upload-form-label-usage-filename": "File name",
-       "foreign-structured-upload-form-label-own-work": "This is my own work",
-       "foreign-structured-upload-form-label-infoform-categories": "Categories",
-       "foreign-structured-upload-form-label-infoform-date": "Date",
-       "foreign-structured-upload-form-label-own-work-message-local": "I confirm that I am uploading this file following the terms of service and licensing policies on {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "If you are not able to upload this file under the policies of {{SITENAME}}, please close this dialog and try another method.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "You may also want to try [[Special:Upload|the default upload page]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "I understand that I am uploading this file to a shared repository. I confirm that I am doing so following the terms of service and licensing policies there.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "If you are not able to upload this file under the policies of the shared repository, please close this dialog and try another method.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if this file can be uploaded there under their policies.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "I attest that I own the copyright on this file, and agree to irrevocably release this file to Wikimedia Commons under the [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] license, and I agree to the [https://wikimediafoundation.org/wiki/Terms_of_Use Terms of Use].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "If you do not own the copyright on this file, or you wish to release it under a different license, consider using the [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if the site allows the upload of this file under their policies.",
+       "upload-form-label-own-work": "This is my own work",
+       "upload-form-label-infoform-categories": "Categories",
+       "upload-form-label-infoform-date": "Date",
+       "upload-form-label-own-work-message-generic-local": "I confirm that I am uploading this file following the terms of service and licensing policies on {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "If you are not able to upload this file under the policies of {{SITENAME}}, please close this dialog and try another method.",
+       "upload-form-label-not-own-work-local-generic-local": "You may also want to try [[Special:Upload|the default upload page]].",
+       "upload-form-label-own-work-message-generic-foreign": "I understand that I am uploading this file to a shared repository. I confirm that I am doing so following the terms of service and licensing policies there.",
+       "upload-form-label-not-own-work-message-generic-foreign": "If you are not able to upload this file under the policies of the shared repository, please close this dialog and try another method.",
+       "upload-form-label-not-own-work-local-generic-foreign": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if this file can be uploaded there under their policies.",
        "backend-fail-stream": "Could not stream file \"$1\".",
        "backend-fail-backup": "Could not backup file \"$1\".",
        "backend-fail-notexists": "The file $1 does not exist.",
        "changecontentmodel-success-text": "The content type of [[:$1]] has been changed.",
        "changecontentmodel-cannot-convert": "The content on [[:$1]] cannot be converted to a type of $2.",
        "changecontentmodel-nodirectediting": "The $1 content model does not support direct editing",
+       "changecontentmodel-emptymodels-title": "No content models available",
+       "changecontentmodel-emptymodels-text": "The content on [[:$1]] cannot be converted to any type.",
        "log-name-contentmodel": "Content model change log",
        "log-description-contentmodel": "Events related to the content models of a page",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|created}} the page $3 using a non-default content model \"$5\"",
        "whatlinkshere-prev": "{{PLURAL:$1|previous|previous $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|next|next $1}}",
        "whatlinkshere-links": "← links",
-       "whatlinkshere-hideredirs": "$1 redirects",
-       "whatlinkshere-hidetrans": "$1 transclusions",
-       "whatlinkshere-hidelinks": "$1 links",
-       "whatlinkshere-hideimages": "$1 file links",
+       "whatlinkshere-hideredirs": "Hide redirects",
+       "whatlinkshere-hidetrans": "Hide transclusions",
+       "whatlinkshere-hidelinks": "Hide links",
+       "whatlinkshere-hideimages": "Hide file links",
        "whatlinkshere-filters": "Filters",
        "whatlinkshere-submit": "Go",
        "autoblockid": "Autoblock #$1",
        "lockdbsuccesstext": "The database has been locked.<br />\nRemember to [[Special:UnlockDB|remove the lock]] after your maintenance is complete.",
        "unlockdbsuccesstext": "The database has been unlocked.",
        "lockfilenotwritable": "The database lock file is not writable.\nTo lock or unlock the database, this needs to be writable by the web server.",
+       "databaselocked": "The database is already locked.",
        "databasenotlocked": "The database is not locked.",
        "lockedbyandtime": "(by {{GENDER:$1|$1}} on $2 at $3)",
        "move-page": "Move $1",
        "timezone-local": "Local",
        "duplicate-defaultsort": "<strong>Warning:</strong> Default sort key \"$2\" overrides earlier default sort key \"$1\".",
        "duplicate-displaytitle": "<strong>Warning:</strong> Display title \"$2\" overrides earlier display title \"$1\".",
+       "restricted-displaytitle": "<strong>Warning:</strong> Display title \"$1\" was ignored since it is not equivalent to the page's actual title.",
        "invalid-indicator-name": "<strong>Error:</strong> Page status indicators' <code>name</code> attribute must not be empty.",
        "version": "Version",
        "version-summary": "",
        "tags-delete-not-found": "The tag \"$1\" does not exist.",
        "tags-delete-too-many-uses": "The tag \"$1\" is applied to more than $2 {{PLURAL:$2|revision|revisions}}, which means it cannot be deleted.",
        "tags-delete-warnings-after-delete": "The tag \"$1\" was deleted, but the following {{PLURAL:$2|warning was|warnings were}} encountered:",
+       "tags-delete-no-permission": "You do not have permission to delete change tags.",
        "tags-activate-title": "Activate tag",
        "tags-activate-question": "You are about to activate the tag \"$1\".",
        "tags-activate-reason": "Reason:",
        "feedback-useragent": "User agent:",
        "searchsuggest-search": "Search",
        "searchsuggest-containing": "containing...",
+       "api-error-autoblocked": "Your IP address has been blocked automatically, because it was used by a blocked user.",
        "api-error-badaccess-groups": "You are not permitted to upload files to this wiki.",
        "api-error-badtoken": "Internal error: Bad token.",
+       "api-error-blocked": "You have been blocked from editing.",
        "api-error-copyuploaddisabled": "Uploading by URL is disabled on this server.",
        "api-error-duplicate": "There {{PLURAL:$1|is another file|are some other files}} already on the site with the same content.",
        "api-error-duplicate-archive": "There {{PLURAL:$1|was another file|were some other files}} already on the site with the same content, but {{PLURAL:$1|it was|they were}} deleted.",
        "api-error-nomodule": "Internal error: No upload module set.",
        "api-error-ok-but-empty": "Internal error: No response from server.",
        "api-error-overwrite": "Overwriting an existing file is not allowed.",
+       "api-error-ratelimited": "You're trying to upload more files in a short space of time than this wiki allows.\nPlease try again in a few minutes.",
        "api-error-stashfailed": "Internal error: Server failed to store temporary file.",
        "api-error-publishfailed": "Internal error: Server failed to publish temporary file.",
        "api-error-stasherror": "There was an error while uploading the file to stash.",
        "log-action-filter-suppress-block": "User supppression by block",
        "log-action-filter-suppress-reblock": "User suppression by reblock",
        "log-action-filter-upload-upload": "New upload",
-       "log-action-filter-upload-overwrite": "Reupload"
+       "log-action-filter-upload-overwrite": "Reupload",
+       "authmanager-authn-not-in-progress": "Authentication is not in progress or session data has been lost. Please start again from the beginning.",
+       "authmanager-authn-no-primary": "The supplied credentials could not be authenticated.",
+       "authmanager-authn-no-local-user": "The supplied credentials are not associated with any user on this wiki.",
+       "authmanager-authn-no-local-user-link": "The supplied credentials are valid but are not associated with any user on this wiki. Login in a different way, or create a new user, and you will have an option to link your previous credentials to that account.",
+       "authmanager-authn-autocreate-failed": "Auto-creation of a local account failed: $1",
+       "authmanager-change-not-supported": "The supplied credentials cannot be changed, as nothing would use them.",
+       "authmanager-create-disabled": "Account creation is disabled.",
+       "authmanager-create-from-login": "To create your account, please fill in the fields below.",
+       "authmanager-create-not-in-progress": "Account creation is not in progress or session data has been lost. Please start again from the beginning.",
+       "authmanager-create-no-primary": "The supplied credentials could not be used for account creation.",
+       "authmanager-link-no-primary": "The supplied credentials could not be used for account linking.",
+       "authmanager-link-not-in-progress": "Account linking is not in progress or session data has been lost. Please start again from the beginning.",
+       "authmanager-authplugin-setpass-failed-title": "Password change failed",
+       "authmanager-authplugin-setpass-failed-message": "The authentication plugin denied the password change.",
+       "authmanager-authplugin-create-fail": "The authentication plugin denied the account creation.",
+       "authmanager-authplugin-setpass-denied": "The authentication plugin does not allow changing passwords.",
+       "authmanager-authplugin-setpass-bad-domain": "Invalid domain.",
+       "authmanager-autocreate-noperm": "Automatic account creation is not allowed.",
+       "authmanager-autocreate-exception": "Automatic account creation temporarily disabled due to prior errors.",
+       "authmanager-userdoesnotexist": "User account \"$1\" is not registered.",
+       "authmanager-userlogin-remembermypassword-help": "Whether the password should be remembered for longer than the length of the session.",
+       "authmanager-username-help": "Username for authentication.",
+       "authmanager-password-help": "Password for authentication.",
+       "authmanager-domain-help": "Domain for external authentication.",
+       "authmanager-retype-help": "Password again to confirm.",
+       "authmanager-email-label": "Email",
+       "authmanager-email-help": "Email address",
+       "authmanager-realname-label": "Real name",
+       "authmanager-realname-help": "Real name of the user",
+       "authmanager-provider-password": "Password-based authentication",
+       "authmanager-provider-password-domain": "Password- and domain-based authentication",
+       "authmanager-account-password-domain": "$1@$2",
+       "authmanager-provider-temporarypassword": "Temporary password",
+       "authprovider-confirmlink-message": "Based on your recent login attempts, the following accounts can be linked to your wiki account. Linking them enables logging in via those accounts. Please select which ones should be linked.",
+       "authprovider-confirmlink-option": "$1 ($2)",
+       "authprovider-confirmlink-request-label": "Accounts which should be linked",
+       "authprovider-confirmlink-request-help": "",
+       "authprovider-confirmlink-success-line": "$1: Linked successfully.",
+       "authprovider-confirmlink-failed-line": "$1: $2",
+       "authprovider-confirmlink-failed": "Account linking did not fully succeed: $1",
+       "authprovider-confirmlink-ok-help": "Continue after displaying linking failure messages.",
+       "authprovider-resetpass-skip-label": "Skip",
+       "authprovider-resetpass-skip-help": "Skip resetting the password.",
+       "authform-nosession-login": "The authentication was successful, but your browser cannot \"remember\" being logged in.\n\n$1",
+       "authform-nosession-signup": "The account was created, but your browser cannot \"remember\" being logged in.\n\n$1",
+       "authform-newtoken": "Missing token. $1",
+       "authform-notoken": "Missing token",
+       "authform-wrongtoken": "Wrong token",
+       "specialpage-securitylevel-not-allowed-title": "Not allowed",
+       "specialpage-securitylevel-not-allowed": "Sorry, you are not allowed to use this page because your identity could not be verified.",
+       "authpage-cannot-login": "Unable to start login.",
+       "authpage-cannot-login-continue": "Unable to continue login. Your session most likely timed out.",
+       "authpage-cannot-create": "Unable to start account creation.",
+       "authpage-cannot-create-continue": "Unable to continue account creation. Your session most likely timed out.",
+       "authpage-cannot-link": "Unable to start account linking.",
+       "authpage-cannot-link-continue": "Unable to continue account linking. Your session most likely timed out.",
+       "cannotauth-not-allowed-title": "Permission denied",
+       "cannotauth-not-allowed": "You are not allowed to use this page",
+       "changecredentials" : "Change credentials",
+       "changecredentials-submit": "Change",
+       "changecredentials-submit-cancel": "Cancel",
+       "changecredentials-invalidsubpage": "$1 is not a valid credential type.",
+       "changecredentials-success": "Your credentials have been changed.",
+       "removecredentials" : "Remove credentials",
+       "removecredentials-submit": "Remove",
+       "removecredentials-submit-cancel": "Cancel",
+       "removecredentials-invalidsubpage": "$1 is not a valid credential type.",
+       "removecredentials-success": "Your credentials have been removed.",
+       "credentialsform-provider": "Credentials type:",
+       "credentialsform-account": "Account name:",
+       "cannotlink-no-provider-title": "There are no linkable accounts",
+       "cannotlink-no-provider": "There are no linkable accounts.",
+       "linkaccounts": "Link accounts",
+       "linkaccounts-success-text": "The account was linked.",
+       "linkaccounts-submit": "Link accounts",
+       "unlinkaccounts": "Unlink accounts",
+       "unlinkaccounts-success": "The account was unlinked."
 }
index eba4a99..8eb1341 100644 (file)
@@ -58,7 +58,7 @@
        "tog-usenewrc": "Grupigi ŝanĝojn laŭ paĝo en \"Lastaj ŝanĝoj\" kaj \"Atentaro\" (bezonas Ĝavaskripton)",
        "tog-numberheadings": "Aŭtomate numerigi sekciojn",
        "tog-showtoolbar": "Montri redakto-breton (per Ĝavaskripto)",
-       "tog-editondblclick": "Redakti per duobla alklako (per Ĝavaskripto)",
+       "tog-editondblclick": "Redakti paĝojn per duobla alklako",
        "tog-editsectiononrightclick": "Ŝalti sekcian redaktadon per dekstra musklako de sekciaj titoloj (per Ĝavaskripto)",
        "tog-watchcreations": "Aldoni miajn kreatajn paĝojn kaj miajn alŝutaĵojn al mia atentaro",
        "tog-watchdefault": "Aldoni al mia atentaro paĝojn kaj dosierojn redaktitajn de mi",
@@ -89,7 +89,7 @@
        "tog-ccmeonemails": "Sendi al mi kopiojn de retpoŝtaĵoj, kiujn mi sendis al aliaj uzantoj.",
        "tog-diffonly": "Ne montri paĝan enhavon sub la ŝanĝmontrilo",
        "tog-showhiddencats": "Montri kaŝitajn kategoriojn",
-       "tog-norollbackdiff": "Preterlasi ŝanĝoelmontron post malfaro",
+       "tog-norollbackdiff": "Nemontri diferencon post plenumado de ŝanĝomalfaro",
        "tog-useeditwarning": "Averti min kiam mi forlasas redaktan paĝon kun nekonservitaj ŝanĝoj",
        "tog-prefershttps": "Ĉiam uzu sekuran konekton ensalutite",
        "underline-always": "Ĉiam",
        "oct": "okt",
        "nov": "nov",
        "dec": "dec",
-       "january-date": "$1-a de januaro",
-       "february-date": "$1-a de februaro",
-       "march-date": "$1-a de marto",
-       "april-date": "$1-a de aprilo",
-       "may-date": "$1-a de majo",
-       "june-date": "$1-a de junio",
-       "july-date": "$1-a de julio",
-       "august-date": "$1-a de aŭgusto",
-       "september-date": "$1-a de septembro",
-       "october-date": "$1-a de oktobro",
-       "november-date": "$1-a de novembro",
-       "december-date": "$1-a de decembro",
+       "january-date": "$1a de januaro",
+       "february-date": "$1a de februaro",
+       "march-date": "$1a de marto",
+       "april-date": "$1a de aprilo",
+       "may-date": "$1a de majo",
+       "june-date": "$1a de junio",
+       "july-date": "$1a de julio",
+       "august-date": "$1a de aŭgusto",
+       "september-date": "$1a de septembro",
+       "october-date": "$1a de oktobro",
+       "november-date": "$1a de novembro",
+       "december-date": "$1a de decembro",
        "period-am": "ATM",
        "period-pm": "PTM",
        "pagecategories": "{{PLURAL:$1|Kategorio|Kategorioj}}",
        "help": "Helpo",
        "search": "Serĉi",
        "searchbutton": "Serĉi",
-       "go": "Ek!",
+       "go": "Ek",
        "searcharticle": "Ek",
        "history": "Historio de versioj",
        "history_short": "Historio",
        "newpage": "Nova paĝo",
        "talkpage": "Diskuti la paĝon",
        "talkpagelinktext": "diskuto",
-       "specialpage": "Speciala Paĝo",
+       "specialpage": "Speciala paĝo",
        "personaltools": "Personaj iloj",
        "articlepage": "Vidi paĝenhavon",
        "talk": "Diskuto",
        "views": "Vidoj",
        "toolbox": "Iloj",
-       "userpage": "Vidi uzantan paĝon",
+       "userpage": "Vidi uzulan paĝon",
        "projectpage": "Rigardi projektopaĝon",
        "imagepage": "Vidi dosieropaĝon",
        "mediawikipage": "Vidi mesaĝopaĝon",
        "missingarticle-rev": "(versio#: $1)",
        "missingarticle-diff": "(Diferenco inter versioj: $1, $2)",
        "readonly_lag": "La datumbazo estis aŭtomate ŝlosita dum la subdatumbazo atingas la ĉefan datumbazon.",
+       "nonwrite-api-promise-error": "La 'Promeso-Ne-Skribi-API-Ago' HTTPa titolo estis sendita sed la peto estis al API skriba modulo.",
        "internalerror": "Interna eraro",
        "internalerror_info": "Interna eraro: $1",
        "internalerror-fatal-exception": "Neriparebla escepto de la tipo \"$1\"",
        "password-change-forbidden": "Ve ne povas ŝanĝi pasvortojn en ĉi tiu vikio.",
        "externaldberror": "Aŭ estis datenbaza eraro rilate al ekstera aŭtentikigado, aŭ vi ne rajtas ĝisdatigi vian eksteran konton.",
        "login": "Ensaluti",
+       "login-security": "Kontrolu vian identecon",
        "nav-login-createaccount": "Ensaluti / Krei novan konton",
        "userlogin": "Ensaluti / Krei novan konton",
        "userloginnocreate": "Ensaluti",
        "nologin": "Ĉu vi ne havas konton? $1.",
        "nologinlink": "Krei konton",
        "createaccount": "Krei konton",
-       "gotaccount": "Ĉu vi jam havas konton? '''$1'''.",
+       "gotaccount": "Ĉu vi jam havas konton? $1.",
        "gotaccountlink": "Ensaluti",
        "userlogin-resetlink": "Ĉu vi forgesis ensalutajn detalojn?",
        "userlogin-resetpassword-link": "Ĉu vi forgesis vian pasvorton?",
        "userlogin-helplink2": "Helpo pri ensaluto",
        "userlogin-loggedin": "Vi jam estas ensalutita kiel {{GENDER:$1|$1}}.\nUzu la formularon suben por ensaluti kiel alia uzanto.",
+       "userlogin-reauth": "Vi devas ensaluti denove por konfirmi ke vi estas {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Krei alian konton",
        "createacct-emailrequired": "Retpoŝta adreso",
        "createacct-emailoptional": "Retpoŝta adreso (nedeviga)",
        "createacct-email-ph": "Enigu vian retpoŝtan adreson",
        "createacct-another-email-ph": "Enigu la retpoŝtan adreson",
        "createaccountmail": "Uzi provizoran hazardsignan pasvorton kaj sendi ĝin al la retpoŝta adreso ĉi-suba",
+       "createaccountmail-help": "Uzebla por krei konton de alia persono sen lerni la pasvorton.",
        "createacct-realname": "Vera nomo (nedeviga)",
        "createaccountreason": "Kialo:",
        "createacct-reason": "Kialo",
        "createacct-reason-ph": "Kial vi kreas plian konton",
+       "createacct-reason-help": "Mesaĝo vidigita en la protokolo pri kreado de konto",
        "createacct-submit": "Krei konton",
        "createacct-another-submit": "Krei konton",
+       "createacct-continue-submit": "Daŭri kreadon de konto",
+       "createacct-another-continue-submit": "Daŭri kreadon de konto",
        "createacct-benefit-heading": "{{SITENAME}} estas kreata de homoj kiel vi.",
        "createacct-benefit-body1": "{{PLURAL:$1|redakto|redaktoj}}",
        "createacct-benefit-body2": "{{PLURAL:$1|paĝo|paĝoj}}",
        "nocookiesnew": "La uzantokonto estis kreita sed vi ne estas ensalutinta. {{SITENAME}} uzas kuketojn por akcepti uzantojn. Kuketoj esta malaktivigitaj ĉe vi. Bonvolu aktivigi ilin kaj ensalutu per viaj novaj salutnomo kaj pasvorto.",
        "nocookieslogin": "{{SITENAME}} uzas kuketojn por akcepti uzantojn. Kuketoj esta malaktivigitaj ĉe vi. Bonvolu aktivigi ilin kaj provu denove.",
        "nocookiesfornew": "La uzantokonto ne estis kreita, ĉar ne konfirmeblas ĝia fonto. Certiginte ke kuketoj estas ebligitaj, reŝargu tiun ĉi paĝon kaj reprovu.",
+       "createacct-loginerror": "La konto estis sukcese kreita sed vi ne povus esti ensalutita aŭtomate. Bonvolu procedi [[Special:UserLogin|malaŭtomatan ensaluton]].",
        "noname": "Vi ne tajpis validan salutnomon.",
        "loginsuccesstitle": "Ensalutis",
        "loginsuccess": "Vi ensalutis ĉe {{SITENAME}} kiel uzanto \"$1\".",
-       "nosuchuser": "Neniu uzanto havas nomon \"$1\".\nNomoj por uzantoj estas usklecodistingaj.\nKontrolu vian literumadon, aŭ [[Special:UserLogin/signup|kreu novan konton]].",
+       "nosuchuser": "Neniu uzanto havas nomon \"$1\".\nNomoj por uzantoj estas usklecodistingaj.\nKontrolu vian literumadon, aŭ [[Special:CreateAccount|kreu novan konton]].",
        "nosuchusershort": "Ne ekzistas uzanto kun la nomo \"$1\". Bonvolu kontroli vian ortografion.",
        "nouserspecified": "Vi devas entajpi salutnomon.",
        "login-userblocked": "Ĉi tiu uzanto estas forbarita. Ensalutado ne estas permesita.",
        "noemail": "Retpoŝtadreso ne estas registrita por uzanto \"$1\".",
        "noemailcreate": "Vi devas provizi validan retadreson",
        "passwordsent": "Oni sendis novan pasvorton al la retpoŝtadreso\nregistrita por \"$1\".\nBonvolu ensaluti denove ricevinte ĝin.",
-       "blocked-mailpassword": "Via IP-adreso estas forbarita de redaktado. Por preventi fiuzojn, la pasvorto-restaŭron estas malpermesita per tiu IP-adreso.",
+       "blocked-mailpassword": "Via IP-adreso estas forbarita de redaktado. Por preventi fiuzojn, la pasvorto-restaŭro per tiu IP-adreso estas malpermesita.",
        "eauthentsent": "Konfirma retmesaĝo estis sendita al la nomita retadreso. Antaŭ ol iu ajn alia mesaĝo estos sendita al la konto, vi devos sekvi la instrukciojn en la mesaĝo por konfirmi ke la konto ja estas via.",
        "throttled-mailpassword": "Retpoŝto kun reŝargita pasvorto estis jam sendita ene de la {{PLURAL:$1|lasta horo|lastaj $1 horoj}}.\nPor preventi misuzon, nur unu reŝargita pasvorto estos sendita dum {{PLURAL:$1|horo|$1 horoj}}.",
        "mailerror": "Okazis eraro sendante retpoŝtaĵon: $1",
        "createacct-another-realname-tip": "La vera nomo estas nenecesa.\nSe vi decidas indiki ĝin, ĝi estos uzata por montri atribuadon de viaj kontribuoj.",
        "pt-login": "Ensaluti",
        "pt-login-button": "Ensaluti",
+       "pt-login-continue-button": "Daŭri ensaluton",
        "pt-createaccount": "Krei konton",
        "pt-userlogout": "Elsaluti",
        "php-mail-error-unknown": "Nekonata eraro en la funkcio mail() de PHP",
        "botpasswords-label-grants": "Uzeblaj permesdonoj:",
        "botpasswords-help-grants": "Ĉiu permesdono provizas aliron al listitaj uzantaj permisoj, kiujn uzantkonto jam havas. Vidu la [[Special:ListGrants|tabelon de permisdonoj]] por pli da informo.",
        "botpasswords-label-restrictions": "Limigoj de uzado:",
-       "botpasswords-label-grants-column": "Permisdonita",
+       "botpasswords-label-grants-column": "Permeso donita",
        "botpasswords-bad-appid": "La robota nomo \"$1\" estas malvalida.",
        "botpasswords-insert-failed": "Aldono de la robota nomo \"$1\" ne sukcesis. Ĉu ĝi jam estis aldonita?",
        "botpasswords-update-failed": "Ĝisdatigo de la robota nomo \"$1\" ne sukcesis. Ĉu ĝi estis forigita?",
        "botpasswords-updated-body": "La robota pasvorto por robota nomo \"$1\" de la uzanto \"$2\" estis ĝisdatigita.",
        "botpasswords-deleted-title": "Robota pasvorto forigita",
        "botpasswords-deleted-body": "La robota pasvorto por robota nomo \"$1\" de la uzanto \"$2\" estis forigita.",
-       "botpasswords-newpassword": "La nova pasvorto por ensaluti kun <strong>$1</strong> estas <strong>$2</strong>. <em>Bonvolu registri tiun por referenconto.",
+       "botpasswords-newpassword": "La nova pasvorto por ensaluti per <strong>$1</strong> estas <strong>$2</strong>. <em>Bonvolu noti ĝin por estonta konsultado.",
        "botpasswords-no-provider": "Robotopasvortensalutoprovizilo (''BotPasswordsSessionProvider'') maldisponeblas.",
-       "botpasswords-restriction-failed": "Limigoj pri robota pasvorto maleblas tiun uzantonomon.",
+       "botpasswords-restriction-failed": "Limigoj pri robota pasvorto malebligas tiun ensalutadon.",
        "botpasswords-invalid-name": "La difinita uzantnomo malenhavas la robotopasvortan disigilon (\"$1\").",
        "botpasswords-not-exist": "Uzanto \"$1\" ne havas robotopasvorton, kiu nomiĝas \"$2\".",
        "resetpass_forbidden": "Pasvortoj ne estas ŝanĝeblaj",
+       "resetpass_forbidden-reason": "Pasvortoj ne povas esti ŝanĝita: $1",
        "resetpass-no-info": "Vi devas ensaluti por atingi ĉi tiun paĝon rekte.",
        "resetpass-submit-loggedin": "Ŝanĝi pasvorton",
        "resetpass-submit-cancel": "Nuligi",
        "passwordreset-emailtext-user": "Uzanto $1 de {{SITENAME}} petis restarigo de via pasvorto por {{SITENAME}}\n($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 devas ensaluti kaj elekti novan pasvorton nun. Se iu alia petis ĉi tion,\naŭ se vi memoris vian originalan pasvorton, kaj vi ne plu volas ŝanĝi\nĝin, vi povas ignori ĉi tiun mesaĝon kaj uzi vian malnovan 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 asocigita kun tiu uzantnomo, tiam sendos retpôstan mesaĝon pri reasigno de pasvorto.",
+       "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-capture": "Retpoŝto kun renovigita pasvorto estis sendita, kiu estas montrata malsupre.",
        "passwordreset-emailerror-capture": "Retpoŝto kun renovigita pasvorto estis generita, montrata sube, sed sendado al la {{GENDER:$2|uzanto}} malsukcesis: $1",
+       "passwordreset-emailsent-capture2": "La {{PLURAL:$1|retpoŝto|retpoŝtojn}} de pasvorta reensignado estis sendita. La {{PLURAL:$1|salutnomo kaj pasvorto|listo de salutnomoj kaj pasvortoj}} estas vidigita sube.",
+       "passwordreset-emailerror-capture2": "Retpoŝtado al la {{GENDER:$2|uzantiĉo|uzantino|uzanto}} malsukcesis: $1 La {{PLURAL:$3|salutnomo kaj pasvorta|listo de salutnomoj kaj pasvortoj}} estas vidigita sube.",
+       "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?",
+       "passwordreset-invalideamil": "Nevalida retpoŝta adreso",
+       "passwordreset-nodata": "Nek salutnomo nek retpoŝta adreso estis provizita",
        "changeemail": "Ŝanĝi aŭ forigi retpoŝtadreson",
        "changeemail-header": "Plenigu ĉi tiun formularon por ŝanĝi vian retpoŝtadreson. Se vi volas forigi la difinon de retpoŝtadreso por via uzantokonto, lasu la kampon por la nova retpoŝtadreso malplena ĉe la transigo.",
        "changeemail-passwordrequired": "Vi devas entajpi vian pasvorton, por konfirmi ĉi tiun ŝanĝon.",
        "subject": "Temo:",
        "minoredit": "Ĉi tiu ŝanĝo estas redakteto",
        "watchthis": "Atenti ĉi tiun paĝon",
-       "savearticle": "Konservi ŝanĝojn",
+       "savearticle": "Konservi paĝon",
+       "publishpage": "Publikigi paĝon",
        "preview": "Antaŭrigardo",
        "showpreview": "Antaŭrigardo",
        "showdiff": "Montri ŝanĝojn",
        "accmailtext": "Hazarde generita pasvorto por [[User talk:$1|$1]] estis sendita al $2.\n\nLa pasvorto por ĉi tiu nova konto povas esti ŝanĝita en la paĝo ''[[Special:ChangePassword|ŝanĝi pasvorton]]'' dum ensalutado.",
        "newarticle": "(Nova)",
        "newarticletext": "Vi sekvis ligilon al paĝo ankoraŭ ne ekzistanta. Se vi volas krei ĝin, ektajpu malsupre (vidu la [$1 helpopaĝon] por klarigoj.) Se vi malintence alvenis ĉi tien, simple alklaku la retrobutonon de via retumilo.",
-       "anontalkpagetext": "---- ''Jen diskutopaĝo por anonima kontribuanto kiu ne jam kreis konton aŭ ne uzas ĝin.\nNi tial devas uzi la cifran IP-adreson por identigi lin/ŝin.\nĈi tia IP-adreso povas esti uzata de pluraj uzantoj.\nSe vi estas anonimulo kaj preferus eviti tiajn mistrafajn komentojn al vi, bonvolu [[Special:UserLogin/signup|krei konton]] aŭ [[Special:UserLogin|ensaluti]] por eviti estontan konfuzon pro aliaj anonimaj uzantoj.''",
+       "anontalkpagetext": "---- ''Jen diskutopaĝo por anonima kontribuanto kiu ne jam kreis konton aŭ ne uzas ĝin.\nNi tial devas uzi la cifran IP-adreson por identigi lin/ŝin.\nĈi tia IP-adreso povas esti uzata de pluraj uzantoj.\nSe vi estas anonimulo kaj preferus eviti tiajn mistrafajn komentojn al vi, bonvolu [[Special:CreateAccount|krei konton]] aŭ [[Special:UserLogin|ensaluti]] por eviti estontan konfuzon pro aliaj anonimaj uzantoj.''",
        "noarticletext": "Mankas teksto en ĉi tiu paĝo.\nVi povas [[Special:Search/{{PAGENAME}}|serĉi ĉi tiun paĝtitolon]] en aliaj paĝoj,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serĉi la rilatajn protokolojn],\naŭ [{{fullurl:{{FULLPAGENAME}}|action=edit}} krei ĉi tiun paĝon]</span>.",
        "noarticletext-nopermission": "Estas neniom da teksto en ĉi tiu paĝo.\nVi povas [[Special:Search/{{PAGENAME}}|serĉi ĉi tiun paĝan titolon]] en aliaj paĝoj,\naŭ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serĉi la rilatajn protokolojn]</span>, sed vi ne rajtas krei ĉi tiun paĝon.",
        "missing-revision": "La revizio n-ro $1 de la paĝo nomata \"{{FULLPAGENAME}}\" ne ekzistas.\n\nTio kutime estas kaŭzata per sekvado de malaktuala historio-ligilo al paĝo forigita.\nDetaloj troveblos en la [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de forigoj].",
        "sitecsspreview": "'''Konsciu ke vi nur antaŭrigardas tiun ĉi CSS.'''\n'''Ĝi ne jam estis savita!''",
        "sitejspreview": "'''Konsciu ke vi nur antaŭrigardas tiun ĉi Ĝavaskripta kodon''. ''Ĝi ne jam estis konservita''.",
        "userinvalidcssjstitle": "'''Averto:''' Ne ekzistas etoso \"$1\".\nRememoru ke individuaj .css-aj kaj .js-aj paĝoj uzas minusklan titolon, ekz. {{ns:user}}:Foo/vector.css kontraŭe al {{ns:user}}:Foo/Vector.css.",
-       "updated": "(Ŝanĝo registrita)",
-       "note": "'''Noto:'''",
+       "updated": "(Ĝisdatigita)",
+       "note": "<strong>Noto:</strong>",
        "previewnote": "'''Memoru, ke ĉi tio estas nur antaŭrigardo.''' \nViaj ŝanĝoj ne ankoraŭ estas konservitaj!",
        "continue-editing": "Iri al redakta spaco",
        "previewconflict": "La jena antaŭrigardo montras la tekston el la supra tekstujo,\nkiel ĝi aperos se vi elektos konservi la paĝon.",
        "permissionserrors": "Eraro pri permeso",
        "permissionserrorstext": "Vi ne rajtas fari tion pro la {{PLURAL:$1|sekva kialo|sekvaj kialoj}}:",
        "permissionserrorstext-withaction": "Vi ne rajtas $2, pro la {{PLURAL:$1|jena kialo|jenaj kialoj}}:",
-       "contentmodelediterror": "Vi ne povas prilabori ĉi tiun reviziaĵo, ĉar ĝia enhavoŝablono estas <code>$1</code>, kiu malsamas la aktualan enhavoŝablonon de la paĝo <code>$2</code>.",
+       "contentmodelediterror": "Vi ne povas prilabori ĉi tiun version, ĉar ĝia enhavomodelo estas <code>$1</code>, kiu malsamas la aktualan enhavomodelon de la paĝo <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Averto: Vi rekreas paĝon, kiu estis antaŭe forigita.'''\n\nVi konsideru, ĉu konvenas daŭre redakti ĉi tiun paĝon.\nJen la protokolo de forigoj kaj alinomigado por via oportuno:",
        "moveddeleted-notice": "Ĉi tiu paĝo estis forigita.\nPliaj detaloj estas en protokolo pri forigado kaj alinomado de tiu ĉi paĝo.",
-       "moveddeleted-notice-recent": "Pardonon, tiu paĝo freŝdate estis forigita (en la dauro de la lasta 24 horoj).\nLa forigo kaj la movprotokolo pri la paĝo estas provizitaj sube por referenco.",
+       "moveddeleted-notice-recent": "Pardonon, tiu ĉi paĝo freŝdate estis forigita (en la daŭro de la lastaj 24 horoj).\nLa foriga kaj la nomŝanĝa protokoloj pri la paĝo estas provizitaj sube por referenco.",
        "log-fulllog": "Vidi kompletan protokolon",
        "edit-hook-aborted": "Redakto estis ĉesigita per etendaĵo de la Vikia softvaro.\nĜi ne donis eksplikon.",
        "edit-gone-missing": "Ne eblis ĝisdatigi la paĝon.\nVerŝajne ĝi estis forigita.",
        "content-model-css": "CSS",
        "content-json-empty-object": "Malplena objeto",
        "content-json-empty-array": "Malplena tabelo",
-       "duplicate-args-warning": "'''Averto:''' [[:$1]] vokas je [[:$2]] kun pli ol unu valoro por la parametro \"$3\". Nur la lasta liverita valoro estas uzonta.",
+       "duplicate-args-warning": "'''Averto:''' [[:$1]] vokas al [[:$2]] kun pli ol unu valoro por la parametro \"$3\". Nur la lasta liverita valoro estos uzata.",
        "duplicate-args-category": "Paĝoj kun pluroblaj argumentoj en ŝablonvokoj",
        "duplicate-args-category-desc": "La paĝo enhavas uzon de ŝablono kun pluroble uzitaj argumentoj, kiel ekzemple <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> aŭ <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "expensive-parserfunction-warning": "Averto: Ĉi tiu paĝo enhavas tro da multekostaj sintaksaj funkcio-vokoj.\n\nĜi havu malpli ol $2 {{PLURAL:$2|vokon|vokojn}}, sed nun estas $1 {{PLURAL:$1|voko|vokoj}}.",
        "revdelete-submit": "Apliki al {{PLURAL:$1|elektita revizio|elektitaj revizioj}}",
        "revdelete-success": "'''Revizivideblecon ĝisdatigis.'''",
        "revdelete-failure": "'''Videblecon de revizio ne eblis ĝisdatigi:'''\n$1",
-       "logdelete-success": "'''Protokolovideblecon ensignitan.'''",
+       "logdelete-success": "'''Protokolovidebleco agordita.'''",
        "logdelete-failure": "'''Protokola videbleco ne estis akordebla:'''\n$1",
        "revdel-restore": "Ŝanĝi videblecon",
        "pagehist": "Paĝa historio",
        "mergehistory-fail-invalid-source": "Fonta paĝo estas malvalida.",
        "mergehistory-fail-invalid-dest": "Cela paĝo estas malvalida.",
        "mergehistory-fail-no-change": "Historio-kunfandado kunfandis neniun revizion. Bonvolu rekontroli la paĝon kaj la tempo-parametrojn.",
-       "mergehistory-fail-permission": "Nesufiĉa permesoj por kunfadi historion.",
+       "mergehistory-fail-permission": "Nesufiĉaj permesoj por kunfandi historion.",
        "mergehistory-fail-self-merge": "Fonta kaj cela paĝoj samas.",
-       "mergehistory-fail-timestamps-overlap": "Fonta revizio surkunigas aŭ postokuras la celan revizion.",
+       "mergehistory-fail-timestamps-overlap": "Fontaj modifoj okazis dum aŭ post la celaj modifoj.",
        "mergehistory-fail-toobig": "Ne eblas kunigi historiojn ĉar pli ol sojlo de $1 {{PLURAL:$1|revizio|revizioj}} estus {{PLURAL:$1|movita|movitaj}}.",
        "mergehistory-no-source": "Fontpaĝo $1 ne ekzistas.",
        "mergehistory-no-destination": "Celpaĝo $1 ne ekzistas.",
        "lineno": "Linio $1:",
        "compareselectedversions": "Kompari la elektitajn versiojn",
        "showhideselectedversions": "Montri/kaŝi elektitajn versiojn",
-       "editundo": "Redaktomalfaru",
+       "editundo": "malfari",
        "diff-empty": "(Neniu diferenco)",
        "diff-multi-sameuser": "({{PLURAL:$1|Unu meza versio|$1 mezaj versioj}} de la sama uzanto ne montriĝas)",
        "diff-multi-otherusers": "({{PLURAL:$1|Unu meza versio|$1 mezaj versioj}} de {{PLURAL:$2|alia uzanto|$2 uzantoj}} ne montriĝas)",
        "search-category": "(kategorio $1)",
        "search-file-match": "(kongruas kun dosiera enhavo)",
        "search-suggest": "Ĉu vi intenciis: $1",
-       "search-rewritten": "Montru rezultojn por $1. Serĉita anstataŭ $2.",
+       "search-rewritten": "Ni montras rezultojn por $1. Serĉi anstataŭe pri $2.",
        "search-interwiki-caption": "Kunprojektoj",
        "search-interwiki-default": "Rezultoj de $1:",
        "search-interwiki-more": "(plu)",
        "prefs-help-recentchangescount": "Ĉi tiu inkluzivas lastajn ŝanĝojn, paĝajn historiojn, kaj protokolojn.",
        "prefs-help-watchlist-token2": "Tio estas la sekreta ŝlosilo al la retfluo de via atentaro.\nĈiu, kiu konas ĝin, povas legi vian atentaron. Do, ne kunhavigu ĝin.\nSe vi devas, [[Special:ResetTokens|vi povas rekomencigi ĝin]].",
        "savedprefs": "Viaj preferoj estas konservitaj.",
-       "savedrights": "La uzanto-rajtojn de {{GENDER:$1|$1}} konservigis.",
+       "savedrights": "La uzanto-rajtoj de {{GENDER:$1|$1}} estis konservitaj.",
        "timezonelegend": "Horzono:",
        "localtime": "Loka tempo:",
        "timezoneuseserverdefault": "Uzi defaŭlton de servilo ($1)",
        "right-createpage": "Kreu paĝojn (kiuj ne estas diskuto-paĝoj)",
        "right-createtalk": "Krei diskuto-paĝojn",
        "right-createaccount": "Krei novajn uzanto-kontojn",
-       "right-autocreateaccount": "Aŭtomate ensaluti eksteruzantan konton",
+       "right-autocreateaccount": "Aŭtomate ensaluti per ekstera uzokonto",
        "right-minoredit": "Marki redaktojn kiel etajn",
        "right-move": "Movi paĝojn",
        "right-move-subpages": "Alinomigi paĝojn kun ĝiaj subpaĝoj",
        "right-bigdelete": "Forigi paĝojn kun grandaj historioj",
        "right-deletelogentry": "Forigi kaj malforigi specifajn enmetojn en la registro.",
        "right-deleterevision": "Forigi kaj malforigi specifajn versiojn de paĝoj",
-       "right-deletedhistory": "Vidi forigitajn historieroj, sen ties asociaj tekstoj",
+       "right-deletedhistory": "Vidi forigitajn historierojn, sen iliaj ligita teksto",
        "right-deletedtext": "Rigardi forigitan tekston kaj ŝanĝojn inter forigitaj revizioj.",
        "right-browsearchive": "Serĉi forigitajn paĝojn",
        "right-undelete": "Restarigi paĝon",
        "right-managechangetags": "Kreado kaj forigado de [[Special:Tags|etikedoj]] de datumbazo",
        "right-applychangetags": "Aldoni [[Special:Tags|etikedojn]] al propraj ŝanĝoj",
        "right-changetags": "Aldoni kaj forigi arbitrajn [[Special:Tags|etikedojn]] ĉe unuopaj revizioj kaj protokoleroj",
+       "right-deletechangetags": "Forigi [[Special:Tags|etikedojn]] de la datenbazo",
        "grant-generic": "\"$1\" rajtaro",
        "grant-group-page-interaction": "Interagi paĝojn",
        "grant-group-file-interaction": "Interagi aŭdvidaĵajn dosierojn",
        "grant-group-watchlist-interaction": "Interagi vian atentaron",
        "grant-group-email": "Sendi retpoŝton",
-       "grant-group-high-volume": "Efektivigi ampleksege aktivecon",
+       "grant-group-high-volume": "Efektivigi ampleksegajn agojn",
        "grant-group-customization": "Personecigoj kaj preferoj",
        "grant-group-administration": "Efektivigi administrajn agojn",
        "grant-group-other": "Diversaj aktivecoj",
        "grant-createeditmovepage": "Krei, redakti kaj alinomi paĝojn",
        "grant-delete": "Forigi paĝojn, reviziaĵojn kaj protokolerojn",
        "grant-editinterface": "Redakti la MediaVikian nomspacon kaj la CSS/Ĝavoskripto de uzanto",
-       "grant-editmycssjs": "Redakti vian uzantan CSS/Ĝavoskripton",
-       "grant-editmyoptions": "Redakti vian uzantan preferojn",
+       "grant-editmycssjs": "Redakti viajn personajn CSS-kodon / Ĝavoskripton",
+       "grant-editmyoptions": "Redakti viajn personajn agordojn",
        "grant-editmywatchlist": "Redakti vian atentaron",
        "grant-editpage": "Redakti ekzistantajn paĝojn",
        "grant-editprotected": "Redakti protektitajn paĝojn",
-       "grant-highvolume": "Ampleksegaj redaktado",
+       "grant-highvolume": "Ampleksega redaktado",
        "grant-oversight": "Kaŝi uzantojn kaj forigi reviziaĵojn",
        "grant-patrol": "Patroli ŝanĝojn al pâgoj",
        "grant-protect": "Protekti kaj malprotekti paĝojn",
-       "grant-rollback": "Malvalidi ŝanĝojn al paĝoj",
+       "grant-rollback": "Malfari ŝanĝojn de paĝoj",
        "grant-sendemail": "Retpoŝti al aliaj uzantoj",
        "grant-uploadeditmovefile": "Alŝuti, anstataŭigi kaj alinomi dosierojn",
        "grant-uploadfile": "Alŝuti novajn dosierojn",
        "action-viewmyprivateinfo": "vidi viajn privatajn informojn",
        "action-editmyprivateinfo": "redakti viajn privatajn informojn",
        "action-editcontentmodel": "redakti paĝan enhavmodelon",
-       "action-managechangetags": "krei kaj forigi etikedojn de datumbazo",
+       "action-managechangetags": "Krei kaj (mal)aktivigi etikedojn",
        "action-applychangetags": "aldoni etikedojn al viaj propraj ŝanĝoj",
        "action-changetags": "aldoni kaj forigi arbitrajn etikedojn ĉe unuopaj revizioj kaj protokoleroj",
+       "action-deletechangetags": "Forigi etikedojn de la datenbazo.",
        "nchanges": "$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ekde lasta vizito}}",
        "enhancedrc-history": "historio",
        "rcshowhidemine": "$1 miajn redaktojn",
        "rcshowhidemine-show": "Montri",
        "rcshowhidemine-hide": "Kaŝi",
-       "rcshowhidecategorization": "$1 paĝokategoriadon",
+       "rcshowhidecategorization": "$1 paĝa enkategoriigo",
        "rcshowhidecategorization-show": "Montri",
        "rcshowhidecategorization-hide": "Kaŝi",
        "rclinks": "Montri $1 lastajn ŝanĝojn dum la $2 lastaj tagoj.<br />$3",
        "recentchangeslinked-summary": "Jen listo de ŝanĝoj faritaj lastatempe al paĝoj ligitaj el specifa paĝo (aŭ al membroj de specifa kategorio).\nPaĝoj en [[Special:Watchlist|via atentaro]] estas '''grasaj'''.",
        "recentchangeslinked-page": "Nomo de paĝo:",
        "recentchangeslinked-to": "Montru ŝanĝojn al paĝoj ligitaj al la specifa paĝo anstataŭe.",
-       "recentchanges-page-added-to-category": "[[:$1]] kategorialdonita",
+       "recentchanges-page-added-to-category": "[[:$1]] aldonita al la kategorio",
        "recentchanges-page-added-to-category-bundled": "[[:$1]] kategorialdonita, [[Special:WhatLinksHere/$1|tiu paĝo estas inkluzivita ene de aliaj paĝoj]]",
        "recentchanges-page-removed-from-category": "[[:$1]] kategoriforigita",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]] kategoriforigita, [[Special:WhatLinksHere/$1|tiu paĝo estas inkluzivita ene de aliaj paĝoj]]",
        "uploadscripted": "HTML-aĵo aŭ skriptokodaĵo troviĝas en tiu ĉi tiu dosiero, kiun TTT-foliumilo eble interpretus erare.",
        "upload-scripted-pi-callback": "Malalŝuteblas dosieron, kiu enhavas instrukcion de XML-stilfolia traktado",
        "uploaded-script-svg": "Trovis skriptelbero \"$1\" en la alŝutita SVGa dosiero.",
-       "uploaded-hostile-svg": "Trovis malsekura CSS en la stilero de alŝutita SVGa dosiero.",
+       "uploaded-hostile-svg": "Trovis malsekuran CSS-kodon en la stila elemento de alŝutita SVG-a dosiero.",
        "uploaded-event-handler-on-svg": "Ensigni eventotraktilajn atributojn <code>$1=\"$2\"</code> estas malpermisita en SVGaj dosieroj.",
        "uploaded-href-attribute-svg": "Atributoj je \"href\" en SVGaj dosieroj nur povas ligi al \"http://\" aŭ \"https://\" celoj, trovis  <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-href-unsafe-target-svg": "Trovis je \"href\" ligita al malsekuraj datenoj: URIa celo <code>&lt;$1 $2=\"$3\"&gt;</code> en la alŝuta SVGa dosiero.",
        "uploaded-animate-svg": "Trovis markon je \"animate\", kiu povus ŝanĝi la atributon je \"href\", per uzi la atributon je \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code> en la alŝuta SVGa dosiero.",
        "uploaded-setting-event-handler-svg": "Ensigni eventotraktilajn atributojn estas blokita, trovis <code>&lt;$1 $2=\"$3\"&gt;</code> en la alŝuta SVGa dosiero.",
        "uploaded-setting-href-svg": "Uzi la markon je \"set\" por aldoni atributon je \"href\" al ujero estas blokita.",
+       "uploaded-wrong-setting-svg": "Uzi la ''ensigni'' (''<span lang=\"en\">set</span>'') markon por aldoni foran/datenan/skriptan celon al ajn atributon estas blokita. Trovis <code>&lt;set to=\"$1\"&gt;</code> en la alŝutita SVGan dosieron.",
+       "uploaded-setting-handler-svg": "SVGa dosiero kiu ensignas la traktilan atributon (''<span lang=\"en\">handler</span>'') kun fora/datena/skripta estas blokita. Trovis <code>$1=\"$2\"</code> en la alsûtita SVGa dosiero.",
+       "uploaded-remote-url-svg": "SVG-a dosiero kiu asignas ajnan stilan atributon kun fora URL estas blokita. Ni trovis <code>$1=\"$2\"</code> en la alsûtita SVG-a dosiero.",
+       "uploaded-image-filter-svg": "Trovis bildan filtrilon kun URL:  <code>&lt;$1 $2=\"$3\"&gt;</code> en la alŝutita SVGa dosiero.",
        "uploadscriptednamespace": "Ĉi tiu SVG-dosiero enhavas nevalidan nomspacon \"$1\"",
        "uploadinvalidxml": "Ne eblas interpreti la XML-sintakson en la alŝutita dosiero",
        "uploadvirus": "Viruso troviĝas en la dosiero! Detaloj: $1",
        "upload-options": "Alŝutaj agordoj",
        "watchthisupload": "Atenti ĉi tiun dosieron",
        "filewasdeleted": "Dosiero de ĉi tiu nomo estis antaŭe alŝutita kaj poste forigita. Bonvolu kontroli en la $1 antaŭ alŝuti ĝin denove.",
+       "filename-thumb-name": "Ĉi tiu aspektas kiel titolo de etigita versio de plena bildo. Bonvolu ne alŝuti etigitajn versiojn de bildoj al la sama vikio. Alimaniere, bonvolu modifi la dosiernomon al pli signifa, kiu ne havas la eta-versian prefikson.",
        "filename-bad-prefix": "La nomo de la dosiero kiun vi alŝutas komencas kun '''\"$1\"''', kiu estas nepriskriba nomo ofte aŭtomate donata de ciferecaj fotiloj. Bonvolu elekti pli priskriban nomon por via bildo.",
        "upload-proto-error": "Malvalida protokolo",
        "upload-proto-error-text": "Fora alŝuto devas URL-on komence de <code>http://</code> aŭ <code>ftp://</code>.",
        "upload-too-many-redirects": "La URL-o enhavis tro multajn alidirektilojn",
        "upload-http-error": "HTTP-eraro okazis: $1",
        "upload-copy-upload-invalid-domain": "Kopio-alŝutoj ne disponiĝas el ĉi tiu domajno.",
+       "upload-foreign-cant-upload": "Tiu vikio ne estas agorita por alŝuti alŝutitan dosieron al la petita fora dosierdeponejo.",
        "upload-dialog-title": "Alŝuti dosieron",
        "upload-dialog-button-cancel": "Nuligi",
        "upload-dialog-button-done": "Farite",
        "upload-dialog-button-upload": "Alŝuti",
        "upload-form-label-infoform-title": "Detaloj",
        "upload-form-label-infoform-name": "Nomo",
+       "upload-form-label-infoform-name-tooltip": "Unika priskriba titolo por tiu ĉi dosiero, kiu servos kiel dosiernomo. Eblas uzi normalan lingvaĵon kun interspacoj. Ne aldonu la dosieran aldonaĵon.",
        "upload-form-label-infoform-description": "Priskribo",
+       "upload-form-label-infoform-description-tooltip": "Bonvolu koncize priskribi ĉion notindan pri la verko.\nPri foto, menciu la esencajn aĵojn bildigitajn, la okazon, aŭ la lokon.",
        "upload-form-label-usage-title": "Uzo",
        "upload-form-label-usage-filename": "Dosiernomo",
-       "foreign-structured-upload-form-label-own-work": "Tio estas mia propra laboro",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorioj",
-       "foreign-structured-upload-form-label-infoform-date": "Dato",
+       "upload-form-label-own-work": "Tio estas mia propra laboro",
+       "upload-form-label-infoform-categories": "Kategorioj",
+       "upload-form-label-infoform-date": "Dato",
+       "upload-form-label-own-work-message-generic-local": "Mi konfirmas ke mi alŝutas tiun dosieron respektante pri la uzadokondiĉoj kaj permesopolitikoj de  {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Se vi ne eblas alŝuti tiun dosieron respektante de politikoj de {{SITENAME}}, bonvolu fermi tiun dialogon kaj provi denove kun alia metodo.",
+       "upload-form-label-not-own-work-local-generic-local": "Vi eble ŝatu egale pravi [[Special:Upload|la defaŭltan paĝon]].",
+       "upload-form-label-own-work-message-generic-foreign": "Mi komprenas ke mi alŝutas tiun dosieron al komunigita deponejo. Mi konfirmas ke mi faras tiun respektante de la uzadtermoj kaj de la permisilopolitikoj tie.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se vi ne eblas alŝuti tiun dosieron respektante de politikoj de komuna deponejo, bonvolu fermi tiun dialogon kaj provi denove kun alia metodo.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Vi ankaŭ eble dezirus provi per uzi [[Special:Upload|la alŝutan paĝon sur {{SITENAME}}]], se ĉi tiu dosiero povas esti alŝutita tie respektante de iliaj politikoj.",
        "backend-fail-stream": "Ne povis fluigi dosieron $1.",
        "backend-fail-backup": "Ne povis enarkivigi dosieron $1.",
        "backend-fail-notexists": "La dosiero $1 ne ekzistas.",
        "backend-fail-read": "Ne povas legi dosieron \"$1\".",
        "backend-fail-create": "Ne povas skribi dosieron $1.",
        "backend-fail-maxsize": "Ne povis skribi la dosieron \"$1,\" ĉar ĝi estas pli granda ol {{PLURAL:$2|bitoko|$2 bitokoj}}.",
-       "backend-fail-readonly": "La interna konservujo \"$1\" nune estas nurlega. La indikata kialo estas: \"''$2''\"",
+       "backend-fail-readonly": "La interna konservujo \"$1\" nune estas nurlega. La indikata kialo estas: <em>$2</em>",
        "backend-fail-synced": "La dosiero \"$1\" estas en nekohera stato kun la internaj konservujoj",
        "backend-fail-connect": "Ne eblis konekti la internan konservujon \"$1\".",
        "backend-fail-internal": "Nekonata eraro okazis en interna konservujo \"$1\".",
        "uploadstash-summary": "Tiu ĉi paĝo alirebligas la dosierojn alŝutitajn (aŭ alŝutatajn), kiuj ne jam estas publikigitaj per la vikio. Tiujn ĉi dosierojn ne povas vidi  iu ajn, krom la alŝutinto mem.",
        "uploadstash-clear": "Malplenigi la dosierkonversejon.",
        "uploadstash-nofiles": "Mankas dosieroj en la konservejo.",
-       "uploadstash-badtoken": "Malsukcesis tiu ago, eble pro tio ke viaj ensalutiloj senvalidiĝis. Reprovu.",
-       "uploadstash-errclear": "Sensukcesis la forigo de la dosieroj.",
+       "uploadstash-badtoken": "Malsukcesis tiu ago, eble pro tio ke viaj ensalutiloj eksvalidiĝis. Bonvolu reprovi.",
+       "uploadstash-errclear": "Malsukcesis la forigo de la dosieroj.",
        "uploadstash-refresh": "Aktualigi la dosierliston.",
-       "uploadstash-thumbnail": "Vidi bildetigon",
+       "uploadstash-thumbnail": "Vidi bildeton",
        "invalid-chunk-offset": "Malvalida deŝovo de dosierpeco",
        "img-auth-accessdenied": "Atingo malpermisita",
        "img-auth-nopathinfo": "Mankas PATH_INFO (informo pri dosiervojo).\nVia servilo ne estas konfigurita por sendi ĉi tiun informon.\nEble ĝi estas CGI-bazita kaj ne subtenas img_auth.\nVidu https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization (angle).",
        "listfiles-latestversion-no": "Ne",
        "file-anchor-link": "Dosiero",
        "filehist": "Dosiera historio",
-       "filehist-help": "Klaku daton/tempon por vidi la dosieron kiel ĝin ŝajnitan tiame.",
+       "filehist-help": "Klaku daton/tempon por vidi la dosieron kia ĝi aspektis tiam.",
        "filehist-deleteall": "forigi ĉiujn",
        "filehist-deleteone": "forigi",
        "filehist-revert": "restarigi",
        "apihelp": "Helpo pri API",
        "apihelp-no-such-module": "Modulo \"$1\" ne estis trovita.",
        "apisandbox": "API testejo",
+       "apisandbox-jsonly": "JavaScript estas postulita por uzi la API provejon.",
        "apisandbox-api-disabled": "API estas malŝalta en ĉi tiu retejo.",
-       "apisandbox-intro": "Uzu tiun ĉi paĝon por eksperimenti kun '''MediaWiki API'''.\nVidu [//www.mediawiki.org/wiki/API:Main_page la API-dokumentadon] por pli da detaloj pri la uzo de API. Ekz-e: [//www.mediawiki.org/wiki/API#A_simple_example atingi la enhavon de la Ĉefpaĝo]. Elektu agon por vidi pliajn ekzemplojn.\n\nNotu ke, kvankam ĉi tiu estas provejo, agoj kiun vi faros en ĉi tiu paĝo povas modifi la vikion.",
+       "apisandbox-intro": "Uzu tiun ĉi paĝon por eksperimenti kun <strong>Mediavikia retserva API</strong>.\nVidu [[mw:API:Main page|la API-dokumentadon]] por pli da detaloj pri la uzo de API. Ekz-e: [//www.mediawiki.org/wiki/API#A_simple_example atingi la enhavon de la Ĉefpaĝo]. Elektu agon por vidi pliajn ekzemplojn.\n\nNotu ke, kvankam ĉi tiu estas provejo, agoj kiun vi faros en ĉi tiu paĝo povas modifi la vikion.",
+       "apisandbox-fullscreen": "Etendi panelon",
+       "apisandbox-fullscreen-tooltip": "Etendi la proveja panelo por plenigi la retumilan fenestron.",
+       "apisandbox-unfullscreen": "Montri paĝon",
+       "apisandbox-unfullscreen-tooltip": "Maletendi la provejan panelon, tiel Mediavikiaj navigadaj ligoj estas haveblaj.",
        "apisandbox-submit": "Fari mendon",
        "apisandbox-reset": "Nuligi",
+       "apisandbox-retry": "Reprovi",
+       "apisandbox-loading": "Ŝutas informon de la APIa modulo je \"$1\"…",
+       "apisandbox-load-error": "Eraro okazis dum ŝutis informon por APIa modulo je \"$1\": $2",
+       "apisandbox-no-parameters": "Ĉi tiu APIa modulo ne havas parametron.",
+       "apisandbox-helpurls": "Ligiloj pri helpo",
        "apisandbox-examples": "Ekzemploj",
        "apisandbox-dynamic-parameters": "Aldonaj parametroj",
        "apisandbox-dynamic-parameters-add-label": "Aldoni parametron:",
        "apisandbox-dynamic-parameters-add-placeholder": "Nomo de parametro",
        "apisandbox-dynamic-error-exists": "Parametro nomata \"$1\" jam ekzistas.",
+       "apisandbox-deprecated-parameters": "Evitindajn parametrojn",
+       "apisandbox-fetch-token": "Aŭtoplenigu ĵetonon",
+       "apisandbox-submit-invalid-fields-title": "Iuj kampoj estas malvalidaj.",
+       "apisandbox-submit-invalid-fields-message": "Bonvolu ĝustigi la markitajn kampojn kaj provi denove.",
        "apisandbox-results": "Rezultoj",
+       "apisandbox-sending-request": "Sendanta aplikprograminterfacan peton…",
+       "apisandbox-loading-results": "Ricevas APIajn rezultojn…",
+       "apisandbox-results-error": "Eraro okazis dum ŝutis la APIan petan respondon: $1.",
        "apisandbox-request-url-label": "Mendi URL-on.",
-       "apisandbox-request-time": "Tempo de peto: $1",
+       "apisandbox-request-time": "Tempo de peto:{{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "Korekti ĵetonon kaj resendi",
+       "apisandbox-results-fixtoken-fail": "Malsukcese venigis ĵetonon je \"$1\".",
+       "apisandbox-alert-page": "Kampoj de ĉi tiu paĝo ne estas validaj.",
+       "apisandbox-alert-field": "La valoro de ĉi tiu kampo ne estas valida.",
        "booksources": "Librofontoj",
        "booksources-search-legend": "Serĉi librofontojn",
        "booksources-search": "Serĉi",
        "booksources-text": "Jen ligilaro al aliaj TTT-ejoj, kiuj vendas librojn,\nkaj/aŭ informumos pri la libro ligita.\nLa {{SITENAME}} ne estas komerce ligita al tiuj vendejoj, kaj la listo ne estu\nkomprenata kiel rekomendo aŭ reklamo.",
        "booksources-invalid-isbn": "La donata ISBN verŝajne estas nevalida; kontrolu pri erara kopiado el la originala fonto.",
        "specialloguserlabel": "Faranto:",
-       "speciallogtitlelabel": "Celo (titolo aŭ uzanto):",
+       "speciallogtitlelabel": "Celo (titolo aŭ  {{ns:user}}:salutnomo por uzanto):",
        "log": "Protokoloj",
        "logeventslist-submit": "Montri",
        "all-logs-page": "Ĉiuj publikaj protokoloj",
        "activeusers-hidebots": "kaŝi robotojn",
        "activeusers-hidesysops": "Kaŝi administrantojn",
        "activeusers-noresult": "Neniuj uzantoj trovitaj.",
+       "activeusers-submit": "Montri la agemajn uzantojn",
        "listgrouprights": "Gruprajtoj de uzantoj",
        "listgrouprights-summary": "Jen listo de uzanto-grupoj difinitaj en ĉi tiu vikio, kun ties asociaj atingrajtoj.\nEstas [[{{MediaWiki:Listgrouprights-helppage}}|aldona informo]] pri individuaj rajtoj.",
        "listgrouprights-key": "* <span class=\"listgrouprights-granted\">Donita rajto</span>\n* <span class=\"listgrouprights-revoked\">Forigita rajto</span>",
        "listgrouprights-namespaceprotection-header": "Nomspacaj restriktoj",
        "listgrouprights-namespaceprotection-namespace": "Nomspaco",
        "listgrouprights-namespaceprotection-restrictedto": "Rajtoj, kiuj permesas al uzanto redakti",
-       "listgrants": "Rajdonaro",
+       "listgrants": "Rajtoj donitaj",
+       "listgrants-grant": "Aljuĝoj",
+       "listgrants-rights": "Rajtoj",
        "trackingcategories": "Kategorioj por kontrolado",
        "trackingcategories-summary": "Ĉi tiu paĝo listigas kategoriojn por kontrolado, aŭtomate farita de la Mediavikia programaro. Ties nomoj estas ŝanĝebla, ŝanĝante la paran sistemmesaĝon en la nomspaco {{ns:8}}.",
        "trackingcategories-msg": "Kategorio pri kontrolado",
        "emailccsubject": "Kopio de via mesaĝo al $1: $2",
        "emailsent": "Retmesaĝo sendita",
        "emailsenttext": "Via retmesaĝo estas sendita.",
-       "emailuserfooter": "Ĉi tiun retpoŝton sendis $1 al $2 per la funkcio \"{{int:emailuser}}\" ĉe {{SITENAME}}.",
+       "emailuserfooter": "Ĉi tiun retpoŝton estis sendita far $1 al $2 per la funkcio \"{{int:emailuser}}\" el {{SITENAME}}.",
        "usermessage-summary": "Lasanta sisteman mesaĝon.",
        "usermessage-editor": "Mesaĝanto de sistemo",
        "watchlist": "Mia atentaro",
        "watchlistanontext": "Bonvolu ensaluti por vidi aŭ redakti vian atentaron.",
        "watchnologin": "Ne ensalutinta",
        "addwatch": "Aldoniĝi al atentaro",
-       "addedwatchtext": "La paĝo \"[[:$1]]\" aldoniĝis al via [[Special:Watchlist|atentaro]]. Estontaj ŝanĝoj de tiu paĝo kaj de ĝia rilata diskutpaĝo aperos tie.",
+       "addedwatchtext": "\"[[:$1]]\" kaj ĝia diskutpaĝo estis aldonitaj al via [[Special:Watchlist|atentaro]].",
        "addedwatchtext-short": "La paĝo \"$1\" estis aldonita al via atento-listo.",
        "removewatch": "Forigi el atentaro",
-       "removedwatchtext": "La paĝo \"[[:$1]]\" estas forigita el via [[Special:Watchlist|atentaro]].",
+       "removedwatchtext": "\"[[:$1]]\" kaj ĝia diskutpaĝo estis forigita el via [[Special:Watchlist|atentaro]].",
        "removedwatchtext-short": "La paĝo \"$1\" estis forigita el via atento-listo.",
        "watch": "Atenti",
        "watchthispage": "Priatenti paĝon",
        "wlshowlast": "Montri el lastaj $1 horoj $2 tagoj",
        "watchlist-hide": "Kaŝi",
        "watchlist-submit": "Montri",
+       "wlshowtime": "Vidigenda tempodaŭro:",
        "wlshowhideminor": "Etaj redaktoj",
        "wlshowhidebots": "robotoj",
        "wlshowhideliu": "registritaj uzantoj",
        "wlshowhideanons": "anonimaj uzantoj",
        "wlshowhidepatr": "patrolitaj redaktoj",
        "wlshowhidemine": "miaj redaktoj",
+       "wlshowhidecategorization": "paĝa enkategoriigo.",
        "watchlist-options": "Opcioj por atentaro",
        "watching": "Aldonata al la atentaro...",
        "unwatching": "Malatentante...",
        "deletepage": "Forigi paĝon",
        "confirm": "Konfirmi",
        "excontent": "enhavis: '$1'",
-       "excontentauthor": "la enteno estis : '$1' (kaj la sola kontribuinto estis '$2')",
+       "excontentauthor": "la enteno estis: '$1', kaj la sola kontribuinto estis \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|talk]])",
        "exbeforeblank": "antaŭ malplenigo enhavis: '$1'",
        "delete-confirm": "Forigi \"$1\"",
        "delete-legend": "Forigi",
        "delete-toobig": "Ĉi tiu paĝo havas grandan redakto-historion, pli ol $1 {{PLURAL:$1|version|versiojn}}. Forigo de ĉi tiaj paĝoj estis limigitaj por preventi akcidentan disrompigon de {{SITENAME}}.",
        "delete-warning-toobig": "Ĉi tiu paĝo havas grandan redakto-historion, pli ol $1 {{PLURAL:$1|version|versiojn}}. Forigo de ĝi povas disrompigi operacion de {{SITENAME}}; forigu singarde.",
        "deleteprotected": "Vi ne povas forigi ĉi tiun paĝon ĉar ĝi estis protektita.",
-       "deleting-backlinks-warning": "'''Atentigo:'''\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|Aliaj paĝoj]] ligas al aŭ transkludas tiun ĉi forigotan paĝon.",
+       "deleting-backlinks-warning": "<strong>Atentigo:</strong>\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|Aliaj paĝoj]] ligas al aŭ transkludas tiun ĉi forigotan paĝon.",
        "rollback": "Restarigi antaŭan redakton",
-       "rollbacklink": "malvalidi",
+       "rollbacklink": "malfari",
        "rollbacklinkcount": "nuligi $1 {{PLURAL:$1|redakton|redaktojn}}",
        "rollbacklinkcount-morethan": "nuligi pli ol $1 {{PLURAL:$1|redakton|redaktojn}}",
        "rollbackfailed": "Malfaro malsukcesis",
        "rollback-success": "Restaris redaktojn de $1; ŝanĝis al lasta versio de $2.",
        "sessionfailure-title": "Seanco malsukcesis",
        "sessionfailure": "Ŝajnas, ke estas problemo kun via ensalutado;\nĈi ago estis nuligita por malhelpi fiensalutadon.\nBonvolu alklaki la reirbutonon kaj reŝargi la paĝon el kiu vi venas, kaj provu denove.",
+       "changecontentmodel": "Ŝanĝi la enhavomodelon de paĝo",
+       "changecontentmodel-legend": "Ŝanĝi la enhavomodelon",
        "changecontentmodel-title-label": "Titolo de paĝo",
+       "changecontentmodel-model-label": "Nova enhavomodelo",
        "changecontentmodel-reason-label": "Kialo:",
        "changecontentmodel-submit": "Ŝanĝi",
+       "changecontentmodel-success-title": "Enhavomodelo estis ŝanĝigita",
+       "changecontentmodel-success-text": "La enhavotipo de [[:$1]] estis ŝanĝigita.",
+       "changecontentmodel-cannot-convert": "La enhavo en [[:$1]] ne transtipeblas al $2.",
+       "changecontentmodel-nodirectediting": "La enhavomodelo $1 ne permesas la rektan redaktadon",
+       "changecontentmodel-emptymodels-title": "Neniu disponebla enhavomodelo",
+       "changecontentmodel-emptymodels-text": "La enhavo en [[:$1]] ne transtipeblas al iu ajn tipo.",
+       "log-name-contentmodel": "Ŝanĝprotokolo de enhavomodelo",
+       "log-description-contentmodel": "Eventoj rilataj kun la enhavomodeloj de paĝo",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|kreis}} la paĝo $3 per uzado de ne-defaŭlta enhavomodelo \"$5\"",
+       "logentry-contentmodel-change": "$1 {{GENDER:$2|ŝanĝis}} la enhavomodelon de la paĝo $3 el \"$4\" al \"$5\"",
        "logentry-contentmodel-change-revertlink": "restarigi",
        "logentry-contentmodel-change-revert": "restarigi",
        "protectlogpage": "Protokolo pri protektoj",
        "protect-locked-blocked": "Vi ne povas ŝanĝi prokekto-nivelojn dum forbarita. Jen la nunaj ecoj de la paĝo '''$1''':",
        "protect-locked-dblock": "Ne povas ŝanĝi nivelojn de protekto pro aktiva datumbaza ŝlosado.\nJen la nunaj agordoj de la paĝo '''$1''':",
        "protect-locked-access": "Via konto ne havas rajton ŝanĝi protekto-nivelojn.\nJen la aktualaj valoroj por la paĝo '''$1''':",
-       "protect-cascadeon": "Ĉi paĝo estas nun protektita kontraŭ redaktado ĉar ĝi estas inkluzivita en {{PLURAL:$1|jena paĝo, kiu mem estas protektita|jenaj paĝoj, kiuj mem estas protektitaj}} per kaskada protekto.\nŜanĝoj de ĝia protektonivelo ne influos la kaskadan protekton.",
+       "protect-cascadeon": "Ĉi paĝo estas nun protektita kontraŭ redaktado ĉar ĝi estas transenhavigita en {{PLURAL:$1|jena paĝo, kiu mem estas protektita|jenaj paĝoj, kiuj mem estas protektitaj}} per kaskada protekto.\nŜanĝoj de ĝia protektonivelo ne influos la kaskadan protekton.",
        "protect-default": "Permesigi ĉiujn uzantojn",
        "protect-fallback": "Permesi nur uzantojn kun la rajto  \"$1\"",
        "protect-level-autoconfirmed": "Permesi nur aŭtomate konfirmitajn uzantojn",
        "undeletepagetext": "La {{PLURAL:$1|jena paĝo estis forigita|jenaj paĝoj estis forigitaj}}, sed ankoraŭ restas {{PLURAL:$1|arkivita|arkivitaj}} kaj {{PLURAL:$1|restarigebla|restarigeblaj}}.\nLa arkivo povas esti malplenigita periode.",
        "undelete-fieldset-title": "Malforigi versiojn",
        "undeleteextrahelp": "Por restarigi la tutan kronologion de la paĝo, lasu ĉiujn markobutonoj malŝaltitaj kaj klaku la butonon '''''{{int:undeletebtn}}'''''.\nPor restarigi selektitajn versiojn de la paĝo, marku la butonojn konformajn al la dezirataj versioj, kaj klaku la butonon '''''{{int:undeletebtn}}'''''.",
-       "undeleterevisions": "$1 {{PLURAL:$1|versio arkivita|versioj arkivitaj}}",
+       "undeleterevisions": "$1 {{PLURAL:$1|versio forigita|versioj forigitaj}}",
        "undeletehistory": "Se vi restarigos la paĝon, ĉiuj versioj estos restarigitaj en la historio.\nSe nova paĝo kun la sama nomo estis kreita post la forigo, la restarigitaj versioj aperos antaŭe en la antaŭa historio.",
        "undeleterevdel": "Restarigo ne estos farita se ĝi rezultos en la supera paĝa aŭ dosiera versio estonte parte forigita. Tiuzake, vi malmarku aŭ malkaŝu la plej novajn forigitajn versiojn.",
        "undeletehistorynoadmin": "Ĉi tiu artikolo estis forigita. La kaŭzo por la forigo estas montrata en la malsupra resumo, kune kun detaloj pri la uzantoj, kiuj redaktis ĉi tiun paĝon antaŭ la forigo. La aktuala teksto de ĉi tiuj forigitaj versioj estas atingebla nur de administrantoj.",
        "whatlinkshere-prev": "{{PLURAL:$1|antaŭa|antaŭaj $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|posta|postaj $1}}",
        "whatlinkshere-links": "← ligiloj",
-       "whatlinkshere-hideredirs": "$1 alidirektilojn",
-       "whatlinkshere-hidetrans": "$1 transinkluzivaĵojn",
-       "whatlinkshere-hidelinks": "$1 ligilojn",
-       "whatlinkshere-hideimages": "$1 ligiloj al bildo",
+       "whatlinkshere-hideredirs": "Kaŝi alidirektilojn",
+       "whatlinkshere-hidetrans": "Kaŝi transinkluzivaĵojn",
+       "whatlinkshere-hidelinks": "Kaŝi ligilojn",
+       "whatlinkshere-hideimages": "Malvidigi ligojn al dosiero",
        "whatlinkshere-filters": "Filtriloj",
        "whatlinkshere-submit": "Ek",
        "autoblockid": "Aŭtomata forbaro #$1",
        "ipb-unblock": "Malforbari salutnomon aŭ IP-adreson",
        "ipb-blocklist": "Vidi ekzistantajn forbarojn",
        "ipb-blocklist-contribs": "Kontribuoj de {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "$1 restas",
        "unblockip": "Malforbari IP-adreson/nomon",
        "unblockiptext": "Per la jena formulo vi povas repovigi al iu\nforbarita IP-adreso/nomo la povon enskribi en la vikio.",
        "ipusubmit": "Forigi ĉi tiun forbaron",
        "block-log-flags-hiddenname": "salutnomo kaŝita",
        "range_block_disabled": "La ebleco de administranto krei forbaritajn intervalojn da IP-adresoj estas malebligita.",
        "ipb_expiry_invalid": "Nevalida blokdaŭro.",
+       "ipb_expiry_old": "Limdato antaŭas la nuntempon.",
        "ipb_expiry_temp": "Kaŝitaj salutnomaj blokoj estu daŭraj.",
        "ipb_hide_invalid": "Ne povas subpremi ĉi tiun konton; ĝi havas {{PLURAL:$1|unu redakton|$1 redaktojn}}.",
        "ipb_already_blocked": "\"$1\" estas jam forbarita",
        "lockdbsuccesstext": "La datumaro de {{SITENAME}} estas ŝlosita.\n<br />Ne forgesu malŝlosi ĝin post kiam vi finos la riparadon.",
        "unlockdbsuccesstext": "La datumaro de {{SITENAME}} estas malŝlosita.",
        "lockfilenotwritable": "La datumbaza dosiero pri ŝlosado ne estas skribebla. Por ŝlosi aŭ malŝlosi la datumbazon, ĉi devas esti skribebla de la TTT-servilo.",
+       "databaselocked": "Datenbazo jam estas ŝlosita.",
        "databasenotlocked": "La datumbazo ne estas ŝlosita.",
        "lockedbyandtime": "(de {{GENDER:$1|$1}} je $2, $3)",
        "move-page": "Alinomi $1",
        "export-download": "Konservi kiel dosieron",
        "export-templates": "Inkluzivi ŝablonojn",
        "export-pagelinks": "Inkluzivi ligitajn paĝoj al profundo de:",
+       "export-manual": "Aldoni paĝojn per uzanta efiko",
        "allmessages": "Ĉiuj mesaĝoj",
        "allmessagesname": "Nomo",
        "allmessagesdefault": "Defaŭlta teksto",
        "tooltip-pt-preferences": "{{GENDER:|Viaj}} preferoj",
        "tooltip-pt-watchlist": "Listo de paĝoj kies ŝanĝojn vi priatentas.",
        "tooltip-pt-mycontris": "Listo de viaj kontribuoj",
+       "tooltip-pt-anoncontribs": "Listo de redaktoj faritaj el ĉi tiu IPa adreso",
        "tooltip-pt-login": "Vi estas invitita ensaluti, tamen ne estas devige.",
        "tooltip-pt-logout": "Elsaluti",
        "tooltip-pt-createaccount": "Ni rekomendas al vi kreon de uzantokonto kaj ensaluto; tamen, tio ne estas deviga",
        "tooltip-n-currentevents": "Trovi fonajn informojn pri nunaj eventoj",
        "tooltip-n-recentchanges": "Listo de la lastaj ŝanĝoj en la vikio.",
        "tooltip-n-randompage": "Iri al hazarda paĝo",
-       "tooltip-n-help": "Serĉopaĝo.",
+       "tooltip-n-help": "La loko por eltrovi",
        "tooltip-t-whatlinkshere": "Listo de ĉiuj vikiaj paĝoj kiuj ligas ĉi tien",
        "tooltip-t-recentchangeslinked": "Lastaj ŝanĝoj en paĝoj kiuj ligas al tiu ĉi paĝo",
        "tooltip-feed-rss": "RSS-fonto por tiu ĉi paĝo",
        "tooltip-feed-atom": "Atom-fonto por ĉi tiu paĝo",
        "tooltip-t-contributions": "Listo de kontribuoj de {{GENDER:$1|ĉi tiu uzanto}}",
-       "tooltip-t-emailuser": "Sendi retmesaĝon al tiu ĉi uzanto",
+       "tooltip-t-emailuser": "Sendi retmesaĝon al {{GENDER:$1|tiu ĉi uzantiĉo|tiu ĉi uzantino|tiu ĉi uzanto}}",
        "tooltip-t-info": "Pli da informo pri ĉi tiu paĝo",
        "tooltip-t-upload": "Alŝuti dosierojn",
        "tooltip-t-specialpages": "Listo de ĉiuj specialaj paĝoj",
        "tooltip-ca-nstab-category": "Vidi la paĝon de la kategorio",
        "tooltip-minoredit": "Marki tiun ŝanĝon kiel etan",
        "tooltip-save": "Konservi viajn ŝanĝojn",
+       "tooltip-publish": "Publikigi viajn ŝanĝojn",
        "tooltip-preview": "Antaŭrigardi viajn ŝanĝojn. Bonvolu uzi tion antaŭ ol konservi ilin!",
        "tooltip-diff": "Montri la ŝanĝojn kiujn vi faris de la teksto.",
        "tooltip-compareselectedversions": "Rigardi la malsamojn inter ambaŭ selektitaj versioj de ĉi tiu paĝo.",
        "tooltip-watchlistedit-raw-submit": "Ĝisdatigi atentaron",
        "tooltip-recreate": "Rekrei la paĝon malgraŭ ĝi estis forigita",
        "tooltip-upload": "Ekalŝuti",
-       "tooltip-rollback": "\"Malvalidi\" malfaras redakto(j)n al ĉi tiu paĝo de la lasta kontribuanto per unu klako.",
+       "tooltip-rollback": "\"Malfari\" per unu klako nuligas redakto(j)n de la lasta kontribuanto al ĉi tiu paĝo.",
        "tooltip-undo": "\"Malfari\" malfaris ĉi tiun redakton kaj malfermas la redakto-paĝon en antaŭvida reĝimo. Permesas aldoni kialon en la resumo.",
        "tooltip-preferences-save": "Konservi preferojn",
        "tooltip-summary": "Enigu mallongan resumon",
        "lastmodifiedatby": "Ĉi paĝo estis laste ŝanĝita je $2, $1 de $3.",
        "othercontribs": "Bazita sur la laboro de $1.",
        "others": "aliaj",
-       "siteusers": "{{PLURAL:$2|uzanto|uzantoj}} de {{SITENAME}} $1",
+       "siteusers": "{{GENDER:$2|uzantiĉo|uzantino|uzanto}}{{PLURAL:$2||j}} $1 de {{SITENAME}}",
        "anonusers": "{{SITENAME}}-{{PLURAL:$2|anonimulo|anonimuloj}} $1",
        "creditspage": "Atribuoj de paĝo",
        "nocredits": "Ne estas informo pri atribuoj por ĉi paĝo.",
        "pageinfo-robot-index": "Permesata",
        "pageinfo-robot-noindex": "Malpermesata",
        "pageinfo-watchers": "Nombro de paĝatentantoj",
+       "pageinfo-visiting-watchers": "Nombro de paĝoatendantoj kiuj vizitis la ĵusajn redaktitojn",
        "pageinfo-few-watchers": "Malpli ol $1 {{PLURAL:$1|atentanto|atentantoj}}",
+       "pageinfo-few-visiting-watchers": "Eble estas aŭ eble eble ne estas paĝatendanta uzanto kiu vizitas ĵusajn redaktitojn",
        "pageinfo-redirects-name": "Nombro da alidirektiloj al ĉi tiu paĝo",
        "pageinfo-subpages-name": "Subpaĝoj de ĉi tiu paĝo",
        "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|alidirektilo|alidirektiloj}}; $3 {{PLURAL:$3|ne-alidirektilo|ne-alidirektiloj}})",
        "pageinfo-category-files": "Nombro de dosieroj",
        "markaspatrolleddiff": "Marki kiel patrolitan",
        "markaspatrolledtext": "Marki ĉi tiun paĝon patrolita",
+       "markaspatrolledtext-file": "Marki ĉi tiu versio de dosiero kiel patrolita",
        "markedaspatrolled": "Markita kiel patrolita",
        "markedaspatrolledtext": "La elektita versio [[:$1]] estas markita kiel patrolita.",
        "rcpatroldisabled": "Patrolado de lastaj ŝanĝoj malaktivigita",
        "svg-long-error": "Malvalida SVG-dosiero: $1",
        "show-big-image": "Fonta dosiero",
        "show-big-image-preview": "Grandeco de ĉi antaŭvido: $1.",
+       "show-big-image-preview-differ": "Grando de tiu $3 antaŭprezento de tiu $2a dosiero: $1.",
        "show-big-image-other": "{{PLURAL:$2|Alia distingivo|Aliaj distingivoj}}: $1.",
        "show-big-image-size": "$1 × $2 rastrumeroj",
        "file-info-gif-looped": "ripeta GIF",
        "newimages-legend": "Dosiernomo",
        "newimages-label": "Dosiernomo (aŭ parto de ĝi):",
        "newimages-showbots": "Montri alŝutojn per robotoj",
+       "newimages-hidepatrolled": "Malvidigi la patrolitajn alŝutitojn",
        "noimages": "Nenio videbla.",
        "ilsubmit": "Serĉi",
        "bydate": "laŭ dato",
        "confirmemail_body_set": "Iu, supozeble vi mem, de IP-adreso $1,\nagordis la retpoŝadreson de konto \"$2\" al ĉi tiu adreso je {{SITENAME}}.\n\nPor konfirmi ke ĉi tiu konto vere apertenas al vi kaj refunkciigi la retpoŝtfunkciojn je {{SITENAME}}, bonvolu\nmalfermi la jenon ligon per via foliumilo:\n\n$3\n\nSe ĉi tiu konto *ne* apertenas al vi, bonvolu sekvi jenan ligon por nuligi la konfirmon pir la retpoŝadreso:\n\n$5\n\nĈi tiu konfirma kodo eksvalidiĝos je $4",
        "confirmemail_invalidated": "Konfirmado de retadreso estas nuligita",
        "invalidateemail": "Nuligi konfirmadon de retadreso",
+       "notificationemail_subject_changed": "La retpôstadreso registrita de {{SITENAME}} estis ŝanĝita",
+       "notificationemail_subject_removed": "La retpôstadreso registrita de {{SITENAME}} estis forigita",
+       "notificationemail_body_changed": "Iu, verŝajne vi, el IPa adreso $1, ŝanĝis la retpoŝtan adreson de la konto \"$2\" al \"$3\" por {{SITENAME}}.\n\nSe tio ne estis vi, kontaktu retejan administranton tuj.",
+       "notificationemail_body_removed": "Iu, verŝajne vi, el IPa adreso $1, forigis la retpoŝtan adreson de la konto \"$2\" por  {{SITENAME}}.\n\nSe tio ne estis vi, kontaktu retejan administranton tuj.",
        "scarytranscludedisabled": "[Intervikia transinkluzivado estas malebligita.]",
        "scarytranscludefailed": "[Akiro de ŝablono $1 malsukcesis.]",
        "scarytranscludefailed-httpstatus": "[Malsukcesis akiri la ŝablonon $1 : HTTP  $2 ]",
        "scarytranscludetoolong": "[URL-o estas tro longa]",
        "deletedwhileediting": "'''Averto''': Ĉi tiu paĝo estis forigita post vi ekredaktis!",
-       "confirmrecreate": "Uzanto [[User:$1|$1]] ([[User talk:$1|diskuto]]) forigis ĉi tiun paĝon post vi ekredaktis ĝin kun kialo:\n: ''$2''\nBonvolu konfirmi ke vi ja volas rekrei la paĝon.",
+       "confirmrecreate": "Uzanto [[User:$1|$1]] ([[User talk:$1|diskuto]]) forigis ĉi tiun paĝon post vi ekredaktis ĝin pro kialo:\n: <em>$2</em>\nBonvolu konfirmi ke vi ja volas rekrei la paĝon.",
        "confirmrecreate-noreason": "Uzanto [[User:$1|$1]] ([[User talk:$1|diskuto-paĝo]]) forigis ĉi tiun paĝon post vi ekredaktis ĝin.\nBonvolu konfirmi ke vi ja volas rekrei la paĝon.",
        "recreate": "Rekrei",
        "confirm_purge_button": "Ek!",
        "watchlistedit-raw-done": "Via atentaro estas ĝisdatigita.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 titolo estis aldonita|$1 titoloj estis aldonitaj}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 titolo estis forigita|$1 titoloj estis forigitaj}}:",
-       "watchlistedit-clear-title": "Malplenigita atentaro",
+       "watchlistedit-clear-title": "Malplenigi la atentaron",
        "watchlistedit-clear-legend": "Malplenigi la atentaron",
        "watchlistedit-clear-explain": "Ĉiuj el la titoloj estos forigitaj el via atentaro",
        "watchlistedit-clear-titles": "Titoloj:",
        "hebrew-calendar-m11-gen": "abo",
        "hebrew-calendar-m12-gen": "elulo",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|diskuto]])",
+       "timezone-local": "Loka",
        "duplicate-defaultsort": "'''Averto:''' Defaŭlta ordiga ŝlosilo \"$2\" anstataŭigas pli fruan defaŭltan ordigan ŝlosilon \"$1\".",
        "duplicate-displaytitle": "<strong>Atentigo:</strong> La montrata titolo \"$2\" transpasas la antaŭan titolon \"$1\".",
+       "restricted-displaytitle": "<strong>Averto:</strong> Vidiga titolo \"$1\" estis ignorita pro tio ke ĝi ne estas ekvivalenta al la efektiva titolo de la paĝo.",
        "invalid-indicator-name": "<strong>Eraro:</strong> Atributo de la paĝstata indikilo <code>name</code> maldevas esti malplena.",
        "version": "Versio",
        "version-extensions": "Instalitaj kromprogramoj",
        "version-libraries-license": "Permesilo",
        "version-libraries-description": "Priskribo",
        "version-libraries-authors": "Aŭtoroj",
-       "redirect": "Alidirektilo laŭ dosiero, uzanto, paĝo aŭ identigilo de revizio.",
+       "redirect": "Alidirektilo laŭ dosiera, uzanta, paĝa, revizia aŭ protokola identigilo.",
        "redirect-summary": "Tiu ĉi paĝo alidirektas al dosiero (laŭ ĝia nomo), paĝo (laŭ ĝia revizio-numero aŭ paĝo-identigilo) aŭ al uzantopaĝo (laŭ numera uzanto-identigilo). Uzado: [[{{#Special:Redirect}}/file/Ekzemplo.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], aŭ [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Ek",
        "redirect-lookup": "Traserĉi:",
        "redirect-page": "Paĝa identigo",
        "redirect-revision": "Revizio de la paĝo",
        "redirect-file": "Dosiernomo",
+       "redirect-logid": "Protokola identigilo",
        "redirect-not-exists": "Valoro ne trovita",
        "fileduplicatesearch": "Serĉu duplikatajn dosierojn",
        "fileduplicatesearch-summary": "Serĉi duplikatajn dosierojn bazite de haketvaloro.",
        "tags-deactivate": "malaktivigi",
        "tags-hitcount": "$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}",
        "tags-manage-no-permission": "Vi ne havas la rajton prilabori markilojn.",
+       "tags-manage-blocked": "Vi ne povas administri ŝanĝajn etikedojn dum blokita.",
        "tags-create-heading": "Krei novan markilon",
        "tags-create-explanation": "Nove kreitaj etikedoj estos defaŭlte disponeblaj por uzado de uzantoj kaj robotoj.",
        "tags-create-tag-name": "Nomo de etikedo:",
        "tags-delete-not-allowed": "Ne eblas forigi etikedojn difinitajn de etendaĵoj, krom se la etendaĵo eksplice permesas tion.",
        "tags-delete-not-found": "La etikedo \"$1\" ne ekzistas.",
        "tags-delete-too-many-uses": "La etikedo \"$1\" estas aldonita al pli ol $2 {{PLURAL:$2|revizio|revizioj}}, kio signifas, ke ĝi ne povas esti forigita.",
-       "tags-delete-warnings-after-delete": "LA etikedo \"$1\" estis sukcese forigita, sed estis {{PLURAL:$2|trafita jena atentigo|trafitaj jenaj atentigoj}}:",
+       "tags-delete-warnings-after-delete": "La etikedo \"$1\" estis forigita, sed trafis {{PLURAL:$2|jenan averton|jenajn avertojn}}:",
+       "tags-delete-no-permission": "Vi ne havas permeson por forigi tiujn etikedojn de ŝanĝo.",
        "tags-activate-title": "Aktivigi markilon",
        "tags-activate-question": "Vi estas aktivigonta la markilon \"$1\".",
        "tags-activate-reason": "Kialo:",
        "tags-deactivate-not-allowed": "Ne eblas malaktivigi la etikedon \"$1\".",
        "tags-deactivate-submit": "Malaktivigi",
        "tags-apply-no-permission": "Vi ne havas permeson por aldoni ŝanĝo-etikedojn al viaj ŝanĝoj.",
+       "tags-apply-blocked": "Vi ne povas apliki etikedojn de ŝanĝo kune kun viaj ŝanĝoj dum blokita.",
        "tags-apply-not-allowed-one": "La etikedon \"$1\" ne eblas aldoni permane.",
        "tags-apply-not-allowed-multi": "Ne estas permesite permane aldoni {{PLURAL:$2|jenan etikedon|jenajn etikedojn}}: $1",
        "tags-update-no-permission": "Vi ne havas permeson por aldoni aŭ forigi ŝanĝo-etikedojn de unuopaj revizioj aŭ protokoleroj.",
+       "tags-update-blocked": "Vi ne povas aldoni aŭ forigi etikedojn de ŝanĝo dum blokita.",
        "tags-update-add-not-allowed-one": "Ne estas permesite permane aldoni la etikedon \"$1\".",
        "tags-update-add-not-allowed-multi": "Ne estas permesite permane aldoni {{PLURAL:$2|jenan etikedon|jenajn etikedojn}}: $1",
        "tags-update-remove-not-allowed-one": "Ne estas permesite permane forigi la etikedon \"$1\".",
        "tags-edit-reason": "Kialo:",
        "tags-edit-revision-submit": "Apliki ŝanĝojn al {{PLURAL:$1|tiu ĉi revizio|$1 revizioj}}",
        "tags-edit-logentry-submit": "Apliki ŝanĝojn al {{PLURAL:$1|tiu ĉi protokolero|$1 protokoleroj}}",
-       "tags-edit-success": "La ŝanĝoj estis sukcese aplikitaj.",
+       "tags-edit-success": "Aplikis la ŝanĝojn.",
        "tags-edit-failure": "La ŝanĝojn ne eblis apliki:\n$1",
        "tags-edit-nooldid-title": "Nevalida cela revizio",
        "tags-edit-nooldid-text": "Vi specifis neniun celan revizion por efektivigi la funkcion aŭ la specifita revizio ne ekzistas.",
        "htmlform-cloner-create": "Aldoni plian",
        "htmlform-cloner-delete": "Forigi",
        "htmlform-cloner-required": "Almenaŭ unu valoro estas nepra.",
+       "htmlform-title-badnamespace": "[[:$1]] ne  estas en \"{{ns:$2}}\" nomspaco.",
+       "htmlform-title-not-creatable": "\"$1\" estas nekreebla titolo por paĝo",
+       "htmlform-title-not-exists": "$1 ne ekzistas.",
+       "htmlform-user-not-exists": "<strong>$1</strong> ne ekzistas.",
+       "htmlform-user-not-valid": "<strong>$1</strong> ne estas valida salutnomo.",
        "sqlite-has-fts": "$1 kun tut-teksta subteno",
        "sqlite-no-fts": "$1 sen tut-teksta subteno",
        "logentry-delete-delete": "$1 forigis paĝon $3",
        "pagelang-language": "Lingvo",
        "pagelang-use-default": "Uzi defaŭltan lingvon",
        "pagelang-select-lang": "Elekti la lingvon",
+       "pagelang-submit": "Ek!",
        "right-pagelang": "Ŝanĝi paĝan lingvon",
        "action-pagelang": "ŝanĝi la lingvon de la paĝo",
-       "log-name-pagelang": "Ŝanĝi la lingvan protokolon",
+       "log-name-pagelang": "Protokolo pri lingvajn ŝanĝojn",
        "log-description-pagelang": "Jen protokolo pri ŝanĝoj de paĝaj lingvoj.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|ŝanĝis}} la paĝan lingvon por $3 de $4 al $5.",
        "default-skin-not-found": "Ups! La defaŭlta etoso por via vikio, difinita en <code dir=\"ltr\">$wgDefaultSkin</code> kiel <code>$1</code> ne estas disponebla.\n\nŜajnas, ke via instalaĵo enhavas {{PLURAL:$4|jenan etoson|jenajn etosojn}}. Vidu [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manlibro:Agordado de etosoj] por informoj kiel {{PLURAL:$4|ĝin ŝalti|ilin ŝalti kaj elekti la defaŭltan}}.\n\n$2\n\n; Se vi ĵus instalis MediaWiki:\n: Vi probable instalis de git aŭ rekte de fontokodo per alia metodo. Tio estas antaŭsupozata. MediaWiki 1.24 kaj pli novaj versioj enhavas neniun etoson en la ĉefa deponejo. Provu instali iujn etosojn de [https://www.mediawiki.org/wiki/Category:All_skins etosa dosierujo en mediawiki.org] per jenaj metodoj:\n:* Elŝutu [https://www.mediawiki.org/wiki/Download pakitan instalilon], kiu enhavas kelkajn etosojn kaj etendaĵojn. Vi povas de ĝi kopii kaj alglui la dosierujon <code>skins/</code>.\n:* Elŝutu unuopajn pakitajn etosojn de [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Uzu Git por elŝuti etosojn].\n: Tio maldevus interkolizii kun via git-deponejo se vi estas evoluiganto de MediaWiki.\n\n; Se vi ĵus promociis MediaWiki:\n: MediaWiki 1.24 kaj pli novaj ne plu aŭtomate ŝaltas instalitajn etosojn (vidu [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manlibro:Aŭtomata malkovrado de etosoj]). Vi povas alglui {{PLURAL:$5|jenan linion|jenajn liniojn}} al <code>LocalSettings.php</code> por ŝalti {{PLURAL:$5|la instalitan etoson|ĉiujn instalitajn etosojn}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Se vi ĵus modifis <code>LocalSettings.php</code>:\n: Denove kontrolu nomon de etosoj pro eblaj mistajpoj.",
        "mediastatistics-header-text": "Tekstaj",
        "mediastatistics-header-executable": "Plenumeblaj dosieroj",
        "mediastatistics-header-archive": "Densigitaj formoj",
+       "mediastatistics-header-total": "Ĉiuj dosieroj",
        "json-warn-trailing-comma": "De JSON estis {{PLURAL:$1|forigita fina komo|forigitaj finaj komoj}}",
        "json-error-unknown": "Okazis problemo pri JSON. Eraro: $1",
        "json-error-depth": "Maksimuma profundeco de stako estis superita",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Simboloj",
        "special-characters-group-greek": "Greka",
+       "special-characters-group-greekextended": "Greka etendita",
        "special-characters-group-cyrillic": "Cirila",
        "special-characters-group-arabic": "Araba",
        "special-characters-group-arabicextended": "araba etendite",
        "special-characters-title-endash": "mallonga streketo",
        "special-characters-title-emdash": "longa streketo",
        "special-characters-title-minus": "minus-signo",
+       "mw-widgets-dateinput-no-date": "Neniu dato elektita",
        "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
        "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
+       "mw-widgets-titleinput-description-new-page": "paĝo ankoraŭ ne ekzistas",
+       "mw-widgets-titleinput-description-redirect": "alidirekti al $1",
        "api-error-blacklisted": "Bonvolu elekti alian, priskriban titolon.",
+       "sessionprovider-generic": "$1 seancoj",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "kuketaj seancoj",
        "randomrootpage": "Hazarda radika paĝo",
        "log-action-filter-all": "Ĉia",
+       "log-action-filter-block-block": "Forbari",
        "log-action-filter-protect-unprotect": "Malprotektado",
        "log-action-filter-upload-upload": "Novalŝuta",
        "log-action-filter-upload-overwrite": "Realŝuta"
index bf7a92b..649a83a 100644 (file)
                        "Transonlohk",
                        "Eloy",
                        "Lemondoge",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Indiralena",
+                       "Rubentl134",
+                       "Codynguyen1116"
                ]
        },
        "tog-underline": "Subrayar los enlaces:",
        "tog-watchdefault": "Añadir las páginas y archivos que edite a mi lista de seguimiento",
        "tog-watchmoves": "Añadir las páginas y archivos que mueva a mi lista de seguimiento",
        "tog-watchdeletion": "Añadir las páginas y archivos que borre a mi lista de seguimiento",
-       "tog-watchuploads": "Agregar nuevos archivos puedo subir a mi lista de favoritos",
+       "tog-watchuploads": "Agregar los archivos nuevos que suba a mi lista de seguimiento",
        "tog-watchrollback": "Añadir las páginas donde haya realizado una reversión a mi lista de seguimiento",
        "tog-minordefault": "Marcar todas las ediciones como menores de manera predeterminada",
        "tog-previewontop": "Mostrar previsualización antes del cuadro de edición",
        "tog-ccmeonemails": "Recibir copias de los correos electrónicos que envíe a otros usuarios",
        "tog-diffonly": "No mostrar el contenido de la página debajo de la lista de diferencias",
        "tog-showhiddencats": "Mostrar categorías ocultas",
-       "tog-norollbackdiff": "Omitir la lista de diferencias después de revertir",
+       "tog-norollbackdiff": "No mostrar la lista de diferencias después de revertir",
        "tog-useeditwarning": "Avisarme cuando abandone una página en edición con cambios sin guardar",
        "tog-prefershttps": "Utilizar siempre conexiones seguras en mis sesiones",
        "underline-always": "Siempre",
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por qué estás creando otra cuenta",
+       "createacct-reason-help": "Mensaje que se muestra en el registro de creación de cuentas",
        "createacct-submit": "Crea tu cuenta",
        "createacct-another-submit": "Crear cuenta",
        "createacct-benefit-heading": "Personas como tú son las que construyen {{SITENAME}}.",
        "nocookiesnew": "Se ha creado la cuenta de usuario, pero aún no has iniciado sesión.\n{{SITENAME}} usa <em>cookies</em> para identificar a los usuarios registrados.\nTu navegador tiene desactivadas las cookies.\nPor favor, actívalas e inicia sesión con tu nuevo nombre de usuario y contraseña.",
        "nocookieslogin": "{{SITENAME}} utiliza <em>cookies</em> para la autenticación de usuarios. Las <em>cookies</em> están desactivadas en tu navegador. Por favor, actívalas e inténtalo de nuevo.",
        "nocookiesfornew": "No se pudo crear la cuenta de usuario, porque no pudimos confirmar su origen.\nAsegúrate de que tienes las cookies activadas, luego recarga esta página e inténtalo de nuevo.",
+       "createacct-loginerror": "La cuenta se ha creado correctamente, pero no se pudo ingresar automáticamente. Procede al [[Special:UserLogin|acceso manual]].",
        "noname": "No se ha especificado un nombre de usuario válido.",
        "loginsuccesstitle": "Has accedido",
        "loginsuccess": "<strong>Has accedido a {{SITENAME}} como «$1».</strong>",
-       "nosuchuser": "No existe ningún usuario llamado «$1».\nLos nombres de usuario son sensibles a las mayúsculas.\nRevisa tu ortografía, o [[Special:UserLogin/signup|crea una cuenta nueva]].",
+       "nosuchuser": "No existe ningún usuario llamado «$1».\nLos nombres de usuario son sensibles a las mayúsculas.\nRevisa tu ortografía, o [[Special:CreateAccount|crea una cuenta nueva]].",
        "nosuchusershort": "No existe ningún usuario llamado «$1». Comprueba que lo has escrito correctamente.",
        "nouserspecified": "Debes especificar un nombre de usuario.",
        "login-userblocked": "No puedes iniciar sesión porque tu cuenta está bloqueada.",
        "accmailtext": "Se ha enviado a $2 una contraseña generada aleatoriamente para [[User talk:$1|$1]]. Una vez iniciada la sesión, se puede cambiar en la página [[Special:ChangePassword|destinada para ello]].",
        "newarticle": "(Nuevo)",
        "newarticletext": "Has seguido un enlace a una página que aún no existe.\nPara crear esta página, escribe en el cuadro que aparece a continuación. Para más información, consulta la [$1 página de ayuda].\nSi llegaste aquí por error, vuelve a la página anterior.",
-       "anontalkpagetext": "---- ''Esta es la página de discusión de un usuario anónimo que aún no ha creado una cuenta, o no la usa. Por lo tanto, tenemos que usar su dirección IP para identificarlo. Puede que varios usuarios compartan una misma dirección IP. Si eres un usuario anónimo y crees que se han dirigido a ti con comentarios improcedentes, por favor [[Special:UserLogin/signup|crea una cuenta]] o, si ya la tienes, [[Special:UserLogin|identifícate]] para evitar confusiones futuras con otros usuarios anónimos.''",
+       "anontalkpagetext": "---- ''Esta es la página de discusión de un usuario anónimo que aún no ha creado una cuenta, o no la usa. Por lo tanto, tenemos que usar su dirección IP para identificarlo. Puede que varios usuarios compartan una misma dirección IP. Si eres un usuario anónimo y crees que se han dirigido a ti con comentarios improcedentes, por favor [[Special:CreateAccount|crea una cuenta]] o, si ya la tienes, [[Special:UserLogin|identifícate]] para evitar confusiones futuras con otros usuarios anónimos.''",
        "noarticletext": "En este momento no hay texto en esta página.\nPuedes [[Special:Search/{{PAGENAME}}|buscar el título de esta página]] en otras páginas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros relacionados],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear esta página]</span>.",
        "noarticletext-nopermission": "Actualmente no hay texto en esta página.\nPuedes [[Special:Search/{{PAGENAME}}|buscar este título de página]] en otras páginas, o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros relacionados]</span>, pero no tienes permiso para crear esta página.",
        "missing-revision": "La revisión n.º $1 de la página «{{FULLPAGENAME}}» no existe.\n\nEsto suele ocurrir cuando se sigue un enlace de historial obsoleto que apunta a una página ya borrada.\nPuedes encontrar detalles en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
        "right-override-export-depth": "Exportar páginas, incluyendo aquellas enlazadas hasta una profundidad de 5",
        "right-sendemail": "Enviar correo electrónico a otros usuarios",
        "right-passwordreset": "Ver los mensajes de restablecimiento de contraseña",
-       "right-managechangetags": "Crear y eliminar [[Special:Tags|etiquetas]] en la base de datos",
+       "right-managechangetags": "Crear y (des)activar [[Special:Tags|etiquetas]]",
        "right-applychangetags": "Aplicar [[Special:Tags|etiquetas]] junto con los cambios propios",
        "right-changetags": "Agregar y quitar [[Special:Tags|etiquetas]] arbitrarias a revisiones individuales y entradas del registro",
+       "right-deletechangetags": "Eliminar [[Special:Tags|tags]] de la base de datos",
        "grant-generic": "Paquete de permisos \"$1\"",
        "grant-group-page-interaction": "Interactuar con páginas",
        "grant-group-file-interaction": "Interactuar con multimedia",
        "action-viewmyprivateinfo": "ver tu información privada",
        "action-editmyprivateinfo": "editar tu información privada",
        "action-editcontentmodel": "editar el modelo de contenido de una página",
-       "action-managechangetags": "crear y eliminar etiquetas en la base de datos",
+       "action-managechangetags": "crear y (des)activar etiquetas",
        "action-applychangetags": "aplicar etiquetas junto con los cambios",
        "action-changetags": "agregar y quitar etiquetas arbitrarias a revisiones individuales y entradas del registro",
+       "action-deletechangetags": "eliminar etiquetas de la base de datos",
        "nchanges": "$1 {{PLURAL:$1|cambio|cambios}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|desde la última visita}}",
        "enhancedrc-history": "historial",
        "upload-form-label-infoform-description-tooltip": "Describe brevemente todo lo destacable acerca del trabajo.\nPara una foto, menciona las cosas principales que se representan, la ocasión o el lugar.",
        "upload-form-label-usage-title": "Uso",
        "upload-form-label-usage-filename": "Nombre del archivo",
-       "foreign-structured-upload-form-label-own-work": "Esto es mi trabajo propio",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorías",
-       "foreign-structured-upload-form-label-infoform-date": "Fecha",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que estoy subiendo este archivo siguiendo los términos del servicio y las políticas de concesión de licencias en {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Si no es capaz de subir este archivo bajo las políticas de {{SITENAME}}, por favor cierre este cuadro de diálogo e intente otro método.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Quizás también quieras probar [[Special:Upload|la página predeterminada de subidas]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Entiendo que voy a subir este archivo a un repositorio compartido. Confirmo que estoy haciéndolo que siguiendo los términos de servicio y políticas de licenciamiento de allí.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Si usted no es capaz de cargar este archivo en virtud de las políticas del repositorio compartido, por favor cierre este cuadro de diálogo y probar con otro método.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Puede que también quieras usar [[Special:Upload|la página de subidas en {{SITENAME}}]], si se puede subir este archivo bajo sus políticas.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Doy fe que soy dueño de los derechos de autor de este archivo, y acepto irrevocablemente liberar este archivo a Wikimedia Commons bajo la licencia [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], y acepto los [https://wikimediafoundation.org/wiki/Terms_of_Use Términos de Uso].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Si no es dueño de los derechos de autor de este archivo, o desea publicarlo bajo una licencia diferentes, considere usar el [https://commons.wikimedia.org/wiki/Special:UploadWizard Asistente de Carga de Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Puede que también quieras usar [[Special:Upload|la página de subidas en {{SITENAME}}]], si el sitio permite la subida de este archivo bajo sus políticas.",
+       "upload-form-label-own-work": "Esto es mi trabajo propio",
+       "upload-form-label-infoform-categories": "Categorías",
+       "upload-form-label-infoform-date": "Fecha",
+       "upload-form-label-own-work-message-generic-local": "Confirmo que estoy subiendo este archivo siguiendo los términos del servicio y las políticas de concesión de licencias en {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Si no es capaz de subir este archivo bajo las políticas de {{SITENAME}}, por favor cierre este cuadro de diálogo e intente otro método.",
+       "upload-form-label-not-own-work-local-generic-local": "Quizás también quieras probar [[Special:Upload|la página predeterminada de subidas]].",
+       "upload-form-label-own-work-message-generic-foreign": "Entiendo que voy a subir este archivo a un repositorio compartido. Confirmo que estoy haciéndolo que siguiendo los términos de servicio y políticas de licenciamiento de allí.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Si usted no es capaz de cargar este archivo en virtud de las políticas del repositorio compartido, por favor cierre este cuadro de diálogo y probar con otro método.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Puede que también quieras usar [[Special:Upload|la página de subidas en {{SITENAME}}]], si se puede subir este archivo bajo sus políticas.",
        "backend-fail-stream": "No se pudo transmitir el archivo «$1».",
        "backend-fail-backup": "No se pudo hacer copia de seguridad del archivo «$1».",
        "backend-fail-notexists": "El archivo  $1  no existe.",
        "changecontentmodel-success-text": "Se ha cambiado el tipo de contenido de [[:$1]].",
        "changecontentmodel-cannot-convert": "El contenido de [[:$1]] no se puede convertir a un tipo de $2.",
        "changecontentmodel-nodirectediting": "El modelo de contenido $1 no admite la edición directa",
+       "changecontentmodel-emptymodels-title": "No hay modelos de contenido disponibles",
        "log-name-contentmodel": "Registro de cambios del modelo de contenido",
        "log-description-contentmodel": "Eventos relacionados con los modelos de contenido de una página",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|creó}} la página $3 usando un modelo de contenido no predeterminado \"$5\"",
        "whatlinkshere-prev": "{{PLURAL:$1|previa|previas $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|siguiente|siguientes $1}}",
        "whatlinkshere-links": "← enlaces",
-       "whatlinkshere-hideredirs": "$1 redirecciones",
-       "whatlinkshere-hidetrans": "$1 inclusiones",
-       "whatlinkshere-hidelinks": "$1 enlaces",
-       "whatlinkshere-hideimages": "$1 enlaces a archivos",
+       "whatlinkshere-hideredirs": "Ocultar redirecciones",
+       "whatlinkshere-hidetrans": "Ocultar transclusiones",
+       "whatlinkshere-hidelinks": "Ocultar enlaces",
+       "whatlinkshere-hideimages": "Ocultar los vínculos de archivo",
        "whatlinkshere-filters": "Filtros",
        "whatlinkshere-submit": "Ir",
        "autoblockid": "Bloqueo automático #$1",
        "lockdbsuccesstext": "La base de datos de {{SITENAME}} ha sido bloqueada.\n<br />Recuerde retirar el bloqueo después de completar las tareas de mantenimiento.",
        "unlockdbsuccesstext": "La base de datos de {{SITENAME}} ha sido desbloqueada.",
        "lockfilenotwritable": "El archivo-cerrojo de la base de datos no tiene permiso de escritura. Para bloquear o desbloquear la base de datos, este archivo tiene que ser escribible por el servidor web.",
+       "databaselocked": "La base de datos ya está bloqueada.",
        "databasenotlocked": "La base de datos no está bloqueada.",
        "lockedbyandtime": "(por {{GENDER:$1|$1}} el $2 a las $3)",
        "move-page": "Trasladar $1",
        "tags-delete-not-found": "La etiqueta «$1» no existe.",
        "tags-delete-too-many-uses": "No se puede borrar la etiqueta \"$1\" porque se ha aplicado a más de {{PLURAL:$2|una revisión|$2 revisiones}}.",
        "tags-delete-warnings-after-delete": "La etiqueta \"$1\" se borró, pero con {{PLURAL:$2|la siguiente advertencia|las siguientes advertencias}}:",
+       "tags-delete-no-permission": "No tienes permiso para eliminar las etiquetas de cambios.",
        "tags-activate-title": "Activar etiqueta",
        "tags-activate-question": "Estás a punto de activar la etiqueta «$1».",
        "tags-activate-reason": "Motivo:",
        "log-action-filter-suppress-block": "Usuario supppression por bloque",
        "log-action-filter-suppress-reblock": "Usuario supresión de rebloqueo",
        "log-action-filter-upload-upload": "Subida nueva",
-       "log-action-filter-upload-overwrite": "Volver a subir"
+       "log-action-filter-upload-overwrite": "Volver a subir",
+       "authmanager-authplugin-setpass-failed-message": "El complemento de autenticación denegó el cambio de contraseña.",
+       "authmanager-authplugin-create-fail": "El complemento de autenticación denegó la creación de la cuenta.",
+       "authmanager-userdoesnotexist": "El usuario «$1» no está registrado.",
+       "authmanager-password-help": "Contraseña para autenticación.",
+       "authmanager-email-label": "Correo electrónico",
+       "authmanager-email-help": "Dirección de correo electrónico",
+       "authmanager-realname-label": "Nombre real",
+       "authmanager-realname-help": "Nombre real del usuario",
+       "authmanager-provider-password": "Autenticación basada en contraseña",
+       "authprovider-confirmlink-success-line": "$1: vinculado exitosamente.",
+       "authprovider-resetpass-skip-label": "Omitir",
+       "authform-nosession-login": "La autenticación fue exitosa, pero tu navegador no puede \"recordar\" haber registrado.",
+       "specialpage-securitylevel-not-allowed": "Lo siento, no tienes permitido usar esta página, porque tu identidad no pudo verificarse.",
+       "authpage-cannot-login-continue": "No se puede continuar con el inicio de sesión. Lo más probable es que tu sesión haya expirado.",
+       "changecredentials": "Cambiar las credenciales",
+       "changecredentials-submit": "Cambiar",
+       "changecredentials-submit-cancel": "Cancelar",
+       "removecredentials-submit": "Eliminar",
+       "removecredentials-submit-cancel": "Cancelar",
+       "credentialsform-account": "Nombre de la cuenta:",
+       "cannotlink-no-provider": "No hay cuentas vinculables.",
+       "linkaccounts": "Vincular cuentas",
+       "linkaccounts-success-text": "La cuenta fue vinculada.",
+       "linkaccounts-submit": "Vincular cuentas",
+       "unlinkaccounts": "Desvincular cuentas"
 }
index 038f823..02ddf8f 100644 (file)
        "noname": "Sa ei sisestanud kasutajanime lubataval kujul.",
        "loginsuccesstitle": "Sisselogimine õnnestus",
        "loginsuccess": "Oled sisse loginud. Sinu kasutajanimi on \"$1\".",
-       "nosuchuser": "Kasutajat \"$1\" pole.\nKasutajanimed on tõstutundlikud.\nKontrolli kirjapilti või [[Special:UserLogin/signup|loo uus konto]].",
+       "nosuchuser": "Kasutajat \"$1\" pole.\nKasutajanimed on tõstutundlikud.\nKontrolli kirjapilti või [[Special:CreateAccount|loo uus konto]].",
        "nosuchusershort": "Kasutajat nimega \"$1\" pole.\nKontrolli kirjapilti.",
        "nouserspecified": "Kasutajanimi puudub.",
        "login-userblocked": "See kasutaja on blokeeritud. Sisselogimine pole lubatud.",
        "accmailtext": "Kasutajale [[User talk:$1|$1]] genereeritud juhuslik parool saadeti aadressile $2.\n\nSeda saab pärast sisselogimist muuta ''[[Special:ChangePassword|parooli muutmise]]'' leheküljel.",
        "newarticle": "(Uus)",
        "newarticletext": "Lehekülge, kuhu link sind suunas, pole veel.\nEt lehekülg luua, alusta allolevas kastis kirjutamist (lisateave [$1 juhendist]).\nKui sattusid siia kogemata, klõpsa veebilehitseja ''tagasi''-nupule.",
-       "anontalkpagetext": "----''See on anonüümse kasutaja arutelulehekülg. See kasutaja pole kontot loonud või ei kasuta seda. Sellepärast tuleb meil kasutaja tuvastamiseks kasutada tema IP-aadressi. Sellist IP-aadressi võib kasutada mitu kasutajat. Kui oled osutatud IP-aadressi kasutaja ning leiad, et siinsed kommentaarid ei puutu kuidagi sinusse, [[Special:UserLogin/signup|loo palun kasutajakonto]] või [[Special:UserLogin|logi sisse]], et sind edaspidi teiste anonüümsete kasutajatega segi ei aetaks.''",
+       "anontalkpagetext": "----''See on anonüümse kasutaja arutelulehekülg. See kasutaja pole kontot loonud või ei kasuta seda. Sellepärast tuleb meil kasutaja tuvastamiseks kasutada tema IP-aadressi. Sellist IP-aadressi võib kasutada mitu kasutajat. Kui oled osutatud IP-aadressi kasutaja ning leiad, et siinsed kommentaarid ei puutu kuidagi sinusse, [[Special:CreateAccount|loo palun kasutajakonto]] või [[Special:UserLogin|logi sisse]], et sind edaspidi teiste anonüümsete kasutajatega segi ei aetaks.''",
        "noarticletext": "Käesoleval leheküljel hetkel teksti ei ole.\nVõid [[Special:Search/{{PAGENAME}}|otsida pealkirjaks olevat fraasi]] teistelt lehtedelt,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} uurida asjassepuutuvaid logisid] või [{{fullurl:{{FULLPAGENAME}}|action=edit}} puuduva lehekülje ise luua]</span>.",
        "noarticletext-nopermission": "Sellel leheküljel pole praegu teksti.\nSaad [[Special:Search/{{PAGENAME}}|otsida selle lehekülje pealkirja]] teistelt lehekülgedelt või <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} otsida seonduvatest logidest]</span>, aga sul pole õigust seda lehekülge alustada.",
        "missing-revision": "Lehekülje \"{{FULLPAGENAME}}\" redaktsiooni $1 pole.\n\nHarilikult tähendab see seda, et sind siia juhatanud link on vananenud ja siin asunud lehekülg on kustutatud.\nÜksikasjad leiad [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} kustutamislogist].",
        "upload-form-label-infoform-description": "Kirjeldus",
        "upload-form-label-usage-title": "Kasutus",
        "upload-form-label-usage-filename": "Failinimi",
-       "foreign-structured-upload-form-label-own-work": "See on minu enda töö",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategooriad",
-       "foreign-structured-upload-form-label-infoform-date": "Kuupäev",
-       "foreign-structured-upload-form-label-own-work-message-local": "Kinnitan, et seda faili üles laadides järgin saidi {{SITENAME}} kasutustingimusi ja litsentsipõhimõtteid.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Kui sul pole võimalik laadida seda faili üles kooskõlas saidi {{SITENAME}} reeglitega, siis palun sule see dialoog ja proovi teisiti toimida.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Võimalik, et soovid kasutada [[Special:Upload|harilikku üleslaadimislehekülge]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Saan aru, et laadin selle faili jagatud varamusse. Kinnitan, et teen seda kooskõlas sealsete kasutustingimuste ja litsentsipõhimõtetega.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Kui sul pole võimalik laadida seda faili üles kooskõlas jagatud varamu reeglitega, siis palun sule see dialoog ja proovi teisiti toimida.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Võimalik, et soovid [[Special:Upload|laadida selle faili üles saidil {{SITENAME}}]], kui seda on võimalik teha kooskõlas siinsete reeglitega.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Kinnitan, et olen selle faili autoriõiguse valdaja ja nõustun faili avaldamisega Wikimedia Commonsis pöördumatult Creative Commonsi litsentsi \"[https://creativecommons.org/licenses/by-sa/4.0/deed.et Autorile viitamine + jagamine samadel tingimustel 4.0]\" all. Samuti nõustun [https://wikimediafoundation.org/wiki/Terms_of_Use kasutustingimustega].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Kui sa pole selle faili autoriõiguse valdaja või kui soovid avaldada seda teise litsentsi all, siis on sul võimalik kasutada [https://commons.wikimedia.org/wiki/Special:UploadWizard Commonsi üleslaadimisviisardit].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Võimalik, et soovid kasutada [[Special:Upload|saidi {{SITENAME}} üleslaadimislehekülge]], kui seda faili on lubatud üles laadida kooskõlas siinsete reeglitega.",
+       "upload-form-label-own-work": "See on minu enda töö",
+       "upload-form-label-infoform-categories": "Kategooriad",
+       "upload-form-label-infoform-date": "Kuupäev",
+       "upload-form-label-own-work-message-generic-local": "Kinnitan, et seda faili üles laadides järgin saidi {{SITENAME}} kasutustingimusi ja litsentsipõhimõtteid.",
+       "upload-form-label-not-own-work-message-generic-local": "Kui sul pole võimalik laadida seda faili üles kooskõlas saidi {{SITENAME}} reeglitega, siis palun sule see dialoog ja proovi teisiti toimida.",
+       "upload-form-label-not-own-work-local-generic-local": "Võimalik, et soovid kasutada [[Special:Upload|harilikku üleslaadimislehekülge]].",
+       "upload-form-label-own-work-message-generic-foreign": "Saan aru, et laadin selle faili jagatud varamusse. Kinnitan, et teen seda kooskõlas sealsete kasutustingimuste ja litsentsipõhimõtetega.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Kui sul pole võimalik laadida seda faili üles kooskõlas jagatud varamu reeglitega, siis palun sule see dialoog ja proovi teisiti toimida.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Võimalik, et soovid [[Special:Upload|laadida selle faili üles saidil {{SITENAME}}]], kui seda on võimalik teha kooskõlas siinsete reeglitega.",
        "backend-fail-stream": "Faili $1 ei saanud edastada.",
        "backend-fail-backup": "Faili $1 ei saanud varundada.",
        "backend-fail-notexists": "Faili $1 pole olemas.",
index c3a8d1c..bb7c10d 100644 (file)
        "noname": "Ez duzu baliozko erabiltzaile izen bat zehaztu.",
        "loginsuccesstitle": "Saio hasiera egina",
        "loginsuccess": "'''Saioa hasi duzu {{SITENAME}}(e)n \"$1\" izenarekin.'''",
-       "nosuchuser": "Ez dago \"$1\" izena duen lankiderik.\nLankide izenak zehatza izan behar du.\nEgiaztatu ondo idatzi duzun, edo [[Special:UserLogin/signup|kontu berria sor ezazu]].",
+       "nosuchuser": "Ez dago \"$1\" izena duen lankiderik.\nLankide izenak zehatza izan behar du.\nEgiaztatu ondo idatzi duzun, edo [[Special:CreateAccount|kontu berria sor ezazu]].",
        "nosuchusershort": "Ez dago \"$1\" izena duen erabiltzailerik. Egiaztatu ongi idatzi duzula.",
        "nouserspecified": "Erabiltzaile izena zehaztu beharra daukazu.",
        "login-userblocked": "Erabiltzailea blokeatua dago. Ezin du saioa hasi.",
        "accmailtext": "[[User talk:$1|$1]]-entzako ausaz sortutako pasahitza $2-(r)a bidali da.\n\n''[[Special:ChangePassword|pasahitz aldaketa]]'' orrialdean alda daiteke, behin barruan sartuta.",
        "newarticle": "(Berria)",
        "newarticletext": "Orrialde hau ez da existitzen oraindik. Orrialde sortu nahi baduzu, beheko koadroan idazten hasi zaitezke (ikus [$1 laguntza orrialdea] informazio gehiagorako). Hona nahi gabe etorri bazara, nabigatzaileko '''atzera''' botoian klik egin.",
-       "anontalkpagetext": "----''Orrialde hau konturik sortu ez edo erabiltzen ez duen erabiltzaile anonimo baten eztabaida orria da.\nBere IP helbidea erabili beharko da beraz identifikatzeko.\nErabiltzaile batek baino gehiagok IP bera erabil dezakete ordea.\nErabiltzaile anonimoa bazara eta zurekin zerikusirik ez duten mezuak jasotzen badituzu, mesedez [[Special:UserLogin/signup|Izena eman]] edo [[Special:UserLogin|saioa hasi]] etorkizunean horrelakoak gerta ez daitezen.''",
+       "anontalkpagetext": "----''Orrialde hau konturik sortu ez edo erabiltzen ez duen erabiltzaile anonimo baten eztabaida orria da.\nBere IP helbidea erabili beharko da beraz identifikatzeko.\nErabiltzaile batek baino gehiagok IP bera erabil dezakete ordea.\nErabiltzaile anonimoa bazara eta zurekin zerikusirik ez duten mezuak jasotzen badituzu, mesedez [[Special:CreateAccount|Izena eman]] edo [[Special:UserLogin|saioa hasi]] etorkizunean horrelakoak gerta ez daitezen.''",
        "noarticletext": "Oraindik ez dago testurik orri honetan.\nEdukiz hornitzeko, aukera hauek dituzu: beste orri batzuetan [[Special:Search/{{PAGENAME}}|orri izenburu hau bilatzea]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} lotutako logak bilatzea],\nedo [{{fullurl:{{FULLPAGENAME}}|action=edit}} orri hau sortzea]</span>.",
        "noarticletext-nopermission": "Une honetan ez dago testurik orrialde honetan.\nBeste orrialdeetan [[Special:Search/{{PAGENAME}}|izenburu hau bilatu dezakezu]],\nedo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} erlazionatutako erregistroak bilatu]</span>, baina ez duzu orrialde hau sortzeko baimenik.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" lankidea ez dago erregistatuta. Mesedez, konprobatu orri hau editatu/sortu nahi duzun.",
        "upload-form-label-infoform-description": "Deskribapena",
        "upload-form-label-usage-title": "Erabilera",
        "upload-form-label-usage-filename": "Fitxategiaren izena",
-       "foreign-structured-upload-form-label-own-work": "Hau neure lana da",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategoriak",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work": "Hau neure lana da",
+       "upload-form-label-infoform-categories": "Kategoriak",
+       "upload-form-label-infoform-date": "Data",
        "backend-fail-stream": "Ezin izan da \"$1\" fitxategiaren stream egin.",
        "backend-fail-backup": "Ezin izan da \"$1\" fitxategiaren backup egin.",
        "backend-fail-notexists": "$1 fitxategia ez da existitzen.",
        "whatlinkshere-links": "← loturak",
        "whatlinkshere-hideredirs": "$1 birzuzenketak",
        "whatlinkshere-hidetrans": "$1 transklusioak",
-       "whatlinkshere-hidelinks": "$1 loturak",
+       "whatlinkshere-hidelinks": "Ezkutatu loturak",
        "whatlinkshere-hideimages": "$1 irudi loturak",
        "whatlinkshere-filters": "Iragazleak",
        "whatlinkshere-submit": "Joan",
index 93d25bf..50d0a8c 100644 (file)
        "noname": "Nu as escrebiu un nombri d'usuáriu corretu.",
        "loginsuccesstitle": "Yeu, lo cúmu va esu?",
        "loginsuccess": "'''Acabihas d'entral en {{SITENAME}} con el nombri \"$1\".'''",
-       "nosuchuser": "Nu dessisti dengún usuáriu anombrau \"$1\".\nCompreba que lo aigas escritu bien, u [[Special:UserLogin/signup|cria una cuenta nueva]].",
+       "nosuchuser": "Nu dessisti dengún usuáriu anombrau \"$1\".\nCompreba que lo aigas escritu bien, u [[Special:CreateAccount|cria una cuenta nueva]].",
        "nosuchusershort": "Nu ai dengún usuáriu llamau \"$1\". Compreba qu'esté bien escritu.",
        "nouserspecified": "Ebis escribil un nombri d'usuáriu.",
        "wrongpassword": "La consínia escrebia nu es correta. Pol favol, preba otra vezi.",
index eac16c4..99ac9c4 100644 (file)
@@ -70,6 +70,7 @@
        "tog-watchdefault": "افزودن صفحه‌ها و پرونده‌هایی که ویرایش می‌کنم به فهرست پیگیری‌های من",
        "tog-watchmoves": "افزودن صفحه‌ها و پرونده‌هایی که منتقل می‌کنم به فهرست پی‌گیری‌های من",
        "tog-watchdeletion": "افزودن صفحات و پرونده‌هایی که حذف می‌کنم به فهرست پی‌گیری‌های من",
+       "tog-watchuploads": "افزودن پرونده‌های جدید به فهرست پیگیری من",
        "tog-watchrollback": "افزودن صفحاتی که واگردانی می‌کنم به فهرست پیگیری‌های من",
        "tog-minordefault": "علامت زدن همهٔ ویرایش‌ها به عنوان «جزئی» به طور پیش‌فرض",
        "tog-previewontop": "نمایش دادن پیش‌نمایش بالای جعبهٔ ویرایش",
        "noname": "شما نام کاربری معتبری مشخص نکرده‌اید.",
        "loginsuccesstitle": "ورود به سامانه",
        "loginsuccess": "'''شما اکنون با نام «$1» به {{SITENAME}} وارد شده‌اید.'''",
-       "nosuchuser": "کاربری با نام «$1» وجود ندارد.\nنام کاربری به بزرگی و کوچکی حروف حساس است.\nاملای نام را بررسی کنید، یا [[Special:UserLogin/signup|یک حساب کاربری تازه بسازید]].",
+       "nosuchuser": "کاربری با نام «$1» وجود ندارد.\nنام کاربری به بزرگی و کوچکی حروف حساس است.\nاملای نام را بررسی کنید، یا [[Special:CreateAccount|یک حساب کاربری تازه بسازید]].",
        "nosuchusershort": "هیچ کاربری با نام ''$1'' وجود ندارد.\nاملایتان را وارسی کنید.",
        "nouserspecified": "باید یک نام کاربری مشخص کنید.",
        "login-userblocked": "این کاربر بسته شده‌است. ورود به سامانه مجاز نیست.",
        "minoredit": "این ویرایش، جزئی است",
        "watchthis": "پی‌گیری این صفحه",
        "savearticle": "صفحه ذخیره شود",
+       "publishpage": "انتشار صفحه",
        "preview": "پیش‌نمایش",
        "showpreview": "پیش‌نمایش",
        "showdiff": "نمایش تغییرات",
        "accmailtext": "یک گذرواژهٔ تصادفی برای [[User talk:$1|$1]] به $2 فرستاده شد. می‌توان آن را از صفحهٔ ''[[Special:ChangePassword|تغییر گذرواژه]]'' که هنگام ثبت ورود نمایش می‌یابد تغییر داد.",
        "newarticle": "(تازه)",
        "newarticletext": "شما پیوندی را دنبال کرده‌اید و به صفحه‌ای رسیده‌اید که هنوز وجود ندارد.\nبرای ایجاد صفحه، در مستطیل زیر شروع به نوشتن کنید (برای اطلاعات بیشتر به [$1 صفحهٔ راهنما] مراجعه کنید).\nاگر به اشتباه اینجا آمده‌اید، دکمهٔ «بازگشت» مرورگرتان را بزنید.",
-       "anontalkpagetext": "----''این صفحهٔ بحث برای کاربر گمنامی است که هنوز حسابی درست نکرده است یا از آن استفاده نمی‌کند.\nبنا بر این برای شناسایی‌اش مجبوریم از نشانی آی‌پی عددی استفاده کنیم.\nچنین نشانی‌های آی‌پی ممکن است توسط چندین کاربر به شکل مشترک استفاده شود.\nاگر شما کاربر گمنامی هستید و تصور می‌کنید اظهار نظرات نامربوط به شما صورت گرفته است، لطفاً برای پیشگیری از اشتباه گرفته شدن با کاربران گمنام دیگر در آینده [[Special:UserLogin/signup|حسابی ایجاد کنید]] یا [[Special:UserLogin|به سامانه وارد شوید]].''",
+       "anontalkpagetext": "----''این صفحهٔ بحث برای کاربر گمنامی است که هنوز حسابی درست نکرده است یا از آن استفاده نمی‌کند.\nبنا بر این برای شناسایی‌اش مجبوریم از نشانی آی‌پی عددی استفاده کنیم.\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}}}} سیاهه‌های مرتبط را بگردید]</span> ولی شما اجازه ایجاد این صفحه را ندارید.",
        "missing-revision": "ویرایش #$1 از صفحهٔ «{{FULLPAGENAME}}» موجود نیست.\n\nمعمولاً در اثر پیوند به تاریخچهٔ به‌روز نشدهٔ صفحهٔ حذف شده است.\nمی‌توانید جزئیات بیشتر را در [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیاههٔ حذف] بیابید.",
        "right-managechangetags": "ایجاد و حذف [[Special:Tags|برچسب‌ها]] از پایگاه داده",
        "right-applychangetags": "تائید [[Special:Tags|برچسب]] بر روی تغییرات یک نفر",
        "right-changetags": "افزودن یا حذف [[Special:Tags|برچسب]] قراردادی بر روی نسخه یا سیاهه ورودی‌ها",
+       "right-deletechangetags": "حذف [[Special:Tags|برچسب‌ها]] از پایگاه داده",
        "grant-generic": "\" $1 \" حقوق بسته",
        "grant-group-page-interaction": "تعامل با صفحات",
        "grant-group-file-interaction": "تعامل با رسانه",
        "action-viewmyprivateinfo": "اطلاعات خصوصی خود را ببینید",
        "action-editmyprivateinfo": "اطلاعات خصوصی خود را ویرایش کنید",
        "action-editcontentmodel": "ویرایش مدل محتوای یک صفحه",
-       "action-managechangetags": "ایجاد و حذف تگ‌ها از پایگاه داده",
+       "action-managechangetags": "ایجاد و (غیر)فعال‌سازی برچسب‌ها",
        "action-applychangetags": "اعمال برچسب بر روی تغییرات شما",
        "action-changetags": "افزودن یا حذف برچسب قراردادی بر روی نسخه یا سیاهه ورودی‌ها",
+       "action-deletechangetags": "حذف برچسب‌ها از پایگاه داده",
        "nchanges": "$1 تغییر",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|از آخرین بازدید}}",
        "enhancedrc-history": "تاریخچه",
        "recentchangeslinked-page": "نام صفحه:",
        "recentchangeslinked-to": "نمایش تغییرات صفحه‌هایی که به صفحهٔ داده‌شده پیوند دارند",
        "recentchanges-page-added-to-category": "[[:$1]] به رده اضافه شد",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] و [[Special:WhatLinksHere/$1|{{PLURAL:$2|یک صفحه|$2 صفحه}}]]دیگر به رده اضافه شدند",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] به رده افزوده شد، [[Special:WhatLinksHere/$1|این صفحه در صفحه‌های دیگر قرار گرفته است.]]",
        "recentchanges-page-removed-from-category": "[[:$1]] از رده حذف شد",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]] و [[Special:WhatLinksHere/$1|{{PLURAL:$2|یک صفحه|$2 صفحه}}]] دیگر از رده حذف شدند",
        "autochange-username": "تغییرات خودکار مدیاویکی",
        "upload-form-label-infoform-description-tooltip": "به صورت خلاصه هرچیز سرشناسی که در مورد اثر باشد را توضیح دهید.\nبرای تصویر موارد اصلی مانند محل وقوع یا موقعیت را شرح دهید.",
        "upload-form-label-usage-title": "کاربرد",
        "upload-form-label-usage-filename": "نام پرونده",
-       "foreign-structured-upload-form-label-own-work": "این کار خودم است",
-       "foreign-structured-upload-form-label-infoform-categories": "رده‌ها",
-       "foreign-structured-upload-form-label-infoform-date": "تاریخ",
-       "foreign-structured-upload-form-label-own-work-message-local": "تائید می کنم که این پرونده را تحت مجوزها و سیاست‌های {{SITENAME}} بارگذاری می‌کنم.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "اگر امکان بارگذاری پرونده تحت سیاست‌های {{SITENAME}} را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "ممکن بخواهید از [[Special:Upload|پنجرهٔ بارگذاری پیش‌فرض]] استفاده کنید.",
-       "foreign-structured-upload-form-label-own-work-message-default": "متوجهم که این پرونده را بر روی مخزن مشترک بارگذاری می‌کنم و تائید می‌کنم که تحت سیاست‌ها و مجوزهای آنجا عمل می‌کنم.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "اگر امکان بارگذاری پرونده تحت سیاست‌ها و مجوزهای مخزن اشتراک‌گذاری را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "در صورتی که نمی‌شود پرونده را تحت سیاست‌ها بارگذاری کنید ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "تصدیق می‌کنم که مالک حق تکثیر این پرونده هستم و موافق اشتراک‌گذاری آن تحت مجوز [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] هستم و موافق [https://wikimediafoundation.org/wiki/Terms_of_Use سیاست نحوهٔ استفاده] هستم.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "اگر مالک حق تکثیر این پرونده نیستید یا قصد بارگذاری تحت مجوز دیگری دارید، از [https://commons.wikimedia.org/wiki/Special:UploadWizard جادوگر بارگذاری ویکی‌انبار] استفاده کنید.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "در صورتی که سایت امکان بارگذاری پرونده را تحت سیاست‌ها بارگذاری می‌دهد ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
+       "upload-form-label-own-work": "این کار خودم است",
+       "upload-form-label-infoform-categories": "رده‌ها",
+       "upload-form-label-infoform-date": "تاریخ",
+       "upload-form-label-own-work-message-generic-local": "تائید می کنم که این پرونده را تحت مجوزها و سیاست‌های {{SITENAME}} بارگذاری می‌کنم.",
+       "upload-form-label-not-own-work-message-generic-local": "اگر امکان بارگذاری پرونده تحت سیاست‌های {{SITENAME}} را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
+       "upload-form-label-not-own-work-local-generic-local": "ممکن بخواهید از [[Special:Upload|پنجرهٔ بارگذاری پیش‌فرض]] استفاده کنید.",
+       "upload-form-label-own-work-message-generic-foreign": "متوجهم که این پرونده را بر روی مخزن مشترک بارگذاری می‌کنم و تائید می‌کنم که تحت سیاست‌ها و مجوزهای آنجا عمل می‌کنم.",
+       "upload-form-label-not-own-work-message-generic-foreign": "اگر امکان بارگذاری پرونده تحت سیاست‌ها و مجوزهای مخزن اشتراک‌گذاری را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
+       "upload-form-label-not-own-work-local-generic-foreign": "در صورتی که نمی‌شود پرونده را تحت سیاست‌ها بارگذاری کنید ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
        "backend-fail-stream": "نمی‌توان پروندهٔ $1 را ارسال کرد.",
        "backend-fail-backup": "نمی‌توان نسخهٔ پشتیبان برای پروندهٔ $1 ایجاد کرد.",
        "backend-fail-notexists": "پروندهٔ $1 وجود ندارد.",
        "exbeforeblank": "محتوای صفحه قبل از خالی‌کردن این بود: «$1»",
        "delete-confirm": "حذف «$1»",
        "delete-legend": "حذف",
-       "historywarning": "<strong>هشدار:</strong> صفحه‌ای که در حال پاک‌کردن آن هستید دارای یک تاریخچه همراه $1 {{PLURAL:$1|بازبینی|بازبینی‌ها}} است:",
+       "historywarning": "<strong>هشدار:</strong> صفحه‌ای که در حال پاک‌کردن آن هستید دارای یک تاریخچه همراه $1 {{PLURAL:$1|بازبینی|بازبینی}} است:",
        "historyaction-submit": "نمایش",
        "confirmdeletetext": "شما در حال حذف کردن یک صفحه یا تصویر از پایگاه داده‌ها همراه با تمام تاریخچهٔ آن هستید.\nلطفاً این عمل را تأیید کنید و اطمینان حاصل کنید که عواقب این کار را می‌دانید و این عمل را مطابق با [[{{MediaWiki:Policy-url}}|سیاست‌ها]] انجام می‌دهید.",
        "actioncomplete": "عمل انجام شد",
        "whatlinkshere-prev": "{{PLURAL:$1|قبلی|$1 مورد قبلی}}",
        "whatlinkshere-next": "{{PLURAL:$1|بعدی|$1 مورد بعدی}}",
        "whatlinkshere-links": "→ پیوندها",
-       "whatlinkshere-hideredirs": "$1 تغییرمسیر",
-       "whatlinkshere-hidetrans": "$1 تراگنجانش‌ها",
-       "whatlinkshere-hidelinks": "$1 پیوند",
+       "whatlinkshere-hideredirs": "پنهان‌کردن تغییرمسیرها",
+       "whatlinkshere-hidetrans": "پنهان‌کردن تراگنجانش‌ها",
+       "whatlinkshere-hidelinks": "پنهان‌کردن پیوندها",
        "whatlinkshere-hideimages": "$1 پیوندهای پرونده",
        "whatlinkshere-filters": "پالایه‌ها",
        "whatlinkshere-submit": "برو",
        "lockdbsuccesstext": "پایگاه داده قفل شد.\n<br />فراموش نکنید که پس از اتمام نگهداری قفل را بردارید.",
        "unlockdbsuccesstext": "پایگاه داده از قفل در آمد.",
        "lockfilenotwritable": "قفل پایگاه داده نوشتنی نیست. برای این که بتوانید پایگاه داده را قفل یا باز کنید، باید این پرونده نوشتنی باشد.",
+       "databaselocked": "در حال حاضر، پایگاه داده قفل است.",
        "databasenotlocked": "پایگاه داده قفل نیست.",
        "lockedbyandtime": "(به وسیلهٔ $1 در $2 ساعت $3)",
        "move-page": "انتقال $1",
        "tooltip-ca-nstab-category": "دیدن صفحهٔ رده",
        "tooltip-minoredit": "این ویرایش را ویرایش جزئی نشانه‌گذاری کن",
        "tooltip-save": "تغییرات خود را ذخیره کنید",
+       "tooltip-publish": "انتشار تغییراتتان",
        "tooltip-preview": "پیش‌نمایش تغییرات شما، لطفاً قبل از ذخیره کردن صفحه از این کلید استفاده کنید.",
        "tooltip-diff": "نمایش تغییراتی که شما در متن داده‌اید.",
        "tooltip-compareselectedversions": "دیدن تفاوت‌های دو نسخهٔ انتخاب‌شده از این صفحه",
        "confirmemail_body_set": "یک نفر، احتمالاً خود شما، از نشانی آی‌پی $1,\nآدرس ایمیل حساب «$2» در {{SITENAME}} را به این آدرس تغییر داده است.\n\nبرای تأیید این که این حساب واقعاً به شما تعلق دارد و فعال کردن دوبارهٔ قابلیت ایمیل در {{SITENAME}}، پیوند زیر را در مرورگرتان باز کنید:\n\n$3\n\nاگر این حساب متعلق به شما نیست، پیوند زیر را باز تا تغییر آدرس ایمیل، لغو شود:\n\n$5\n\nاین تأییدیه در $4 منقضی می‌گردد.",
        "confirmemail_invalidated": "تأیید نشانی ایمیل لغو شد",
        "invalidateemail": "لغو تأیید نشانی ایمیل",
+       "notificationemail_subject_changed": "نشانی ایمیل ثبت شدهٔ {{SITENAME}} تغییر یافته است",
+       "notificationemail_subject_removed": "نشانی ایمیل ثبت شدهٔ {{SITENAME}} حذف شده است",
        "scarytranscludedisabled": "[تراگنجانش بین‌ویکیانه فعال نیست]",
        "scarytranscludefailed": "[فراخوانی الگو برای $1 میسر نشد]",
        "scarytranscludefailed-httpstatus": "[فراخوانی الگو برای $1 میسر نشد: خطای اچ‌تی‌تی‌پی $2]",
        "tags-delete-not-found": "تگ «$1» وجود ندارد.",
        "tags-delete-too-many-uses": "برچسب \"$1\" در بیش از $2 نسخه اعمال شده است و نمی‌توان آن را حذف نمود.",
        "tags-delete-warnings-after-delete": "برچسب \"$1\" حذف شد، اما با {{PLURAL:$2|خطای|خطاهای}} زیر همراه بود:",
+       "tags-delete-no-permission": "شما اجازهٔ حذف برچسب‌های تغییر را ندارید.",
        "tags-activate-title": "فعال‌سازی برچسب",
        "tags-activate-question": "شما در حال فعال‌سازی تگ «$1» هستید.",
        "tags-activate-reason": "دلیل:",
        "logentry-protect-protect-cascade": "$1 $3 $4 {{GENDER:$2|محافظت کرد}} [آبشاری]",
        "logentry-protect-modify": "$1 سطح محافظت $3 را {{GENDER:$2|تغییر داد}} $4",
        "logentry-protect-modify-cascade": "$1 سطح حفاظت برای $3 $4 را {{GENDER:$2|تغییر داد}}[آبشاری]",
-       "logentry-rights-rights": "$1 Ø¹Ø¶Ù\88Û\8cت $3 Ø±Ø§ Ø§Ø² Ú¯Ø±Ù\88Ù\87 $4 Ø¨Ù\87 $5 {{GENDER:$2|تغÛ\8cÛ\8cر Ø¯Ø§Ø¯}}",
+       "logentry-rights-rights": "$1 Ø¯Ø³ØªØ±Ø³Û\8c $3 Ø±Ø§ Ø§Ø² Ú¯Ø±Ù\88Ù\87 $4 Ø¨Ù\87 $5 ØªØºÛ\8cÛ\8cر Ø¯Ø§Ø¯",
        "logentry-rights-rights-legacy": "$1 گروه عضویت $3 را {{GENDER:$2|تغییر داد}}",
        "logentry-rights-autopromote": "$1 به طور خودکار از $4 به $5 {{GENDER:$2|ارتقاء داد}}",
        "logentry-upload-upload": "$1 $3 را {{GENDER:$2|بارگذاری کرد}}",
        "feedback-useragent": "رابط کاربر:",
        "searchsuggest-search": "جستجو",
        "searchsuggest-containing": "صفحه‌های دربردارنده...",
+       "api-error-autoblocked": "نشانی آی‌پی شما به صورت خودکار بسته شده‌است، چون توسط یک کاربر بسته‌شده استفاده می‌شد.",
        "api-error-badaccess-groups": "شما اجازهٔ بارگذاری پرونده‌ها را در این ویکی ندارید.",
        "api-error-badtoken": "خطای داخلی: کد امنیتی اشتباه (Bad token).",
+       "api-error-blocked": "شما از ویرایش بسته شده‌اید.",
        "api-error-copyuploaddisabled": "بارگذاری با استفاده از نشانی اینترنتی در این کارساز غیرفعال است.",
        "api-error-duplicate": "{{PLURAL:$1|پروندهٔ دیگری|چند پروندهٔ دیگر}} در تارنما با محتوای یکسان وجود داشت.",
        "api-error-duplicate-archive": "{{PLURAL:$1| پروندهٔ دیگری|چند پروندهٔ دیگر}} در تارنما با محتوای یکسان وجود داشت، ولی حذف {{PLURAL:$1|شده است|شده‌اند}}.",
        "sessionprovider-nocookies": "کوکی‌ها ممکن است غیر فعال شده باشند. اطمینان کسب کنید که کوکی‌ها را فعال کرده‌اید و دوباره آغاز کنید.",
        "randomrootpage": "صفحهٔ ریشهٔ تصادفی",
        "log-action-filter-block": "نوع بسته شدن:",
+       "log-action-filter-contentmodel": "تغییر نوع contentmodel:",
        "log-action-filter-delete": "نوع حذف:",
        "log-action-filter-import": "نوع واردات",
        "log-action-filter-managetags": "نوع مدیریت",
        "log-action-filter-block-reblock": "تصحیح بلاک",
        "log-action-filter-block-unblock": "باز شدن",
        "log-action-filter-contentmodel-change": "تغییر نوع محتوا",
+       "log-action-filter-contentmodel-new": "ایجاد صفحه با contentmodel غیر استاندارد",
        "log-action-filter-delete-delete": "حذف صفحه",
        "log-action-filter-delete-restore": "احیای صفحه",
        "log-action-filter-delete-event": "حذف سیاهه",
        "log-action-filter-managetags-delete": "حذف کردن تگ",
        "log-action-filter-managetags-activate": "فعالسازی تگ",
        "log-action-filter-managetags-deactivate": "تغییر تگ",
+       "log-action-filter-move-move": "انتقال بدون بازنویسی تغییر مسیرها",
+       "log-action-filter-move-move_redir": "انتقال با بازنویسی تغییر مسیرها",
        "log-action-filter-newusers-create": "ایجاد شده توسط کاربر ناشناس",
        "log-action-filter-newusers-create2": "ایجاد شده توسط کاربر ثبت نام شده",
        "log-action-filter-newusers-autocreate": "ایجاد خودکار",
        "log-action-filter-newusers-byemail": "ایجاد پسورد با ارسال به ایمیل",
+       "log-action-filter-patrol-patrol": "گشت غیرخودکار",
        "log-action-filter-patrol-autopatrol": "گشت خودکار",
        "log-action-filter-protect-protect": "محافظت",
        "log-action-filter-protect-modify": "اصلاح حفاظت",
        "log-action-filter-suppress-event": "جلوگیری از ورود",
        "log-action-filter-suppress-revision": "جلوگیری از ویرایش",
        "log-action-filter-suppress-delete": "متوقف سازی صفحه",
+       "log-action-filter-suppress-block": "مخفی‌سازی کاربر با بستن",
+       "log-action-filter-suppress-reblock": "مخفی‌سازی کاربر با بستن مجدد",
        "log-action-filter-upload-upload": "بارگذاری جدید",
        "log-action-filter-upload-overwrite": "بارگذاری دوباره"
 }
index db908f1..7c6a371 100644 (file)
        "noname": "Et ole määritellyt kelvollista käyttäjänimeä.",
        "loginsuccesstitle": "Olet kirjautunut sisään",
        "loginsuccess": "'''Olet kirjautunut sivustolle {{SITENAME}} käyttäjänä $1.'''",
-       "nosuchuser": "Käyttäjää ”$1” ei ole olemassa. Nimet ovat kirjainkoosta riippuvaisia. Tarkista kirjoititko nimen oikein, tai [[Special:UserLogin/signup|luo uusi käyttäjätunnus]].",
+       "nosuchuser": "Käyttäjää ”$1” ei ole olemassa. Nimet ovat kirjainkoosta riippuvaisia. Tarkista kirjoititko nimen oikein, tai [[Special:CreateAccount|luo uusi käyttäjätunnus]].",
        "nosuchusershort": "Käyttäjää nimeltä ”$1” ei ole. Kirjoititko nimen oikein?",
        "nouserspecified": "Käyttäjätunnusta ei ole määritelty.",
        "login-userblocked": "Tämä käyttäjä on estetty. Kirjautuminen ei ole sallittua.",
        "botpasswords-label-resetpassword": "Uudista salasana",
        "botpasswords-label-grants": "Valittavissa olevat toimintaoikeudet:",
        "botpasswords-label-restrictions": "Käyttörajoitukset:",
+       "botpasswords-label-grants-column": "Myönnetään",
        "botpasswords-bad-appid": "Botin nimi \"$1\" ei kelpaa.",
        "botpasswords-insert-failed": "Botin nimen \"$1\" lisääminen epäonnsitui. Onko se jo lisätty?",
        "botpasswords-update-failed": "Botin nimen \"$1\" päivittäminen epäonnistui. Onko se poistettu?",
        "minoredit": "Tämä on pieni muutos",
        "watchthis": "Tarkkaile tätä sivua",
        "savearticle": "Tallenna sivu",
+       "publishpage": "Julkaise sivu",
        "preview": "Esikatselu",
        "showpreview": "Esikatsele",
        "showdiff": "Näytä muutokset",
        "accmailtext": "Satunnaisesti generoitu salasana käyttäjälle [[User talk:$1|$1]] on lähetetty osoitteeseen $2. Sen voi vaihtaa kirjautumisen jälkeen [[Special:ChangePassword|asetussivulla]].",
        "newarticle": "(Uusi)",
        "newarticletext": "Linkki toi sivulle, jota ei vielä ole.\nVoit luoda sivun kirjoittamalla alla olevaan kenttään (katso [$1 ohjesivulta] lisätietoja).\nJos et halua luoda sivua, käytä selaimen paluutoimintoa.",
-       "anontalkpagetext": "----''Tämä on nimettömän käyttäjän keskustelusivu. Hän ei ole joko luonut itselleen käyttäjätunnusta tai ei käytä sitä. Siksi hänet tunnistetaan nyt numeerisella IP-osoitteella. Kyseinen IP-osoite voi olla useamman henkilön käytössä. Jos olet nimetön käyttäjä, ja sinusta tuntuu, että aiheettomia kommentteja on ohjattu sinulle, [[Special:UserLogin/signup|luo itsellesi käyttäjätunnus]] tai [[Special:UserLogin|kirjaudu sisään]] välttääksesi jatkossa sekaannukset muiden nimettömien käyttäjien kanssa.''",
+       "anontalkpagetext": "----''Tämä on nimettömän käyttäjän keskustelusivu. Hän ei ole joko luonut itselleen käyttäjätunnusta tai ei käytä sitä. Siksi hänet tunnistetaan nyt numeerisella IP-osoitteella. Kyseinen IP-osoite voi olla useamman henkilön käytössä. Jos olet nimetön käyttäjä, ja sinusta tuntuu, että aiheettomia kommentteja on ohjattu sinulle, [[Special:CreateAccount|luo itsellesi käyttäjätunnus]] tai [[Special:UserLogin|kirjaudu sisään]] välttääksesi jatkossa sekaannukset muiden nimettömien käyttäjien kanssa.''",
        "noarticletext": "Tällä hetkellä tällä sivulla ei ole tekstiä.\nVoit [[Special:Search/{{PAGENAME}}|etsiä sivun nimellä]] muilta sivuilta,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hakea aiheeseen liittyviä lokeja]\ntai [{{fullurl:{{FULLPAGENAME}}|action=edit}} luoda tämän sivun]</span>.",
        "noarticletext-nopermission": "Tällä hetkellä tällä sivulla ei ole tekstiä.\nVoit [[Special:Search/{{PAGENAME}}|etsiä sivun nimellä]] muilta sivuilta tai <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hakea aiheeseen liittyviä lokeja]</span>, mutta sinulla ei ole oikeutta luoda tätä sivua.",
        "missing-revision": "Sivusta \"{{FULLPAGENAME}}\" ei ole olemassa versiota $1.\n\nYleensä tämä johtuu vanhentuneesta historialinkistä sivulle, joka on poistettu.\nTarkempia tietoja löytyy [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} poistolokista].",
        "right-override-export-depth": "Viedä sivuja sisältäen viitatut sivut viiden syvyydellä",
        "right-sendemail": "Lähettää sähköpostia muille käyttäjille",
        "right-passwordreset": "Tarkastella salasanan alustusviestejä",
-       "right-managechangetags": "Luoda ja poistaa [[Special:Tags|merkkauksia]] tietokannasta",
+       "right-managechangetags": "Luoda ja ottaa käyttöön [[Special:Tags|merkkauksia]]",
        "right-applychangetags": "Asettaa [[Special:Tags|merkkauksia]] omien muutosten yhteyteen",
        "right-changetags": "Lisätä ja poistaa satunnaisia [[Special:Tags|merkkauksia]] yksittäisissä sivuversioissa tai lokimerkinnöissä",
+       "right-deletechangetags": "Poistaa [[Special:Tags|merkkauksia]] tietokannasta",
        "grant-generic": "\"$1\" oikeuksien joukko",
+       "grant-group-page-interaction": "Ole vuorovaikutuksessa sivujen kanssa",
+       "grant-group-file-interaction": "Ole vuorovaikutuksessa mediatiedostojen kanssa",
+       "grant-group-watchlist-interaction": "Ole vuorovaikutuksessa oman tarkkailulistasi kanssa",
        "grant-group-email": "Lähettää sähköpostia",
        "grant-group-high-volume": "Suorittaa suuri määrä toimintoja",
        "grant-group-customization": "Mukauttaminen ja asetukset",
        "action-viewmyprivateinfo": "katsoa omia yksityisiä tietojasi",
        "action-editmyprivateinfo": "muokata omia yksityisiä tietojasi",
        "action-editcontentmodel": "muokata sivun sisältömallia",
-       "action-managechangetags": "luoda ja poistaa merkkauksia tietokannasta",
+       "action-managechangetags": "luoda ja ottaa käyttöön merkkauksia",
        "action-applychangetags": "käyttää merkkauksia muutostesi yhteydessä",
        "action-changetags": "lisätä ja poistaa satunnaisia merkkauksia yksittäisissä sivuversioissa ja lokimerkinnöissä",
+       "action-deletechangetags": "poistaa merkkauksia tietokannasta",
        "nchanges": "$1 {{PLURAL:$1|muutos|muutosta}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|viimeisen käynnin jälkeen}}",
        "enhancedrc-history": "historia",
        "upload-form-label-infoform-description-tooltip": "Kuvaa lyhyesti kaikkea, mikä on teoksessa huomionarvoista.\nJos kyseessä on valokuva, mainitse kuvatut pääasiat, tapahtuma tai paikka.",
        "upload-form-label-usage-title": "Käyttö",
        "upload-form-label-usage-filename": "Tiedostonimi",
-       "foreign-structured-upload-form-label-own-work": "Tämä on oma työni",
-       "foreign-structured-upload-form-label-infoform-categories": "Luokat",
-       "foreign-structured-upload-form-label-infoform-date": "Päivämäärä",
-       "foreign-structured-upload-form-label-own-work-message-local": "Vakuutan, että tallennan tämän tiedoston noudattaen sivustolla {{SITENAME}} voimassa olevia käyttöehtoja sekä lisenssejä koskevia käytäntöjä.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Jos et kykene tallentamaan tätä tiedostoa noudattaen niitä käytäntöjä, jotka ovat voimassa sivustolla {{SITENAME}}, sulje tämä dialogi ja kokeile jotain toista menetelmää.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Voit myös kokeilla [[Special:Upload|yleistä tallentamista]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Ymmärrän, että olen tallentamassa tätä tiedostoa yhteiseen mediasäilöön. Vakuutan, että teen tämän noudattaen asiaankuuluvia käyttöehtoja ja lisenssejä koskevia käytäntöjä.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Jos et kykene tallentamaan tätä tiedostoa noudattaen niitä käytäntöjä, jotka ovat voimassa yhteisessä mediasäilössä, sulje tämä dialogi ja kokeile jotain toista menetelmää.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Voit myös kokeilla [[Special:Upload|tallennussivua sivustolla {{SITENAME}}]]. Saattaa olla, että tämän tiedoston tallentaminen sinne on mahdollista siellä voimassa olevien käytäntöjen mukaisesti.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Vakuutan, että minä omistan tämän tiedoston tekijänoikeudet, ja sitoudun siihen, että luovutan peruuttamattomasti tämän tiedoston kohteeseen Wikimedia Commons niillä ehdoilla, jotka liittyvät lisenssiin [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0]. Sitoudun myös noudattamaan [https://wikimediafoundation.org/wiki/Terms_of_Use käyttöehtoja].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Jos sinulla ei ole tähän tiedostoon tekijänoikeutta tai jos haluat luovuttaa tiedoston käyttäen jotain toista lisenssiä, voit käyttää erityistä [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard] -toimintoa.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Voit myös kokeilla [[Special:Upload|tallennussivua sivustolla {{SITENAME}}]]. Saattaa olla, että sivusto antaa tallentaa tämän tiedoston sinne siellä voimassa olevien käytäntöjen mukaisesti.",
+       "upload-form-label-own-work": "Tämä on oma työni",
+       "upload-form-label-infoform-categories": "Luokat",
+       "upload-form-label-infoform-date": "Päivämäärä",
+       "upload-form-label-own-work-message-generic-local": "Vakuutan, että tallennan tämän tiedoston noudattaen sivustolla {{SITENAME}} voimassa olevia käyttöehtoja sekä lisenssejä koskevia käytäntöjä.",
+       "upload-form-label-not-own-work-message-generic-local": "Jos et kykene tallentamaan tätä tiedostoa noudattaen niitä käytäntöjä, jotka ovat voimassa sivustolla {{SITENAME}}, sulje tämä dialogi ja kokeile jotain toista menetelmää.",
+       "upload-form-label-not-own-work-local-generic-local": "Voit myös kokeilla [[Special:Upload|yleistä tallentamista]].",
+       "upload-form-label-own-work-message-generic-foreign": "Ymmärrän, että olen tallentamassa tätä tiedostoa yhteiseen mediasäilöön. Vakuutan, että teen tämän noudattaen asiaankuuluvia käyttöehtoja ja lisenssejä koskevia käytäntöjä.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Jos et kykene tallentamaan tätä tiedostoa noudattaen niitä käytäntöjä, jotka ovat voimassa yhteisessä mediasäilössä, sulje tämä dialogi ja kokeile jotain toista menetelmää.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Voit myös kokeilla [[Special:Upload|tallennussivua sivustolla {{SITENAME}}]]. Saattaa olla, että tämän tiedoston tallentaminen sinne on mahdollista siellä voimassa olevien käytäntöjen mukaisesti.",
        "backend-fail-stream": "Tiedoston $1 virtauttaminen epäonnistui.",
        "backend-fail-backup": "Tiedostoa $1 ei voitu varmuuskopioida.",
        "backend-fail-notexists": "Tiedostoa $1 ei ole olemassa.",
        "apisandbox-results-error": "Tapahtui virhe ladattaessa API-kyselyn vastausta: $1",
        "apisandbox-request-url-label": "Pyynnön URL",
        "apisandbox-request-time": "Pyyntöön kulunut aika: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "Korjaa \"token\" ja lähetä uudelleen",
        "apisandbox-alert-page": "Tällä sivulla olevat kentät eivät ole kelvollisia.",
        "apisandbox-alert-field": "Tässä kentässä oleva arvo ei ole kelvollinen.",
        "booksources": "Kirjalähteet",
        "changecontentmodel-success-text": "Sisältötyyppiä kohteessa [[:$1]] on muutettu.",
        "changecontentmodel-cannot-convert": "Sisältöä sivulla [[:$1]] ei voida muuntaa tyypiksi $2.",
        "changecontentmodel-nodirectediting": "Sisältömalli $1 ei tue suoraa muokkaamista",
+       "changecontentmodel-emptymodels-title": "Mitään sisältömallia ei ole saatavilla",
+       "changecontentmodel-emptymodels-text": "Sisältöä sivulla [[:$1]] ei voida muuntaa mihinkään muotoon.",
        "log-name-contentmodel": "Sisältömallin muutosloki",
        "log-description-contentmodel": "Tapahtumat, jotka liittyvät sivun sisältömalleihin",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|loi}} sivun $3 käyttäen normaalista poikkeavaa sisältömallia \"$5\"",
        "whatlinkshere-prev": "← {{PLURAL:$1|edellinen sivu|$1 edellistä sivua}}",
        "whatlinkshere-next": "{{PLURAL:$1|seuraava sivu|$1 seuraavaa sivua}} →",
        "whatlinkshere-links": "viittaukset",
-       "whatlinkshere-hideredirs": "$1 ohjaukset",
-       "whatlinkshere-hidetrans": "$1 sisällytykset",
-       "whatlinkshere-hidelinks": "$1 linkit",
-       "whatlinkshere-hideimages": "$1 tiedostolinkit",
+       "whatlinkshere-hideredirs": "Piilota ohjaukset",
+       "whatlinkshere-hidetrans": "Piilota sisällytykset",
+       "whatlinkshere-hidelinks": "Piilota linkit",
+       "whatlinkshere-hideimages": "Piilota tiedostolinkit",
        "whatlinkshere-filters": "Suotimet",
        "whatlinkshere-submit": "Siirry",
        "autoblockid": "Automaattinen esto #$1",
        "lockdbsuccesstext": "Tietokanta on lukittu.<br />\nMuista [[Special:UnlockDB|poistaa tietokannan lukitus]] kun huolto on tehty.",
        "unlockdbsuccesstext": "Tietokannan lukitus on poistettu.",
        "lockfilenotwritable": "Tietokannan lukitustiedostoa ei voi kirjoittaa. Tarkista oikeudet.",
+       "databaselocked": "Tietokanta on jo lukittu.",
        "databasenotlocked": "Tietokantaa ei ole lukittu.",
        "lockedbyandtime": "(lukinnut {{GENDER:$1|$1}} $2 kello $3)",
        "move-page": "Siirrä $1",
        "tooltip-ca-nstab-category": "Näytä luokkasivu",
        "tooltip-minoredit": "Merkitse tämä pieneksi muutokseksi",
        "tooltip-save": "Tallenna muokkaukset",
+       "tooltip-publish": "Julkaise tekemäsi muutokset",
        "tooltip-preview": "Esikatsele muokkausta ennen tallennusta",
        "tooltip-diff": "Näytä tehdyt muutokset",
        "tooltip-compareselectedversions": "Vertaile valittuja versioita",
        "tags-delete-not-found": "Merkkausta \"$1\" ei ole olemassa.",
        "tags-delete-too-many-uses": "Tämä merkkaus \"$1\" on käytössä useammassa kuin $2 sivuversiossa, joten sitä ei voi poistaa.",
        "tags-delete-warnings-after-delete": "Merkkaus \"$1\" poistettiin, mutta toimenpide sai aikaan {{PLURAL:$2|seuraavan varoituksen|seuraavat varoitukset}}:",
+       "tags-delete-no-permission": "Sinulla ei ole oikeutta poistaa muutoksien yhteydessä olevia merkkauksia.",
        "tags-activate-title": "Aktivoi merkkaus",
        "tags-activate-question": "Olet nyt aktivoimassa merkkausta \"$1\".",
        "tags-activate-reason": "Syy:",
        "feedback-useragent": "User agent:",
        "searchsuggest-search": "Hae",
        "searchsuggest-containing": "sisältää...",
+       "api-error-autoblocked": "Sinun IP-osoitteesi on estetty automaattisesti, koska sitä on käyttänyt estetty käyttäjätunnus.",
        "api-error-badaccess-groups": "Sinulla ei ole oikeutta tallentaa tiedostoja tähän wikiin.",
        "api-error-badtoken": "Sisäinen virhe: virheellinen tarkistussumma.",
+       "api-error-blocked": "Sinut on estetty muokkaamasta.",
        "api-error-copyuploaddisabled": "Tallentaminen URL-osoitteesta ei ole käytössä.",
        "api-error-duplicate": "Samansisältöisiä tiedostoja löytyi {{PLURAL:$1|yksi kappale|useampia kappaleita}}.",
        "api-error-duplicate-archive": "Sivustolla oli aiemmin {{PLURAL:$1|toinen samansisältöinen tiedosto|toisia samansisältöisiä tiedostoja}}, mutta {{PLURAL:$1|se|ne}} poistettiin.",
index d12e340..247ec5f 100644 (file)
        "noname": "Tú hevur ikki skrivað eitt gyldugt brúkaranavn.",
        "loginsuccesstitle": "Innritan væleydnað",
        "loginsuccess": "'''Tú hevur nú ritað inn í {{SITENAME}} sum \"$1\".'''",
-       "nosuchuser": "Eingin brúkari er við navninum \"$1\". \nBrúkaranøvn eru følsom fyri stórum og lítlum bókstavum.\nEftirkanna um tú hevur stavað rætt, ella [[Special:UserLogin/signup|stovna eina nýggja konto]].",
+       "nosuchuser": "Eingin brúkari er við navninum \"$1\". \nBrúkaranøvn eru følsom fyri stórum og lítlum bókstavum.\nEftirkanna um tú hevur stavað rætt, ella [[Special:CreateAccount|stovna eina nýggja konto]].",
        "nosuchusershort": "Eingin brúkari er við navninum \"$1\". Kanna stavseting.",
        "nouserspecified": "Tú mást skriva eitt brúkaranavn.",
        "login-userblocked": "Hesin brúkarin er blokkaður. Tað er ikki loyvt at logga á.",
        "accmailtext": "Eitt tilvildarliga valt loyniorð fyri brúkaran [[User talk:$1|$1]] er blivið  sent til $2.\nTað kann broytast á ''[[Special:ChangePassword|broyt loyniorð]]'' síðuni tá tú ritar inn.",
        "newarticle": "(Nýggj)",
        "newarticletext": "Tú ert komin eftir eini slóð til eina síðu, ið ikki er til enn. Skriva í kassan niðanfyri, um tú vilt byrja uppá hesa síðuna.\n(Sí [$1 hjálparsíðuna] um tú ynskir fleiri upplýsingar).\nErt tú komin higar av einum mistaki, kanst tú trýsta á '''aftur'''-knøttin á kagaranum.",
-       "anontalkpagetext": "----''Hetta er ein kjaksíða hjá einum dulnevndum brúkara, sum ikki hevur stovnað eina kontu enn, ella ikki brúkar hana. \nTí noyðast vit at brúka nummerisku IP-adressuna hjá honum ella henni.\nEin slík IP-adressa kann verða brúkt av fleiri brúkarum.\nErt tú ein dulnevndur brúkari, og meinar, at óviðkomandi viðmerkingar eru vendar til tín, so er best fyri teg at [[Special:UserLogin/signup|stovna eina kontu]] ella [[Special:UserLogin|rita inn]] fyri at sleppa undan samanblanding við aðrar dulnevndar brúkarar í framtíðini.''",
+       "anontalkpagetext": "----''Hetta er ein kjaksíða hjá einum dulnevndum brúkara, sum ikki hevur stovnað eina kontu enn, ella ikki brúkar hana. \nTí noyðast vit at brúka nummerisku IP-adressuna hjá honum ella henni.\nEin slík IP-adressa kann verða brúkt av fleiri brúkarum.\nErt tú ein dulnevndur brúkari, og meinar, at óviðkomandi viðmerkingar eru vendar til tín, so er best fyri teg at [[Special:CreateAccount|stovna eina kontu]] ella [[Special:UserLogin|rita inn]] fyri at sleppa undan samanblanding við aðrar dulnevndar brúkarar í framtíðini.''",
        "noarticletext": "Tað er í løtuni ongin tekstur á hesi síðu.\nTú kanst [[Special:Search/{{PAGENAME}}|leita eftir hesum síðu heitinum]] á øðrum síðum,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} leita í líknandi loggum],\nella [{{fullurl:{{FULLPAGENAME}}|action=edit}} rætta hesa síðu]</span>.",
        "noarticletext-nopermission": "Tað er í løtuni ongin tekstur á hesi síðu.\nTú kanst [[Special:Search/{{PAGENAME}}|leita eftir hesum síðuheiti]] á øðrum síðum, \nella <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} leita eftir viðkomandi loggum]</span>, men tú hevur ikki loyvi til at stovna hesa síðu.",
        "missing-revision": "Endurskoðan #$1 av síðuni við heitinum \"{{FULLPAGENAME}}\" er ikki til.\n\nHetta skyldast vanliga tað, at tú fylgir einari gamlari søguslóð til eina síðu, sum er blivin slettað. \nNærri frágreiðing kanst tú finna í [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} strikingar logginum].",
        "categories": "Bólkar",
        "categoriespagetext": "Fylgjandi {{PLURAL:$1|bólkur inniheldur|bólkar innihalda}} síður ella miðlar (media).\n[[Special:UnusedCategories|Ikki brúktir bólkar]] eru ikki vístar her.\nSí eisini [[Special:WantedCategories|ynsktir bólkar]].",
        "categoriesfrom": "Vís bólkar, byrja við:",
-       "special-categories-sort-count": "sortera eftir stødd",
-       "special-categories-sort-abc": "uppdeil í bókstavarøð",
        "deletedcontributions": "Slettaði brúkaraíkøst",
        "deletedcontributions-title": "Slettaði brúkaraíkøst",
        "sp-deletedcontributions-contribs": "íkøst",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|versjón|versjónir}}",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|versjón|versjónir}} frá $2",
        "javascripttest": "Royndarkoyring av JavaScript",
-       "javascripttest-pagetext-noframework": "Henda síðan er løgd av til at koyra JavaScript royndir.",
-       "javascripttest-pagetext-skins": "Vel eina útsjónd at koyra royndirnar við:",
        "tooltip-pt-userpage": "Tín brúkarasíða",
        "tooltip-pt-anonuserpage": "Brúkarasíðan fyri IP adressuna, sum tú rættar frá",
        "tooltip-pt-mytalk": "Tín kjaksíða",
index 4b97915..5b0317c 100644 (file)
                        "Gnangbade",
                        "Frigory",
                        "Lemondoge",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Yasten",
+                       "Psychoslave",
+                       "Trial"
                ]
        },
        "tog-underline": "Soulignement des liens :",
        "tog-editsectiononrightclick": "Activer la modification des sections par un clic droit sur les titres de section",
        "tog-watchcreations": "Ajouter à ma liste de suivi les pages que je crée et les fichiers que j’importe",
        "tog-watchdefault": "Ajouter à ma liste de suivi les pages et les fichiers que je modifie",
-       "tog-watchmoves": "Ajouter à ma liste de suivi les pages et les fichiers que je renomme",
+       "tog-watchmoves": "Ajouter les pages et les fichiers que je déplace dans ma liste de suivi",
        "tog-watchdeletion": "Ajouter à ma liste de suivi les pages et les fichiers que je supprime",
        "tog-watchuploads": "Ajouter les nouveaux fichiers que j’importe à ma liste de suivi",
        "tog-watchrollback": "Ajouter à ma liste de suivi les pages sur lesquelles j’ai effectué une révocation",
        "tog-enotifusertalkpages": "M’avertir par courriel si ma page de discussion est modifiée",
        "tog-enotifminoredits": "M’avertir par courriel également lors des modifications mineures des pages ou des fichiers",
        "tog-enotifrevealaddr": "Afficher mon adresse électronique dans les courriels de notification",
-       "tog-shownumberswatching": "Afficher le nombre d’utilisateurs qui suivent la page",
+       "tog-shownumberswatching": "Afficher le nombre d’utilisateurs en cours",
        "tog-oldsig": "Signature existante :",
        "tog-fancysig": "Traiter la signature comme du wikitexte (sans lien automatique)",
        "tog-uselivepreview": "Utiliser l’aperçu rapide",
        "viewhelppage": "Voir la page d'aide",
        "categorypage": "Voir la page de catégorie",
        "viewtalkpage": "Voir la page de discussion",
-       "otherlanguages": "Autres langues",
+       "otherlanguages": "Dans d'autres langues",
        "redirectedfrom": "(Redirigé depuis $1)",
        "redirectpagesub": "Page de redirection",
        "redirectto": "Rediriger vers :",
        "confirmable-no": "Non",
        "thisisdeleted": "Désirez-vous afficher ou restaurer $1 ?",
        "viewdeleted": "Voir $1 ?",
-       "restorelink": "{{PLURAL:$1|la modification effacée|les $1 modifications effacées}}",
+       "restorelink": "{{PLURAL:$1|une modification effacée|$1 modifications effacées}}",
        "feedlinks": "Flux :",
-       "feed-invalid": "Type de flux invalide.",
+       "feed-invalid": "Type de flux invalide pour abonnement.",
        "feed-unavailable": "Les flux de syndication ne sont pas disponibles",
        "site-rss-feed": "Flux RSS de $1",
        "site-atom-feed": "Flux Atom de $1",
        "yourpasswordagain": "Confirmez le mot de passe :",
        "createacct-yourpasswordagain": "Confirmez le mot de passe",
        "createacct-yourpasswordagain-ph": "Entrez à nouveau le mot de passe",
-       "remembermypassword": "Me reconnecter automatiquement lors des prochaines visites avec ce navigateur (durant au maximum $1 jour{{PLURAL:$1||s}})",
+       "remembermypassword": "Mémoriser mes données de connection avec ce navigateur (durant au maximum $1 jour{{PLURAL:$1||s}})",
        "userlogin-remembermypassword": "Garder ma session active",
        "userlogin-signwithsecure": "Utiliser une connexion sécurisée",
        "cannotloginnow-title": "Impossible de se connecter maintenant",
        "cannotloginnow-text": "La connexion n’est pas possible en utilisant $1.",
        "yourdomainname": "Votre domaine :",
        "password-change-forbidden": "Vous ne pouvez pas modifier les mots de passe sur ce wiki.",
-       "externaldberror": "Une erreur s’est produite sur la base de données d’authentification externe, ou bien vous n’êtes pas autorisé à mettre à jour votre compte externe.",
+       "externaldberror": "Soit une erreur s’est produite sur la base de données d’authentification, soit vous n’êtes pas autorisé à mettre à jour votre compte externe.",
        "login": "Connexion",
+       "login-security": "Vérifier votre identité",
        "nav-login-createaccount": "Créer un compte ou se connecter",
        "userlogin": "Créer un compte ou se connecter",
        "userloginnocreate": "Connexion",
        "userlogin-resetpassword-link": "Mot de passe oublié ?",
        "userlogin-helplink2": "Aide pour se connecter",
        "userlogin-loggedin": "Vous êtes déjà connecté{{GENDER:$1||e|(e)}} en tant que $1.\nUtilisez le formulaire ci-dessous pour vous connecter avec un autre compte utilisateur.",
+       "userlogin-reauth": "Vous devez vous reconnecter pour vérifier que vous êtes {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Créer un autre compte",
        "createacct-emailrequired": "Adresse de courriel",
        "createacct-emailoptional": "Adresse de courriel (facultative)",
        "createacct-email-ph": "Entrez votre adresse de courriel",
        "createacct-another-email-ph": "Entrez l’adresse de courriel",
        "createaccountmail": "Utiliser un mot de passe aléatoire temporaire et l’envoyer à l’adresse de courriel spécifiée",
+       "createaccountmail-help": "Peut être utilisé pour créer un compte pour une autre personne sans connaître le mot de passe.",
        "createacct-realname": "Nom réel (facultatif)",
        "createaccountreason": "Motif :",
        "createacct-reason": "Motif",
        "createacct-reason-ph": "Pourquoi créez-vous un autre compte",
+       "createacct-reason-help": "Message affiché dans le journal de création de compte",
        "createacct-submit": "Créez votre compte",
        "createacct-another-submit": "Créer le compte",
+       "createacct-continue-submit": "Continuer la création de compte",
+       "createacct-another-continue-submit": "Continuer la création de compte",
        "createacct-benefit-heading": "{{SITENAME}} est écrit par des gens comme vous.",
        "createacct-benefit-body1": "modification{{PLURAL:$1||s}}",
        "createacct-benefit-body2": "page{{PLURAL:$1||s}}",
        "nocookiesnew": "Le compte utilisateur a été créé, mais vous n’êtes pas connecté{{GENDER:||e|(e)}}.\n{{SITENAME}} utilise des cookies pour conserver la connexion mais vous les avez désactivés.\nVeuillez les activer et vous reconnecter avec le même nom et le même mot de passe.",
        "nocookieslogin": "{{SITENAME}} utilise des cookies pour conserver la connexion mais vous les avez désactivés.\nVeuillez les activer et vous reconnecter.",
        "nocookiesfornew": "Le compte utilisateur n’a pas été créé, car nous n’avons pas pu identifier son origine.\nVérifiez que vous avez activé les cookies, rechargez la page et essayez à nouveau.",
+       "createacct-loginerror": "Le compte a bien été créé mais vous ne pouvez pas vous connecter automatiquement? Veuillez vous [[Special:UserLogin|connecter manuellement]].",
        "noname": "Vous n’avez pas saisi un nom d’utilisateur valide.",
        "loginsuccesstitle": "Connecté",
        "loginsuccess": "<strong>Vous êtes maintenant connecté{{GENDER:$1||e|(e)}} à {{SITENAME}} en tant que « $1 ».</strong>",
-       "nosuchuser": "L'utilisateur « $1 » n’existe pas.\nLes noms d’utilisateurs sont sensibles à la casse.\nVérifiez l’orthographe, ou [[Special:UserLogin/signup|créez un nouveau compte]].",
+       "nosuchuser": "L’utilisateur « $1 » n’existe pas.\nLes noms d’utilisateurs sont sensibles à la casse.\nVérifiez l’orthographe, ou [[Special:CreateAccount|créez un nouveau compte]].",
        "nosuchusershort": "Il n’y a pas de contributeur avec le nom « $1 ».\nVeuillez vérifier l’orthographe.",
        "nouserspecified": "Vous devez saisir un nom d’utilisateur.",
        "login-userblocked": "Cet utilisateur est bloqué. Connexion non autorisée.",
        "noemail": "Aucune adresse de courriel n’a été enregistrée pour l'utilisateur « $1 ».",
        "noemailcreate": "Vous devez fournir une adresse de courriel valide",
        "passwordsent": "Un nouveau mot de passe a été envoyé à l’adresse de courriel de l’utilisateur « $1 ».\nVeuillez vous reconnecter après l’avoir reçu.",
-       "blocked-mailpassword": "Votre adresse IP est bloquée pour la modification. Pour éviter les abus, il n’est pas autorisé d’utiliser la récupération de mot de passe à partir de cette adresse IP.",
+       "blocked-mailpassword": "Votre adresse IP est bloquée en modification. Pour éviter les abus, il n’est pas autorisé d’utiliser la récupération de mot de passe à partir de cette adresse IP.",
        "eauthentsent": "Un courriel de confirmation a été envoyé à l’adresse indiquée.\nAvant qu’un autre courriel ne soit envoyé à ce compte, vous devrez suivre les instructions du courriel et confirmer que le compte est bien le vôtre.",
        "throttled-mailpassword": "Un courriel de réinitialisation de votre mot de passe a déjà été envoyé durant {{PLURAL:$1|la dernière heure|les $1 dernières heures}}. \nAfin d’éviter les abus, un seul courriel de réinitialisation de votre mot de passe sera envoyé par {{PLURAL:$1|heure|intervalle de $1 heures}}.",
        "mailerror": "Erreur lors de l’envoi du courriel : $1",
        "createacct-another-realname-tip": "Le véritable nom est optionnel.\nSi vous décidez de le fournir, il sera utilisé pour créditer l’auteur de ses travaux.",
        "pt-login": "Se connecter",
        "pt-login-button": "Se connecter",
+       "pt-login-continue-button": "Continuer la connexion",
        "pt-createaccount": "Créer un compte",
        "pt-userlogout": "Se déconnecter",
        "php-mail-error-unknown": "Erreur inconnue dans la fonction <code>mail()</code> de PHP.",
        "botpasswords-invalid-name": "Le nom d’utilisateur spécifié ne contient pas de séparateur de mot de passe de robots (« $1 »).",
        "botpasswords-not-exist": "L’utilisateur « $1 » n’a pas de nom de mot de passe de robots appelé « $2 ».",
        "resetpass_forbidden": "Les mots de passe ne peuvent pas être changés",
+       "resetpass_forbidden-reason": "Les mots de passe ne peuvent pas être modifiés : $1",
        "resetpass-no-info": "Vous devez être connecté pour avoir accès à cette page.",
        "resetpass-submit-loggedin": "Changer de mot de passe",
        "resetpass-submit-cancel": "Annuler",
        "passwordreset-emailsentusername": "S’il y a une adresse de courriel associée à ce nom d’utilisateur, alors un courriel de réinitialisation de mot de passe sera envoyé.",
        "passwordreset-emailsent-capture": "Un courriel de réinitialisation de mot de passe a été envoyé, qui est affiché ci-dessous.",
        "passwordreset-emailerror-capture": "Un courriel de réinitialisation de mot de passe a été généré, qui est affiché ci-dessous, mais l'envoi à l'{{GENDER:$2|utilisateur|utilisatrice}} a échoué : $1",
+       "passwordreset-emailsent-capture2": "The password reset {{PLURAL:$1|Le courriel de réinitialisation du mot de passe a été envoyé|Les courriels de réinitialisation du mot de passe ont été envoyés}}. {{PLURAL:$1|Le nom d’utilisateur et le mot de passe sont affichés|La liste des noms d’utilisateur et mots de passe est affichée}} ci-dessous.",
+       "passwordreset-emailerror-capture2": "L’envoi de courriel à {{GENDER:$2|l’utilisateur|l’utilisatrice}} a échoué : $1 {{PLURAL:$3|Le nom d’utilisateur et le mot de passe sont affichés|La liste des noms d’utilisateur et des mots de passe est affichée}} ci-dessous.",
+       "passwordreset-nocaller": "Un appelant doit être fourni",
+       "passwordreset-nosuchcaller": "L’appelant n’existe pas : $1",
+       "passwordreset-ignored": "La réinitialisation du mot de passe n’a pas été gérée. Peut-être aucun fournisseur n’a été configuré ?",
+       "passwordreset-invalideamil": "Adresse de messagerie non valide",
+       "passwordreset-nodata": "Aucun nom d’utilisateur ni d’adresse de messagerie n’a été fourni",
        "changeemail": "Changer ou supprimer l’adresse de courriel",
        "changeemail-header": "Complétez ce formulaire pour modifier votre adresse de courriel. Si vous voulez supprimer l’association d’une adresse de courriel avec votre compte, laissez la nouvelle adresse de courriel vide lors de la soumission du formulaire.",
        "changeemail-passwordrequired": "Vous devrez saisir votre mot de passe pour confirmer cette modification.",
        "accmailtext": "Un mot de passe généré aléatoirement pour [[User talk:$1|$1]] a été envoyé à $2.\nIl peut être modifié sur la page ''[[Special:ChangePassword|Changement de mot de passe]]'' après connexion.",
        "newarticle": "(Nouveau)",
        "newarticletext": "Vous avez suivi un lien vers une page qui n’existe pas encore. \nAfin de créer cette page, entrez votre texte dans la boîte ci-après (vous pouvez consulter [$1 la page d’aide] pour plus d’informations). \nSi vous êtes arrivé{{GENDER:||e}} ici par erreur, cliquez sur le bouton '''retour''' de votre navigateur.",
-       "anontalkpagetext": "---- ''Vous êtes sur la page de discussion d'un utilisateur anonyme qui n'a pas encore créé de compte ou qui n'en utilise pas. Pour cette raison, nous devons utiliser son adresse IP pour l'identifier. Une adresse IP peut être partagée par plusieurs utilisateurs. Si vous êtes un{{GENDER:||e|}} utilisat{{GENDER:|eur|rice|eur}} anonyme et si vous constatez que des commentaires qui ne vous concernent pas vous ont été adressés, vous pouvez [[Special:UserLogin/signup|créer un compte]] ou [[Special:UserLogin|vous connecter]] afin d'éviter toute confusion future avec d'autres contributeurs anonymes.''",
+       "anontalkpagetext": "----\n<em>Vous êtes sur la page de discussion d’un utilisateur anonyme qui n’a pas encore créé de compte ou qui n’en utilise pas</em>.\nPour cette raison, nous devons utiliser son adresse IP pour l’identifier.\nUne adresse IP peut être partagée par plusieurs utilisateurs.\nSi vous êtes un{{GENDER:||e|}} utilisat{{GENDER:|eur|rice|eur}} anonyme et si vous constatez que des commentaires qui ne vous concernent pas vous ont été adressés, vous pouvez [[Special:CreateAccount|créer un compte]] ou [[Special:UserLogin|vous connecter]] afin d’éviter toute confusion future avec d’autres contributeurs anonymes.",
        "noarticletext": "Il n’y a pour l’instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|lancer une recherche sur ce titre]] dans les autres pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les opérations liées]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} créer cette page]</span>.",
        "noarticletext-nopermission": "Il n'y a pour l'instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|faire une recherche sur ce titre]] dans les autres pages,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les journaux associés]</span>.",
        "missing-revision": "La révision n° $1 de la page intitulée « {{FULLPAGENAME}} » n'existe pas.\n\nCela survient en général en suivant un lien historique obsolète vers une page qui a été supprimée.\nVous pouvez trouver plus de détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
        "userpage-userdoesnotexist": "Le compte utilisateur « <nowiki>$1</nowiki> » n'est pas enregistré. Veuillez vérifier que vous voulez créer cette page.",
        "userpage-userdoesnotexist-view": "Le compte utilisateur « $1 » n'est pas enregistré.",
        "blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est indiquée ci-dessous à titre d’information :",
-       "clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> Maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) ;\n* <strong>Google Chrome :</strong> Appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) ;\n* <strong>Internet Explorer :</strong> Maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ;\n* <strong>Opera :<strong> Allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d'exploration  → Images et fichiers en cache</em>.",
+       "clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> Maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) ;\n* <strong>Google Chrome :</strong> Appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) ;\n* <strong>Internet Explorer :</strong> Maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ;\n* <strong>Opera :</strong> Allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d'exploration → Images et fichiers en cache</em>.",
        "usercssyoucanpreview": "'''Astuce :''' utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille CSS avant de l'enregistrer.",
        "userjsyoucanpreview": "'''Astuce :''' utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille JavaScript avant de l'enregistrer.",
        "usercsspreview": "'''Rappelez-vous que vous n'êtes qu'en train de prévisualiser votre propre feuille CSS.'''\n'''Elle n'a pas encore été enregistrée !'''",
        "right-override-export-depth": "Exporter les pages en incluant les pages liées jusqu'à une profondeur de 5 niveaux",
        "right-sendemail": "Envoyer un courriel aux autres utilisateurs",
        "right-passwordreset": "Voir les courriels de réinitialisation des mots de passe",
-       "right-managechangetags": "Créer et supprimer des [[Spécial:Balises|balises]] de la base de données",
+       "right-managechangetags": "Créer et (dés)activer des [[Special:Tags|balises]]",
        "right-applychangetags": "Appliquer [[Special:Tags|les balises]] avec ses propres modifications",
        "right-changetags": "Ajouter et supprimer de façon arbitraire [[Special:Tags|des balises]] sur des révisions individuelles et des entrées de journal",
+       "right-deletechangetags": "Supprimer des [[Special:Tags|balises]] de la base de données",
        "grant-generic": "ensemble de droits « $1 »",
        "grant-group-page-interaction": "Interagir avec des pages",
        "grant-group-file-interaction": "Interagir avec des médias",
        "action-viewmyprivateinfo": "voir vos informations personnelles",
        "action-editmyprivateinfo": "modifier vos informations personnelles",
        "action-editcontentmodel": "modifier le modèle de contenu d’une page",
-       "action-managechangetags": "créer et supprimer des balises de la base de données",
+       "action-managechangetags": "créer et (dés)activer des balises",
        "action-applychangetags": "appliquer les balises avec vos modifications",
        "action-changetags": "ajouter et supprimer de façon arbitraire des balises sur des révisions individuelles et des entrées de journal",
+       "action-deletechangetags": "supprimer des balises de la base de données",
        "nchanges": "$1 modification{{PLURAL:$1||s}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|depuis la dernière visite}}",
        "enhancedrc-history": "historique",
        "upload-form-label-infoform-description-tooltip": "Décrire brièvement tout ce qu’il y a de particulier concernant cette œuvre.\nPour une photo, mentionner les choses principales qui sont vues, l’occasion, ou l’endroit.",
        "upload-form-label-usage-title": "Utilisation",
        "upload-form-label-usage-filename": "Nom du fichier",
-       "foreign-structured-upload-form-label-own-work": "Je suis l’auteur de cette œuvre",
-       "foreign-structured-upload-form-label-infoform-categories": "Catégories",
-       "foreign-structured-upload-form-label-infoform-date": "Date",
-       "foreign-structured-upload-form-label-own-work-message-local": "Je confirme que je télécharge ce fichier suivant les conditions et les politiques de licence de {{SITENAME}}",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Si vous ne pouvez pas télécharger ce fichier d’après les politiques de {{SITENAME}}, veuillez fermer cette boîte de dialogue et essayer une autre méthode.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Vous pouvez aussi essayer [[Special:Upload|la page de téléchargement par défaut]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Je comprends que je téléverse ce fichier vers un dépôt partagé. Je confirme agir en accord avec les conditions d’utilisation et les règles relatives aux licences de celui-ci.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Si vous n’êtes pas en mesure de téléverser ce fichier de façon conforme aux règles de ce dépôt partagé, veuillez fermer cette boîte de dialogue et essayer une autre méthode.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Vous pouvez également essayer d’utiliser [[Special:Upload|la page de téléversement de {{SITENAME}}]], si les règles du site autorisent le téléversement du fichier.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Je certifie être le détenteur des droits d’auteur sur ce fichier, j’accepte de publier ce fichier sur Wikimedia Commons en le plaçant irrévocablement sous licence [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] et j’accepte les [https://wikimediafoundation.org/wiki/Terms_of_Use conditions d’utilisation].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Si vous n’êtes pas le détenteur des droits d’auteur sur ce fichier ou que vous voulez le publier sous une licence différente, vous pouvez utiliser l’[https://commons.wikimedia.org/wiki/Special:UploadWizard assistant d’import].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Vous pouvez également essayer d’utiliser [[Special:Upload|la page de téléversement de {{SITENAME}}]], si les règles du site autorisent le téléversement du fichier.",
+       "upload-form-label-own-work": "Je suis l’auteur de cette œuvre",
+       "upload-form-label-infoform-categories": "Catégories",
+       "upload-form-label-infoform-date": "Date",
+       "upload-form-label-own-work-message-generic-local": "Je confirme que je télécharge ce fichier suivant les conditions et les politiques de licence de {{SITENAME}}",
+       "upload-form-label-not-own-work-message-generic-local": "Si vous ne pouvez pas télécharger ce fichier d’après les politiques de {{SITENAME}}, veuillez fermer cette boîte de dialogue et essayer une autre méthode.",
+       "upload-form-label-not-own-work-local-generic-local": "Vous pouvez aussi essayer [[Special:Upload|la page de téléchargement par défaut]].",
+       "upload-form-label-own-work-message-generic-foreign": "Je comprends que je téléverse ce fichier vers un dépôt partagé. Je confirme agir en accord avec les conditions d’utilisation et les règles relatives aux licences de celui-ci.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Si vous n’êtes pas en mesure de téléverser ce fichier de façon conforme aux règles de ce dépôt partagé, veuillez fermer cette boîte de dialogue et essayer une autre méthode.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Vous pouvez également essayer d’utiliser [[Special:Upload|la page de téléversement de {{SITENAME}}]], si les règles du site autorisent le téléversement du fichier.",
        "backend-fail-stream": "Impossible de lire le fichier $1.",
        "backend-fail-backup": "Impossible de sauvegarder le fichier $1.",
        "backend-fail-notexists": "Le fichier $1 n’existe pas.",
        "changecontentmodel-success-text": "Le modèle de contenu de [[:$1]] a été modifié.",
        "changecontentmodel-cannot-convert": "Le contenu sur [[:$1]] n’a pas pu être converti en un type de $2.",
        "changecontentmodel-nodirectediting": "Le modèle de contenu $1 ne permet pas la modification directe",
+       "changecontentmodel-emptymodels-title": "Aucun modèle de contenu disponible",
+       "changecontentmodel-emptymodels-text": "Le contenu sur [[:$1]] ne peut être converti en aucun type.",
        "log-name-contentmodel": "Journal de modification de modèle de contenu",
        "log-description-contentmodel": "Événements relatifs aux modèles de contenu d’une page",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|a créé}} la page $3 en utilisant un modèle de contenu « $5 » autre que celui par défaut",
        "whatlinkshere-prev": "{{PLURAL:$1|précédente|$1 précédentes}}",
        "whatlinkshere-next": "{{PLURAL:$1|suivante|$1 suivantes}}",
        "whatlinkshere-links": "← liens",
-       "whatlinkshere-hideredirs": "$1 les redirections",
-       "whatlinkshere-hidetrans": "$1 les inclusions",
-       "whatlinkshere-hidelinks": "$1 les liens",
-       "whatlinkshere-hideimages": "$1 les liens vers le fichier",
+       "whatlinkshere-hideredirs": "Masquer les redirections",
+       "whatlinkshere-hidetrans": "Masquer les inclusions",
+       "whatlinkshere-hidelinks": "Masquer les liens",
+       "whatlinkshere-hideimages": "Masquer les liens vers le fichier",
        "whatlinkshere-filters": "Filtres",
        "whatlinkshere-submit": "Lister",
        "autoblockid": "Blocage automatique #$1",
        "lockdbsuccesstext": "La base de données a été verrouillée.<br />\nN'oubliez pas de la [[Special:UnlockDB|déverrouiller]] lorsque vous aurez terminé votre opération de maintenance.",
        "unlockdbsuccesstext": "La base de données a été déverrouillée.",
        "lockfilenotwritable": "Le fichier de verrouillage de la base de données n'est pas inscriptible.\nPour bloquer ou débloquer la base de données, il doit être accessible par le serveur web.",
+       "databaselocked": "La base de données est déjà verrouillée.",
        "databasenotlocked": "La base de données n'est pas verrouillée.",
        "lockedbyandtime": "(par $1 le $2 à $3)",
        "move-page": "Renommer $1",
        "invalidateemail": "Annuler la confirmation de l'adresse de courriel",
        "notificationemail_subject_changed": "L’adresse courriel enregistrée sur {{SITENAME}} a été changée",
        "notificationemail_subject_removed": "L’adresse courriel enregistrée sur {{SITENAME}} a été supprimée",
-       "notificationemail_body_changed": "Quelqu’un, probablement vous, connecté depuis l’adresse IP $1,\na changé l’adresse courriel associée au compte « $2 » sur {{SITENAME}}\nen « $3 ».\n\nSi ce n’était pas vous, contactez un administrateur du site immédiatement.",
+       "notificationemail_body_changed": "Quelqu’un, probablement vous, connecté depuis l’adresse IP $1, a changé l’adresse courriel\nassociée au compte « $2 » sur {{SITENAME}} en « $3 ».\n\nSi ce n’était pas vous, contactez un administrateur du site immédiatement.",
        "notificationemail_body_removed": "Quelqu’un, probablement vous, connecté depuis l’adresse IP $1,\na suprrimé l’adresse courriel associée au compte « $2 » sur {{SITENAME}}.\n\nSi ce n’était pas vous, contactez un administrateur du site immédiatement.",
        "scarytranscludedisabled": "[La transclusion interwiki est désactivée]",
        "scarytranscludefailed": "[La récupération de modèle a échoué pour $1]",
        "timezone-local": "Local",
        "duplicate-defaultsort": "Attention : la clé de tri par défaut « $2 » écrase la précédente clé « $1 ».",
        "duplicate-displaytitle": "<strong>Attention :</strong> Le titre d'affichage « $2 » remplace l'ancien titre d'affichage « $1 ».",
+       "restricted-displaytitle": "<strong>Avertissement :</strong> le titre d’affichage \"$1\" a été ignoré car il n'est pas équivalent au titre effectif de la page.",
        "invalid-indicator-name": "<strong>Erreur :</strong> L’attribut <code>name</code> des indicateurs d’état de la page ne doit pas être vide.",
        "version": "Version",
        "version-extensions": "Extensions installées",
        "tags-delete-not-found": "La balise « $1 » n’existe pas.",
        "tags-delete-too-many-uses": "La balise « $1 » est appliquée à plus de $2 {{PLURAL:$2|révision|révisions}}, ce qui signifie qu'elle ne peut pas être supprimée.",
        "tags-delete-warnings-after-delete": "La balise « $1 » a été supprimée, mais {{PLURAL:$2|l’avertissement suivant a|les avertissements suivants ont}} été rencontré{{PLURAL:$2||s}} :",
+       "tags-delete-no-permission": "Vous n’avez pas le droit de supprimer des balises de changement.",
        "tags-activate-title": "Activer la balise",
        "tags-activate-question": "Vous êtes sur le point d'activer la balise « $1 ».",
        "tags-activate-reason": "Motif :",
        "feedback-useragent": "Agent utilisateur :",
        "searchsuggest-search": "Rechercher",
        "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-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.",
        "api-error-duplicate": "Il y a déjà {{PLURAL:$1|un autre fichier présent|d'autres fichiers présents}} sur le site avec le même contenu.",
        "api-error-duplicate-archive": "Il y avait déjà {{PLURAL:$1|un autre fichier présent|d'autres fichiers présents}} sur le site avec le même contenu, mais {{PLURAL:$1|il a été supprimé|ils ont été supprimés}}.",
        "api-error-nomodule": "Erreur interne : aucun module de versement défini.",
        "api-error-ok-but-empty": "Erreur interne : Le serveur n'a pas répondu.",
        "api-error-overwrite": "Écraser un fichier existant n'est pas autorisé.",
+       "api-error-ratelimited": "Vous essayez de téléverser plus de fichiers dans un court espace de temps que ce que ce wiki peut accepter.\nVeuillez réessayer dans quelques minutes.",
        "api-error-stashfailed": "Erreur interne : le serveur n'a pas pu enregistrer le fichier temporaire.",
        "api-error-publishfailed": "Erreur interne : Le serveur n'a pas pu publier le fichier temporaire.",
        "api-error-stasherror": "Une erreur s'est produite lors du téléchargement du fichier pour le dissimuler.",
        "log-action-filter-suppress-block": "Suppression d’utilisateur par blocage",
        "log-action-filter-suppress-reblock": "Suppression d’utilisateur par blocage réitéré",
        "log-action-filter-upload-upload": "Nouveau téléversement",
-       "log-action-filter-upload-overwrite": "Réitérer le téléversement"
+       "log-action-filter-upload-overwrite": "Réitérer le téléversement",
+       "authmanager-authn-not-in-progress": "L’authentification n’est pas en cours ou les données de session ont été perdues. Veuillez recommencer depuis le début.",
+       "authmanager-authn-no-primary": "Les informations d’identification fournies n’ont pas pu être authentifiées.",
+       "authmanager-authn-no-local-user": "Les informations d’identification ne sont associées à aucun utilisateur sur ce wiki.",
+       "authmanager-authn-no-local-user-link": "Les informations d’authentification sont valides mais ne sont associées à aucun utilisateur sur ce wiki. Connectez-vous d’une autre manière, ou créez un nouvel utilisateur, et vous aurez la possibilité de lier vos informations précédentes à ce compte.",
+       "authmanager-authn-autocreate-failed": "La création automatique d’un compte local a échoué : $1",
+       "authmanager-change-not-supported": "Les informations d’identification fournies ne peuvent pas être modifiées, car rien ne les utiliserait.",
+       "authmanager-create-disabled": "La création de compte est désactivée.",
+       "authmanager-create-from-login": "Pour créer votre compte, veuillez remplir les champs ci-dessous.",
+       "authmanager-create-not-in-progress": "La création de compte n’est pas en cours, ou les données de session ont été perdues. Veuillez recommencer depuis le début.",
+       "authmanager-create-no-primary": "Les informations d’identification fournies n’ont pas pu être utilisées pour la création de compte.",
+       "authmanager-link-no-primary": "Les informations d’identification fournies n’ont pas pu être utilisées pour lier un compte.",
+       "authmanager-link-not-in-progress": "La liaison de compte n’est pas en cours ou les données de session ont été perdues. Veuillez redémarrer depuis le début.",
+       "authmanager-authplugin-setpass-failed-title": "Échec du changement de mot de passe",
+       "authmanager-authplugin-setpass-failed-message": "Le module d’authentification a refusé le changement de mot de passe.",
+       "authmanager-authplugin-create-fail": "Le module d’authentification a refusé la création de compte.",
+       "authmanager-authplugin-setpass-denied": "Le module d’authentification ne permet pas la modification des mots de passe.",
+       "authmanager-authplugin-setpass-bad-domain": "Domaine non valide.",
+       "authmanager-autocreate-noperm": "La création automatique de compte n’est pas autorisée.",
+       "authmanager-autocreate-exception": "La création automatique de compte est désactivée temporairement, du fait d’erreurs antérieures.",
+       "authmanager-userdoesnotexist": "Le compte utilisateur « $1 » n’est pas inscrit.",
+       "authmanager-userlogin-remembermypassword-help": "Indique si le mot de passe doit être mémorisé au-delà de la durée de la session.",
+       "authmanager-username-help": "Nom d’utilisateur pour l’authentification.",
+       "authmanager-password-help": "Mot de passe pour l’authentification.",
+       "authmanager-domain-help": "Domaine pour l’authentification externe.",
+       "authmanager-retype-help": "Mot de passe de nouveau pour confirmation.",
+       "authmanager-email-label": "Courriel",
+       "authmanager-email-help": "Adresse de messagerie",
+       "authmanager-realname-label": "Nom réel",
+       "authmanager-realname-help": "Nom réel de l’utilisateur",
+       "authmanager-provider-password": "Authentification par mot de passe",
+       "authmanager-provider-password-domain": "Authentification par mot de passe et domaine",
+       "authmanager-provider-temporarypassword": "Mot de passe temporaire",
+       "authprovider-confirmlink-message": "D’après vos dernières tentatives de connexion, les comptes suivants peuvent être liés à votre compte wiki. Les lier vous permettra de se connecter via ces comptes. Veuillez sélectionner lesquels doivent être liés.",
+       "authprovider-confirmlink-request-label": "Comptes qui doivent être liés",
+       "authprovider-confirmlink-success-line": "$1 : Liés avec succès.",
+       "authprovider-confirmlink-failed": "La liaison du compte n’a pas bien réussi : $1",
+       "authprovider-confirmlink-ok-help": "Continuer après l’affichage des messages d’échec de liaison.",
+       "authprovider-resetpass-skip-label": "Sauter",
+       "authprovider-resetpass-skip-help": "Sauter la réinitialisation du mot de passe.",
+       "authform-nosession-login": "L’authentification aréussi, mais votre navigateur ne peut pas se « souvenir » d’avoir été connecté.\n\n$1",
+       "authform-nosession-signup": "Le compte a été créé, mais votre navigateur ne peut pas se « souvenir » avoir été connecté.\n\n$1",
+       "authform-newtoken": "Jeton manquant. $1",
+       "authform-notoken": "Jeton manquant",
+       "authform-wrongtoken": "Mauvais jeton",
+       "specialpage-securitylevel-not-allowed-title": "Interdit",
+       "specialpage-securitylevel-not-allowed": "Désolé, vous n’êtes pas autorisé à utiliser cette page car votre identité n’a pu être vérifiée.",
+       "authpage-cannot-login": "Impossible de démarrer la connexion.",
+       "authpage-cannot-login-continue": "Impossible de continuer la connexion. Votre session a certainement expiré.",
+       "authpage-cannot-create": "Impossible de commencer la création de compte.",
+       "authpage-cannot-create-continue": "Impossible de poursuivre la création du compte. Votre session a sans doute expiré.",
+       "authpage-cannot-link": "Impossible de commencer la liaison du compte.",
+       "authpage-cannot-link-continue": "Impossible de continuer la liaison du compte. Votre session a sans doute expiré.",
+       "cannotauth-not-allowed-title": "Autorisation refusée",
+       "cannotauth-not-allowed": "Vous n’êtes pas autorisé à utiliser cette page",
+       "changecredentials": "Modifier les informations d’identification",
+       "changecredentials-submit": "Modifier",
+       "changecredentials-submit-cancel": "Annuler",
+       "changecredentials-invalidsubpage": "$1 n’est pas un type d’information d’identification valide.",
+       "changecredentials-success": "Vos informations d’identification ont été modifiées.",
+       "removecredentials": "Supprimer les informations d’identification",
+       "removecredentials-submit": "Supprimer",
+       "removecredentials-submit-cancel": "Annuler",
+       "removecredentials-invalidsubpage": "$1 n’est pas un type d’information d’identification valide.",
+       "removecredentials-success": "Vos informations d’identification ont été supprimées.",
+       "credentialsform-provider": "Type d’information d’identification :",
+       "credentialsform-account": "Nom de compte :",
+       "cannotlink-no-provider-title": "Il n’y a pas de comptes pouvant être liés",
+       "cannotlink-no-provider": "Il n’y a pas de compte pouvant être lié.",
+       "linkaccounts": "Lier les comptes",
+       "linkaccounts-success-text": "Le compte a été lié.",
+       "linkaccounts-submit": "Lier les comptes",
+       "unlinkaccounts": "Dissocier les comptes",
+       "unlinkaccounts-success": "Le compte a été dissocié."
 }
index 6f53f26..32459c7 100644 (file)
        "category-empty": "<em>Ora cela catègoria contint gins de pâge de mèdiâ.</em>",
        "hidden-categories": "{{PLURAL:$1|Catègoria cachiêe|Catègories cachiêes}}",
        "hidden-category-category": "Catègories cachiêes",
-       "category-subcat-count": "Cela catègoria at {{PLURAL:$2|mas que cela sot-catègoria-que.|{{PLURAL:$1|cela sot-catègoria|celes $1 sot-catègories}}-que, sur na soma de $2.}}",
-       "category-subcat-count-limited": "Cela catègoria at {{PLURAL:$1|cela sot-catègoria|celes $1 sot-catègories}}-que.",
-       "category-article-count": "{{PLURAL:$2|Cela catègoria contint mas que cela pâge-que.|{{PLURAL:$1|Cela pâge-que est|Celes $1 pâges-que sont}} a cela catègoria, sur na soma de $2.}}",
-       "category-article-count-limited": "{{PLURAL:$1|Cela pâge-que figure|Celes $1 pâges-que figuront}} dedens la presenta catègoria.",
-       "category-file-count": "{{PLURAL:$2|Cela catègoria contint mas que cél fichiér-que.|{{PLURAL:$1|Cél fichiér-que est|Celos $1 fichiérs-que sont}} a cela catègoria, sur na soma de $2.}}",
-       "category-file-count-limited": "{{PLURAL:$1|Cél fichiér-que figure|Celos $1 fichiérs-que figuront}} dedens la presenta catègoria.",
+       "category-subcat-count": "Cela catègoria at {{PLURAL:$2|mas que ceta sot-catègoria.|{{PLURAL:$1|ceta sot-catègoria|cetes $1 sot-catègories}}, sur na soma de $2.}}",
+       "category-subcat-count-limited": "Cela catègoria at {{PLURAL:$1|ceta sot-catègoria|cetes $1 sot-catègories}}.",
+       "category-article-count": "{{PLURAL:$2|Cela catègoria contint mas que ceta pâge.|{{PLURAL:$1|Ceta pâge est|Cetes $1 pâges sont}} a cela catègoria, sur na soma de $2.}}",
+       "category-article-count-limited": "{{PLURAL:$1|Ceta pâge figure|Cetes $1 pâges figuront}} dedens la presenta catègoria.",
+       "category-file-count": "{{PLURAL:$2|Cela catègoria contint mas que ceti fichiér.|{{PLURAL:$1|Ceti fichiér est|Cetos $1 fichiérs sont}} a cela catègoria, sur na soma de $2.}}",
+       "category-file-count-limited": "{{PLURAL:$1|Ceti fichiér figure|Cetos $1 fichiérs figuront}} dedens la presenta catègoria.",
        "listingcontinuesabbrev": "(suita)",
        "index-category": "Pâges endèxâyes",
        "noindex-category": "Pâges pas endèxâyes",
        "title-invalid-magic-tilde": "Lo titro de la pâge demandâye contint na cobla de tildes magicos pas justa (<nowiki>~~~</nowiki>).",
        "title-invalid-too-long": "Lo titro de la pâge demandâye est trop long. Dêt pas dèpassar $1 octèt{{PLURAL:$1||s}} dens l’encodâjo UTF-8.",
        "title-invalid-leading-colon": "Lo titro de la pâge demandâye contint un doux-pouents pas justo u comencement.",
-       "perfcached": "Celes balyês-que sont en cacho et pôvont pas étre a jorn. Por lo més {{PLURAL:$1|un rèsultat est disponiblo|$1 rèsultats sont disponiblos}} dedens lo cacho.",
-       "perfcachedts": "Celes balyês-que sont en cacho et sont étâyes betâyes a jorn por lo dèrriér côp lo $1. Por lo més {{PLURAL:$4|un rèsultat est disponiblo|$4 rèsultats sont disponiblos}} dedens lo cacho.",
+       "perfcached": "Cetes balyês sont en cacho et pôvont pas étre a jorn. Por lo més {{PLURAL:$1|un rèsultat est disponiblo|$1 rèsultats sont disponiblos}} dedens lo cacho.",
+       "perfcachedts": "Cetes balyês sont en cacho et sont étâyes betâyes a jorn por lo dèrriér côp lo $1. Por lo més {{PLURAL:$4|un rèsultat est disponiblo|$4 rèsultats sont disponiblos}} dedens lo cacho.",
        "querypage-no-updates": "Ora les mêses a jorn por cela pâge sont dèsactivâyes.\nLes balyês ique seront pas betâyes a jorn.",
        "viewsource": "Vêre lo tèxto sôrsa",
        "viewsource-title": "Vêre lo tèxto sôrsa de $1",
        "protectedinterface": "Cela pâge balye de tèxto d’entredoux por la programeria sur cél vouiqui et est vêr protègiêe por èvitar los abus.\nPor apondre ou ben changiér de traduccions sur tôs los vouiquis, se vos plét empleyéd [//translatewiki.net/ translatewiki.net], lo projèt de localisacion de MediaWiki.",
        "editinginterface": "<strong>Atencion :</strong> vos éte aprés changiér na pâge empleyêe por fâre lo tèxto d’entredoux de la programeria.\nLos changements sè cognetront sur l’aparence de l’entredoux utilisator por los ôtros utilisators de cél vouiqui.",
        "translateinterface": "Por apondre changiér de traduccions sur tôs los vouiquis, se vos plét empleyéd [//translatewiki.net/ translatewiki.net], lo projèt de localisacion de MediaWiki.",
-       "cascadeprotected": "Cela pâge est protègiêe contre los changements, el est transcllua per {{PLURAL:$1|cela pâge-que qu’est étâye protègiêe|celes pâges-que que sont étâyes protègiêes}} avouéc lo chouèx « protèccion en cascâda » activâ :\n$2",
+       "cascadeprotected": "Cela pâge est protègiêe contre los changements, el est transcllua per {{PLURAL:$1|ceta pâge qu’est étâye protègiêe|cetes pâges que sont étâyes protègiêes}} avouéc lo chouèx « protèccion en cascâda » activâ :\n$2",
        "namespaceprotected": "Vos éd pas la pèrmission de changiér les pâges de l’èspâço de noms « <strong>$1</strong> ».",
        "customcssprotected": "Vos éd pas la pèrmission de changiér cela pâge CSS, contint la configuracion a sè d’un ôtr’utilisator.",
        "customjsprotected": "Vos éd pas la pèrmission de changiér cela pâge JavaScript, contint la configuracion a sè d’un ôtr’utilisator.",
        "noname": "Vos éd pas spècifiâ un nom d’utilisator justo.",
        "loginsuccesstitle": "Branchiê",
        "loginsuccess": "<strong>Ora vos éte branchiê{{GENDER:$1||e}} a {{SITENAME}} coment « $1 ».</strong>",
-       "nosuchuser": "Y at gins d’utilisator avouéc lo nom « $1 ».\nLos noms d’utilisator sont sensiblos a la câssa.\nSe vos plét, controlâd l’ortografia ou ben [[Special:UserLogin/signup|féte un comptio novél]].",
+       "nosuchuser": "Y at gins d’utilisator avouéc lo nom « $1 ».\nLos noms d’utilisator sont sensiblos a la câssa.\nSe vos plét, controlâd l’ortografia ou ben [[Special:CreateAccount|féte un comptio novél]].",
        "nosuchusershort": "Y at gins d’utilisator avouéc lo nom « $1 ».\nSe vos plét, controlâd l’ortografia.",
        "nouserspecified": "Vos dête spècifiar un nom d’utilisator.",
        "login-userblocked": "Cél utilisator est blocâ. Branchement pas ôtorisâ.",
        "mailerror": "Fôta pendent l’èxpèdicion du mèssâjo : $1",
        "acct_creation_throttle_hit": "Des vesitors de cél vouiqui qu’emplèyont voutron adrèce IP ant fêt $1 comptio{{PLURAL:$1||s}} pendent lo jorn passâ, cen qu’est lo més ôtorisâ dens ceti temps.\nDu côp los vesitors qu’emplèyont cel’adrèce IP pôvont fâre gins de comptio por lo moment.",
        "emailauthenticated": "Voutron adrèce èlèctronica est étâye confirmâye lo $2 a $3.",
-       "emailnotauthenticated": "Voutron adrèce èlèctronica est p’oncor confirmâye.\nNion mèssâjo serat mandâ por châcuna de celes fonccionalitâts-que.",
+       "emailnotauthenticated": "Voutron adrèce èlèctronica est p’oncor confirmâye.\nNion mèssâjo serat mandâ por châcuna de cetes fonccionalitâts.",
        "noemailprefs": "Spècifiâd un’adrèce èlèctronica dens voutres prèferences por empleyér celes fonccionalitâts.",
        "emailconfirmlink": "Confirmâd voutron adrèce èlèctronica",
        "invalidemailaddress": "Cel’adrèce èlèctronica pôt pas étre accèptâye, semble avêr un format pas justo.\nSe vos plét, buchiéd un’adrèce ben formatâye ou ben lèssiéd cél champ vouedo.",
        "passwordreset-capture-help": "Se vos pouentâd cela câsa, lo mèssâjo (avouéc lo contresegno temporèro) vos serat montrâ quand serat mandâ a l’utilisator.",
        "passwordreset-email": "Adrèce èlèctronica :",
        "passwordreset-emailtitle": "Dètalys du comptio dessus {{SITENAME}}",
-       "passwordreset-emailtext-ip": "Yon (de sûr vos, avouéc l’adrèce IP $1) at demandâ na remês’a zérô de voutron\ncontresegno por {{SITENAME}} ($4). {{PLURAL:$3|Cél comptio utilisator-que est associyê|Celos comptios utilisators-que sont associyês}}\na cel’adrèce èlèctronica :\n\n$2\n\n{{PLURAL:$3|Cél contresegno temporèro èxpirerat|Celos contresegnos temporèros èxpireront}} dens {{PLURAL:$5|un jorn|$5 jorns}}.\nOra vos vos dête branchiér et pués chouèsir un contresegno novél. Se cela demanda vint pas de vos\nou ben se vos vos éte rapelâ de voutron contresegno originâl et que vos en voléd pas més changiér,\nvos pouede ignorar cél mèssâjo et continuar a empleyér voutron viely contresegno.",
-       "passwordreset-emailtext-user": "L’utilisator $1 dessus {{SITENAME}} at demandâ na remês’a zérô de voutron contresegno por {{SITENAME}}\n($4). {{PLURAL:$3|Cél comptio utilisator-que est associyê|Celos comptios utilisators-que sont associyês}}\na cel’adrèce èlèctronica :\n\n$2\n\n{{PLURAL:$3|Cél contresegno temporèro èxpirerat|Celos contresegnos temporèros èxpireront}} dens {{PLURAL:$5|un jorn|$5 jorns}}.\nOra vos vos dête branchiér et pués chouèsir un contresegno novél. Se cela demanda vint pas de vos\nou ben se vos vos éte rapelâ de voutron contresegno originâl et que vos en voléd pas més changiér,\nvos pouede ignorar cél mèssâjo et continuar a empleyér voutron viely contresegno.",
+       "passwordreset-emailtext-ip": "Yon (de sûr vos, avouéc l’adrèce IP $1) at demandâ na remês’a zérô de voutron\ncontresegno por {{SITENAME}} ($4). {{PLURAL:$3|Ceti comptio utilisator est associyê|Cetos comptios utilisators sont associyês}}\na cel’adrèce èlèctronica :\n\n$2\n\n{{PLURAL:$3|Cél contresegno temporèro èxpirerat|Celos contresegnos temporèros èxpireront}} dens {{PLURAL:$5|un jorn|$5 jorns}}.\nOra vos vos dête branchiér et pués chouèsir un contresegno novél. Se cela demanda vint pas de vos\nou ben se vos vos éte rapelâ de voutron contresegno originâl et que vos en voléd pas més changiér,\nvos pouede ignorar cél mèssâjo et continuar a empleyér voutron viely contresegno.",
+       "passwordreset-emailtext-user": "L’utilisator $1 dessus {{SITENAME}} at demandâ na remês’a zérô de voutron contresegno por {{SITENAME}}\n($4). {{PLURAL:$3|Ceti comptio utilisator est associyê|Cetos comptios utilisators sont associyês}}\na cel’adrèce èlèctronica :\n\n$2\n\n{{PLURAL:$3|Cél contresegno temporèro èxpirerat|Celos contresegnos temporèros èxpireront}} dens {{PLURAL:$5|un jorn|$5 jorns}}.\nOra vos vos dête branchiér et pués chouèsir un contresegno novél. Se cela demanda vint pas de vos\nou ben se vos vos éte rapelâ de voutron contresegno originâl et que vos en voléd pas més changiér,\nvos pouede ignorar cél mèssâjo et continuar a empleyér voutron viely contresegno.",
        "passwordreset-emailelement": "Nom d’utilisator :\n$1\n\nContresegno temporèro :\n$2",
        "passwordreset-emailsentemail": "Se cel’adrèce èlèctronica est associyêe a voutron comptio, adonc un mèssâjo de remês’a zérô de contresegno serat mandâ.",
        "passwordreset-emailsentusername": "S’y at un’adrèce èlèctronica associyêe a cél nom d’utilisator, adonc un mèssâjo de remês’a zérô de contresegno serat mandâ.",
        "minoredit": "O est un petiôt changement",
        "watchthis": "Siuvre cela pâge",
        "savearticle": "Encartar la pâge",
+       "publishpage": "Publeyér la pâge",
        "preview": "Apèrçu",
        "showpreview": "Montrar un apèrçu",
        "showdiff": "Montrar los changements",
        "accmailtext": "Un contresegno fêt a l’hasârd por [[User talk:$1|$1]] est étâ mandâ a $2. Pôt étre changiê dessus la pâge de <em>[[Special:ChangePassword|changement de contresegno]]</em> aprés branchement.",
        "newarticle": "(Novél)",
        "newarticletext": "Vos éd siuvu un lim de vers na pâge qu’ègziste p’oncor.\nPor fâre cela pâge, buchiéd voutron tèxto dedens la bouèta ce-desot (vêde la [$1 pâge d’éde] por més d’enformacions).\nSe vos éte arrevâ{{GENDER:||ye}} ique per fôta, cllicâd dessus lo boton <strong>Devant</strong> de voutron navegator.",
-       "anontalkpagetext": "----\n<em>O est la pâge de discussion d’un utilisator anonimo qu’at p’oncor fêt un comptio ou ben que nen emplèye pas.</em>\nPor cen, nos devens empleyér son adrèce IP numerica por o identifiar.\nUn’adrèce IP d’ense pôt étre partagiêe per un mouél d’utilisators.\nSe vos éte {{GENDER:|un utilisator|un’utilisatrice}} anonim{{GENDER:|o|a}} et pués se vos constatâd que de comentèros que vos regârdont pas vos sont étâs adrèciês, se vos plét [[Special:UserLogin/signup|féte un comptio]] ou ben [[Special:UserLogin|branchiéd-vos]] por èvitar tota confusion que vint avouéc d’ôtros utilisators anonimos.",
+       "anontalkpagetext": "----\n<em>O est la pâge de discussion d’un utilisator anonimo qu’at p’oncor fêt un comptio ou ben que nen emplèye pas.</em>\nPor cen, nos devens empleyér son adrèce IP numerica por o identifiar.\nUn’adrèce IP d’ense pôt étre partagiêe per un mouél d’utilisators.\nSe vos éte {{GENDER:|un utilisator|un’utilisatrice}} anonim{{GENDER:|o|a}} et pués se vos constatâd que de comentèros que vos regârdont pas vos sont étâs adrèciês, se vos plét [[Special:CreateAccount|féte un comptio]] ou ben [[Special:UserLogin|branchiéd-vos]] por èvitar tota confusion que vint avouéc d’ôtros utilisators anonimos.",
        "noarticletext": "Ora y at gins de tèxto dedens cela pâge.\nVos pouede [[Special:Search/{{PAGENAME}}|fâre na rechèrche sur cél titro]] dedens les ôtres pâges,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechèrchiér dedens los jornâls liyês]\nou ben [{{fullurl:{{FULLPAGENAME}}|action=edit}} fâre cela pâge]</span>.",
        "noarticletext-nopermission": "Ora y at gins de tèxto dedens cela pâge.\nVos pouede [[Special:Search/{{PAGENAME}}|fâre na rechèrche sur cél titro]] dedens les ôtres pâges ou ben <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechèrchiér dedens los jornâls liyês]</span>, mas vos éd pas la pèrmission de fâre cela pâge.",
        "missing-revision": "La vèrsion n° $1 de la pâge apelâye « {{FULLPAGENAME}} » ègziste pas.\n\nEn g·ènèrâl cen arreve en siuvent un lim dèpassâ d’un historico de vers na pâge qu’est étâye suprimâye.\nVos pouede trovar més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
        "userpage-userdoesnotexist": "Lo comptio utilisator « $1 » est pas encartâ.\nSe vos plét, controlâd se vos voléd fâre changiér cela pâge.",
        "userpage-userdoesnotexist-view": "Lo comptio utilisator « $1 » est pas encartâ.",
        "blocked-notice-logextract": "Ora {{GENDER:$1|cél utilisator|cel’utilisatrice}} est blocâ{{GENDER:$1||ye}}.\nLa dèrriére entrâ du jornâl des blocâjos est balyêe ce-desot coment rèference :",
-       "clearyourcache": "<strong>Nota :</strong> aprés avêr encartâ, sè pôt que vos deveyéd forciér lo rechargement complèt du cacho de voutron navegator por vêre los changements.\n* <strong>Firefox / Safari :</strong> mantegnéd la toche <em>Granta Lètra</em> (<em>Shift</em>) en clliquent dessus <em>Rechargiér</em> (<em>Reload</em>) ou ben apoyéd dessus <em>Ctrl-F5</em> <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac)\n* <strong>Google Chrome :</strong> apoyéd dessus <em>Ctrl-Shift-R</em> (''⌘-Shift-R</em> sur un Mac)\n* <strong>Internet Explorer :</strong> mantegnéd la toche <em>Ctrl</em> en clliquent dessus <em>Rafrèchir</em> (<em>Refresh</em>) ou ben apoyéd dessus <em>Ctrl-F5</em>\n* <strong>Opera :</strong> vouedâd lo cacho dedens <em>Outils → Prèferences</em>",
+       "clearyourcache": "<strong>Nota :</strong> aprés avêr encartâ, sè pôt que vos deveyéd forciér lo rechargement complèt du cacho de voutron navegator por vêre los changements.\n* <strong>Firefox / Safari :</strong> mantegnéd la toche <em>Granta Lètra</em> (<em>Shift</em>) en clliquent dessus <em>Rechargiér</em> (<em>Reload</em>) ou ben apoyéd dessus <em>Ctrl-F5</em> <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac)\n* <strong>Google Chrome :</strong> apoyéd dessus <em>Ctrl-Shift-R</em> (''⌘-Shift-R</em> sur un Mac)\n* <strong>Internet Explorer :</strong> mantegnéd la toche <em>Ctrl</em> en clliquent dessus <em>Rafrèchir</em> (<em>Refresh</em>) ou ben apoyéd dessus <em>Ctrl-F5</em>\n* <strong>Opera :<strong> alâd dedens <em>Menu → Settings</em> (<em>Opera → Prèferences</em> sur un Mac) et pués a <em>Confidencialitât & sècuritât → Vouedar les balyês d’èxploracion → Émâges et fichiérs en cacho</em>.",
        "usercssyoucanpreview": "<strong>Combina :</strong> empleyéd lo boton « {{int:showpreview}} » por èprovar voutra fôlye CSS novèla devant que l’encartar.",
        "userjsyoucanpreview": "<strong>Combina :</strong> empleyéd lo boton « {{int:showpreview}} » por èprovar voutra fôlye JavaScript novèla devant que l’encartar.",
        "usercsspreview": "<strong>Rapelâd-vos que vos éte ren qu’aprés prèvêre voutra fôlye CSS.\nEl est p’oncor étâye encartâye !</strong>",
        "readonlywarning": "<strong>Atencion : la bâsa de balyês est étâye cotâye por na rotina d’entretin, cen fât que vos porréd vêr pas encartar voutros changements orendrêt.</strong>\nVos pouede copiyér et côlar voutron tèxto dedens un fichiér tèxto et pués l’encartar por ples târd.\n\nL’administrator sistèmo que l’at cotâ at balyê cel’èxplicacion : $1",
        "protectedpagewarning": "<strong>Atencion : cela pâge est étâye protègiêe por que solament los utilisators qu’ant los drêts d’administrator la pouessont changiér.</strong>\nLa dèrriére entrâ du jornâl est balyêe ce-desot coment rèference :",
        "semiprotectedpagewarning": "<strong>Nota :</strong> cela pâge est étâye protègiêe por que solament los utilisators encartâs la pouessont changiér.\nLa dèrriére entrâ du jornâl est balyêe ce-desot coment rèference :",
-       "cascadeprotectedwarning": "<strong>Atencion :</strong> cela pâge est étâye protègiêe por que solament los utilisators qu’ant los drêts d’administrator la pouessont changiér, el est transcllua dedens {{PLURAL:$1|cela pâge protègiêe|celes pâges protègiêes}}-que avouéc la « protèccion en cascâda » activâye :",
+       "cascadeprotectedwarning": "<strong>Atencion :</strong> cela pâge est étâye protègiêe por que solament los utilisators qu’ant los drêts d’administrator la pouessont changiér, el est transcllua dedens {{PLURAL:$1|ceta pâge protègiêe|cetes pâges protègiêes}} avouéc la « protèccion en cascâda » activâye :",
        "titleprotectedwarning": "<strong>Atencion : cela pâge est étâye protègiêe por que de [[Special:ListGroupRights|drêts spècificos]] sont nècèssèros por la povêr fâre.</strong>\nLa dèrriére entrâ du jornâl est balyêe ce-desot coment rèference :",
        "templatesused": "{{PLURAL:$1|Modèlo empleyê|Modèlos empleyês}} per cela pâge :",
        "templatesusedpreview": "{{PLURAL:$1|Modèlo empleyê|Modèlos empleyês}} dedens cél apèrçu :",
        "sectioneditnotsupported-title": "Changement de sèccion pas recognu",
        "sectioneditnotsupported-text": "Lo changement d’una sèccion est pas recognu dens cela pâge.",
        "permissionserrors": "Fôta de pèrmission",
-       "permissionserrorstext": "Vos éd pas la pèrmission de fâre l’accion demandâye por {{PLURAL:$1|cela rêson|celes rêsons}}-que :",
-       "permissionserrorstext-withaction": "Vos éd pas la pèrmission de $2 por {{PLURAL:$1|cela rêson|celes rêsons}}-que :",
+       "permissionserrorstext": "Vos éd pas la pèrmission de fâre l’accion demandâye por {{PLURAL:$1|ceta rêson|cetes rêsons}} :",
+       "permissionserrorstext-withaction": "Vos éd pas la pèrmission de $2 por {{PLURAL:$1|ceta rêson|cetes rêsons}} :",
        "contentmodelediterror": "Vos pouede pas changiér cela vèrsion, son modèlo de contegnu est <code>$1</code>, cen que sè difèrence du modèlo de contegnu d’ora de la pâge <code>$2</code>.",
        "recreate-moveddeleted-warn": "<strong>Atencion : vos éte aprés refâre na pâge qu’est étâye suprimâye dês devant.</strong>\n\nDemandâd-vos se fôt franc continuar son changement.\nLo jornâl de les suprèssions et des dèplacements de cela pâge est balyê ce-desot por comoditât :",
        "moveddeleted-notice": "Cela pâge est étâye suprimâye.\nLo jornâl de les suprèssions et des dèplacements de cela pâge est balyê ce-desot coment rèference.",
        "logdelete-text": "Los èvènements du jornâl suprimâs continueront a aparêtre dedens los jornâls, mas na partia de lor contegnu serat inaccèssibla u publico.",
        "revdelete-text-others": "Los ôtros administrators porront adés arrevar u contegnu cachiê et lo refâre, a muens que des rèstriccions de més seyont dèfenies.",
        "revdelete-confirm": "Se vos plét, confirmâd qu’o est franc cen que vos voléd fâre, que vos en compregnéd les consèquences, et pués que vos o féte en acôrd avouéc les [[{{MediaWiki:Policy-url}}|politiques]].",
-       "revdelete-suppress-text": "La rèprèssion dêt étre empleyêe <strong>ren que</strong> dens celos câs-que :\n* Enformacions que pôvont étre difamatouères\n* Enformacions a sè que vant pas avouéc\n*: <em>adrèce, numerô de tèlèfono, numerô d’identificacion nacionâl, et tot cen que vat avouéc</em>",
+       "revdelete-suppress-text": "La rèprèssion dêt étre empleyêe <strong>ren que</strong> dens cetos câs :\n* Enformacions que pôvont étre difamatouères\n* Enformacions a sè que vant pas avouéc\n*: <em>adrèce, numerô de tèlèfono, numerô d’identificacion nacionâl, et tot cen que vat avouéc</em>",
        "revdelete-legend": "Dèfenir de rèstriccions de visibilitât",
        "revdelete-hide-text": "Tèxto de la vèrsion",
        "revdelete-hide-image": "Cachiér lo contegnu du fichiér",
        "mergehistory-from": "Pâge d’origina :",
        "mergehistory-into": "Pâge de dèstinacion :",
        "mergehistory-list": "Historico des changements que pôvont étre fusionâs",
-       "mergehistory-merge": "Celes vèrsions-que de [[:$1]] pôvont étre fusionâyes dedens [[:$2]].\nEmpleyéd la colona de botons de chouèx por fusionar mas que les vèrsions fêtes dês lo comencement tant qu’a la dâta spècifiâye.\nNotâd que l’usâjo des lims de navegacion rebeterat a zérô cela colona.",
+       "mergehistory-merge": "Cetes vèrsions de [[:$1]] pôvont étre fusionâyes dedens [[:$2]].\nEmpleyéd la colona de botons de chouèx por fusionar mas que les vèrsions fêtes dês lo comencement tant qu’a la dâta spècifiâye.\nNotâd que l’usâjo des lims de navegacion rebeterat a zérô cela colona.",
        "mergehistory-go": "Montrar los changements que pôvont étre fusionâs",
        "mergehistory-submit": "Fusionar les vèrsions",
        "mergehistory-empty": "Niona vèrsion pôt étre fusionâye.",
        "upload_directory_read_only": "Lo rèpèrtouèro de tèlèchargement ($1) est pas enscriptiblo per lo sèrvior Vouèbe.",
        "uploaderror": "Fôta pendent lo tèlèchargement",
        "upload-recreate-warning": "<strong>Atencion : un fichiér avouéc cél nom est étâ suprimâ dèplaciê.</strong>\n\nLo jornâl de les suprèssions et des dèplacements de cela pâge est balyê ique por comoditât :",
-       "uploadtext": "Empleyéd lo formulèro ce-desot por tèlèchargiér de fichiérs.\nPor vêre rechèrchiér de fichiérs tèlèchargiês dês devant, vêde la [[Special:FileList|lista des fichiérs tèlèchargiês]]. Los (re-)tèlèchargements sont asse-ben encartâs dessus lo [[Special:Log/upload|jornâl des tèlèchargements]], et les suprèssions dessus lo [[Special:Log/delete|jornâl de les suprèssions]].\n\nPor rapondre un fichiér dedens na pâge, empleyéd un lim de yona de celes fôrmes-que :\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.jpg]]</nowiki></code></strong> por empleyér la vèrsion en plêna largior du fichiér\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.png|200px|thumb|left|tèxto dèscriptif]]</nowiki></code></strong> por empleyér na miniatura de 200 pixèls de lârjo dedens na bouèt’a gôche avouéc « tèxto dèscriptif » coment dèscripcion\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Fichiér.ogg]]</nowiki></code></strong> por liyér tot drêt vers lo fichiér sen lo fâre vêre",
+       "uploadtext": "Empleyéd lo formulèro ce-desot por tèlèchargiér de fichiérs.\nPor vêre rechèrchiér de fichiérs tèlèchargiês dês devant, vêde la [[Special:FileList|lista des fichiérs tèlèchargiês]]. Los (re-)tèlèchargements sont asse-ben encartâs dessus lo [[Special:Log/upload|jornâl des tèlèchargements]], et les suprèssions dessus lo [[Special:Log/delete|jornâl de les suprèssions]].\n\nPor rapondre un fichiér dedens na pâge, empleyéd un lim de yona de cetes fôrmes :\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.jpg]]</nowiki></code></strong> por empleyér la vèrsion en plêna largior du fichiér\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.png|200px|thumb|left|tèxto dèscriptif]]</nowiki></code></strong> por empleyér na miniatura de 200 pixèls de lârjo dedens na bouèt’a gôche avouéc « tèxto dèscriptif » coment dèscripcion\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Fichiér.ogg]]</nowiki></code></strong> por liyér tot drêt vers lo fichiér sen lo fâre vêre",
        "upload-permitted": "Tipo{{PLURAL:$2||s}} de fichiérs ôtorisâ{{PLURAL:$2||s}} : $1.",
        "upload-preferred": "Tipo{{PLURAL:$2||s}} de fichiérs prèferâ{{PLURAL:$2||s}} : $1.",
        "upload-prohibited": "Tipo{{PLURAL:$2||s}} de fichiérs dèfendu{{PLURAL:$2||s}} : $1.",
        "file-thumbnail-no": "Lo nom du fichiér comence per <strong>$1</strong>.\nSemble étre un’émâge en talye rèduita <em>(miniatura)</em>.\nSe vos éd cel’émâge en hôta rèsolucion, tèlèchargiéd-la, ôtrament changiéd son nom, se vos plét.",
        "fileexists-forbidden": "Un fichiér avouéc cél nom ègziste ja et pôt pas étre ècllafâ.\nSe vos voléd adés tèlèchargiér voutron fichiér, se vos plét tornâd arriér et pués empleyéd un novél nom.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Un fichiér avouéc cél nom ègziste ja dedens lo dèpôt de fichiérs partagiê.\nSe vos voléd adés tèlèchargiér voutron fichiér, se vos plét tornâd arriér et pués empleyéd un novél nom.\n[[File:$1|thumb|center|$1]]",
-       "file-exists-duplicate": "Cél fichiér est un doblo de {{PLURAL:$1|cél fichiér|celos fichiérs}}-que :",
+       "file-exists-duplicate": "Cél fichiér est un doblo de {{PLURAL:$1|ceti fichiér|cetos fichiérs}} :",
        "file-deleted-duplicate": "Un fichiér pariér a ceti ([[:$1]]) est ja étâ suprimâ.\nVos devriâd controlar lo jornâl de les suprèssions de cél fichiér devant que lo tornar tèlèchargiér.",
        "file-deleted-duplicate-notitle": "Un fichiér pariér a ceti est ja étâ suprimâ et pués lo titro rèprimâ.\nVos devriâd demandar a quârqu’un avouéc la possibilitât de vêre les balyês du fichiér rèprimâ por ègzamenar la situacion devant que lo tornar tèlèchargiér.",
        "uploadwarning": "Atencion !",
        "upload-form-label-infoform-description-tooltip": "Dècrire vito tot cen qu’y at de particuliér por cel’ôvra.\nPor na fotô, mencionar les chouses principâles que sont semondues, l’ocasion ou ben l’endrêt.",
        "upload-form-label-usage-title": "Usâjo",
        "upload-form-label-usage-filename": "Nom du fichiér",
-       "foreign-structured-upload-form-label-own-work": "Su l’ôtor de cel’ôvra",
-       "foreign-structured-upload-form-label-infoform-categories": "Catègories",
-       "foreign-structured-upload-form-label-infoform-date": "Dâta",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que tèlèchârjo cél fichiér d’aprés les condicions d’usâjo et les politiques de licence de {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Se vos pouede pas tèlèchargiér cél fichiér d’aprés les politiques de {{SITENAME}}, se vos plét cllôde cela bouèta de dialogo et pués èprovâd un’ôtra mètoda.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Vos pouede asse-ben èprovar la [[Special:Upload|pâge de tèlèchargement per dèfôt]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Compregno que tèlèchârjo cél fichiér vers un dèpôt partagiê. Confirmo qu’o fé d’aprés les condicions d’usâjo et les politiques de licence de ceti.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Se vos pouede pas tèlèchargiér cél fichiér d’aprés les politiques du dèpôt partagiê, se vos plét cllôde cela bouèta de dialogo et pués èprovâd un’ôtra mètoda.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Vos pouede asse-ben èprovar d’empleyér la [[Special:Upload|pâge de tèlèchargement de {{SITENAME}}]], se cél fichiér y pôt étre tèlèchargiê d’aprés lors politiques.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Cèrtifio étre lo dètentior des drêts d’ôtor sur cél fichiér, et j’accèpto de publeyér cél fichiér dessus Wikimedia Commons en lo betent irrèvocâblament desot licence [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Atribucion - Partâjo dens les Mémes Condicions 4.0] et pués j’accèpto les [https://wikimediafoundation.org/wiki/Terms_of_Use condicions d’usâjo].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Se vos éte pas lo dètentior des drêts d’ôtor sur cél fichiér ou ben que vos lo voléd publeyér desot na licence difèrenta, vos pouede empleyér l’[https://commons.wikimedia.org/wiki/Special:UploadWizard assistent de tèlèchargement de Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Vos pouede asse-ben èprovar d’empleyér la [[Special:Upload|pâge de tèlèchargement de {{SITENAME}}]], se cél fichiér y pôt étre tèlèchargiê d’aprés lors politiques.",
+       "upload-form-label-own-work": "Su l’ôtor de cel’ôvra",
+       "upload-form-label-infoform-categories": "Catègories",
+       "upload-form-label-infoform-date": "Dâta",
+       "upload-form-label-own-work-message-generic-local": "Confirmo que tèlèchârjo cél fichiér d’aprés les condicions d’usâjo et les politiques de licence de {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Se vos pouede pas tèlèchargiér cél fichiér d’aprés les politiques de {{SITENAME}}, se vos plét cllôde cela bouèta de dialogo et pués èprovâd un’ôtra mètoda.",
+       "upload-form-label-not-own-work-local-generic-local": "Vos pouede asse-ben èprovar la [[Special:Upload|pâge de tèlèchargement per dèfôt]].",
+       "upload-form-label-own-work-message-generic-foreign": "Compregno que tèlèchârjo cél fichiér vers un dèpôt partagiê. Confirmo qu’o fé d’aprés les condicions d’usâjo et les politiques de licence de ceti.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se vos pouede pas tèlèchargiér cél fichiér d’aprés les politiques du dèpôt partagiê, se vos plét cllôde cela bouèta de dialogo et pués èprovâd un’ôtra mètoda.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Vos pouede asse-ben èprovar d’empleyér la [[Special:Upload|pâge de tèlèchargement de {{SITENAME}}]], se cél fichiér y pôt étre tèlèchargiê d’aprés lors politiques.",
        "backend-fail-stream": "Y at pas moyen de tramandar lo fichiér « $1 ».",
        "backend-fail-backup": "Y at pas moyen d’encartar lo fichiér « $1 ».",
        "backend-fail-notexists": "Lo fichiér $1 ègziste pas.",
        "filehist-filesize": "Talye du fichiér",
        "filehist-comment": "Comentèro",
        "imagelinks": "Usâjo du fichiér",
-       "linkstoimage": "{{PLURAL:$1|Cela pâge-que emplèye|Celes $1 pâges-que emplèyont}} cél fichiér :",
-       "linkstoimage-more": "Més {{PLURAL:$1|d’una pâge emplèye|de $1 pâges emplèyont}} cél fichiér.\nCela lista-que montre mas que {{PLURAL:$1|la premiére pâge qu’emplèye|les $1 premiéres pâges qu’emplèyont}} cél fichiér.\nNa [[Special:WhatLinksHere/$2|lista complèta]] est disponibla.",
+       "linkstoimage": "{{PLURAL:$1|Ceta pâge emplèye|Cetes $1 pâges emplèyont}} cél fichiér :",
+       "linkstoimage-more": "Més {{PLURAL:$1|d’una pâge emplèye|de $1 pâges emplèyont}} cél fichiér.\nCeta lista montre mas que {{PLURAL:$1|la premiére pâge qu’emplèye|les $1 premiéres pâges qu’emplèyont}} cél fichiér.\nNa [[Special:WhatLinksHere/$2|lista complèta]] est disponibla.",
        "nolinkstoimage": "Niona pâge emplèye cél fichiér.",
        "morelinkstoimage": "Vêde [[Special:WhatLinksHere/$1|més de lims]] de vers cél fichiér.",
        "linkstoimage-redirect": "$1 (redirèccion de fichiér) $2",
        "unusedtemplatestext": "Cela pâge liste totes les pâges de l’èspâço de noms « {{ns:template}} » que sont pas rapondues dedens nion’ôtra pâge.\nOubliâd pas de controlar s’y at gins d’ôtro lim de vers los modèlos devant que los suprimar.",
        "unusedtemplateswlh": "ôtros lims",
        "randompage": "Pâge a l’hasârd",
-       "randompage-nopages": "Y at gins de pâge dedens {{PLURAL:$2|cél èspâço|celos èspâços}} de noms-que : $1.",
+       "randompage-nopages": "Y at gins de pâge dedens {{PLURAL:$2|cet’èspâço|cetos èspâços}} de noms : $1.",
        "randomincategory": "Pâge a l’hasârd dedens la catègoria",
        "randomincategory-invalidcategory": "« $1 » est pas un nom de catègoria justo.",
        "randomincategory-nopages": "Y at gins de pâge dedens la catègoria [[:Category:$1|$1]].",
        "double-redirect-fixed-maintenance": "Corrèccion ôtomatica de la redirèccion dobla de [[$1]] de vers [[$2]] dens un ovrâjo d’entretin.",
        "double-redirect-fixer": "Corrèctor de redirèccion",
        "brokenredirects": "Redirèccions câsses",
-       "brokenredirectstext": "Celes redirèccions-que mènont vers de pâges inègzistentes :",
+       "brokenredirectstext": "Cetes redirèccions mènont vers de pâges inègzistentes :",
        "brokenredirects-edit": "changiér",
        "brokenredirects-delete": "suprimar",
        "withoutinterwiki": "Pâges sen lims entèrlengoues",
-       "withoutinterwiki-summary": "Celes pâges-que ant gins de lim de vers des vèrsions en ôtres lengoues.",
+       "withoutinterwiki-summary": "Cetes pâges ant gins de lim de vers des vèrsions en ôtres lengoues.",
        "withoutinterwiki-legend": "Prèfixo",
        "withoutinterwiki-submit": "Montrar",
        "fewestrevisions": "Pâges les muens changiêes",
        "ntransclusions": "empleyê dessus $1 pâge{{PLURAL:$1||s}}",
        "specialpage-empty": "Y at gins de rèsultat a fâre vêre.",
        "lonelypages": "Pâges orfenes",
-       "lonelypagestext": "Celes pâges-que sont ni liyêes ni transcllues per d’ôtres pâges de {{SITENAME}}.",
+       "lonelypagestext": "Cetes pâges sont ni liyêes ni transcllues per d’ôtres pâges de {{SITENAME}}.",
        "uncategorizedpages": "Pâges sen catègories",
        "uncategorizedcategories": "Catègories sen catègories",
        "uncategorizedimages": "Fichiérs sen catègories",
        "wantedpages-summary": "Lista de les pâges inègzistentes qu’ant lo més de lims de vers lor, en èxcllusent les pâges qu’ant ren que de redirèccions pouentent vers lor. Por avêr na lista de les pâges inègzistentes qu’ant de redirèccions pouentent vers lor, vêde la [[{{#special:BrokenRedirects}}|lista de les redirèccions câsses]].",
        "wantedpages-badtitle": "Titro pas justo dedens l’ensemblo de rèsultats : $1",
        "wantedfiles": "Fichiérs demandâs",
-       "wantedfiletext-cat": "Celos fichiérs-que sont empleyês, mas ègzistont pas. Los fichiérs de dèpôts de defôr pôvont étre listâs mémo s’ègzistont. Tôs celos fôx positifs seront <del>barrâs</del>. Et pués les pâges qu’apondont de fichiérs qu’ègzistont pas sont listâs dedens [[:$1]].",
-       "wantedfiletext-cat-noforeign": "Celos fichiérs-que sont empleyês, mas ègzistont pas. Et pués les pâges qu’apondont de fichiérs qu’ègzistont pas sont listâs dedens [[:$1]].",
-       "wantedfiletext-nocat": "Celos fichiérs-que sont empleyês, mas ègzistont pas. Los fichiérs de dèpôts de defôr pôvont étre listâs mémo s’ègzistont. Tôs celos fôx positifs seront <del>barrâs</del>.",
-       "wantedfiletext-nocat-noforeign": "Celos fichiérs-que sont empleyês, mas ègzistont pas.",
+       "wantedfiletext-cat": "Cetos fichiérs sont empleyês, mas ègzistont pas. Los fichiérs de dèpôts de defôr pôvont étre listâs mémo s’ègzistont. Tôs celos fôx positifs seront <del>barrâs</del>. Et pués les pâges qu’apondont de fichiérs qu’ègzistont pas sont listâs dedens [[:$1]].",
+       "wantedfiletext-cat-noforeign": "Cetos fichiérs sont empleyês, mas ègzistont pas. Et pués les pâges qu’apondont de fichiérs qu’ègzistont pas sont listâs dedens [[:$1]].",
+       "wantedfiletext-nocat": "Cetos fichiérs sont empleyês, mas ègzistont pas. Los fichiérs de dèpôts de defôr pôvont étre listâs mémo s’ègzistont. Tôs celos fôx positifs seront <del>barrâs</del>.",
+       "wantedfiletext-nocat-noforeign": "Cetos fichiérs sont empleyês, mas ègzistont pas.",
        "wantedtemplates": "Modèlos demandâs",
        "mostlinked": "Pâges les ples liyêes",
        "mostlinkedcategories": "Catègories les ples liyêes",
        "shortpages": "Pâges côrtes",
        "longpages": "Pâges longes",
        "deadendpages": "Pâges en charriére borgne",
-       "deadendpagestext": "Celes pâges-que contegnont gins de lim de vers d’ôtres pâges de {{SITENAME}}.",
+       "deadendpagestext": "Cetes pâges contegnont gins de lim de vers d’ôtres pâges de {{SITENAME}}.",
        "protectedpages": "Pâges protègiêes",
        "protectedpages-indef": "Mas que les protèccions sen fin",
        "protectedpages-summary": "Cela pâge liste les pâges ègzistentes que sont ora protègiêes. Por na lista des titros protègiês contre la crèacion, vêde [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "ancientpages": "Pâges les ples vielyes",
        "move": "Dèplaciér",
        "movethispage": "Dèplaciér cela pâge",
-       "unusedimagestext": "Celos fichiérs-que ègzistont, mas sont pas apondus a niona pâge.\nSe vos plét, notâd que d’ôtros setos Vouèbe pôvont avêr un lim de vers un fichiér avouéc un’URL drêta, adonc un fichiér pôt adés étre listâ ique pendent qu’il est en usâjo actif.",
-       "unusedcategoriestext": "Celes catègories-que ègzistont, mas nion’ôtra pâge niona catègoria les emplèye.",
+       "unusedimagestext": "Cetos fichiérs ègzistont, mas sont pas apondus a niona pâge.\nSe vos plét, notâd que d’ôtros setos Vouèbe pôvont avêr un lim de vers un fichiér avouéc un’URL drêta, adonc un fichiér pôt adés étre listâ ique pendent qu’il est en usâjo actif.",
+       "unusedcategoriestext": "Cetes catègories ègzistont, mas nion’ôtra pâge niona catègoria les emplèye.",
        "notargettitle": "Niona ciba",
        "notargettext": "Vos éd pas spècifiâ na pâge un utilisator ciba que vos voléd fâre cel’accion.",
        "nopagetitle": "Niona pâge ciba d’ense",
        "cachedspecial-refresh-now": "Vêre la ples novèla.",
        "categories": "Catègories",
        "categories-submit": "Montrar",
-       "categoriespagetext": "{{PLURAL:$1|Cela catègoria-que contint|Celes catègories-que contegnont}} de pâges de mèdiâs.\nLes [[Special:UnusedCategories|catègories pas empleyêes]] sont pas montrâyes ique.\nVêde avouéc les [[Special:WantedCategories|catègories demandâyes]].",
+       "categoriespagetext": "{{PLURAL:$1|Ceta catègoria contint|Cetes catègories contegnont}} de pâges de mèdiâs.\nLes [[Special:UnusedCategories|catègories pas empleyêes]] sont pas montrâyes ique.\nVêde avouéc les [[Special:WantedCategories|catègories demandâyes]].",
        "categoriesfrom": "Fâre vêre les catègories dês :",
        "deletedcontributions": "Contribucions suprimâyes",
        "deletedcontributions-title": "Contribucions suprimâyes",
        "protect-locked-blocked": "Vos pouede pas changiér los nivéls de protèccion tant que vos éte blocâ{{GENDER:||ye}}.\nVê-que la configuracion d’ora de la pâge <strong>$1</strong> :",
        "protect-locked-dblock": "Los nivéls de protèccion pôvont pas étre changiês, la bâsa de balyês est cotâye.\nVê-que la configuracion d’ora de la pâge <strong>$1</strong> :",
        "protect-locked-access": "Voutron comptio at pas los drêts nècèssèros por changiér los nivéls de protèccion de pâges.\nVê-que la configuracion d’ora de la pâge <strong>$1</strong> :",
-       "protect-cascadeon": "Ora cela pâge est protègiêe, el est transcllua dedens {{PLURAL:$1|cela pâge-que qu’est étâye protègiêe|celes pâges-que que sont étâyes protègiêes}} avouéc lo chouèx « protèccion en cascâda » activâ.\nLos changements du nivél de protèccion de cela pâge afècteront pas la protèccion en cascâda.",
+       "protect-cascadeon": "Ora cela pâge est protègiêe, el est transcllua dedens {{PLURAL:$1|ceta pâge qu’est étâye protègiêe|cetes pâges que sont étâyes protègiêes}} avouéc lo chouèx « protèccion en cascâda » activâ.\nLos changements du nivél de protèccion de cela pâge afècteront pas la protèccion en cascâda.",
        "protect-default": "Ôtorisar tôs los utilisators",
        "protect-fallback": "Ôtorisar mas que los utilisators avouéc lo drêt « $1 »",
        "protect-level-autoconfirmed": "Ôtorisar mas que los utilisators ôtoconfirmâs",
        "restriction-level-all": "tôs los nivéls",
        "undelete": "Vêre les pâges suprimâyes",
        "undeletepage": "Vêre et refâre de pâges suprimâyes",
-       "undeletepagetitle": "<strong>Cela lista-que contint de vèrsions suprimâyes de [[:$1|$1]].</strong>",
+       "undeletepagetitle": "<strong>Ceta lista contint de vèrsions suprimâyes de [[:$1|$1]].</strong>",
        "viewdeletedpage": "Vêre les pâges suprimâyes",
-       "undeletepagetext": "{{PLURAL:$1|Cela pâge-que est étâye suprimâye et pués sè trôve|Celes pâges-que sont étâyes suprimâyes et pués sè trôvont}} adés dedens les arch·ives, de yô que pô{{PLURAL:$1||von}}t étre refêt{{PLURAL:$1|a|es}}.\nLes arch·ives pôvont étre neteyêes règuliérement.",
+       "undeletepagetext": "{{PLURAL:$1|Ceta pâge est étâye suprimâye et pués sè trôve|Cetes pâges sont étâyes suprimâyes et pués sè trôvont}} adés dedens les arch·ives, de yô que pô{{PLURAL:$1||von}}t étre refêt{{PLURAL:$1|a|es}}.\nLes arch·ives pôvont étre neteyêes règuliérement.",
        "undelete-fieldset-title": "Refâre les vèrsions",
        "undeleteextrahelp": "Por refâre l’historico complèt de la pâge, lèssiéd totes les câses pas pouentâyes et pués cllicâd dessus <strong><em>{{int:undeletebtn}}</em></strong>.\nPor fâre na rèstoracion a mêtiêt, pouentâd les câses que corrèspondont a les vèrsions a refâre et pués cllicâd dessus <strong><em>{{int:undeletebtn}}</em></strong>.",
        "undeleterevisions": "$1 {{PLURAL:$1|vèrsion suprimâye|vèrsions suprimâyes}}",
        "whatlinkshere": "Pâges liyêes",
        "whatlinkshere-title": "Pâges que pouentont vers « $1 »",
        "whatlinkshere-page": "Pâge :",
-       "linkshere": "Celes pâges-que contegnont un lim de vers <strong>[[:$1]]</strong> :",
+       "linkshere": "Cetes pâges contegnont un lim de vers <strong>[[:$1]]</strong> :",
        "nolinkshere": "Niona pâge contint de lim de vers <strong>[[:$1]]</strong>.",
        "nolinkshere-ns": "Niona pâge contint de lim de vers <strong>[[:$1]]</strong> dedens l’èspâço de noms chouèsi.",
        "isredirect": "pâge de redirèccion",
        "import-interwiki-submit": "Importar",
        "import-mapping-default": "Importar ux endrêts per dèfôt",
        "import-mapping-namespace": "Importar vers un èspâço de noms :",
-       "import-mapping-subpage": "Importar coment sot-pâges de cela pâge-que :",
+       "import-mapping-subpage": "Importar coment sot-pâges de ceta pâge :",
        "import-upload-filename": "Nom du fichiér :",
        "import-comment": "Comentèro :",
        "importtext": "Se vos plét, èxportâd lo fichiér dês lo vouiqui d’origina en empleyent son [[Special:Export|outil d’èxportacion]].\nEncartâd-lo sur voutron ordenator et pués tèlèchargiéd-lo ique.",
        "import-error-special": "La pâge « $1 » est pas étâye importâye, el est a un èspâço de noms spèciâl qu’ôtorise gins de pâge.",
        "import-error-invalid": "La pâge « $1 » est pas étâye importâye, lo nom que lyé serêt étâye importâye desot est pas justo sur cél vouiqui.",
        "import-error-unserialize": "La vèrsion $2 de la pâge « $1 » pôt pas étre dèssèrialisâye. La vèrsion ére raportâye coment empleyent lo modèlo de contegnu $3 sèrialisâ en $4.",
-       "import-error-bad-location": "La vèrsion $2 qu’emplèye lo modèlo de contegnu $3 at pas possu étre stocâye dessus « $1 » sur cél vouiqui-que, cél modèlo est pas recognu sur cela pâge.",
+       "import-error-bad-location": "La vèrsion $2 qu’emplèye lo modèlo de contegnu $3 at pas possu étre stocâye dessus « $1 » sur ceti vouiqui, cél modèlo est pas recognu sur cela pâge.",
        "import-options-wrong": "{{PLURAL:$2|Crouyo chouèx|Crouyos chouèx}} : <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "La pâge racena balyêe est un titro pas justo.",
        "import-rootpage-nosubpage": "L’èspâço de noms « $1 » de la pâge racena ôtorise pas les sot-pâges.",
        "tooltip-ca-nstab-category": "Vêre la pâge de la catègoria",
        "tooltip-minoredit": "Marcar mos changements coment petiôts",
        "tooltip-save": "Encartar voutros changements",
+       "tooltip-publish": "Publeyér voutros changements",
        "tooltip-preview": "Prèvêde voutros changements. Se vos plét, empleyéd-lo devant qu’encartar.",
        "tooltip-diff": "Montrar los changements que vos éd aportâs u tèxto",
        "tooltip-compareselectedversions": "Fâre ressortir les difèrences entre les doves vèrsions chouèsies de cela pâge",
        "nocredits": "Y at gins d’enformacion d’atribucion disponibla por cela pâge.",
        "spamprotectiontitle": "Filtro de protèccion contre los mèssâjos cofos",
        "spamprotectiontext": "Lo tèxto que vos éd volu encartar est étâ blocâ per lo filtro contre los mèssâjos cofos.\nO est probâblament diu a un lim de vers un seto de defôr qu’aparêt sur la lista nêre.",
-       "spamprotectionmatch": "Cél tèxto-que est cen qu’at dècllenchiê noutron filtro contre los mèssâjos cofos : $1",
+       "spamprotectionmatch": "Ceti tèxto est cen qu’at dècllenchiê noutron filtro contre los mèssâjos cofos : $1",
        "spambot_username": "Neteyâjo de mèssâjos cofos per MediaWiki",
        "spam_reverting": "Rètablissement de la dèrriére vèrsion que contint gins de lim de vers $1",
        "spam_blanking": "Totes les vèrsions que contegnont de lims de vers $1 sont blanchies",
        "exif-meteringmode-0": "Encognua",
        "exif-meteringmode-1": "Moyena",
        "exif-meteringmode-2": "Moyena d’aplomb u centro",
-       "exif-meteringmode-3": "Pouent",
-       "exif-meteringmode-4": "MultiPouent",
+       "exif-meteringmode-3": "Spote",
+       "exif-meteringmode-4": "Multi-spote",
        "exif-meteringmode-5": "Modèlo",
-       "exif-meteringmode-6": "Encomplèta",
+       "exif-meteringmode-6": "Parciâla",
        "exif-meteringmode-255": "Ôtra",
        "exif-lightsource-0": "Encognua",
        "exif-lightsource-1": "Lumiére du jorn",
        "exif-lightsource-2": "Fluorèscenta",
-       "exif-lightsource-3": "Tungstène (lumiére chôdâ a blanc)",
+       "exif-lightsource-3": "Tungstène (lumiére enfarâye)",
        "exif-lightsource-4": "Èludo",
        "exif-lightsource-9": "Temps cllâr",
        "exif-lightsource-10": "Temps enneblo",
        "exif-lightsource-11": "Ombra",
-       "exif-lightsource-12": "Lumiére fluorèscenta « lumiére du jorn » (D 5700 – 7100 K)",
-       "exif-lightsource-13": "Lumiére fluorèscenta blanche « jorn » (N 4600 – 5400 K)",
-       "exif-lightsource-14": "Lumiére fluorèscenta blanche « frêd » (W 3900 – 4500 K)",
-       "exif-lightsource-15": "Lumiére fluorèscenta blanche (WW 3200 – 3700 K)",
-       "exif-lightsource-17": "Lumiére estandârd A",
-       "exif-lightsource-18": "Lumiére estandârd B",
-       "exif-lightsource-19": "Lumiére estandârd C",
+       "exif-lightsource-12": "Ècllèrâjo fluorèscent « lumiére du jorn » (D 5700 – 7100 K)",
+       "exif-lightsource-13": "Ècllèrâjo fluorèscent blanc « jorn » (N 4600 – 5400 K)",
+       "exif-lightsource-14": "Ècllèrâjo fluorèscent blanc « frêd » (W 3900 – 4500 K)",
+       "exif-lightsource-15": "Ècllèrâjo fluorèscent blanc (WW 3200 – 3700 K)",
+       "exif-lightsource-17": "Lumiére standârd A",
+       "exif-lightsource-18": "Lumiére standârd B",
+       "exif-lightsource-19": "Lumiére standârd C",
        "exif-lightsource-24": "Tungstène ISO de studiô",
        "exif-lightsource-255": "Ôtra sôrsa de lumiére",
        "exif-flash-fired-0": "Èludo pas dècllenchiê",
        "exif-flash-fired-1": "Èludo dècllenchiê",
-       "exif-flash-return-0": "nion stroboscopo retorne una fonccion de dètèccion",
-       "exif-flash-return-2": "lo stroboscopo retorne una lumiére pas dècelâ",
-       "exif-flash-return-3": "lo stroboscopo retorne una lumiére dècelâ",
+       "exif-flash-return-0": "nion stroboscopo retôrne na fonccion de dècelâjo",
+       "exif-flash-return-2": "lo stroboscopo dècèle gins de lumiére retornâye",
+       "exif-flash-return-3": "lo stroboscopo dècèle un retôrn de lumiére",
        "exif-flash-mode-1": "lumiére de l’èludo oblegatouèra",
        "exif-flash-mode-2": "suprèssion de l’èludo oblegatouèra",
        "exif-flash-mode-3": "fôrma ôtomatica",
-       "exif-flash-function-1": "Gins de fonccion d’èludo",
+       "exif-flash-function-1": "Niona fonccion d’èludo",
        "exif-flash-redeye-1": "fôrma anti-uelys rojos",
        "exif-focalplaneresolutionunit-2": "pôjos",
        "exif-sensingmethod-1": "Pas dèfeni",
-       "exif-sensingmethod-2": "Captior de color a yona puge",
+       "exif-sensingmethod-2": "Captior de color a na puge",
        "exif-sensingmethod-3": "Captior de color a doves puges",
        "exif-sensingmethod-4": "Captior de color a três puges",
        "exif-sensingmethod-5": "Captior de color sèquencièl",
        "exif-sensingmethod-7": "Captior trilinèâr",
        "exif-sensingmethod-8": "Captior de color linèâr sèquencièl",
        "exif-filesource-3": "Aparèly-fotô numerico",
-       "exif-scenetype-1": "Émâge fotografiâ tot drêt",
-       "exif-customrendered-0": "Maniére normala",
-       "exif-customrendered-1": "Maniére pèrsonalisâ",
+       "exif-scenetype-1": "Émâge fotografiâye tot drêt",
+       "exif-customrendered-0": "Pratica normala",
+       "exif-customrendered-1": "Pratica pèrsonalisâye",
        "exif-exposuremode-0": "Èxposicion ôtomatica",
-       "exif-exposuremode-1": "Ã\88xposicion manuèla",
+       "exif-exposuremode-1": "Ã\88xposicion manuâla",
        "exif-exposuremode-2": "Forchèta ôtomatica",
        "exif-whitebalance-0": "Balance des blancs ôtomatica",
-       "exif-whitebalance-1": "Balance des blancs manuèla",
-       "exif-scenecapturetype-0": "Estandârd",
+       "exif-whitebalance-1": "Balance des blancs manuâla",
+       "exif-scenecapturetype-0": "Standârd",
        "exif-scenecapturetype-1": "Payisâjo",
        "exif-scenecapturetype-2": "Portrèt",
        "exif-scenecapturetype-3": "Scèna de nuet",
        "exif-gaincontrol-0": "Nion",
-       "exif-gaincontrol-1": "Guen fêblament positif",
-       "exif-gaincontrol-2": "Guen fôrtament positif",
-       "exif-gaincontrol-3": "Guen fêblament nègatif",
-       "exif-gaincontrol-4": "Guen fôrtament nègatif",
+       "exif-gaincontrol-1": "Fêblo guen positif",
+       "exif-gaincontrol-2": "Fôrt guen positif",
+       "exif-gaincontrol-3": "Fêblo guen nègatif",
+       "exif-gaincontrol-4": "Fôrt guen nègatif",
        "exif-contrast-0": "Normal",
        "exif-contrast-1": "Fêblo",
        "exif-contrast-2": "Fôrt",
        "exif-saturation-0": "Normala",
-       "exif-saturation-1": "Saturacion fêbla",
+       "exif-saturation-1": "Saturacion bâssa",
        "exif-saturation-2": "Saturacion hôta",
        "exif-sharpness-0": "Normala",
        "exif-sharpness-1": "Doce",
        "exif-subjectdistancerange-1": "Vision en grôs",
        "exif-subjectdistancerange-2": "Vision de prés",
        "exif-subjectdistancerange-3": "Vision de luen",
-       "exif-gpslatitude-n": "Latituda bise (''nord'')",
-       "exif-gpslatitude-s": "Latituda mié-jorn (''sud'')",
-       "exif-gpslongitude-e": "Longituda levant (''èst'')",
-       "exif-gpslongitude-w": "Longituda ponant (''ouèst'')",
-       "exif-gpsaltitude-above-sealevel": "$1 mètre{{PLURAL:$1||s}} en-d’amont du nivél de la mar",
-       "exif-gpsaltitude-below-sealevel": "$1 mètre{{PLURAL:$1||s}} en-desot du nivél de la mar",
+       "exif-gpslatitude-n": "Latituda bise (<em>nord</em>)",
+       "exif-gpslatitude-s": "Latituda mié-jorn (<em>sud</em>)",
+       "exif-gpslongitude-e": "Longituda levant (<em>èste</em>)",
+       "exif-gpslongitude-w": "Longituda cuchient (<em>ouèste</em>)",
+       "exif-gpsaltitude-above-sealevel": "$1 mètro{{PLURAL:$1||s}} d’amont lo nivél de la mar",
+       "exif-gpsaltitude-below-sealevel": "$1 mètro{{PLURAL:$1||s}} desot lo nivél de la mar",
        "exif-gpsstatus-a": "Mesera en cors",
-       "exif-gpsstatus-v": "Entèropèrabilitât de la mesera",
+       "exif-gpsstatus-v": "Entèrfonccionement de la mesera",
        "exif-gpsmeasuremode-2": "Mesera a 2 dimensions",
        "exif-gpsmeasuremode-3": "Mesera a 3 dimensions",
-       "exif-gpsspeed-k": "Kilomètres per hora",
-       "exif-gpsspeed-m": "Miles per hora",
+       "exif-gpsspeed-k": "Kilomètros a l’hora",
+       "exif-gpsspeed-m": "Milos a l’hora",
        "exif-gpsspeed-n": "Nuods",
-       "exif-gpsdestdistance-k": "Kilomètres",
-       "exif-gpsdestdistance-m": "Miles",
-       "exif-gpsdestdistance-n": "Miles marins",
+       "exif-gpsdestdistance-k": "Kilomètros",
+       "exif-gpsdestdistance-m": "Milos",
+       "exif-gpsdestdistance-n": "Milos navâls",
        "exif-gpsdop-excellent": "Famosa ($1)",
        "exif-gpsdop-good": "Bôna ($1)",
        "exif-gpsdop-moderate": "Moyena ($1)",
-       "exif-gpsdop-fair": "Passâbla ($1)",
+       "exif-gpsdop-fair": "Tot justo bôna ($1)",
        "exif-gpsdop-poor": "Crouye ($1)",
-       "exif-objectcycle-a": "Matin solament",
-       "exif-objectcycle-p": "Nuet solament",
+       "exif-objectcycle-a": "Ren qu’u matin",
+       "exif-objectcycle-p": "Ren qu’a nuet",
        "exif-objectcycle-b": "Matin et nuet",
-       "exif-gpsdirection-t": "Veré dirèccion",
-       "exif-gpsdirection-m": "Dirèccion magnètica",
+       "exif-gpsdirection-t": "Veretâbla bise (<em>nord</em>)",
+       "exif-gpsdirection-m": "Bise (<em>nord</em>) magnètica",
        "exif-ycbcrpositioning-1": "Centrâ",
-       "exif-ycbcrpositioning-2": "Co-situâ",
+       "exif-ycbcrpositioning-2": "Co-placiê",
        "exif-dc-contributor": "Contributors",
-       "exif-dc-coverage": "Portâ espaciâla ou ben temporèla du mèdia",
+       "exif-dc-coverage": "Portâ spaciâla ou ben temporâla du mèdiâ",
        "exif-dc-date": "Dâta(/-es)",
        "exif-dc-publisher": "Èditor",
-       "exif-dc-relation": "Mèdias liyês",
+       "exif-dc-relation": "Mèdiâs liyês",
        "exif-dc-rights": "Drêts",
-       "exif-dc-source": "Mèdia sôrsa",
-       "exif-dc-type": "Tipo de mèdia",
+       "exif-dc-source": "Mèdiâ sôrsa",
+       "exif-dc-type": "Tipo de mèdiâ",
        "exif-rating-rejected": "Refusâ",
        "exif-isospeedratings-overflow": "Ples grant que 65535",
-       "exif-iimcategory-ace": "Ârts, cultura et amusament",
+       "exif-iimcategory-ace": "Ârts, cultura et spèctâcllos",
        "exif-iimcategory-clj": "Crimo et drêt",
        "exif-iimcategory-dis": "Catastrofes et accidents",
        "exif-iimcategory-fin": "Èconomia et afâres",
        "exif-iimcategory-edu": "Èducacion",
        "exif-iimcategory-evn": "Enveronance",
        "exif-iimcategory-hth": "Santât",
-       "exif-iimcategory-hum": "Entèrèt de l’homo",
+       "exif-iimcategory-hum": "Entèrèt d’homo",
        "exif-iimcategory-lab": "Travâly",
        "exif-iimcategory-lif": "Fôrma de via et pâssa-temps",
        "exif-iimcategory-pol": "Politica",
        "exif-iimcategory-rel": "Religion et creyences",
-       "exif-iimcategory-sci": "Science et tècnolog·ie",
+       "exif-iimcategory-sci": "Science et tècnologia",
        "exif-iimcategory-soi": "Quèstions sociâles",
        "exif-iimcategory-spo": "Sports",
-       "exif-iimcategory-war": "Guèrra, conflit et troblo",
-       "exif-iimcategory-wea": "Mètèô",
+       "exif-iimcategory-war": "Guèrra, disputa et troblo",
+       "exif-iimcategory-wea": "Temps",
        "exif-urgency-normal": "Normala ($1)",
-       "exif-urgency-low": "Fêbla ($1)",
+       "exif-urgency-low": "Bâssa ($1)",
        "exif-urgency-high": "Hôta ($1)",
-       "exif-urgency-other": "Prioritât dèfenia per l’usanciér ($1)",
+       "exif-urgency-other": "Prioritât dèfenia per l’utilisator ($1)",
        "namespacesall": "Tôs",
        "monthsall": "tôs",
        "confirmemail": "Confirmar l’adrèce èlèctronica",
-       "confirmemail_noemail": "Vos éd pas dèfeni una adrèce èlèctronica valida dens voutres [[Special:Preferences|prèferences]].",
-       "confirmemail_text": "{{SITENAME}} at fôta du contrôlo de voutra adrèce èlèctronica devant que povêr utilisar tota fonccion de mèssageria.\nUtilisâd lo boton ce-desot por mandar un mèssâjo de confirmacion a voutra adrèce.\nLo mèssâjo encllurat un lim que contint un code a usâjo solèt et limitâ dens lo temps ;\nchargiéd cél lim dens voutron navigator por confirmar que voutra adrèce èlèctronica est valida.",
-       "confirmemail_pending": "Un code de confirmacion vos at ja étâ mandâ per mèssageria èlèctronica ;\nse vos vegnéd de fâre voutron compto, volyéd atendre doux-três menutes que lo mèssâjo arreve devant que demandar un code novél.",
+       "confirmemail_noemail": "Vos éd pas dèfeni un’adrèce èlèctronica justa dens voutres [[Special:Preferences|prèferences]].",
+       "confirmemail_text": "{{SITENAME}} at fôta du contrôlo de voutron adrèce èlèctronica devant que povêr empleyér tota fonccionalitât de mèssageria èlèctronica.\nEmpleyéd lo boton ce-desot por mandar un mèssâjo de confirmacion a voutron adrèce.\nLo mèssâjo rapondrat un lim que contint un code ;\nchargiéd cél lim dedens voutron navegator por confirmar que voutron adrèce èlèctronica est justa.",
+       "confirmemail_pending": "Un code de confirmacion vos est ja étâ mandâ per mèssageria èlèctronica ;\nse vos vegnéd de fâre voutron comptio, se vos plét atende-vos doux-três menutes que lo mèssâjo arreve devant que demandar un code novél.",
        "confirmemail_send": "Mandar un code de confirmacion",
        "confirmemail_sent": "Mèssâjo de confirmacion mandâ.",
-       "confirmemail_oncreate": "Un code de confirmacion at étâ mandâ a voutra adrèce èlèctronica.\nCél code est pas nècèssèro por sè branchiér, mas vos lo devréd balyér por activar tota fonccionalitât liyê a la mèssageria èlèctronica sur ceti vouiqui.",
-       "confirmemail_sendfailed": "{{SITENAME}} vos at pas possu mandar lo mèssâjo de confirmacion.\nVolyéd controlar que voutra adrèce èlèctronica contint gins de caractèro dèfendu.\n\nLo programo d’èxpèdicion de mèssâjo at retornâ ceta endicacion : $1",
-       "confirmemail_invalid": "Code de confirmacion fôx.\nCeti at pôt-étre èxpirâ.",
-       "confirmemail_needlogin": "Vos vos dête $1 por confirmar voutra adrèce èlèctronica.",
-       "confirmemail_success": "Voutra adrèce èlèctronica at étâ confirmâ.\nOra, vos vos pouede [[Special:UserLogin|branchiér]] et profitar du vouiqui.",
-       "confirmemail_loggedin": "Ora, voutra adrèce èlèctronica est confirmâ.",
+       "confirmemail_oncreate": "Un code de confirmacion est étâ mandâ a voutron adrèce èlèctronica.\nCél code est pas nècèssèro por sè branchiér, mas vos lo devréd balyér por activar tota fonccionalitât de mèssageria èlèctronica sur cél vouiqui.",
+       "confirmemail_sendfailed": "{{SITENAME}} vos at pas possu mandar lo mèssâjo de confirmacion.\nSe vos plét, controlâd que voutron adrèce èlèctronica contint gins de caractèro pas justo.\n\nLo programo d’èxpèdicion de mèssâjo at retornâ cen : $1",
+       "confirmemail_invalid": "Code de confirmacion pas justo.\nPôt-étre ceti at èxpirâ.",
+       "confirmemail_needlogin": "Vos vos dête $1 por confirmar voutron adrèce èlèctronica.",
+       "confirmemail_success": "Voutron adrèce èlèctronica est étâye confirmâye.\nOra vos vos pouede [[Special:UserLogin|branchiér]] et pués profitar du vouiqui.",
+       "confirmemail_loggedin": "Ora voutron adrèce èlèctronica est confirmâye.",
        "confirmemail_subject": "Confirmacion d’adrèce èlèctronica por {{SITENAME}}",
-       "confirmemail_body": "Quârqu’un, probâblament vos, avouéc l’adrèce IP $1,\nat encartâ un compto « $2 » avouéc cela adrèce èlèctronica dessus {{SITENAME}}.\n\nPor confirmar que cél compto est franc a vos et por\nactivar les fonccions de mèssageria dessus {{SITENAME}},\nvolyéd uvrir ceti lim dens voutron navigator :\n\n$3\n\nSe vos éd *pas* encartâ lo compto, siude ceti lim\npor anular la confirmacion de l’adrèce èlèctronica :\n\n$5\n\nCél code de confirmacion èxpirerat lo $4.",
-       "confirmemail_body_changed": "Quârqu’un, probâblament vos, avouéc l’adrèce IP $1,\nat changiê l’adrèce èlèctronica du compto « $2 » a cela adrèce dessus {{SITENAME}}.\n\nPor confirmar que cél compto est franc a vos et por\nreactivar les fonccions de mèssageria dessus {{SITENAME}},\nvolyéd uvrir ceti lim dens voutron navigator :\n\n$3\n\nSe lo compto est *pas* a vos, siude ceti lim\npor anular la confirmacion de l’adrèce èlèctronica :\n\n$5\n\nCél code de confirmacion èxpirerat lo $4.",
-       "confirmemail_body_set": "Quârqu’un, probâblament vos, avouéc l’adrèce IP $1,\nat changiê l’adrèce èlèctronica du compto « $2 » a cela adrèce dessus {{SITENAME}}.\n\nPor confirmar que cél compto est franc a vos et por\nreactivar les fonccions de mèssageria dessus {{SITENAME}},\nvolyéd uvrir ceti lim dens voutron navigator :\n\n$3\n\nSe lo compto est *pas* a vos, siude ceti lim\npor anular la confirmacion de l’adrèce èlèctronica :\n\n$5\n\nCél code de confirmacion èxpirerat lo $4.",
-       "confirmemail_invalidated": "Confirmacion de l’adrèce èlèctronica anulâ",
+       "confirmemail_body": "Yon, de sûr vos, avouéc l’adrèce IP $1,\nat encartâ un comptio « $2 » avouéc cel’adrèce èlèctronica dessus {{SITENAME}}.\n\nPor confirmar que cél comptio est franc a vos et por activar\nles fonccionalitâts de mèssageria èlèctronica dessus {{SITENAME}},\nse vos plét uvréd cél lim dedens voutron navegator :\n\n$3\n\nSe vos éd *pas* encartâ lo comptio, siude l’ôtro lim\npor anular la confirmacion de l’adrèce èlèctronica :\n\n$5\n\nCél code de confirmacion èxpirerat lo $4.",
+       "confirmemail_body_changed": "Yon, de sûr vos, avouéc l’adrèce IP $1,\nat changiê l’adrèce èlèctronica du comptio « $2 » dessus {{SITENAME}} en cel’adrèce.\n\nPor confirmar que cél comptio est franc a vos et por reactivar\nles fonccionalitâts de mèssageria èlèctronica dessus {{SITENAME}},\nse vos plét uvréd cél lim dedens voutron navegator :\n\n$3\n\nSe lo compto est *pas* a vos, siude l’ôtro lim\npor anular la confirmacion de l’adrèce èlèctronica :\n\n$5\n\nCél code de confirmacion èxpirerat lo $4.",
+       "confirmemail_body_set": "Yon, de sûr vos, avouéc l’adrèce IP $1,\nat dèfeni l’adrèce èlèctronica du comptio « $2 » dessus {{SITENAME}} en cel’adrèce.\n\nPor confirmar que cél comptio est franc a vos et por activar\nles fonccionalitâts de mèssageria èlèctronica dessus {{SITENAME}},\nse vos plét uvréd cél lim dedens voutron navegator :\n\n$3\n\nSe lo compto est *pas* a vos, siude l’ôtro lim\npor anular la confirmacion de l’adrèce èlèctronica :\n\n$5\n\nCél code de confirmacion èxpirerat lo $4.",
+       "confirmemail_invalidated": "Confirmacion de l’adrèce èlèctronica anulâye",
        "invalidateemail": "Anular la confirmacion de l’adrèce èlèctronica",
-       "scarytranscludedisabled": "[La transcllusion entèrvouiqui est dèsactivâ]",
-       "scarytranscludefailed": "[La rècupèracion de modèlo at pas reussia por $1]",
+       "notificationemail_subject_changed": "L’adrèce èlèctronica encartâye dessus {{SITENAME}} est étâye changiêe",
+       "notificationemail_subject_removed": "L’adrèce èlèctronica encartâye dessus {{SITENAME}} est étâye enlevâye",
+       "notificationemail_body_changed": "Yon, de sûr vos, avouéc l’adrèce IP $1,\nat changiê l’adrèce èlèctronica du comptio « $2 » dessus {{SITENAME}} en « $3 ».\n\nS’o ére pas vos, veriéd-vos d’abôrd vers un administrator du seto.",
+       "notificationemail_body_removed": "Yon, de sûr vos, avouéc l’adrèce IP $1,\nat enlevâ l’adrèce èlèctronica du comptio « $2 » dessus {{SITENAME}}.\n\nS’o ére pas vos, veriéd-vos d’abôrd vers un administrator du seto.",
+       "scarytranscludedisabled": "[La transcllusion entèrvouiqui est dèsactivâye]",
+       "scarytranscludefailed": "[La rècupèracion de modèlo at pas reussi por $1]",
+       "scarytranscludefailed-httpstatus": "[La rècupèracion de modèlo at pas reussi por $1 : HTTP $2]",
        "scarytranscludetoolong": "[L’URL est trop longe]",
-       "deletedwhileediting": "'''Atencion :''' ceta pâge at étâ suprimâ aprés que vos vos éte betâ a la changiér !",
-       "confirmrecreate": "L’usanciér [[User:$1|$1]] ([[User talk:$1|Discussion]]) at suprimâ ceta pâge, pendent que vos vos érâd betâ a la changiér, por ceta rêson :\n: ''$2''\nVolyéd confirmar que vos voléd franc refâre cela pâge.",
-       "confirmrecreate-noreason": "L’usanciér [[User:$1|$1]] ([[User talk:$1|Discussion]]) at suprimâ ceta pâge, pendent que vos vos érâd betâ a la changiér.  Volyéd confirmar que vos voléd franc refâre cela pâge.",
+       "deletedwhileediting": "<strong>Atencion :</strong> cela pâge est étâye suprimâye aprés que vos vos éte betâ a la changiér !",
+       "confirmrecreate": "L’utilisat{{GENDER:$1|or|rice}} [[User:$1|$1]] ([[User talk:$1|discussion]]) at suprimâ cela pâge, pendent que vos vos érâd betâ a la changiér, por ceta rêson :\n: <em>$2</em>\nSe vos plét, confirmâd que vos voléd franc refâre cela pâge.",
+       "confirmrecreate-noreason": "L’utilisat{{GENDER:$1|or|rice}} [[User:$1|$1]] ([[User talk:$1|discussion]]) at suprimâ cela pâge, pendent que vos vos érâd betâ a la changiér. Se vos plét, confirmâd que vos voléd franc refâre cela pâge.",
        "recreate": "Refâre",
        "confirm_purge_button": "Confirmar",
-       "confirm-purge-top": "Voléd-vos purgiér lo cache de ceta pâge ?",
-       "confirm-purge-bottom": "Purgiér una pâge èface lo cache et pués fôrce la dèrriére vèrsion a étre montrâ.",
+       "confirm-purge-top": "Vos voléd purgiér lo cacho de cela pâge ?",
+       "confirm-purge-bottom": "Purgiér na pâge vouede lo cacho et pués fôrce la dèrriére vèrsion a étre montrâye.",
        "confirm-watch-button": "D’acôrd",
-       "confirm-watch-top": "Apondre ceta pâge a voutra lista de survelyence ?",
+       "confirm-watch-top": "Apondre cela pâge a voutra lista de gouârda ?",
        "confirm-unwatch-button": "D’acôrd",
-       "confirm-unwatch-top": "Enlevar ceta pâge de voutra lista de survelyence ?",
+       "confirm-unwatch-top": "Enlevar cela pâge de voutra lista de gouârda ?",
        "semicolon-separator": "&nbsp;;&#32;",
        "colon-separator": "&nbsp;:&#32;",
        "percent": "$1&#160;%",
+       "quotation-marks": "« $1 »",
        "imgmultipageprev": "← pâge devant",
        "imgmultipagenext": "pâge aprés →",
        "imgmultigo": "Emmodar !",
        "imgmultigoto": "Alar a la pâge $1",
+       "img-lang-default": "(lengoua per dèfôt)",
+       "img-lang-info": "Montrar cel’émâge en $1 $2.",
        "img-lang-go": "Emmodar",
-       "ascending_abbrev": "que crêt",
-       "descending_abbrev": "que dècrêt",
+       "ascending_abbrev": "crès.",
+       "descending_abbrev": "dècr.",
        "table_pager_next": "Pâge aprés",
        "table_pager_prev": "Pâge devant",
        "table_pager_first": "Premiére pâge",
        "table_pager_last": "Dèrriére pâge",
-       "table_pager_limit": "Montrar $1 èlèment{{PLURAL:$1||s}} per pâge",
-       "table_pager_limit_label": "Rèsultats per pâge :",
+       "table_pager_limit": "Montrar $1 piéces per pâge",
+       "table_pager_limit_label": "Piéces per pâge :",
        "table_pager_limit_submit": "Emmodar",
-       "table_pager_empty": "Gins de rèsultat",
+       "table_pager_empty": "Nion rèsultat",
        "autosumm-blank": "Pâge blanchia",
        "autosumm-replace": "Contegnu remplaciê per « $1 »",
-       "autoredircomment": "Pâge redirigiê vers [[$1]]",
+       "autoredircomment": "Pâge redirigiêe vers [[$1]]",
        "autosumm-new": "Pâge fêta avouéc « $1 »",
+       "autosumm-newblank": "Pâge voueda fêta",
        "size-bytes": "$1 o",
        "size-kilobytes": "$1 Kio",
        "size-megabytes": "$1 Mio",
        "size-gigabytes": "$1 Gio",
-       "lag-warn-normal": "Los changements que dâtont de muens de $1 {{PLURAL:$1|seconda|secondes}} pôvont pas aparêtre dens ceta lista.",
-       "lag-warn-high": "En rêson d’un retârd important du sèrvor de bâsa de balyês, los changements que dâtont de muens de $1 {{PLURAL:$1|seconda|secondes}} pôvont pas aparêtre dens ceta lista.",
-       "watchlistedit-normal-title": "Changiér la lista de survelyence",
-       "watchlistedit-normal-legend": "Enlevar des titros de la lista de survelyence",
-       "watchlistedit-normal-explain": "Los titros de voutra lista de survelyence sont montrâs ce-desot.\nPor enlevar un titro (et sa pâge de discussion), pouentâd la câsa a coutâ et pués clicâd sur lo boton « {{int:Watchlistedit-normal-submit}} ».\nVos pouede asse-ben changiér la [[Special:EditWatchlist/raw|lista en fôrma bruta]].",
+       "lag-warn-normal": "Los changements que dâtont de muens de $1 second{{PLURAL:$1|a|es}} pôvont pas aparêtre dedens cela lista.",
+       "lag-warn-high": "A côsa d’un retârd important du sèrvior de bâsa de balyês, los changements que dâtont de muens de $1 second{{PLURAL:$1|a|es}} pôvont pas aparêtre dedens cela lista.",
+       "watchlistedit-normal-title": "Changiér la lista de gouârda",
+       "watchlistedit-normal-legend": "Enlevar de titros de la lista de gouârda",
+       "watchlistedit-normal-explain": "Los titros de voutra lista de gouârda sont montrâs ce-desot.\nPor enlevar un titro, pouentâd la câsa a coutâ et pués cllicâd sur lo boton « {{int:Watchlistedit-normal-submit}} ».\nVos pouede asse-ben [[Special:EditWatchlist/raw|changiér la lista en fôrma bruta]].",
        "watchlistedit-normal-submit": "Enlevar los titros",
-       "watchlistedit-normal-done": "{{PLURAL:$1|Yon titro at étâ enlevâ|$1 titros ont étâ enlevâs}} de voutra lista de survelyence :",
-       "watchlistedit-raw-title": "Changiér la lista de survelyence en fôrma bruta",
-       "watchlistedit-raw-legend": "Changement de la lista de survelyence en fôrma bruta",
-       "watchlistedit-raw-explain": "Los titros de voutra lista de survelyence sont montrâs ce-desot et pôvont étre changiês en los apondent ou ben en los enlevent de la lista ;\nyon titro per legne.\nQuand vos éd feni, clicâd sur lo boton « {{int:Watchlistedit-raw-submit}} ».\nVos pouede asse-ben utilisar l’[[Special:EditWatchlist|èditor normal]].",
+       "watchlistedit-normal-done": "{{PLURAL:$1|Un titro est étâ enlevâ|$1 titros sont étâs enlevâs}} de voutra lista de gouârda :",
+       "watchlistedit-raw-title": "Changiér la lista de gouârda en fôrma bruta",
+       "watchlistedit-raw-legend": "Changement de la lista de gouârda en fôrma bruta",
+       "watchlistedit-raw-explain": "Los titros de voutra lista de gouârda sont montrâs ce-desot et pôvont étre changiês en los apondent los enlevent de la lista ;\nun titro per legne.\nQuand vos éd chavono, cllicâd sur lo boton « {{int:Watchlistedit-raw-submit}} ».\nVos pouede asse-ben [[Special:EditWatchlist|empleyér l’èditor standârd]].",
        "watchlistedit-raw-titles": "Titros :",
-       "watchlistedit-raw-submit": "Betar a jorn la lista de survelyence",
-       "watchlistedit-raw-done": "Voutra lista de survelyence at étâ betâ a jorn.",
-       "watchlistedit-raw-added": "{{PLURAL:$1|Yon titro at étâ apondu|$1 titros ont étâ apondus}} :",
-       "watchlistedit-raw-removed": "{{PLURAL:$1|Yon titro at étâ enlevâ|$1 titros ont étâ enlevâs}} :",
-       "watchlisttools-view": "Lista de survelyence",
-       "watchlisttools-edit": "Vêre et changiér la lista de survelyence",
-       "watchlisttools-raw": "Changiér la lista de survelyence en fôrma bruta",
+       "watchlistedit-raw-submit": "Betar a jorn la lista de gouârda",
+       "watchlistedit-raw-done": "Voutra lista de gouârda est étâye betâye a jorn.",
+       "watchlistedit-raw-added": "{{PLURAL:$1|Un titro est étâ apondu|$1 titros sont étâs apondus}} :",
+       "watchlistedit-raw-removed": "{{PLURAL:$1|Un titro est étâ enlevâ|$1 titros sont étâs enlevâs}} :",
+       "watchlistedit-clear-title": "Vouedar la lista de gouârda",
+       "watchlistedit-clear-legend": "Vouedar la lista de gouârda",
+       "watchlistedit-clear-explain": "Tôs los titros seront enlevâs de voutra lista de gouârda",
+       "watchlistedit-clear-titles": "Titros :",
+       "watchlistedit-clear-submit": "Vouedar la lista de gouârda (o est por de bon !)",
+       "watchlistedit-clear-done": "Voutra lista de gouârda est étâye vouedâye.",
+       "watchlistedit-clear-removed": "{{PLURAL:$1|Un titro est étâ enlevâ|$1 titros sont étâs enlevâs}} :",
+       "watchlistedit-too-many": "Y at trop de pâges a fâre vêre ique.",
+       "watchlisttools-clear": "Vouedar la lista de gouârda",
+       "watchlisttools-view": "Lista de gouârda",
+       "watchlisttools-edit": "Vêre et changiér la lista de gouârda",
+       "watchlisttools-raw": "Changiér la lista de gouârda en fôrma bruta",
        "iranian-calendar-m1": "de farvardin",
        "iranian-calendar-m2": "d’ordibehèch·ete",
        "iranian-calendar-m3": "de c’hordâde",
        "hebrew-calendar-m10-gen": "de tamouz",
        "hebrew-calendar-m11-gen": "d’av",
        "hebrew-calendar-m12-gen": "d’èloul",
-       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|discutar]])",
-       "duplicate-defaultsort": "'''Atencion :''' la cllâf de tri per dèfôt « $2 » ècllafe cela « $1 ».",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|discussion]])",
+       "timezone-local": "Locâl",
+       "duplicate-defaultsort": "<strong>Atencion :</strong> la cllâf de chouèx per dèfôt « $2 » ècllafe la vielye cllâf « $1 ».",
+       "duplicate-displaytitle": "<strong>Atencion :</strong> lo titro de viua « $2 » ècllafe lo viely titro « $1 ».",
+       "invalid-indicator-name": "<strong>Fôta :</strong> l’atribut <code>name</code> des endiquiors d’ètat de la pâge dêt pas étre vouedo.",
        "version": "Vèrsion",
-       "version-extensions": "Èxtensions enstalâs",
-       "version-skins": "Habelyâjos",
+       "version-extensions": "Èxtensions enstalâyes",
+       "version-skins": "Habelyâjos enstalâs",
        "version-specialpages": "Pâges spèciâles",
-       "version-parserhooks": "Grèfons du parsor",
+       "version-parserhooks": "Èxtensions parsiors",
        "version-variables": "Variâbles",
-       "version-antispam": "Prèvencion du spame",
+       "version-antispam": "Prèvencion de mèssâjos cofos",
        "version-other": "De totes sôrtes",
-       "version-mediahandlers": "Maneyors de mèdia",
-       "version-hooks": "Grèfons",
-       "version-parser-extensiontags": "Balises d’èxtension du parsor",
-       "version-parser-function-hooks": "Grèfons de les fonccions du parsor",
-       "version-hook-name": "Nom du grèfon",
-       "version-hook-subscribedby": "Soscrit per",
+       "version-mediahandlers": "Maneyors de mèdiâs",
+       "version-hooks": "Èxtensions",
+       "version-parser-extensiontags": "Balises d’èxtensions parsiors",
+       "version-parser-function-hooks": "Èxtensions de les fonccions parsiors",
+       "version-hook-name": "Nom de l’èxtension",
+       "version-hook-subscribedby": "Abonâs :",
        "version-version": "($1)",
-       "version-license": "Licence",
-       "version-poweredby-credits": "Ceti vouiqui fonccione grâce a '''[https://www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
+       "version-no-ext-name": "[nion nom]",
+       "version-license": "Licence MediaWiki",
+       "version-ext-license": "Licence",
+       "version-ext-colheader-name": "Èxtension",
+       "version-skin-colheader-name": "Habelyâjo",
+       "version-ext-colheader-version": "Vèrsion",
+       "version-ext-colheader-license": "Licence",
+       "version-ext-colheader-description": "Dèscripcion",
+       "version-ext-colheader-credits": "Ôtors",
+       "version-license-title": "Licence por $1",
+       "version-license-not-found": "Nion’enformacion dètalyêe de la licence est étâye trovâye por cel’èxtension.",
+       "version-credits-title": "Grant-marci por $1",
+       "version-credits-not-found": "Nion’enformacion dètalyêe du grant-marci est étâye trovâye por cel’èxtension.",
+       "version-poweredby-credits": "Cél vouiqui fonccione grâce a <strong>[https://www.mediawiki.org/ MediaWiki]</strong>, copyright © 2001-$1 $2.",
        "version-poweredby-others": "ôtros",
-       "version-license-info": "MediaWiki est una programeria libra ; vos la pouede tornar distribuar et / ou changiér d’aprés los tèrmos de la Licence publica g·ènèrala GNU coment publeyê per la Free Software Foundation ; seye la vèrsion 2 de la Licence, ou ben (a voutron chouèx) tota novèla vèrsion.\n\nMediaWiki est distribuâ dens l’èsperance que serat utila, mas SEN GINS DE GARANTIA ; sen mémo la garantia emplicita de COMÈRCIALISACION ou ben d’ADAPTACION A UN USÂJO PARTICULIÉR. Vêde la Licence publica g·ènèrala GNU por més de dètalys.\n\nVos devriâd avêr reçu un [{{SERVER}}{{SCRIPTPATH}}/COPYING ègzemplèro de la Licence publica g·ènèrala GNU] avouéc ceti programo ; ôtrament, ècrîde a la « Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA » ou ben [//www.gnu.org/licenses/old-licenses/gpl-2.0.html liéséd-la en legne].",
-       "version-software": "Programeries enstalâs",
+       "version-poweredby-translators": "traductors de translatewiki.net",
+       "version-credits-summary": "Nos tegnens a remarciér cetes gens por lor contribucion a [[Special:Version|MediaWiki]].",
+       "version-license-info": "MediaWiki est na programeria libra ; vos la pouede redistribuar et / ou changiér d’aprés los tèrmos de la Licence publica g·ènèrâla GNU coment publeyêe per la Free Software Foundation ; ou ben la vèrsion 2 de la Licence ou ben (a voutron chouèx) tota vèrsion ples novèla.\n\nMediaWiki est distribuâ dens l’èsperance que serat utila, mas SEN NIONA GARANTIA ; sen mémo la garantia sosentendua de COMÈRCIALISACION d’ADAPTACION A UN USÂJO PARTICULIÉR. Vêde la Licence publica g·ènèrâla GNU por més de dètalys.\n\nVos devriâd avêr reçu na [{{SERVER}}{{SCRIPTPATH}}/COPYING copia de la Licence publica g·ènèrâla GNU] avouéc cél programo ; ôtrament, ècrîde a la « Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA » ou ben [//www.gnu.org/licenses/old-licenses/gpl-2.0.html liéséd-la en legne].",
+       "version-software": "Programeries enstalâyes",
        "version-software-product": "Marchandie",
        "version-software-version": "Vèrsion",
-       "version-entrypoints": "URL de pouent d’entrâ",
+       "version-entrypoints": "URLs des pouents d’entrâ",
        "version-entrypoints-header-entrypoint": "Pouent d’entrâ",
        "version-entrypoints-header-url": "URL",
+       "version-libraries": "Bibliotèques enstalâyes",
+       "version-libraries-library": "Bibliotèca",
+       "version-libraries-version": "Vèrsion",
+       "version-libraries-license": "Licence",
+       "version-libraries-description": "Dèscripcion",
+       "version-libraries-authors": "Ôtors",
+       "redirect": "Redirigiér per ID de fichiér, utilisator, pâge, vèrsion ou ben jornâl",
+       "redirect-summary": "Cela pâge spèciâla redirige vers un fichiér (lo nom de fichiér balyê), na pâge (l’ID de vèrsion de pâge balyê), na pâge d’utilisator (l’ID numerico a l’utilisator balyê) ou ben un’entrâ de jornâl (l’ID du jornâl balyê). Usâjo : [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] ou ben [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Emmodar",
+       "redirect-lookup": "Rechèrche :",
+       "redirect-value": "Valor :",
+       "redirect-user": "ID a l’utilisator",
+       "redirect-page": "ID de pâge",
+       "redirect-revision": "Vèrsion de la pâge",
+       "redirect-file": "Nom du fichiér",
+       "redirect-logid": "ID du jornâl",
+       "redirect-not-exists": "Valor entrovâbla",
        "fileduplicatesearch": "Rechèrche des fichiérs en doblo",
        "fileduplicatesearch-summary": "Rechèrche des fichiérs en doblo d’aprés lor mârca de chaplâjo.",
        "fileduplicatesearch-filename": "Nom du fichiér :",
        "fileduplicatesearch-submit": "Rechèrchiér",
        "fileduplicatesearch-info": "$1 × $2 pixèls<br />Talye du fichiér : $3<br />Tipo MIME : $4",
        "fileduplicatesearch-result-1": "Lo fichiér « $1 » at gins de doblo pariér.",
-       "fileduplicatesearch-result-n": "Lo fichiér « $1 » at $2 {{PLURAL:$2|doblo pariér|doblos pariérs}}.",
-       "fileduplicatesearch-noresults": "Nion fichiér apelâ « $1 » at étâ trovâ.",
+       "fileduplicatesearch-result-n": "Lo fichiér « $1 » at {{PLURAL:$2|1 doblo pariér|$2 doblos pariérs}}.",
+       "fileduplicatesearch-noresults": "Nion fichiér apelâ « $1 » est étâ trovâ.",
        "specialpages": "Pâges spèciâles",
-       "specialpages-note": "* Pâges spèciâles normales.\n* <span class=\"mw-specialpagerestricted\">Pâges spèciâles rètrentes.</span>\n* <span class=\"mw-specialpagecached\">Pâges spèciâles solament en cache (porriant étre dèpassâs).</span>",
-       "specialpages-group-maintenance": "Rapôrts de mantegnence",
+       "specialpages-note-top": "Lègenda",
+       "specialpages-note": "* Pâges spèciâles normales.\n* <span class=\"mw-specialpagerestricted\">Pâges spèciâles rètrentes.</span>",
+       "specialpages-group-maintenance": "Rapôrts d’entretin",
        "specialpages-group-other": "Ôtres pâges spèciâles",
-       "specialpages-group-login": "Sè branchiér / fâre un compto",
-       "specialpages-group-changes": "Dèrriérs changements et jornals",
-       "specialpages-group-media": "Rapôrts et tèlèchargements de fichiérs mèdia",
-       "specialpages-group-users": "Usanciérs et drêts apondus",
+       "specialpages-group-login": "Sè branchiér / fâre un comptio",
+       "specialpages-group-changes": "Dèrriérs changements et jornâls",
+       "specialpages-group-media": "Rapôrts de mèdiâs et tèlèchargements",
+       "specialpages-group-users": "Utilisators et drêts",
        "specialpages-group-highuse": "Pâges d’usâjo important",
        "specialpages-group-pages": "Listes de pâges",
        "specialpages-group-pagetools": "Outils por les pâges",
-       "specialpages-group-wiki": "Balyês du vouiqui et outils",
-       "specialpages-group-redirects": "Pâges spèciâles redirigiês",
-       "specialpages-group-spam": "Outils anti-spame",
-       "specialpages-group-developer": "Outils u dèvelopor",
+       "specialpages-group-wiki": "Balyês et outils",
+       "specialpages-group-redirects": "Pâges spèciâles que redirijont",
+       "specialpages-group-spam": "Outils contre los mèssâjos cofos",
+       "specialpages-group-developer": "Outils u dèvelopior",
        "blankpage": "Pâge voueda",
-       "intentionallyblankpage": "Ceta pâge est lèssiê èxprès voueda.",
-       "external_image_whitelist": "  #Lèssiéd ceta legne justo d’ense.<pre>\n#Endicâd los bocons d’èxprèssions racionèles (solament la partia endicâ entre-mié los //) ce-desot.\n#Corrèspondront avouéc los lims hipèrtèxtos de les émâges (ben liyês) de defôr.\n#Celos que corrèspondont sè montreront coment des émâges, ôtrament solament un lim de vers l’émâge serat montrâ.\n#Les legnes que començont per un # seront considèrâs coment des comentèros.\n#Ceta lista est pas sensibla a la câssa.\n\n#Betâd tôs los bocons d’èxprèssions racionèles (*RegEx*) en-dessus de ceta legne. Lèssiéd ceta legne justo d’ense.</pre>",
-       "tags": "Balises des changements valides",
+       "intentionallyblankpage": "Cela pâge est lèssiêe volontèrament voueda.",
+       "external_image_whitelist": " #Lèssiéd ceta legne justo d’ense.<pre>\n#Endicâd los bocons d’èxprèssions racionâles (solament la partia endicâye entre-mié los //) ce-desot.\n#Corrèspondront avouéc les URLs de les émâges (ben liyêes) de defôr.\n#Celos que corrèspondont sè faront vêre coment d’émâges, ôtrament solament un lim de vers l’émâge serat montrâ.\n#Les legnes que començont per un # seront considèrâyes coment de comentèros.\n#O est pas sensiblo a la câssa.\n\n#Betâd tôs los bocons d’èxprèssions racionâles d’amont ceta legne. Lèssiéd cela legne justo d’ense.</pre>",
+       "tags": "Balises des changements justos",
        "tag-filter": "Filtrar les [[Special:Tags|balises]] :",
        "tag-filter-submit": "Filtrar",
        "tag-list-wrapper": "([[Special:Tags|Balis{{PLURAL:$1|a|es}}]] : $2)",
        "tags-title": "Balises",
-       "tags-intro": "Ceta pâge liste les balises que la programeria pôt utilisar por marcar un changement et lor significacion.",
+       "tags-intro": "Cela pâge liste les balises que la programeria pôt empleyér por marcar un changement et lor significacion.",
        "tags-tag": "Nom de la balisa",
-       "tags-display-header": "Aparence dens les listes de changements",
+       "tags-display-header": "Aparence dedens les listes de changements",
        "tags-description-header": "Dèscripcion complèta de la balisa",
+       "tags-source-header": "Sôrsa",
+       "tags-active-header": "Actif ?",
        "tags-hitcount-header": "Changements balisâs",
+       "tags-actions-header": "Accions",
+       "tags-active-yes": "Ouè",
+       "tags-active-no": "Nan",
+       "tags-source-extension": "Dèfenia per un’èxtension",
+       "tags-source-manual": "Aplicâye a la man per los utilisators et los robots",
+       "tags-source-none": "Dèpassâye",
        "tags-edit": "changiér",
+       "tags-delete": "suprimar",
+       "tags-activate": "activar",
+       "tags-deactivate": "dèsactivar",
        "tags-hitcount": "$1 changement{{PLURAL:$1||s}}",
+       "tags-manage-no-permission": "Vos éd pas la pèrmission de maneyér les balises de changement.",
+       "tags-manage-blocked": "Vos pouede pas maneyér les balises de changement quand vos éte blocâ{{GENDER:||ye}}.",
+       "tags-create-heading": "Fâre na balisa novèla",
+       "tags-create-explanation": "Per dèfôt, les balises novèles fêtes seront disponibles por los utilisators et los robots.",
+       "tags-create-tag-name": "Nom de la balisa :",
+       "tags-create-reason": "Rêson :",
+       "tags-create-submit": "Fâre",
+       "tags-create-no-name": "Vos dête spècifiar un nom de balisa.",
        "comparepages": "Comparar des pâges",
        "compare-page1": "Pâge 1",
        "compare-page2": "Pâge 2",
index c52df0a..27ac2b2 100644 (file)
        "noname": "Dü skel en rochten brükernööm uundu.",
        "loginsuccesstitle": "Uunmeldin hää loket.",
        "loginsuccess": "'''Dü beest nü üs „$1“ bi {{SITENAME}} uunmeldet.'''",
-       "nosuchuser": "Di brükernööm „$1“ jaft at ei. Aachte üüb det skriiwwiis (an uk üüb grat- an letjskriiwang), an do [[Special:UserLogin/signup|melde di nei uun]].",
+       "nosuchuser": "Di brükernööm „$1“ jaft at ei. Aachte üüb det skriiwwiis (an uk üüb grat- an letjskriiwang), an do [[Special:CreateAccount|melde di nei uun]].",
        "nosuchusershort": "Diar as nään brüker mä di nööm \"$1\".\nHeest dü ham uk rocht skrewen?",
        "nouserspecified": "Dü skel en brükernööm uundu.",
        "login-userblocked": "Didiar brüker as speret wurden. Hi mut ham ei uunmelde.",
        "accmailtext": "En tufelag iinracht paaswurd för [[User talk:$1|$1]] as tu $2 ferschüürd wurden. Det koon üüb det spezial-sidj ''[[Special:ChangePassword|Paaswurd anre]]'' feranert wurd, wan dü uunmeldet beest.",
        "newarticle": "(Nei)",
        "newarticletext": "Dü beest en ferwisang tu en sidj fulagt, diar't noch ei jaft.\nAm det sidj iinturachten, skriiw dan tekst uun det fial för't bewerkin iin.\nÜüb det [$1 halepsidj] fanjst dü halep.\nWan dü ütj fersen heer beest, trak ianfach üüb di <strong>turag</strong>-knoop faan dan browser.",
-       "anontalkpagetext": "----''Üüb detheer sidj könst dü en ünbekäänden brüker en nooracht du. Det lääpt auer sin IP adres. IP adresen kön faan flook brükern brükt wurd. Wan dü mä detheer nooracht niks began könst, do as det ferlicht för hoker ööders mend weesen. Dü säärst niks widjer onernem. Wan dü en aanj [[Special:UserLogin/signup|brükerkonto iinrachst]] of di [[Special:UserLogin|uunmeldest]], komt sowat ei weder föör.",
+       "anontalkpagetext": "----''Üüb detheer sidj könst dü en ünbekäänden brüker en nooracht du. Det lääpt auer sin IP adres. IP adresen kön faan flook brükern brükt wurd. Wan dü mä detheer nooracht niks began könst, do as det ferlicht för hoker ööders mend weesen. Dü säärst niks widjer onernem. Wan dü en aanj [[Special:CreateAccount|brükerkonto iinrachst]] of di [[Special:UserLogin|uunmeldest]], komt sowat ei weder föör.",
        "noarticletext": "Üüb detdiar sidj stäänt noch niks.\nDü könst didiar tiitel üüb ööder sidjen [[Special:Search/{{PAGENAME}}|schük]],\n<span class=\"plainlinks\">uun [{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} logbuken schük] of detdiar sidj [{{fullurl:{{FULLPAGENAME}}|action=edit}} bewerke]</span>.",
        "noarticletext-nopermission": "Üüb detdiar sidj stäänt noch niks, oober dü mutst diar uk niks iinskriiw.\nDü könst diar üüb ööder sidjen efter [[Special:Search/{{PAGENAME}}|schük]] of a <span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{FULLPAGENAME}}}} logbuken uunluke].</span>",
        "missing-revision": "Det werjuun #$1 faan det sidj \"{{FULLPAGENAME}}\" jaft at ei.\n\nDet komt diar miast faan, dat en ual ferwisang stregen wurden as.\nDü könst det uun't [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} logbuk faan stregen sidjen] efterlees.",
index 42da004..46ec76f 100644 (file)
        "noname": "No tu âs inserît un non utent valit.",
        "loginsuccesstitle": "Jentrât cun sucès",
        "loginsuccess": "Cumò tu sês jentrât te {{SITENAME}} sicu \"$1\".",
-       "nosuchuser": "Non è registrato alcun utente di nome \"$1\".\nI nomi utente sono sensibili alle maiuscole.\nVerificare il nome inserito o [[Special:UserLogin/signup|creare una nuova utenza]].",
+       "nosuchuser": "Non è registrato alcun utente di nome \"$1\".\nI nomi utente sono sensibili alle maiuscole.\nVerificare il nome inserito o [[Special:CreateAccount|creare una nuova utenza]].",
        "nosuchusershort": "Nol esist nissun utent cul non \"$1\". Controle di no vê sbaliât di scrivi.",
        "nouserspecified": "Tu scugnis specificâ un non utent.",
        "wrongpassword": "La peraule clâf zontade no je juste. Torne par plasê a provâ.",
        "categories": "Categoriis",
        "categoriespagetext": "Lis categoriis ca sot a àn dentri pagjinis o elements multimediâi.\nLis [[Special:UnusedCategories|categoriis no dopradis]] no son mostradis culì.\nCjale ancje lis [[Special:WantedCategories|categoriis desideradis]].",
        "categoriesfrom": "Mostre lis categoriis scomençant di:",
-       "special-categories-sort-count": "met in ordin par numar",
-       "special-categories-sort-abc": "met in ordin alfabetichementri",
        "deletedcontributions": "Contribûts dal utent eliminâts",
        "deletedcontributions-title": "Contribûts dal utent eliminâts",
        "sp-deletedcontributions-contribs": "contribûts",
index 41368ee..0146cb2 100644 (file)
        "noname": "Jo moatte in meidognamme opjaan.",
        "loginsuccesstitle": "Oanmelden slagge.",
        "loginsuccess": "<strong>Jo binne no oanmelden op de {{SITENAME}} as: \"$1.\"</strong>",
-       "nosuchuser": "Der is gjin meidogger \"$1\".\nKontrolearje de stavering, of [[Special:UserLogin/signup|meitsje in nije meidogger oan]].",
+       "nosuchuser": "Der is gjin meidogger \"$1\".\nKontrolearje de stavering, of [[Special:CreateAccount|meitsje in nije meidogger oan]].",
        "nosuchusershort": "Der is gjin meidogger mei de namme \"$1\". It is goed skreaun?",
        "nouserspecified": "Jo moatte in brûkersnamme opjaan.",
        "wrongpassword": "Meidochnamme en wachtwurd hearre net by elkoar. Besykje op 'e nij, of fier it wachtwurd twa kear yn en meitsje nije meidoggersynstellings.",
        "accmailtext": "It wachtwurd foar \"$1\" is ferstjoerd nei $2.",
        "newarticle": "(Nij)",
        "newarticletext": "Jo hawwe in keppeling folge nei in side dêr't noch gjin tekst op stiet.\nOm sels tekst te meistjsen kinne jo dy gewoan yntype in dit bewurkingsfjild\n([$1 Mear ynformaasje oer bewurkjen].)\nOars kinne jo tebek mei de tebek-knop fan jo blêder.",
-       "anontalkpagetext": "----''Dit is de oerlisside fan in ûnbekende meidogger; in meidogger dy't him/har net oanmeld hat. Om't der gjin namme bekend is, wurdt it ynternet-adres brûkt om oan te jaan wa. Mar faak is it sa dat sa'n adres net altyd troch deselde persoan brûkt wurdt. As jo it idee hawwe dat jo as ûnbekende meidogger opmerkings foar in oar krije, dan kinne jo jo [[Special:UserLogin/signup|registrearje]], of jo [[Special:UserLogin|oanmelde]]. Fan in oanmelde meidogger is it ynternet-adres net sichtber, en as oanmelde meidogger krije jo allinnich opmerkings dy't foar josels bedoeld binne.''",
+       "anontalkpagetext": "----''Dit is de oerlisside fan in ûnbekende meidogger; in meidogger dy't him/har net oanmeld hat. Om't der gjin namme bekend is, wurdt it ynternet-adres brûkt om oan te jaan wa. Mar faak is it sa dat sa'n adres net altyd troch deselde persoan brûkt wurdt. As jo it idee hawwe dat jo as ûnbekende meidogger opmerkings foar in oar krije, dan kinne jo jo [[Special:CreateAccount|registrearje]], of jo [[Special:UserLogin|oanmelde]]. Fan in oanmelde meidogger is it ynternet-adres net sichtber, en as oanmelde meidogger krije jo allinnich opmerkings dy't foar josels bedoeld binne.''",
        "noarticletext": "Der stjit noch gjin tekst op dizze side. Jo kinne\n[[Special:Search/{{PAGENAME}}|hjirboppe nei dy tekst sykje]], of [{{fullurl:{{FULLPAGENAME}}|action=edit}} de side skriuwe].",
        "userpage-userdoesnotexist": "Jo bewurkje in brûkersside fan in brûker dy't net bestiet (brûker \"<nowiki>$1</nowiki>\").\nKontrolearje oft jo dizze side wol oanmeitsje/bewurkje wolle.",
        "clearyourcache": "<strong>Opmerking:</strong> Nei it fêstlizzen kin it nedich wêze de oerslach fan dyn blêder te leegjen foardat de wizigings te sjen binne.\n* <strong>Firefox / Safari:</strong> Hâld <em>Shift</em> yntreaun wylst jo op <em>Dizze side fernije</em> klikke, of typ <em>Ctrl-F5</em> of <em>Ctrl-R</em> (<em>⌘-R</em> op in Mac)\n* <strong>Google Chrome:</strong> Typ <em>CTRL-Shift-R</em> (<em>⌘-Shift-R</em> op in Mac)\n* <strong>Internet Explorer:</strong> Hâld <em>Ctrl</em> yntreaun wylst jo <em>Vernieuwen'' klikke of typ <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Leegje jo cache yn <em>Extra → Voorkeuren</em>",
index 06cbd5c..fd2344e 100644 (file)
        "noname": "Níor thug tú ainm úsáideora bailí.",
        "loginsuccesstitle": "Logáladh isteach thú",
        "loginsuccess": "'''Tá tú logáilte isteach anois sa {{SITENAME}} mar \"<nowiki>$1</nowiki>\".'''",
-       "nosuchuser": "Níl aon úsáideoir ann leis an ainm \"$1\".\nTá ainmneacha úsáideoir cásíogair.\nCinntigh do litriú, nó [[Special:UserLogin/signup|bain úsáid as an foirm thíos]] chun cuntas úsáideora nua a chruthú.",
+       "nosuchuser": "Níl aon úsáideoir ann leis an ainm \"$1\".\nTá ainmneacha úsáideoir cásíogair.\nCinntigh do litriú, nó [[Special:CreateAccount|bain úsáid as an foirm thíos]] chun cuntas úsáideora nua a chruthú.",
        "nosuchusershort": "Níl aon úsáideoir ann leis an ainm \"$1\". Cinntigh do litriú.",
        "nouserspecified": "Caithfidh ainm úsáideoir a shonrú.",
        "login-userblocked": "Tá an t-úsáideoir seo faoi bhac. Níl cead aige/aici logáil isteach.",
index d8659c0..d8f5d4d 100644 (file)
        "morenotlisted": "Chan eil an liosta seo iomlan.",
        "mypage": "Duilleag",
        "mytalk": "Deasbaireachd",
-       "anontalk": "Deasbaireachd airson an IP seo",
+       "anontalk": "Deasbaireachd",
        "navigation": "Seòladh",
        "and": "&#32;agus",
        "qbfind": "Lorg",
        "laggedslavemode": "<strong>Rabhadh:</strong> Faodaidh nach eil ùrachaidhean a rinneadh o chionn ghoirid a' nochdadh san duilleag.",
        "readonly": "Stòr-dàta glaiste",
        "enterlockreason": "Cuir a-steach adhbhar a' ghlais, a' gabhail a-steach tuairmeas air fuasgladh a' ghlais.",
-       "readonlytext": "Tha an stòr-dàta glaiste do chlàir ùra 's mùthaidhean eile, ma dh'fhaoidte air sgàth obair-chàraidh chunbhalach an stòir-dhàta 's bidh e mar as àbhaist às dèidh sin.\n\nChuir an rianadair a ghlas e an cèill na leanas: $1",
+       "readonlytext": "Tha an stòr-dàta glaiste do chlàir ùra ’s mùthaidhean eile, ma dh’fhaoidte air sgàth obair-chàraidh chunbhalach an stòir-dhàta ’s bidh e mar as àbhaist às dèidh sin.\n\nThuirt rianadair an t-siostaim a ghlas e na leanas: $1",
        "missing-article": "Cha do lorg an stòr-dàta teacsa de dhuilleag a bu chòir a bhith air a lorg aige 's air a bheil \"$1\" $2.\n\n'S e diofar no ceangal eachdraidheil ro shean ri duilleag a chaidh a sguabadh às a bhios coireach à seo mar is trice.\n\nMur eil seo fìor, faodaidh gun do lorg thu buga sa bhathar-bhog.\nAn dèan thu aithris air seo do [[Special:ListUsers/sysop|rianadair]], ag innse dhaibh dè an t-URL a bha ann.",
        "missingarticle-rev": "(mùthadh#: $1)",
        "missingarticle-diff": "(Diofar: $1, $2)",
        "viewsource": "Seall an tùs",
        "viewsource-title": "Seall an tùs aig $1",
        "actionthrottled": "Gnìomh air a mhùchadh",
-       "actionthrottledtext": "Gus casg a chur air spama, chan urrainn dhut an gnìomh seo a dhèanamh ro thric am broinn ùine ghoirid agus chaidh thu thairis air a' chrìoch seo.\nFeuch ris a-rithist às a dhèidh seo.",
+       "actionthrottledtext": "Gus casg a chur air droch-ghiùlan, chan urrainn dhut an gnìomh seo a dhèanamh ro thric am broinn ùine ghoirid agus chaidh thu thairis air a’ chrìoch seo.\nFeuch ris a-rithist ann am beagan mhionaidean.",
        "protectedpagetext": "Chaidh an duilleag seo a dhìon gus casg a chur air deasachadh.",
-       "viewsourcetext": "'S urrainn dhut coimhead air tùs na duilleige seo 's lethbhreac a dhèanamh dheth:",
-       "viewyourtext": "'S urrainn dhut coimhead air <strong>na dheasaich thu</strong> 's lethbhreac a dhèanamh dheth air an duilleag seo:",
+       "viewsourcetext": "’S urrainn dhut coimhead air tùs na duilleige seo ’s lethbhreac a dhèanamh dheth.",
+       "viewyourtext": "’S urrainn dhut coimhead air <strong>na dheasaich thu</strong> ’s lethbhreac a dhèanamh dheth air an duilleag seo.",
        "protectedinterface": "Bheir an duilleag seo dhut teacsa eadar-aghaidh airson a' bhathar-bhog air an uicidh seo 's chaidh a ghlasadh gus casg a chur air mì-chleachdadh. Gus eadar-theangachadh atharrachadh no a chur ris airson gach uicidh, cleachd [//translatewiki.net/ translatewiki.net], pròiseactan eadar-theangachadh MediaWiki.",
        "editinginterface": "<strong>Rabhadh:</strong> Tha thu a' deasachadh duilleag a tha 'ga chleachdadh a chum teacsa eadar-aghaidh a sholar airson a' bhathar-bhog.\nMa dh'atharraicheas tu an duilleag seo, bidh buaidh ann air coltas na h-eadar-aghaidh mar a chì càch e air an uicidh seo.",
        "translateinterface": "Gus eadar-theangachadh atharrachadh no a chur ris airson gach uicidh, cleachd [//translatewiki.net/ translatewiki.net], am pròiseact eadar-theangachaidh aig MediaWiki.",
-       "cascadeprotected": "Chaidh an duilleag seo a dhìon o dheasachadh a chionn 's gu bheil e am broinn {{PLURAL:$1|na duilleige|nan duilleagan}} a leanas a chaidh a dhìon 's an roghainn \"mar eas\" air:\n$2",
+       "cascadeprotected": "Chaidh an duilleag seo a dhìon o dheasachadh a chionn ’s gu bheil e fillte a-staigh {{PLURAL:$1|san duilleag|sna duilleagan}} a leanas a chaidh a dhìon ’s an roghainn “mar eas” air:\n$2",
        "namespaceprotected": "Chan eil cead agad duilleagan san ainm-spàs <strong>$1</strong> a dheasachadh.",
        "customcssprotected": "Chan eil cead agad an duilleag CSS seo a dheasachadh a chionn 's gu bheil na roghainnean pearsanta aig cleachdaiche eile innte.",
        "customjsprotected": "Chan eil cead agad an duilleag JavaScript seo a dheasachadh a chionn 's gu bheil na roghainnean pearsanta aig cleachdaiche eile innte.",
        "mypreferencesprotected": "Chan eil cead agad na roghainnean agad a dheasachadh.",
        "ns-specialprotected": "Chan ghabh duilleagan sònraichte a dheasachadh.",
        "titleprotected": "Chaidh an duilleag seo a dhìon o chruthachadh le [[User:$1|$1]].\nSeo am mìneachadh: <em>$2</em>.",
-       "filereadonlyerror": "Cha ghabh am faidhle \"$1\" atharrachadh a chionn 's gu bheil ionad-tasgaidh fhaidhlichean \"$2\" ri leughadh a-mhàin.\nThug an rianaire a ghlais e seachad an t-adhbhar a leanas: \"$3\".",
+       "filereadonlyerror": "Cha ghabh am faidhle “$1” atharrachadh a chionn ’s gu bheil ionad-tasgaidh fhaidhlichean “$2” ri leughadh a-mhàin.\nThuirt rianaire an t-siostaim a ghlais e na leanas: “$3”.",
        "invalidtitle-knownnamespace": "Tiotal mì-dhligheach leis an ainm-spàs \"$2\" agus an teacsa \"$3\"",
        "invalidtitle-unknownnamespace": "Tiotal mì-dhligheach leis an àireamh ainm-spàis $1 agus an teacsa \"$2\"",
        "exception-nologin": "Chan eil thu air logadh a-steach",
        "createacct-reason": "Adhbhar",
        "createacct-reason-ph": "Carson a tha thu a' cruthachadh cunntas eile?",
        "createacct-submit": "Cruthaich an cunntas agad",
-       "createacct-another-submit": "Cruthaich cunntas eile",
+       "createacct-another-submit": "Cruthaich cunntas",
        "createacct-benefit-heading": "Tha {{SITENAME}} 'ga chruthachadh le daoine mar thu fhèin.",
        "createacct-benefit-body1": "{{PLURAL:$1|deasachadh|deasachaidhean}}",
        "createacct-benefit-body2": "{{PLURAL:$1|duilleag|duilleagan}}",
        "noname": "Cha do thagh thu ainm-cleachdaiche dligheach.",
        "loginsuccesstitle": "Rinn thu logadh a-steach",
        "loginsuccess": "<strong>Rinn thu logadh a-steach air {{SITENAME}} mar \"$1\".</strong>",
-       "nosuchuser": "Chan eil cleachdaiche ann air a bheil \"$1\".\nTha ainmean chleachdaichean mothaichail do litrichean mòra 's beaga.\nThoir sùil air an litreachadh no [[Special:UserLogin/signup|cruthaich cunntas ùr]].",
+       "nosuchuser": "Chan eil cleachdaiche ann air a bheil \"$1\".\nTha ainmean chleachdaichean mothaichail do litrichean mòra 's beaga.\nThoir sùil air an litreachadh no [[Special:CreateAccount|cruthaich cunntas ùr]].",
        "nosuchusershort": "Chan eil cleachdaiche ann leis an ainm \"$1\".\nCuir sùil air an litreachadh.",
        "nouserspecified": "Tha agad ri ainm-cleachdaiche a chur ann.",
        "login-userblocked": "Chaidh an cleachdaiche seo a chasgadh. Chan eil logadh a-steach ceadaichte dhaibh.",
        "noemail": "Cha deach post-d a chlàradh airson a' chleachdaiche \"$1\".",
        "noemailcreate": "Feumaidh tu post-d dligheach a chur ann",
        "passwordsent": "Chaidh facal-faire ùr a chur dhan phost-d a chaidh a chlàradh airson \"$1\".\nDèan logadh a-steach a-rithist nuair a gheibh thu e.",
-       "blocked-mailpassword": "Chaidh bacadh a chur air an t-seòladh IP agad 's chan eil cead deasachaidh agad agus chan urrainn dhut an gleus a chum aiseag an fhacail-fhaire a chleachdadh gus casg a chur air mì-ghnàthachadh.",
+       "blocked-mailpassword": "Chaidh bacadh deasachaidh a chur air an t-seòladh IP agad. Airson casg a chur air mì-ghnàthachadh, chan urrainn dhut an gleus a chum aiseag an fhacail-fhaire a chleachdadh an-dràsta fhèin.",
        "eauthentsent": "Chaidh post-d dearbhaidh a chur dhan phost-d a chaidh ainmeachadh.\nMus dèid post-d sam bith eile a chur dhan chunntas, feumaidh tu leantainn ris an stiùireadh sa phost-d mar dhearbhadh gur ann agadsa a tha an cunntas.",
        "throttled-mailpassword": "Chaidh post-d a chur airson ath-shuidheachadh facail-fhaire mu thràth $1 {{PLURAL:$1|uair|uair|uairean|uair}} a thìde air ais.\nGus casg a chur air mì-ghnàthachadh, cha chuir sinn ach aon chuimhneachan facail-fhaire gach $1 {{PLURAL:$1|uair|uair|uairean|uair}} a thìde.",
        "mailerror": "Mearachd a' cur post: $1",
        "createaccount-title": "Cruthachadh cunntais airson {{SITENAME}}",
        "createaccount-text": "Chruthaich cuideigin cunntas airson a' phost-d agad air {{SITENAME}} ($4) air a bheil \"$2\", leis an fhacal-fhaire \"$3\".\nBu chòir dhut logadh a-steach agus am facal-faire agad atharrachadh gu h-ìosal an-dràsta.\n\n'S urrainn dhut an teachdaireachd seo a leigeil seachad ma chaidh an cunntas a chruthachadh air mhearachd.",
        "login-throttled": "Dh'fheuch thu ri logadh a-steach ro thric o chionn ghoirid.\nFuirich ort $1 mus feuch thu ris a-rithist.",
-       "login-abort-generic": "Cha do shoirbhich leat leis an logadh a-steach - Chaidh sgur dheth",
+       "login-abort-generic": "Dh’fhàillig an logadh a-steach - Sguireadh dheth",
        "login-migrated-generic": "Chaidh an cunntas agad imrich 's chan eil an t-ainm cleachdaiche agad ann tuilleadh air an uicidh seo.",
        "loginlanguagelabel": "Cànan: $1",
        "suspicious-userlogout": "Chaidh d' iarrtas airson logadh a-mach a dhiùltadh a chionn 's gu bheil coltas gun deach a chur le brabhsair briste no le progsaidh tasglannaidh.",
        "resetpass-no-info": "Feumaidh tu logadh a-steach mus dèan thu inntrigeadh dìreach dhan duilleag seo.",
        "resetpass-submit-loggedin": "Atharraich am facal-faire",
        "resetpass-submit-cancel": "Sguir dheth",
-       "resetpass-wrong-oldpass": "Tha am facal-faire sealach no làithreach mì-dhligheach.\nSaoil an do dh'atharraich thu am facal-faire agad mu thràth no an do dh'iarr thu facal-faire sealach ùr?",
+       "resetpass-wrong-oldpass": "Tha am facal-faire sealach no làithreach mì-dhligheach.\nSaoil an do dh’atharraich thu am facal-faire agad mu thràth no an do dh’iarr thu facal-faire sealach ùr?",
        "resetpass-recycled": "Tagh facal-faire ùr nach eil co-ionnann ris an fhacal-fhaire a tha agad an-dràsta.",
        "resetpass-temp-emailed": "Rinn thu logadh a-steach le còd sealach a fhuair thu air a' phost-d.\nAirson logadh a-steach slàn a dhèanamh, feumaidh tu facal-faire ùr a shuidheachadh an-seo:",
        "resetpass-temp-password": "Facal-faire sealach:",
        "passwordreset-emailtext-ip": "Dh'iarr cuideigin (thu fhèin, 's mathaid, on t-seòladh IP $1) am facal-faire airson {{SITENAME}} ($4) ath-shuidheachadh. Tha {{PLURAL:$3|an cunntas|na cunntasan}} a leanas co-cheangailte ris a' phost-d seo:\n\n$2\n\nFalbhaidh an ùine air {{PLURAL:$3|an fhacal-fhaire shealach|na faclan-faire sealach}} seo an ceann $5 {{PLURAL:$5|latha|latha|làithean|latha}}.\nBu chòir dhut logadh a-steach agus facal-faire ùr a thaghadh an-dràsta. Ma dh'iarr cuideigin eile seo no ma chuimhnich thu air an fhacal-fhaire agad 's mur eil thu airson atharrachadh tuilleadh, leig seachad an teachdaireachd seo 's lean ort leis an t-seann fhacal-fhaire.",
        "passwordreset-emailtext-user": "Dh'iarr an cleachdaiche $1 air {{SITENAME}} ath-shuidheachadh an fhacail-fhaire air {{SITENAME}} ($4). Tha {{PLURAL:$3|an cunntas-cleachdaiche|na cunntasan-cleachdaiche}} a leanas co-cheangailte ris an t-seòladh puist-d seo:\n\n$2\n\nFalbhaidh an ùine air {{PLURAL:$3|an fhacal-fhaire shealach|na faclan-faire sealach}} seo an ceann $5 {{PLURAL:$5|latha|latha|làithean|latha}}.\nBu chòir dhut logadh a-steach agus facal-faire ùr a thaghadh an-dràsta. Ma dh'iarr cuideigin eile seo no ma chuimhnich thu air an fhacal-fhaire agad 's mur eil thu airson atharrachadh tuilleadh, leig seachad an teachdaireachd seo 's lean ort leis an t-seann fhacal-fhaire.",
        "passwordreset-emailelement": "Ainm-cleachdaiche: \n$1\n\nFacal-faire sealach: \n$2",
-       "passwordreset-emailsentemail": "Chaidh post-d airson ath-shuidheachadh an fhacail-fhaire a chur.",
+       "passwordreset-emailsentemail": "Ma tha am post-d seo co-cheangailte ris a’ chunntas agad, thèid post-d airson ath-shuidheachadh an fhacail-fhaire a chur.",
        "passwordreset-emailsent-capture": "Chaidh post-d a chum ath-shuidheachadh an fhacail-fhaire a chur agus chì thu sin gu h-ìosal.",
        "passwordreset-emailerror-capture": "Chaidh post-d a chum ath-shuidheachadh an fhacail-fhaire a ghintinn agus chì thu sin gu h-ìosal ach cha b' urrainn dhuinn a chur dhan chleachdaiche: $1",
-       "changeemail": "Atharraich am post-d",
-       "changeemail-header": "Atharraich cunntas a' phuist-d",
+       "changeemail": "Atharraich no thoir air falbh an seòladh puist-d",
+       "changeemail-header": "Lìon am foirm seo a dh’atharrachadh an t-seòlaidh phuist-d agad. Ma tha thu airson an co-cheangal eadar post-d sam bith is an cunntas agad a thoirt air falbh, fàg an seòladh ùr bàn nuair a chuireas tu am foirm.",
        "changeemail-no-info": "Feumaidh tu logadh a-steach mus dèan thu inntrigeadh dìreach dhan duilleag seo.",
        "changeemail-oldemail": "An seòladh puist-d làithreach:",
        "changeemail-newemail": "An seòladh puist-d ùr:",
        "sig_tip": "D' ainm sgrìobhte le stampa-ama",
        "hr_tip": "Loidhne rèidh (na cleachd ro thric e)",
        "summary": "Gearr-chunntas:",
-       "subject": "Cuspair/ceann-loidhne:",
+       "subject": "Cuspair:",
        "minoredit": "Seo mùthadh beag",
        "watchthis": "Cum sùil air an duilleag seo",
        "savearticle": "Sàbhail an duilleag",
        "missingsummary": "<strong>Cuimhnich:</strong> Cha dug thu seachad gearr-chunntas air na dh'atharraich thu.\nMa bhriogas tu air \"{{int:savearticle}}\" a-rithist, thèid na dheasaich thu a shàbhaladh as aonais gearr-chunntais.",
        "selfredirect": "<strong>Rabhadh:</strong> Tha thu ag ath-stiùireadh duilleag dha fhèin.\nDh'fhaoidte gun do shònraich thu an t-amas cearr airson an ath-stiùiridh no gu bheil thu a' deasachadh an duilleag cearr.\nMa nì thu briogadh air \"{{int:savearticle}}\" a-rithist,m thèid an ath-stiùireadh a chruthachadh co-dhiù.",
        "missingcommenttext": "Cuir a-steach beachd gu h-ìosal.",
-       "missingcommentheader": "<strong>Cuimhnich:</strong> Cha dug thu seachad cuspair/ceann airson a' bheachd seo.\nMa bhriogas tu air \"{{int:savearticle}}\" a-rithist, thèid na dheasaich thu a shàbhaladh as aonais.",
+       "missingcommentheader": "<strong>Cuimhnich:</strong> Cha dug thu seachad cuspair airson a’ bheachd seo.\nMa bhriogas tu air “{{int:savearticle}}” a-rithist, thèid na dheasaich thu a shàbhaladh as aonais.",
        "summary-preview": "Ro-shealladh a' ghearr-chunntais:",
-       "subject-preview": "Ro-shealladh air a' chuspair/air a' cheann:",
+       "subject-preview": "Ro-shealladh a’ chuspair:",
        "blockedtitle": "Tha an cleachdair air a bhacadh",
        "blockedtext": "<strong>Chaidh an t-ainm-cleachdaiche no an seòladh IP agad a bhacadh.</strong>\n\n'S e $1 a chur am bacadh seo ort.\n{{GENDER:$1|Thug e|Thug i|Thugadh}} an cèill gun do {{GENDER:$1|rinn e|rinn i|rinneadh}} sin air sgàth an adhbhair seo: <em>$2</em>.\n\n* Toiseach a' bhacaidh: $8\n* Deireadh a' bhacaidh: $6\n* An neach air a bheil am bacadh: $7\n\n'S urrainn dhut fios a chur gu $1 no [[{{MediaWiki:Grouppage-sysop}}|rianair]] eile gus am bacadh seo a dheasbad.\nChan urrainn dhut am feart \"Cuir post-d dhan chleachdaiche seo\" a chleachdadh ach ma tha seòladh puist-d dligheach ann an [[Special:Preferences|roghainnean a' chunntais agad]] agus mura deach bacadh a chur air a chleachdadh.\n'S e $3 an seòladh IP làithreach agus agus 's e #$5 ID a' bhacaidh.\nThoir iomradh air a' mhion-fhiosrachadh gu h-àrd ma chuireas tu ceist sam bith mu dhèidhinn.",
        "autoblockedtext": "Chaidh an seòladh IP agad a bhacadh gu fèin-obrachail a chionn 's gun deach a chleachdadh le cuideigin eile a chaidh a bhacadh le $1.\n{{GENDER:$1|Thug e|Thug i|Thugadh}} an cèill gun do {{GENDER:$1|rinn e|rinn i|rinneadh}} sin air sgàth an adhbhair seo: \n\n:<em>$2</em>.\n\n* Toiseach a' bhacaidh: $8\n* Deireadh a' bhacaidh: $6\n* An neach air a bheil am bacadh: $7\n\n'S urrainn dhut fios a chur gu $1 no [[{{MediaWiki:Grouppage-sysop}}|rianair]] eile gus am bacadh seo a dheasbad.\n\nDh'fhaoidte nach urrainn dhut am feart \"Cuir post-d dhan chleachdaiche seo\" a chleachdadh ach ma tha seòladh puist-d dligheach ann an [[Special:Preferences|roghainnean a' chunntais agad]] agus mura deach bacadh a chur air a chleachdadh.\n\n'S e $3 an seòladh IP làithreach agus agus 's e #$5 ID a' bhacaidh.\nThoir iomradh air a' mhion-fhiosrachadh gu h-àrd ma chuireas tu ceist sam bith mu dhèidhinn.",
        "accmailtext": "Chaidh facal-faire a chruthachadh air thuaiream airson [[User talk:$1|$1]] 's a chur gu $2.\n\nGabhaidh am facal-faire airson a' chunntais ùir seo atharrachadh air an fo <em>[[Special:ChangePassword|atharraich facal-faire]]</em> às dèidh dhan chleachdaiche logadh a-steach.",
        "newarticle": "(Ùr)",
        "newarticletext": "Lean thu ri ceangal gu duilleag nach eil ann fhathast.\nCuir teacs sa bhogsa gu h-ìosal gus an duilleag seo a chruthachadh (seall air [$1 duilleag na cobharach] airson barrachd fiosrachaidh).\nMura robh dùil agad ris an duilleag seo a ruigsinn, briog air a' phutan <strong>air ais</strong> 'nad bhrabhsair.",
-       "anontalkpagetext": "----\n<em>Seo an duilleag deasbaireachd aig cleachdaiche gun urra nach do chruthaich cunntas fhathast no nach eil 'ga chleachdadh.</em>\nFeumaidh sinn an àireamh IP aca a chleachdadh air sgàth sin.\nFaodadh grunn chleachdaichean seòladh IP mar a chleachdadh còmhla.\nMas e cleachdaiche gun urra a tha annad 's ma tha thu dhen bheachd nach eil na beachdan seo a' buntainn riut, nach [[Special:UserLogin/signup|clàraich thu]] no nach dèan thu [[Special:UserLogin|logadh a-steach]] gus bùrach mar seo a sheachnadh san àm ri teachd?",
-       "noarticletext": "Chan eil teacsa sam bith anns an duilleag seo an-dràsta.\n'S urrainn dhut [[Special:Search/{{PAGENAME}}|an tiotal seo a lorg]] ann an duilleagan eile,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} na logaichean co-cheangailte a rannsachadh],\nno [{{fullurl:{{FULLPAGENAME}}|action=edit}} an duilleag seo a dheasachadh]</span>.",
+       "anontalkpagetext": "----\n<em>Seo an duilleag deasbaireachd aig cleachdaiche gun urra nach do chruthaich cunntas fhathast no nach eil 'ga chleachdadh.</em>\nFeumaidh sinn an àireamh IP aca a chleachdadh air sgàth sin.\nFaodadh grunn chleachdaichean seòladh IP mar a chleachdadh còmhla.\nMas e cleachdaiche gun urra a tha annad 's ma tha thu dhen bheachd nach eil na beachdan seo a' buntainn riut, nach [[Special:CreateAccount|clàraich thu]] no nach dèan thu [[Special:UserLogin|logadh a-steach]] gus bùrach mar seo a sheachnadh san àm ri teachd?",
+       "noarticletext": "Chan eil teacsa sam bith anns an duilleag seo an-dràsta.\n'S urrainn dhut [[Special:Search/{{PAGENAME}}|an tiotal seo a lorg]] ann an duilleagan eile,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} na logaichean co-cheangailte a rannsachadh],\nno [{{fullurl:{{FULLPAGENAME}}|action=edit}} an duilleag seo a chruthachadh]</span>.",
        "noarticletext-nopermission": "Chan eil teacsa sam bith san duilleag seo an-dràsta.\n'S urrainn dhut [[Special:Search/{{PAGENAME}}|tiotal na duilleige seo a lorg]] ann an duilleagan eile, no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} na logaichean co-cheangailte a rannsachadh]</span> ach chan eil cead agad an duilleag seo a chruthachadh.",
        "missing-revision": "Chan eil mùthadh #$1 na duilleige \"{{FULLPAGENAME}}\" ann.\n\nMar is trice, tachraidh seo ma leanas tu ceangal san eachdraidh a tha fìor aosta 's a tha a' dol gu duilleag a chaidh a sguabadh às.\nGheibh thu mion-fhiosrachadh ann an [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} loga nan duilleagan a chaidh a sguabadh às].",
        "userpage-userdoesnotexist": "Chan e cunntas clàraichte a tha ann an \"$1\".\nDèan cinnteach gu bheil thu airson an duilleag seo a chruthachadh/dheasachadh.",
        "userpage-userdoesnotexist-view": "Cha deach an cunntas cleachdaiche \"$1\" a chlàradh.",
        "blocked-notice-logextract": "Tha an cleachdaiche seo air a bhacadh an-dràsta fhèin.\nChì thu loga a' bhacaidh mu dheireadh gu h-ìosal mar fhiosrachadh dhut:",
-       "clearyourcache": "<strong>An aire:</strong> As dèidh dhut sàbhaladh, 's mathaid gum bi agad tasgadan a' bhrabhsair agad a chur air gleus mus fhaic thu na dh'atharraich thu.\n* <strong>Firefox / Safari:</strong> Cum shìos <em>Shift</em> is briog air <em>Ath-luchdaich</em> no brùth <em>Ctrl-F5</em> no <em>Ctrl-R</em> (<em>⌘-R</em> air Mac)\n* <strong>Google Chrome:</strong> Brùth <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> air Mac)\n* <strong>Internet Explorer:</strong> Cum shìos <em>Ctrl</em> is briog air <em>Ath-nuadhaich</em> no brùth <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Falamhaich an tasgadan ann an <em>Innealan → Roghainnean</em>",
+       "clearyourcache": "<strong>An aire:</strong> As dèidh dhut sàbhaladh, ’s mathaid gum bi agad tasgadan a’ bhrabhsair agad a chur air gleus mus fhaic thu na dh’atharraich thu.\n* <strong>Firefox / Safari:</strong> Cum shìos <em>Shift</em> is briog air <em>Ath-luchdaich</em> no brùth <em>Ctrl-F5</em> no <em>Ctrl-R</em> (<em>⌘-R</em> air Mac)\n* <strong>Google Chrome:</strong> Brùth <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> air Mac)\n* <strong>Internet Explorer:</strong> Cum shìos <em>Ctrl</em> is briog air <em>Ath-nuadhaich</em> no brùth <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Rach gu <em>Clàr-taice → Roghainnean</em> (<em>Opera → Roghainnean</em> air Mac) agus an uairsin gu <em>Prìobhaideachd ⁊ tèarainteachd → Falamhaich an dàta brabhsaidh → Dealbhan is faidhlichean san tasgadan</em>.",
        "usercssyoucanpreview": "<strong>Gliocas:</strong> Cleachd am putan \"{{int:showpreview}}\" airson an CSS agad a chur fo dheuchainn mus sàbhail thu e.",
        "userjsyoucanpreview": "<strong>Gliocas:</strong> Cleachd am putan \"{{int:showpreview}}\" gus an JavaScript ùr agad a chur fo dheuchainn mus sàbhail thu e.",
        "usercsspreview": "<strong>Cuimhnich nach e seo ach ro-shealladh air a' CSS chleachdaiche agad.\nCha deach a shàbhaladh fhathast!</strong>",
        "previewnote": "<strong>Cuimhnich nach eil ann ach ro-shealladh.</strong>\nCha deach na mùthaidhean agad a shàbhaladh fhathast!",
        "continue-editing": "Rach gun raon deasachaidh",
        "previewconflict": "Tha an ro-shealladh seo a' sealltainn dhut an teacsa san raon teacsa gu h-àrd mar a nochdas e ma shàbhaileas tu an-dràsta.",
-       "session_fail_preview": "<strong>Duilich! Cha b' urrainn dhuinn na dheasaich thu a làimhseachadh air sgàth call dàta an t-seisein.</strong>\nNach fheuch thu ris a-rithist?\nMur obraich e fhathast, feuch is [[Special:UserLogout|log a-mach]] is a-steach a-rithist an uairsin.",
-       "session_fail_preview_html": "<strong>Duilich! Cha b' urrainn dhuinn na dheasaich thu a làimhseachadh air sgàth call dàta an t-seisein.</strong>\n\n<em>A chionn 's gun do chuir {{SITENAME}} còd HTML an comas, tha an ro-shealladh falaichte mar dhìon an aghaidh ionnsaighean JavaScript.</em>\n\n<strong>Mas e deasachadh dligheach a tha seo, feuch ris a-rithist.</strong>\nMur obraich e fhathast, feuch is dèan [[Special:UserLogout|logadh a-mach]] is a-steach a-rithist an uairsin.",
+       "session_fail_preview": "Tha sinn duilich ach cha b’ urrainn dhuinn na dheasaich thu a làimhseachadh air sgàth call dàta an t-seisein.\n\nDh’fhaoidte gun deach do chlàradh a-mach. <strong>Dèan cinnteach gu bheil thu clàraichte a-staigh fhathast is feuch ris a-rithist</strong>.\nMur obraich e fhathast, feuch is [[Special:UserLogout|log a-mach]] is a-steach a-rithist an uairsin is dèan cinnteach gu bheil am brabhsair agad a’ gabhail ri briosgaidean on làrach seo.",
+       "session_fail_preview_html": "Tha sinn duilich ach cha b’ urrainn dhuinn na dheasaich thu a làimhseachadh air sgàth call dàta an t-seisein.\n\n<em>A chionn ’s gun do chuir {{SITENAME}} còd HTML an comas, tha an ro-shealladh falaichte mar dhìon an aghaidh ionnsaighean JavaScript.</em>\n\n<strong>Mas e deasachadh dligheach a tha seo, feuch ris a-rithist.</strong>\nMur obraich e fhathast, feuch is dèan [[Special:UserLogout|logadh a-mach]] is a-steach a-rithist an uairsin is dèan cinnteach gu bheil am brabhsair agad a’ gabhail ri briosgaidean on làrach seo.",
        "token_suffix_mismatch": "<strong>Dhiùlt sinn na dheasaich thu a chionn 's gun do chuir an cliant agad na caractaran puingeachaidh tro chèile san tòcan deasachaidh.</strong>\nDhiùlt sinn na dheasaich thu air eagal 's gun coirbeadh e teacsa na duilleige.\nTachraidh seo uaireannan ma chleachdar seirbheis-lìn progsaidh gun urra a tha làn de mhearachdan.",
        "edit_form_incomplete": "<strong>Cha do ràinig cuid dhen fhoirm deasachaidh am frithealaichte; dèan cinnteach gu bheil gach deasachadh agad slàn is feuch ris a-rithist.</strong>",
        "editing": "A' deasachadh $1",
        "copyrightwarning": "Thoir an aire gu bheilear a' tuigsinn gu bheil gach obair a chuireas tu ri {{SITENAME}} air a leigeil mu sgaoil fo $2 (see $1 airson mion-fhiosrachadh).\nMura bi thu toilichte 's daoine eile a' deasachadh gun tròcair na sgrìobh tu 's 'ga sgaoileadh mar a thogras iad, na cuir an-seo e.<br />\nTha thu a' toirt geall cuideachd gun do sgrìobh thu fhèin seo no gun do rinn thu lethbhreac dheth o àrainn phoblach no tùs saor coltach ris.\n<strong>Na cuir ann rudan fo chòir-lethbhreac gun chead!</strong>",
        "copyrightwarning2": "Thoir an aire gum faod deasaichean eile gach obair a chuireas tu ri {{SITENAME}} a dheasachadh, atharrachadh no a thoirt air falbh.\nMur eil thu ag iarraidh gun deasaich cuideigin eile na sgrìobh thu gun truas, na cuir a-null e.<br />\nNì thu gealladh dhuinn cuideachd gur e thu fhèin a sgrìobh e no gun do rinn thu lethbhreac dheth o Public Domain no stòras saor dhen leithid (faic $1 airson barrachd fiosrachaidh). <br />\n<strong>Na cuir a-null obair a tha fo chòir-lethbhreac gun chead!</strong>",
        "longpageerror": "<strong>Mearachd: Tha an teacsa a chur thu thugainn $1 {{PLURAL:$1|chileabaidht|chileabaidht|cileabaidhtichean|cileabaidht}} a dh'fhaid is tha sin nas fhaide na tha ceadaichte ($2 {{PLURAL:$2|chileabaidht|chileabaidht|cileabaidhtichean|cileabaidht}}).</strong>\nCha ghabh a shàbhaladh.",
-       "readonlywarning": "<strong>Rabhadh: Chaidh an stòr-dàta a ghlasadh a chum obair-ghlèidhidh agus chan urrainn dhut na dheasaich thu a shàbhaladh an-dràsta fhèin.</strong>\n'S mathaid gum b' fheairrde dhut lethbhreac a dhèanamh dhen teacsa agus a shàbhaladh ann am faidhle ach an urrainn dhut a chleachdadh as a dhèidh seo.\n\nSeo am mìneachadh a thug an rianaire a ghlais e: $1",
+       "readonlywarning": "<strong>Rabhadh: Chaidh an stòr-dàta a ghlasadh a chum obair-ghlèidhidh agus chan urrainn dhut na dheasaich thu a shàbhaladh an-dràsta fhèin.</strong>\n’S mathaid gum b’ fheairrde dhut lethbhreac a dhèanamh dhen teacsa agus a shàbhaladh ann am faidhle ach an urrainn dhut a chleachdadh as a dhèidh seo.\n\nSeo am mìneachadh a thug rianaire an t-siostaim a ghlais e: $1",
        "protectedpagewarning": "<strong>Rabhadh: Chaidh an duilleag seo a dhìon 's chan urrainn ach dhan fheadhainn aig a bheil ùghdarras rianaire a dheasachadh.</strong>\nChì thu an clàr mu dheireadh san loga mar fhiosrachadh dhut gu h-ìosal:",
        "semiprotectedpagewarning": "<strong>An aire:</strong> Chaidh an duilleag seo a dhìon 's chan fhaod ach cleachdaichean clàraichte a dheasachadh.\nSeo an rud mu dheireadh san loga mar fhiosrachadh dhut:",
-       "cascadeprotectedwarning": "<strong>Rabhadh:</strong> Chaidh an duilleag seo a dhìon 's chan fhaod ach rianairean a dheasachadh a chionn 's gun robh e am broinn {{PLURAL:$1|na duilleige|nan duilleagan}} a leanas a tha dìonta o bhith mar eas.",
+       "cascadeprotectedwarning": "<strong>Rabhadh:</strong> Chaidh an duilleag seo a dhìon ’s chan fhaod ach rianairean a dheasachadh a chionn ’s gun robh e fillte a-staigh {{PLURAL:$1|san duilleag|sna duilleagan}} a leanas a tha dìonta o bhith mar eas.",
        "titleprotectedwarning": "<strong>Rabhadh: Chaidh an duilleag seo a dhìon 's feumar [[Special:ListGroupRights|ceadan sònraichte]] gus a dheasachadh.</strong>\nSeo an rud mu dheireadh san loga mar fhiosrachadh dhut:",
        "templatesused": "Tha {{PLURAL:$1|teamplaid 'ga cleachdadh|teamplaidean 'gan cleachdadh}} air an duilleag seo:",
        "templatesusedpreview": "Tha {{PLURAL:$1|teamplaid 'ga cleachdadh|teamplaidean 'gan cleachdadh}} san ro-shealladh seo:",
        "rev-showdeleted": "seall",
        "revisiondelete": "Sguab às/neo-dhèan sguabadh às mùthaidhean",
        "revdelete-nooldid-title": "Tha am mùthadh seo mì-dhligheach",
-       "revdelete-nooldid-text": "Cha do shònraich thu mùthadh airson seo a dhèanamh, chan eil e ann no tha thu a' feuchainn ris am mùthadh làithreach a chur am falach.",
+       "revdelete-nooldid-text": "Cha do shònraich thu mùthadh airson seo a dhèanamh, chan eil e ann no tha thu a feuchainn ris am mùthadh làithreach a chur am falach.",
        "revdelete-no-file": "Chan eil am faidhle a shònraich thu ann.",
        "revdelete-show-file-confirm": "A bheil thu cinnteach gu bheil thu airson coimhead air mùthadh an fhaidhle \"<nowiki>$1</nowiki>\" a chaidh a sguabadh às $2 aig $3?",
        "revdelete-show-file-submit": "Tha",
        "revdelete-unsuppress": "Thoir air falbh na bacaidhean air mùthaidhean a chaidh aiseag",
        "revdelete-log": "Adhbhar:",
        "revdelete-submit": "Cuir an sàs e air {{PLURAL:$1|am mùthadh|na mùthaidhean}} a thagh thu",
-       "revdelete-success": "Chaidh so-fhaicsinneachd a' mhùthaidh ùrachadh.",
+       "revdelete-success": "Chaidh so-fhaicsinneachd a mhùthaidh ùrachadh.",
        "revdelete-failure": "Cha b' urrainn dhuinn so-fhaicsinneachd a' mhùthaidh ùrachadh:\n$1",
        "logdelete-success": "Chaidh faicsinneachd an loga a shuidheachadh.",
        "logdelete-failure": "Cha b' urrainn dhuinn faicsinneachd an loga a shuidheachadh:\n$1",
        "prefs-watchlist-token": "Tòcan a' chlàir-fhaire:",
        "prefs-misc": "Measgachadh",
        "prefs-resetpass": "Atharraich am facal-faire",
-       "prefs-changeemail": "Atharraich am post-d",
+       "prefs-changeemail": "Atharraich no thoirt air falbh post-d",
        "prefs-setemail": "Suidhich seòladh puist-d",
        "prefs-email": "Roghainnean a' phuist-d",
        "prefs-rendering": "Coltas",
        "rows": "Sreathan",
        "columns": "Colbhan",
        "searchresultshead": "Lorg",
-       "stub-threshold": "An stairsneach airson fòrmatadh <a href=\"#\" class=\"stub\">cheanglaichean nam bun</a> (bytes):",
+       "stub-threshold": "An stairsneach airson fòrmatadh cheanglaichean nam bun ($1):",
        "stub-threshold-disabled": "À comas",
        "recentchangesdays": "Co mheud latha a thèid a shealltainn sna mùthaidhean ùra:",
        "recentchangesdays-max": "$1 {{PLURAL:$1|latha|latha|làithean|latha}} air a' char as motha",
        "badsig": "Tha co-chàradh an t-soidhnidh mì-dhligheach.\nThoir sùil air na tagaichean HTML.",
        "badsiglength": "Tha an t-earr-sgrìobhadh agad ro fhada.\nChan fhaod e a bhith nas fhaide na $1 {{PLURAL:$1|charactar|charactar|caractaran|caractar}}.",
        "yourgender": "Do ghnè:",
-       "gender-unknown": "B' fhearr leam gun a bhith 'ga leigeil ris",
+       "gender-unknown": "Nì am bathar-bog a dhìcheall gun a bhith a’ cleachdadh faclan a nochdas gnè seach gnè",
        "gender-male": "Deasaichidh e duilleagan na h-uicidh",
        "gender-female": "Deasaichidh i duilleagan na h-Uicipeid",
        "prefs-help-gender": "Cha leig thu leas an roghainn seo a shuidheachadh.\nCleachdaidh am bathar-bog an luach aice gus bruidhinn riut le d' ainm 's iomradh a thoirt ort gu càch leis a' ghnè ghramataigeach iomchaidh.\nBidh am fiosrachadh seo poblach.",
        "userrights": "Stiùireadh ceadan a' chleachdaiche",
        "userrights-lookup-user": "Stiùirich na buidhnean chleachdaichean",
        "userrights-user-editname": "Cuir a-steach ainm-cleachdaiche:",
-       "editusergroup": "Deasaich na buidhnean chleachdaichean",
-       "editinguser": "Ag atharrachadh ceadan a' chleachdaiche <strong>[[User:$1|$1]]</strong> $2",
+       "editusergroup": "Deasaich na buidhnean {{GENDER:$1|chleachdaichean}}",
+       "editinguser": "Ag atharrachadh ceadan a’ {{GENDER:$1|chleachdaiche}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Deasaich na buidhnean chleachdaichean",
-       "saveusergroups": "Sàbhail na buidhnean chleachdaichean",
+       "saveusergroups": "Sàbhail na buidhnean {{GENDER:$1|chleachdaichean}}",
        "userrights-groupsmember": "Ball de:",
        "userrights-groupsmember-auto": "Ball fèin-obrachail de:",
        "userrights-groupsmember-type": "$1",
        "group-bot": "Bots",
        "group-sysop": "Rianadairean",
        "group-bureaucrat": "Biurocratan",
-       "group-suppress": "Marasgalan",
+       "group-suppress": "Mùchadairean",
        "group-all": "(na h-uile)",
        "group-user-member": "cleachdaiche",
        "group-autoconfirmed-member": "cleachdaiche fèin-dearbhte",
        "group-bot-member": "bot",
        "group-sysop-member": "rianaire",
        "group-bureaucrat-member": "biùrocrat",
-       "group-suppress-member": "marasgal",
+       "group-suppress-member": "{{GENDER:$1|mùchadair}}",
        "grouppage-user": "{{ns:project}}:Cleachdaichean",
        "grouppage-autoconfirmed": "{{ns:project}}:Cleachdaichean fèin-dearbhte",
        "grouppage-bot": "{{ns:project}}:Bots",
        "grouppage-sysop": "{{ns:project}}:Rianadairean",
        "grouppage-bureaucrat": "{{ns:project}}:Biurocratan",
-       "grouppage-suppress": "{{ns:project}}:Marasgal",
+       "grouppage-suppress": "{{ns:project}}:Mùch",
        "right-read": "Cead-leughaidh",
        "right-edit": "Cead-deasachaidh",
        "right-createpage": "Cead-cruthachaidh (de dhuilleagan nach eil 'nan duilleagan deasbaireachd)",
        "boteditletter": "bt",
        "unpatrolledletter": "!",
        "number_of_watching_users_pageview": "[Tha $1 {{PLURAL:$1|chleachdaiche|chleachdaiche|cleachdaichean|cleachdaiche}} a' cumail sùil air]",
-       "rc_categories": "Cuingich gu roinnean-seòrsa (sgaraich le \"|\")",
-       "rc_categories_any": "Roinn-seòrsa sam bith",
+       "rc_categories": "Dìreach sna roinnean-seòrsa (sgaraich le “|”):",
+       "rc_categories_any": "Gin dhe na chaidh a thaghadh",
        "rc-change-size": "$1",
        "rc-change-size-new": "$1 {{PLURAL:$1|bhaidht|bhaidht|baidhtichean|baidht}} às dèidh an atharrachaidh",
        "newsectionsummary": "Earrann ùr /* $1 */",
        "backend-fail-read": "Cha b' urrainn dhuinn am faidhle \"$1\" a leughadh.",
        "backend-fail-create": "Cha b' urrainn dhuinn sgrìobhadh san fhaidhle \"$1\".",
        "backend-fail-maxsize": "Cha b' urrainn dhuinn am faidhle \"$1\" a sgrìobhadh on a tha e nas motha na $2 {{PLURAL:$2|bhaidht|bhaidht|baidhtichean|baidht}}.",
-       "backend-fail-readonly": "Chan eil backend an stòraidh \"$1\" ach ri leughadh a-mhàin an-dràsta. Seo as adhbhar: \"<em>$2</em>\"",
+       "backend-fail-readonly": "Chan eil backend an stòraidh “$1” ach ri leughadh a-mhàin an-dràsta. Seo as adhbhar: <em>$2</em>",
        "backend-fail-synced": "Tha am faidhle \"$1\" ann an staid mì-chòrdail a thaobh nam backends stòraidh inntearnail.",
        "backend-fail-connect": "Cha deach leinn ceangal ri backend an stòraidh \"$1\".",
        "backend-fail-internal": "Chathair mearachd neo-aithnichte le backend an stòraidh \"$1\".",
        "uploadstash-summary": "Bheir an duilleag seo inntrigeadh dhut a dh'fhaidhlichean a chaidh a luchdadh suas no a tha 'gan luchdadh suas ach nach deach fhoillseachadh air an uicidh fhathast. Chan fhaic duine na faidhlichean seo ach an cleachdaiche a rinn an luchdadh suas.",
        "uploadstash-clear": "Glan na faidhlichean ann an tasgadan an luchdaidh suas",
        "uploadstash-nofiles": "Chan eil faidhle agad ann an tasgadan an luchdaidh suas.",
-       "uploadstash-badtoken": "Cha deach an gnìomh seo a choileanadh, 's dòcha air sgàth 's gun do dh'fhalbh an ùine air an teisteanas deasachaidh agad. Am feuch thu ris a-rithist?",
+       "uploadstash-badtoken": "Cha deach an gnìomh seo a choileanadh, ’s dòcha air sgàth ’s gun do dh’fhalbh an ùine air an teisteanas deasachaidh agad. Am feuch thu ris a-rithist?",
        "uploadstash-errclear": "Cha deach leinn na faidhlichean a ghlanadh air falbh.",
        "uploadstash-refresh": "Ath-nuadhaich liosta nam faidhlichean",
        "invalid-chunk-offset": "Frith-àireamh a' chnaip mhì-dhligheach",
        "nopagetext": "Chan eil an duilleag-uidhe a thug thu seachad ann.",
        "pager-newer-n": "{{PLURAL:$1|an $1 nas ùire|na $1 nas ùire}}",
        "pager-older-n": "{{PLURAL:$1|an $1 nas sine|na $1 nas sine}}",
-       "suppress": "Marasgal",
+       "suppress": "Mùch",
        "querypage-disabled": "Chaidh an duilleag shònraichte seo a chur à comas a chum dèanadais.",
        "booksources": "Tùsan a tha 'nan leabhraichean",
        "booksources-search-legend": "Lorg tùsan a tha 'nan leabhraichean",
        "booksources-text": "Chì thu liosta dhe cheanglaichean gu làraichean eile a reiceas leabhraichean ùra 's cleachdte gu h-ìosal 's ma dh'fhaoidte gum faigh thu barrachd fiosrachaidh orra mu leabhraichean a tha thu a' sireadh:",
        "booksources-invalid-isbn": "Tha coltas mì-dhligheach air an ISBN a chaidh a thoirt seachad; dearbhaich gun deach lethbhreac a dhèanamh dheth on tùs gun mhearachd.",
        "specialloguserlabel": "Cò rinn e:",
-       "speciallogtitlelabel": "Ceann-uidhe (tiotal no cleachdaiche):",
+       "speciallogtitlelabel": "Ceann-uidhe (tiotal no {{ns:user}}:ainm-cleachdaiche a’ chleachdaiche):",
        "log": "Logaichean",
        "all-logs-page": "A h-uile loga poblach",
        "alllogstext": "Sealladh co-mheasgaichte dhen a h-uile loga aig {{SITENAME}} a tha ri làimh.\n'S urrainn dhut an sealladh a chuingeachadh 's tu a' taghadh seòrsa an loga, ainm a' chleachdaiche (le aire do litrichean mòra 's beaga) no an duilleag a tha fo bhuaidh (le aire do litrichean mòra 's beaga).",
index eb1494d..43ccccb 100644 (file)
        "password-change-forbidden": "Non pode mudar os contrasinais neste wiki.",
        "externaldberror": "Ou ben se produciu un erro da base de datos na autenticación externa ou ben non se lle permite actualizar a súa conta externa.",
        "login": "Acceder ao sistema",
+       "login-security": "Verifique a súa identidade",
        "nav-login-createaccount": "Rexistro",
        "userlogin": "Rexistro",
        "userloginnocreate": "Rexistro",
        "userlogin-resetpassword-link": "Esqueceu o contrasinal?",
        "userlogin-helplink2": "Axuda co rexistro",
        "userlogin-loggedin": "Xa accedeu ao sistema como {{GENDER:$1|$1}}.\nUtilice o formulario inferior para acceder como outro usuario.",
+       "userlogin-reauth": "Debe conectarse de novo para verificar que é {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Crear outra conta",
        "createacct-emailrequired": "Enderezo de correo electrónico",
        "createacct-emailoptional": "Enderezo de correo electrónico (opcional)",
        "createacct-email-ph": "Insira o seu enderezo de correo electrónico",
        "createacct-another-email-ph": "Insira o enderezo de correo electrónico",
        "createaccountmail": "Utilizar un contrasinal aleatorio temporal e envialo ao enderezo de correo electrónico especificado",
+       "createaccountmail-help": "Pode usarse para crear unha conta para outra persoa sen coñecer o contrasinal.",
        "createacct-realname": "Nome real (opcional)",
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por que crea outra conta?",
+       "createacct-reason-help": "Mensaxe que se mostra no rexistro de creación de contas",
        "createacct-submit": "Crear a conta",
        "createacct-another-submit": "Crear conta",
+       "createacct-continue-submit": "Continuar a creación da conta",
+       "createacct-another-continue-submit": "Continuar a creación da conta",
        "createacct-benefit-heading": "Xente coma vostede elabora {{SITENAME}}.",
        "createacct-benefit-body1": "{{PLURAL:$1|edición|edicións}}",
        "createacct-benefit-body2": "{{PLURAL:$1|páxina|páxinas}}",
        "nocookiesnew": "A conta de usuario foi creada, pero non accedeu ao sistema.\n{{SITENAME}} para rexistrar os usuarios.\nVostede ten as cookies deshabilitadas.\nPor favor, habilíteas e logo acceda ao sistema co seu novo nome de usuario e contrasinal.",
        "nocookieslogin": "{{SITENAME}} usa cookies para rexistrar os usuarios.\nVostede ten as cookies deshabilitadas.\nPor favor, habilíteas e inténteo de novo.",
        "nocookiesfornew": "Non se creou a conta de usuario porque non puidemos confirmar a súa orixe.\nAsegúrese de que ten as cookies habilitadas, volva cargar a páxina e inténteo de novo.",
+       "createacct-loginerror": "A conta creouse correctamente, pero non se puido conectar automaticamente. Proceda ó [[Special:UserLogin|acceso manual]].",
        "noname": "Non especificou un nome de usuario válido.",
        "loginsuccesstitle": "Conectado",
        "loginsuccess": "<strong>Accedeu ao sistema de {{SITENAME}} como \"$1\".</strong>",
-       "nosuchuser": "Non existe ningún usuario chamado \"$1\".\nOs nomes de usuario diferencian entre maiúsculas e minúsculas.\nVerifique o nome que inseriu ou [[Special:UserLogin/signup|cree unha nova conta]].",
+       "nosuchuser": "Non existe ningún usuario chamado \"$1\".\nOs nomes de usuario diferencian entre maiúsculas e minúsculas.\nVerifique a ortografía ou [[Special:CreateAccount|cree unha nova conta]].",
        "nosuchusershort": "Non existe ningún usuario chamado \"$1\".\nVerifique o nome que inseriu.",
        "nouserspecified": "Cómpre especificar un nome de usuario.",
        "login-userblocked": "Este usuario está bloqueado. Acceso non autorizado.",
        "createacct-another-realname-tip": "O nome real é opcional.\nSe escolle dalo utilizarase para atribuír ao usuario o seu traballo.",
        "pt-login": "Acceder ao sistema",
        "pt-login-button": "Acceder ao sistema",
+       "pt-login-continue-button": "Continuar a conexión",
        "pt-createaccount": "Crear unha conta",
        "pt-userlogout": "Saír",
        "php-mail-error-unknown": "Erro descoñecido na función mail() do PHP.",
        "botpasswords-invalid-name": "O nome de usuario especificado non contén o separador de contrasinal de bot (\"$1\").",
        "botpasswords-not-exist": "O usuario \"$1\" non ten un contrasinal de bot de nome \"$2\".",
        "resetpass_forbidden": "Non se poden mudar os contrasinais",
+       "resetpass_forbidden-reason": "Os contrasinais non poden cambiarse: $1",
        "resetpass-no-info": "Debe rexistrarse para acceder directamente a esta páxina.",
        "resetpass-submit-loggedin": "Cambiar o contrasinal",
        "resetpass-submit-cancel": "Cancelar",
        "passwordreset-emailsentusername": "Se hai unha dirección de correo electrónico asociada con este nome de usuario, entón enviarase un correo electrónico para o restablecemento do contrasinal.",
        "passwordreset-emailsent-capture": "Enviouse un correo electrónico de restablecemento do contrasinal, mostrado a continuación.",
        "passwordreset-emailerror-capture": "Xerouse un correo electrónico de restablecemento do contrasinal, mostrado a continuación, pero o envío {{GENDER:$2|ao usuario|á usuaria}} fallou: $1",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|O correo de reinicialización do contrasinal foi enviado|Os correos de reinicialización do contrasinal foron enviados}}. {{PLURAL:$1|O nome de usuario e contrasinal móstrase abaixo|A lista de nomes de usuarios e contrasinais móstranse abaixo}}.",
+       "passwordreset-emailerror-capture2": "O envío do correo {{GENDER:$2|ó usuario|á usuaria}} fallou: $1 {{PLURAL:$3|O nome de usuario e contrasinal móstrase abaixo|A lista de usuarios e contrasinais móstranse abaixo}}.",
+       "passwordreset-ignored": "A reinicialización do contrasinal non puido realizarse. Quizais non configurou o provedor?",
+       "passwordreset-invalideamil": "O enderezo de correo electrónico non é válido",
+       "passwordreset-nodata": "Non se indicou o nome de usuario ou a dirección de correo electrónico",
        "changeemail": "Cambiar ou eliminar o enderezo de correo electrónico",
        "changeemail-header": "Encha este formulario para cambiar o seu enderezo de correo electrónico. Se vostede quere eliminar a asociación da dirección de correo electrónico da súa conta, deixe en branco a nova dirección de correo electrónico cando envíe o formulario.",
        "changeemail-passwordrequired": "Terá que escribir o seu contrasinal para confirmar este cambio.",
        "accmailtext": "Un contrasinal xerado ao chou para [[User talk:$1|$1]] foi enviado a $2. Pode modificarse na páxina de [[Special:ChangePassword|cambio de contrasinais]] tras acceder ao sistema.",
        "newarticle": "(Novo)",
        "newarticletext": "Seguiu unha ligazón a unha páxina que aínda non existe.\nPara crear a páxina, comece a escribir na caixa inferior (consulte a [$1 páxina de axuda] para obter máis información).\nSe chegou aquí por erro, simplemente prema no botón '''atrás''' do seu navegador.",
-       "anontalkpagetext": "----''Esta é a páxina de conversa dun usuario anónimo que aínda non creou unha conta ou que non a usa. Polo tanto, empregamos o enderezo IP para a súa identificación. Este enderezo IP pódeno compartir varios usuarios distintos. Se pensa que foron dirixidos contra a súa persoa comentarios inadecuados, por favor, [[Special:UserLogin/signup|cree unha conta]] ou [[Special:UserLogin|acceda ao sistema]] para evitar futuras confusións con outros usuarios anónimos.''",
+       "anontalkpagetext": "----\n<em>Esta é a páxina de conversa dun usuario anónimo que aínda non creou unha conta ou que non a usa.</em> Polo tanto, empregamos o enderezo IP para a súa identificación. Este enderezo IP pódeno compartir varios usuarios distintos. Se pensa que foron dirixidos contra a súa persoa comentarios inadecuados, por favor, [[Special:CreateAccount|cree unha conta]] ou [[Special:UserLogin|acceda ao sistema]] para evitar futuras confusións con outros usuarios anónimos.",
        "noarticletext": "Actualmente non hai ningún texto nesta páxina.\nPode [[Special:Search/{{PAGENAME}}|procurar polo título desta páxina]] noutras páxinas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ollar os rexistros relacionados]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear a páxina]</span>.",
        "noarticletext-nopermission": "Actualmente non hai ningún texto nesta páxina.\nPode [[Special:Search/{{PAGENAME}}|procurar polo título desta páxina]] noutras páxinas ou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ollar os rexistros relacionados]</span>, pero non ten os permisos necesarios para crear esta páxina.",
        "missing-revision": "A revisión nº$1 da páxina chamada \"{{FULLPAGENAME}}\" non existe.\n\nA miúdo, isto está provocado por seguir unha ligazón de historial obsoleta cara a unha páxina que foi borrada.\nO [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rexistro de borrados] contén máis detalles.",
        "right-override-export-depth": "Exportar páxinas incluíndo as páxinas ligadas ata unha profundidade de 5",
        "right-sendemail": "Enviar correos electrónicos a outros usuarios",
        "right-passwordreset": "Ver os correos electrónicos de restablecemento de contrasinais",
-       "right-managechangetags": "Crear e borrar [[Special:Tags|tags]] da base de datos",
+       "right-managechangetags": "Crear e (des)activar [[Special:Tags|tags]]",
        "right-applychangetags": "Aplicar [[Special:Tags|etiquetas]] xunto cos cambios propios",
        "right-changetags": "Engadir e quitar [[Special:Tags|etiquetas]] arbitrarias a revisións individuais e entradas do rexistro",
+       "right-deletechangetags": "Suprimir as [[Special:Tags|etiquetas]] da base de datos",
        "grant-generic": "conxunto de dereitos \"$1\"",
        "grant-group-page-interaction": "Interactuar con páxinas",
        "grant-group-file-interaction": "Interactuar con ficheiros multimedia",
        "action-viewmyprivateinfo": "ver a súa información privada",
        "action-editmyprivateinfo": "editar a súa información privada",
        "action-editcontentmodel": "editar o modelo de contido dunha páxina",
-       "action-managechangetags": "crear e borrar etiquetas da base de datos",
+       "action-managechangetags": "crear e (des)activar etiquetas",
        "action-applychangetags": "aplicar etiquetas xunto cos cambios",
        "action-changetags": "engadir e quitar etiquetas arbitrarias a revisións individuais e entradas do rexistro",
+       "action-deletechangetags": "borrar etiquetas da base de datos",
        "nchanges": "$1 {{PLURAL:$1|modificación|modificacións}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|desde a última visita}}",
        "enhancedrc-history": "historial",
        "upload-form-label-infoform-description-tooltip": "Describa brevemente todo o destacable acerca do traballo.\nPara unha foto, mencione as cousas principais que se representan, a ocasión ou o lugar.",
        "upload-form-label-usage-title": "Uso",
        "upload-form-label-usage-filename": "Nome do ficheiro",
-       "foreign-structured-upload-form-label-own-work": "Isto é o meu propio traballo",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorías",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que estou a cargar este ficheiro seguindo os termos de uso e políticas de licenza de {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Se non é capaz de cargar este ficheiro baixo as políticas de {{SITENAME}}, por favor peche este diálogo e intente outro método.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Quizais tamén queira probar [[Special:Upload|a páxina predeterminada de subidas]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Comprendo que estou a cargar este ficheiro nun repositorio compartido. Confirmo que fago isto seguindo os termos de uso e políticas de licenza existentes alí.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Se non é capaz de cargar este ficheiro baixo as políticas do repositorio compartido, por favor peche este diálogo e intente outro método.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Tamén pode interesarlle usar [[Special:Upload|a páxina de carga en {{SITENAME}}]], se este ficheiro pode ser cargado alí baixo as súas políticas.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Certifico que son o propietario dos dereitos de autor deste ficheiro, e que concordo a liberar irrevocablemente este ficheiro a Wikimedia Commons baixo a licenza [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], e que concordo cos [https://wikimediafoundation.org/wiki/Terms_of_Use Termos de uso].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Se non posúe os dereitos de autor deste ficheiro, ou quere liberalo baixo unha licenza diferente, considere usar o [https://commons.wikimedia.org/wiki/Special:UploadWizard Asistente de subas de Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Tamén pode interesarlle usar [[Special:Upload|a páxina de carga en {{SITENAME}}]], se o sitio permite a suba deste ficheiro nas súas políticas.",
+       "upload-form-label-own-work": "Isto é o meu propio traballo",
+       "upload-form-label-infoform-categories": "Categorías",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Confirmo que estou a cargar este ficheiro seguindo os termos de uso e políticas de licenza de {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Se non é capaz de cargar este ficheiro baixo as políticas de {{SITENAME}}, por favor peche este diálogo e intente outro método.",
+       "upload-form-label-not-own-work-local-generic-local": "Quizais tamén queira probar [[Special:Upload|a páxina predeterminada de subidas]].",
+       "upload-form-label-own-work-message-generic-foreign": "Comprendo que estou a cargar este ficheiro nun repositorio compartido. Confirmo que fago isto seguindo os termos de uso e políticas de licenza existentes alí.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se non é capaz de cargar este ficheiro baixo as políticas do repositorio compartido, por favor peche este diálogo e intente outro método.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Tamén pode interesarlle usar [[Special:Upload|a páxina de carga en {{SITENAME}}]], se este ficheiro pode ser cargado alí baixo as súas políticas.",
        "backend-fail-stream": "Non se puido transmitir o ficheiro \"$1\".",
        "backend-fail-backup": "Non se puido facer unha copia de seguridade do ficheiro \"$1\".",
        "backend-fail-notexists": "O ficheiro \"$1\" non existe.",
        "changecontentmodel-success-text": "O tipo de contido de [[:$1]] foi modificado.",
        "changecontentmodel-cannot-convert": "O contido en [[:$1]] non pode converterse ó tipo de $2.",
        "changecontentmodel-nodirectediting": "O modelo de contido $1 non permite a modificación directa",
+       "changecontentmodel-emptymodels-title": "Non hai modelos de contido dispoñibles",
+       "changecontentmodel-emptymodels-text": "O contido de [[:$1]] non pode converterse a ningún tipo.",
        "log-name-contentmodel": "Rexistro de cambios de modelo de contido",
        "log-description-contentmodel": "Eventos relacinados cos modelos de contido dunha páxina",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|creou}} a páxina $3 usando un modelo de contido non predeterminado \"$5\"",
        "whatlinkshere-prev": "{{PLURAL:$1|anterior|$1 anteriores}}",
        "whatlinkshere-next": "{{PLURAL:$1|seguinte|$1 seguintes}}",
        "whatlinkshere-links": "← ligazóns",
-       "whatlinkshere-hideredirs": "$1 as redireccións",
-       "whatlinkshere-hidetrans": "$1 as inclusións",
-       "whatlinkshere-hidelinks": "$1 as ligazóns",
-       "whatlinkshere-hideimages": "$1 as ligazóns ao ficheiro",
+       "whatlinkshere-hideredirs": "Ocultar as redireccións",
+       "whatlinkshere-hidetrans": "Ocultar as inclusións",
+       "whatlinkshere-hidelinks": "Ocultar as ligazóns",
+       "whatlinkshere-hideimages": "Ocultar as ligazóns ao ficheiro",
        "whatlinkshere-filters": "Filtros",
        "whatlinkshere-submit": "Ir",
        "autoblockid": "Bloqueo automático nº$1",
        "lockdbsuccesstext": "Pechouse a base de datos.<br />\nLembre [[Special:UnlockDB|eliminar o bloqueo]] unha vez completado o seu mantemento.",
        "unlockdbsuccesstext": "A base de datos foi desbloqueada.",
        "lockfilenotwritable": "Non se pode escribir no ficheiro de bloqueo da base de datos. Para bloquear ou desbloquear a base de datos, o servidor web ten que poder escribir neste ficheiro.",
+       "databaselocked": "A base de datos xa está bloqueada.",
        "databasenotlocked": "A base de datos non está bloqueada.",
        "lockedbyandtime": "(por $1 o $2 ás $3)",
        "move-page": "Mover \"$1\"",
        "tags-delete-not-found": "A páxina \"$1\" non existe.",
        "tags-delete-too-many-uses": "A etiqueta \"$1\" aplícase a máis de $2 {{PLURAL:$2|revisión|revisións}}; isto significa que non se pode borrar.",
        "tags-delete-warnings-after-delete": "A etiqueta \"$1\" borrouse; con todo, {{PLURAL:$2|atopouse o seguinte aviso|atopáronse os seguintes avisos}}:",
+       "tags-delete-no-permission": "Non ten permisos para borrar etiquetas de cambio.",
        "tags-activate-title": "Activar unha etiqueta",
        "tags-activate-question": "Está a piques de activar a etiqueta\"$1\".",
        "tags-activate-reason": "Motivo:",
        "feedback-useragent": "Axente de usuario:",
        "searchsuggest-search": "Procurar",
        "searchsuggest-containing": "que conteña...",
+       "api-error-autoblocked": "A súa dirección IP foi bloqueada automaticamente porque foi usada por un usuario bloqueado.",
        "api-error-badaccess-groups": "Non ten os permisos necesarios para cargar ficheiros neste wiki.",
        "api-error-badtoken": "Erro interno: Pase incorrecto.",
+       "api-error-blocked": "Foi bloqueado fronte á edición.",
        "api-error-copyuploaddisabled": "As cargas mediante URL están desactivadas neste servidor.",
        "api-error-duplicate": "Xa hai {{PLURAL:$1|outro ficheiro| outros ficheiros}} no wiki co mesmo contido.",
        "api-error-duplicate-archive": "Había {{PLURAL:$1|outro ficheiro|outros ficheiros}} no sitio co mesmo contido, pero {{PLURAL:$1|foi borrado|foron borrados}}.",
        "api-error-nomodule": "Erro interno: Non hai ningún módulo de cargas.",
        "api-error-ok-but-empty": "Erro interno: Non hai resposta do servidor.",
        "api-error-overwrite": "Non está permitido sobrescribir un ficheiro existente.",
+       "api-error-ratelimited": "Está intentando subir máis ficheiros nun pequeno espazo de tempo do que permite este wiki.\nPor favor, inténteo de novo nuns minutos.",
        "api-error-stashfailed": "Erro interno: O servidor non puido almacenar o ficheiro temporal.",
        "api-error-publishfailed": "Erro interno: O servidor non puido publicar o ficheiro temporal.",
        "api-error-stasherror": "Houbo un erro ao subir o ficheiro ao depósito.",
        "log-action-filter-suppress-block": "Supresión de usuario por bloqueo",
        "log-action-filter-suppress-reblock": "Supresión de usuario por bloqueo reiterado",
        "log-action-filter-upload-upload": "Nova subida",
-       "log-action-filter-upload-overwrite": "Resubida"
+       "log-action-filter-upload-overwrite": "Resubida",
+       "authmanager-authn-no-primary": "A información de identificación proporcionada non pode ser autenticada.",
+       "authmanager-email-label": "Correo electrónico",
+       "authmanager-email-help": "Enderezo de correo electrónico",
+       "authmanager-realname-label": "Nome real",
+       "authmanager-realname-help": "Nome real do usuario",
+       "authmanager-provider-temporarypassword": "Contrasinal temporal",
+       "authprovider-resetpass-skip-label": "Omitir",
+       "authprovider-resetpass-skip-help": "Saltar a reinicialización do contrasinal.",
+       "specialpage-securitylevel-not-allowed-title": "Non permitido",
+       "changecredentials-submit": "Cambiar",
+       "changecredentials-submit-cancel": "Cancelar",
+       "removecredentials-submit": "Eliminar",
+       "removecredentials-submit-cancel": "Cancelar",
+       "credentialsform-account": "Nome da conta:"
 }
index 530a48c..d81df3d 100644 (file)
        "compareselectedversions": "वेंचिल्ल्या पुनर्नियाळांची तुळा करात",
        "editundo": "केल्लें परतावचें",
        "diff-multi-sameuser": "(ह्या वांगड्या सयत {{PLURAL:$1|केल्लें मदलें एक अवतरण दाखोवंक ना|केल्लें मदलें $1 अवतरण दाखोवंक ना}})",
-       "searchresults": "सà¥\8bदाà¤\9aà¥\87 à¤ªà¤°à¤¿à¤£à¤¾à¤®à¤¾à¤\82",
+       "searchresults": "सà¥\8bदाà¤\9aà¥\8b à¤¨à¤¿à¤\95ाल",
        "searchresults-title": "\"$1\" हाच्या सोदाचे परिणामां",
        "prevn": "आदलें{{PLURAL:$1|$1}}",
        "nextn": "दुसरें {{PLURAL:$1|$1}}",
index 9441df7..a56f74a 100644 (file)
@@ -96,8 +96,8 @@
        "category-empty": "''Hea vorgan sodhea ekui pan vo madheom na''",
        "hidden-categories": "{{PLURAL:$1|Lipoilolo vorg|Lipoilole vorg}}",
        "hidden-category-category": "Lipoiloleo vorg",
-       "category-subcat-count": "{{PLURAL:$2|Hea vorgan fokot hi ek upvorg asa.|Hea vorgan {{PLURAL:$1|hi upvorg asa|heo $1 upvorg asat}}, beriz $2 upvorga modem.}}",
-       "category-article-count": "{{PLURAL:$2|Hea vorgan fokot hi ek pan asa.|Hea vorgan {{PLURAL:$1|hi pan asa|him $1 panam asat}} beriz $2 panam modem.}}",
+       "category-subcat-count": "{{PLURAL:$2|Hea vorgan fokot ho ek upvorg asa.|Hea vorgan {{PLURAL:$1|ho upvorg asa|heo $1 upvorg asat}}, beriz $2 upvorga modem.}}",
+       "category-article-count": "{{PLURAL:$2|Hea vorgan fokot hem ek pan asa.|Hea vorgan {{PLURAL:$1|hem pan asa|him $1 panam asat}} beriz $2 panam modem.}}",
        "category-file-count": "{{PLURAL:$2|Hea vorgan fokot hi ek fail asa.|Hea vorgan {{PLURAL:$1|hi fail asa|heo $1 faili asat}}, beriz $2 faili modem.}}",
        "listingcontinuesabbrev": "chalu",
        "index-category": "Suchi-potran zodlelim panam",
        "newpassword": "Novem gupitutor:",
        "retypenew": "Novem gupitutor portun boroi:",
        "resetpass_submit": "Gupitutor tharai ani sotrorombh kor",
-       "changepassword-success": "Tujem gupitutor bodlop yoshosvi tharlam.",
+       "changepassword-success": "Tujem gupitutor bodol'lam!",
        "resetpass_forbidden": "Gupitutram bodlunk zaina",
        "resetpass-submit-loggedin": "Gupitutoor bodol",
        "resetpass-submit-cancel": "Roddh kor",
index 1b3138d..06bfece 100644 (file)
        "noname": "Οὐ καθὠρισας ἔγκυρόν τι ὄνομα χρωμένου.",
        "loginsuccesstitle": "Ἐπιτυχῶς συνεδέθης",
        "loginsuccess": "'''συνδέδεσαι ἤδη τῷ {{SITENAME}} ὡς \"$1\".'''",
-       "nosuchuser": "Οὐκ ἐστὶ χρώμενος ὀνόματι \"$1\".\nΤὰ γράμματα τῶν ὀνομάτων χρωμένων διακρίνονται εἰς κεφαλαῖα καὶ μικρά.\nΣκόπει τὴν τῶν γραμμάτων ἀκριβείαν ἢ [[Special:UserLogin/signup|λογισμὸν νέον ποίει]].",
+       "nosuchuser": "Οὐκ ἐστὶ χρώμενος ὀνόματι \"$1\".\nΤὰ γράμματα τῶν ὀνομάτων χρωμένων διακρίνονται εἰς κεφαλαῖα καὶ μικρά.\nΣκόπει τὴν τῶν γραμμάτων ἀκριβείαν ἢ [[Special:CreateAccount|λογισμὸν νέον ποίει]].",
        "nosuchusershort": "Οὐκ ἐστὶ χρώμενος ἔχων τὸ ὄνομα \"$1\".\nἜλεγξον τὴν ὀρθογραφίαν σου.",
        "nouserspecified": "Ὄνομα χρωμένου καθοριστέον ὑποχρεωτικώς.",
        "wrongpassword": "Εἰσηγμένον σύνθημα ἐσφαλμένον. Πείρασον πάλιν.",
index 75906f0..55120ea 100644 (file)
        "noname": "Du muesch e Benutzername aagee.",
        "loginsuccesstitle": "Aamäldig erfolgrych",
        "loginsuccess": "'''Du bisch jetz als \"$1\" bi {{SITENAME}} aagmäldet.'''",
-       "nosuchuser": "Dr Benutzername \"$1\" git s nit.\n\nIberprief d Schrybwys, oder mäld Di as [[Special:UserLogin/signup|neje Benutzer aa]].",
+       "nosuchuser": "Dr Benutzername \"$1\" git s nit.\n\nIberprief d Schrybwys, oder mäld Di as [[Special:CreateAccount|neje Benutzer aa]].",
        "nosuchusershort": "S git kei Benutzername „$1“. Bitte iberprief d Schrybwys.",
        "nouserspecified": "Bitte gib e Benutzername yy.",
        "login-userblocked": "Dää Benutzer isch gsperrt. Aamäldig nit erlaubt.",
        "accmailtext": "E zuefällig generiert Passwort fir [[User talk:$1|$1]] isch an $2 gschickt wore.\n\nS Passwort fir des nej Benutzerkonto cha uf dr Spezialsyte „[[Special:ChangePassword|Passwort ändere]]“ gänderet wäre.",
        "newarticle": "(Nej)",
        "newarticletext": "Du bisch eme Link nogange zuen ere Syte, wu s nid git.\nZum die Syte aalege, chasch do in däm Chaschte unte aafange schrybe (lueg [$1 Hilfe] fir meh Informatione).\nWänn do nid hesch welle aane goh, no druck in Dyynem Browser uf '''Zruck'''.",
-       "anontalkpagetext": "----''Des isch e Diskussionssyte vun eme anonyme Benutzer, wu kei Zuegang aagleit het oder wu ne nit bruucht. Sälleweg mien mir di numerisch IP-Adräss bruuche zum ihn oder si z identifiziere. So ne IP-Adräss cha au vu mehrere Benutzer teilt wäre. Wenn Du ne anonyme Benutzer bisch un s Gfiel hesch, ass do irrelevanti Kommentar an di grichtet wäre, derno [[Special:UserLogin/signup|leg e Konto aa]] oder [[Special:UserLogin|mäld di aa]] zum in Zuekumft Verwirrige mit andere anonyme Benutzer z vermyyde.''",
+       "anontalkpagetext": "----''Des isch e Diskussionssyte vun eme anonyme Benutzer, wu kei Zuegang aagleit het oder wu ne nit bruucht. Sälleweg mien mir di numerisch IP-Adräss bruuche zum ihn oder si z identifiziere. So ne IP-Adräss cha au vu mehrere Benutzer teilt wäre. Wenn Du ne anonyme Benutzer bisch un s Gfiel hesch, ass do irrelevanti Kommentar an di grichtet wäre, derno [[Special:CreateAccount|leg e Konto aa]] oder [[Special:UserLogin|mäld di aa]] zum in Zuekumft Verwirrige mit andere anonyme Benutzer z vermyyde.''",
        "noarticletext": "Uf däre Syte het s no kei Täxt. Du chasch uf andere Syte [[Special:Search/{{PAGENAME}}|dä Yytrag sueche]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} dr Logbuechyytrag sueche, wo dezue ghert],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte bearbeite]</span>.",
        "noarticletext-nopermission": "In däre Syte het s zur Zyt no kei Text.\nDu chasch dää Titel uf andre Syte [[Special:Search/{{PAGENAME}}|sueche]]\noder <span class=\"plainlinks\">in dr zuegherige [{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbiecher sueche].</span> Du derfsch aber die Syte nit aalege.",
        "missing-revision": "D Version $1 vu dr Syte mit Name „{{FULLPAGENAME}}“ git s nit.\n\nDää Fähler chunnt normalerwyys dur e veraltete Link zue dr Versionsgschicht vun ere Syte, wu in dr Zwischezyt glescht woren isch.\nEinzelheite chasch im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lesch-Logbuech] bschaue.",
        "upload-form-label-infoform-description": "Beschrybig",
        "upload-form-label-usage-title": "Verwändig",
        "upload-form-label-usage-filename": "Dateiname",
-       "foreign-structured-upload-form-label-own-work": "Das han i sälber gmacht.",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorië",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "I bestätige, das i die Datei under de Nutzigsbedingigen und Lizänzrichtlinje vo {{SITENAME}} ufelade.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "We du die Datei nid under de Nutzigsbedingigen und Lizänzrichtlinje vo {{SITENAME}} chasch ufelade, de schliess bitte dä Dialog u probier’s uf’nen anderi Art.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Du chasch es ou mit der [[Special:Upload|Standardsyte zum Ufelade]] probiere.",
-       "foreign-structured-upload-form-label-own-work-message-default": "I bi mer bewusst, das i die Datei in es gmeinsams Repository ufelade. I bestätige, das mi derby a d Nutzigs- und Lizänzbedingige dört halte.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Bitte schliess dä Dialog und versuech’s mit eren andere Methode, falls du die Datei nid under de Bedingige vom gmeinsame Repository chasch ufelade.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Du chasch es ou mit der [[Special:Upload|Syte zum Ufeladen uf {{SITENAME}}]] probiere, falls du die Datei dört under denen irne Bedingige chasch ufelade.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "I bestätige, das ds Copyright vo dere Datei mir ghört. I stimmen unwiderruefflech zue, das die Datei uf Wikimedia Commons under der Lizänz [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] veröffentlecht wird. I bi mit de [https://wikimediafoundation.org/wiki/Terms_of_Use/de Nutzigsbedingigen] yverstande.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Falls ds Copyright vo dere Datei nid dir ghört oder falls du sen under eren andere Lizänz wosch veröffentleche, de chönntsch der [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard] bruuche.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Du chasch es ou mit der [[Special:Upload|Syte zum Ufeladen uf {{SITENAME}}]] probiere, falls dä Website ds Ufelade vo der Datei under syne Bedingige zuelat.",
+       "upload-form-label-own-work": "Das han i sälber gmacht.",
+       "upload-form-label-infoform-categories": "Kategorië",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "I bestätige, das i die Datei under de Nutzigsbedingigen und Lizänzrichtlinje vo {{SITENAME}} ufelade.",
+       "upload-form-label-not-own-work-message-generic-local": "We du die Datei nid under de Nutzigsbedingigen und Lizänzrichtlinje vo {{SITENAME}} chasch ufelade, de schliess bitte dä Dialog u probier’s uf’nen anderi Art.",
+       "upload-form-label-not-own-work-local-generic-local": "Du chasch es ou mit der [[Special:Upload|Standardsyte zum Ufelade]] probiere.",
+       "upload-form-label-own-work-message-generic-foreign": "I bi mer bewusst, das i die Datei in es gmeinsams Repository ufelade. I bestätige, das mi derby a d Nutzigs- und Lizänzbedingige dört halte.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Bitte schliess dä Dialog und versuech’s mit eren andere Methode, falls du die Datei nid under de Bedingige vom gmeinsame Repository chasch ufelade.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Du chasch es ou mit der [[Special:Upload|Syte zum Ufeladen uf {{SITENAME}}]] probiere, falls du die Datei dört under denen irne Bedingige chasch ufelade.",
        "backend-fail-stream": "D Datei $1 het nit chenne ibertrait wäre.",
        "backend-fail-backup": "D Datei $1 het nit chenne gsicheret wäre.",
        "backend-fail-notexists": "D Datei $1 git s nit.",
index c478e9d..2e12a73 100644 (file)
        "noname": "તમે પ્રમાણભૂત સભ્યનામ જણાવેલ નથી.",
        "loginsuccesstitle": "પ્રવેશ સફળ",
        "loginsuccess": "'''તમે હવે {{SITENAME}}માં \"$1\" તરીકે પ્રવેશી ચુક્યા છો.'''",
-       "nosuchuser": "\"$1\" નામ ધરાવતો કોઇ સભ્ય અસ્તિત્વમાં નથી.\n\nસભ્યનામો અક્ષરસંવેદી (કેસ સેન્સિટીવ) હોય છે.\n\nકૃપા કરી સ્પેલીંગ/જોડણી ચકાસો અથવા [[Special:UserLogin/signup|નવું ખાતુ ખોલો]].",
+       "nosuchuser": "\"$1\" નામ ધરાવતો કોઇ સભ્ય અસ્તિત્વમાં નથી.\n\nસભ્યનામો અક્ષરસંવેદી (કેસ સેન્સિટીવ) હોય છે.\n\nકૃપા કરી સ્પેલીંગ/જોડણી ચકાસો અથવા [[Special:CreateAccount|નવું ખાતુ ખોલો]].",
        "nosuchusershort": "\"$1\" નામનો કોઇ સભ્ય નથી, તમારી જોડણી તપાસો.",
        "nouserspecified": "તમારે સભ્ય નામ દર્શાવવાની જરૂર છે.",
        "login-userblocked": "આ યુઝર પ્રતિબંધિત છે. પ્રવેશ વર્જીત.",
        "accmailtext": "[[User talk:$1|$1]] માટે રચેલ ગુપ્તસંજ્ઞા $2 ને મોકલાવી દેવાઇ છે. આ નવા ખાતાનીગુપ્તસંજ્ઞા પ્રવેશ કર્યા બાદ ''[[Special:ChangePassword|ગુપ્તસંજ્ઞા બદલો]]'' વાપરીને બદલી શકાશે.",
        "newarticle": "(નવીન)",
        "newarticletext": "<div style=\"background: #F9F9F9; margin-top: 1em; padding: 1em; border: 1px solid #ccc; border-right: 2px solid #ccc; border-bottom: 2px solid #ccc\"><center>'''આ વિકિ પ્રકલ્પ પર આ પ્રકારનો લેખ હાલમાં નથી'''</center>\n----\n* [[Image:Searchtool.svg|25px|alt=|link=]] '''[[{{ns:special}}:Search/{{PAGENAME}}|“{{PAGENAME}}”]]''' માટે શોધો.\n* [[Image:Nuvola apps fonts.png|25px|alt=|link=]] જે લેખોનું શીર્ષક આ પ્રત્યય સાથે શરુ થાય છે તેનો  [[{{ns:special}}:Prefixindex/{{FULLPAGENAME}}|ઉપસર્ગ]] જૂઓ.\n* [[Image:Nuvola apps ksig.png|25px|alt=|link=]] <span class=\"plainlinks\">'''[{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=edit}} આ શીર્ષકનું નવું પૃષ્ઠ બનાવો.]'''</span> \n* [[Image:WP-TranslationProject TwoFlags.svg|25px|alt=|link=]] ''વૈશ્વિક લેખ યોજના'' દ્વારા આ લેખને [//vs.aka-online.de/cgi-bin/globalwpsearch.pl?search={{PAGENAMEE}} અન્ય ભાષાઓમાં શોધો.]\n<div style=\"margin: 1em 2em 1em 3em; font-size: 90%;\">\nયોગદાનકર્તાઓ માટે:\n* જો આપ હાલમાં આ લેખ બનાવી રહ્યા છો તો પોતાના બ્રાઉઝરની <span class=\"plainlinks\">[{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=purge}} કૈશ ખાલી]</span> કરો, અથવા થોડી વધુ રાહ જૂઓ, પછી કામ આગળ વધારો.\n* કદાચ આ પાનું દૂર કરાયું છે, આ જોવા માટે કૃપા કરીને <span class=\"plainlinks\">[{{fullurl:Special:Log|type=delete&page={{FULLPAGENAMEE}}}} આ પાનાનો વિલોપન ઇતિહાસ]</span> જૂઓ.\n* આપ આ લેખ બનાવવા માગો છો તો ક્રુપા કરીને નીચે આપેલા ખાનામાં લખવાનું શરુ કરો.\n* જો આપ ભૂલમાં અહીં આવી ગયા છો તો આપના બ્રાઉઝરના બૅક બટન પર ક્લિક કરીને પરત ફરો.</div></div>",
-       "anontalkpagetext": "----\n<em>આ એક અજ્ઞાત સભ્યનું ચર્ચા પાનું છે જેમણે ક્યાં તો પોતાનું ખાતું ખોલ્યું નથી અથવા તો તેને વાપરતા નથી.</em>\nઆથી તેમને ઓળખવા માટે અમારે સાંખ્યિક IP સરનામાની મદદ લેવી પડી છે.\nઆવું IP સરનામું ઘણાં અન્ય સભ્યો પણ વાપરતા હોઇ શકે.\nજો તમે અજ્ઞાત સભ્ય હોવ અને માનતા હોવ કે અસંધિત ટિપ્પણીઓ તમારી તરફ વાળવામાં આવી છે, તો કૃપયા  [[Special:UserLogin/signup|create an account]] અથવા [[Special:UserLogin|log in]]નો ઉપયોગ કરશો જેથી તમને કોઈ અજ્ઞાત સભ્ય સમજવાની ભૂલ ભવિષ્યમાં ટાળી શકાય.",
+       "anontalkpagetext": "----\n<em>આ એક અજ્ઞાત સભ્યનું ચર્ચા પાનું છે જેમણે ક્યાં તો પોતાનું ખાતું ખોલ્યું નથી અથવા તો તેને વાપરતા નથી.</em>\nઆથી તેમને ઓળખવા માટે અમારે સાંખ્યિક IP સરનામાની મદદ લેવી પડી છે.\nઆવું IP સરનામું ઘણાં અન્ય સભ્યો પણ વાપરતા હોઇ શકે.\nજો તમે અજ્ઞાત સભ્ય હોવ અને માનતા હોવ કે અસંધિત ટિપ્પણીઓ તમારી તરફ વાળવામાં આવી છે, તો કૃપયા  [[Special:CreateAccount|create an account]] અથવા [[Special:UserLogin|log in]]નો ઉપયોગ કરશો જેથી તમને કોઈ અજ્ઞાત સભ્ય સમજવાની ભૂલ ભવિષ્યમાં ટાળી શકાય.",
        "noarticletext": "આ પાનામાં હાલમાં કોઇ માહિતિ નથી.\nતમે  [[Special:Search/{{PAGENAME}}|આ શબ્દ]] ધરાવતાં અન્ય લેખો શોધી શકો છો, <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}}}} સંલગ્ન માહિતિ પત્રકોમાં શોધી શકો છો], પરંતુ તમને આ પાનું બનાવવાની મંજૂરી નથી.",
        "userpage-userdoesnotexist": "સભ્ય ખાતું \"<nowiki>$1</nowiki>\"ની નોંધણીનથી થઈ.\nશું તમે ખરેખર આ પાનાની રચના કે ફેરફાર કરવા માંગો છો",
index 679b35a..c1757c9 100644 (file)
        "noname": "Cha honree uss ennym ymmydeyr fondagh.",
        "loginsuccesstitle": "T'ou loggit stiagh",
        "loginsuccess": "'''T'ou loggit stiagh ayns {{SITENAME}} myr \"$1\".'''",
-       "nosuchuser": "Cha nel ymmydeyr ayn lesh yn ennym \"$1\".<br />\nTa case ny lettyryn ayns enmyn ymmydeyr dendeaysagh.<br />\nCur streean er dty lettraghey, ny [[Special:UserLogin/signup|croo coontys noa]].",
+       "nosuchuser": "Cha nel ymmydeyr ayn lesh yn ennym \"$1\".<br />\nTa case ny lettyryn ayns enmyn ymmydeyr dendeaysagh.<br />\nCur streean er dty lettraghey, ny [[Special:CreateAccount|croo coontys noa]].",
        "nosuchusershort": "Cha nel ymmydeyr ayn lesh yn ennym \"$1\".\nCur streean er dty lettraghey.",
        "nouserspecified": "Shegin diu ennym ymmydeyr y honraghey.",
        "login-userblocked": "Ta'n ymmydeyr shoh fo ghlass.  Cha lowit {{GENDER:|da|jee|da}} loggal stiagh.",
        "allpagessubmit": "Gow",
        "allpagesprefix": "Taishbyn ny duillagyn lesh roie-ockle:",
        "categories": "Ronnaghyn",
-       "special-categories-sort-count": "sorçhaghey rere coontey",
-       "special-categories-sort-abc": "sorçhaghey rere lettyr",
        "linksearch": "Ronsaghey kianglaghyn çheumooie",
        "linksearch-ok": "Ronsee",
        "linksearch-line": "Ta kiangley ayn veih $2 gys $1",
index 9f3e157..128b824 100644 (file)
        "noname": "汝還吂輸入一隻有效嘅用戶名。",
        "loginsuccesstitle": "登入成功",
        "loginsuccess": "汝今下以\"$1\"嘅身份在{{SITENAME}}登入。",
-       "nosuchuser": "尋毋到用戶 \"$1\"。\n用戶名稱係有大小寫區分嘅。\n檢查汝嘅拼寫,或者用下面嘅表格[[Special:UserLogin/signup|建立一隻新賬號]]。",
+       "nosuchuser": "尋毋到用戶 \"$1\"。\n用戶名稱係有大小寫區分嘅。\n檢查汝嘅拼寫,或者用下面嘅表格[[Special:CreateAccount|建立一隻新賬號]]。",
        "nosuchusershort": "無有喊做“$1”嘅用戶。請檢查汝輸入嘅文字係毋係有差錯。",
        "nouserspecified": "汝愛指定一隻用戶名。",
        "login-userblocked": "邇隻用戶已分封鎖。毋做得登入。",
index 65176b1..2de6b59 100644 (file)
        "createaccountreason": "Kumu:",
        "createacct-reason": "Kumu",
        "createacct-reason-ph": "No ke aha mai ke kāinoa nei i kekahi moʻokāki ʻē aʻe",
-       "createacct-captcha": "Hōʻoiana palekana",
-       "createacct-imgcaptcha-ph": "E kikokiko i ke kikokikona  i luna",
        "createacct-submit": "Kāinoa",
        "createacct-another-submit": "Kāinoa hou",
        "createacct-benefit-heading": "Hana ʻia ʻo {{SITENAME}} e nā kanaka e like me ʻoe.",
        "noname": "ʻAʻole hana ʻoe i kekahi inoa mea hoʻohana pono.",
        "loginsuccesstitle": "ʻEʻe kūleʻa",
        "loginsuccess": "<strong>Ua ʻeʻe ʻo \"$1\" iā {{SITENAME}}.</strong>",
-       "nosuchuser": "Loaʻa ʻole i ka inoa mea hoʻohana ʻo \"$1\".\nMakaʻala maʻaka ka inoa mea hoʻohana.\nHōʻoia i kāu kepela ʻana, aiʻole [[Special:UserLogin/signup|kāinoa i kekahi mōʻaukaki hou]].",
+       "nosuchuser": "Loaʻa ʻole i ka inoa mea hoʻohana ʻo \"$1\".\nMakaʻala maʻaka ka inoa mea hoʻohana.\nHōʻoia i kāu kepela ʻana, aiʻole [[Special:CreateAccount|kāinoa i kekahi mōʻaukaki hou]].",
        "nosuchusershort": "Loaʻa ʻole i ka Loaʻa ʻole i ka inoa mea hoʻohana ʻo \"$1\".\nHōʻoia i kāu kepela ʻana.",
        "nouserspecified": "Pono ʻoe e kāhuakomo i ka inoa mea hoʻohana.",
        "login-userblocked": "Pale ʻia kēia mea hoʻohana. ʻAʻole hiki ke ʻeʻe.",
        "resetpass-temp-password": "ʻŌlelo hūnā kūikawā:",
        "resetpass-expired": "Ua pau kāu ʻōlelo hūnā. E ʻoluʻolu, e loaʻa kahi ʻōlelo hūnā hou no ka ʻeʻe ʻana.",
        "passwordreset": "Kāinoa hou i ka ʻōlelo hūnā",
-       "passwordreset-legend": "Kāinoa hou i ka ʻōlelo hūnā",
        "passwordreset-username": "Inoa mea ho'ohana:",
        "passwordreset-email": "Wahinoho lekauila:",
        "changeemail": "Hoʻololi i ka wahinoho lekauila",
+       "changeemail-header": "Hoʻololi i ka wahinoho lekauila moʻokāki",
        "changeemail-oldemail": "Wahinoho lekauila hananei:",
        "changeemail-newemail": "Wahinoho lekauila hou:",
        "changeemail-none": "(ʻaʻohe)",
        "accmailtitle": "Ua ho‘ouna ‘ia ka ‘ōlelo hūnā",
        "newarticle": "(Hou)",
        "newarticletext": "Ua hāhai ʻoe i kekahi loulou i kekahi ʻaoʻao e haku ʻole.\nNo ka haku ʻana i ka ʻaoʻao, kikokiko i loko o ka pahu i lalo (ʻike i ka [$1 ʻaoʻao kōkua] no nā ʻike ʻē aʻe).\nInā hewa kou hele ʻana, kāomi i ka pihi <strong>hoʻi</strong>.",
-       "anontalkpagetext": "----\n<em>ʻO kēia ka ʻaoʻao kūkākūkā no kekahi mea ho‘ohana me ka inoa ʻole.</em>\nNo laila, pono mākou e ho‘ohana i ka IP no ka hōʻoia ʻana iā ia a hiki i kekahi mau mea hoʻohana ke hoʻokaʻana i kēia  IP.\nInā he mea ho‘ohana inoa ʻole ʻoe a loaʻa kekahi mau manaʻo nāuʻole, e ʻoluʻolu [[Special:UserLogin/signup|e kāinoa]] a i ʻole [[Special:UserLogin|e ʻeʻe]].''",
+       "anontalkpagetext": "----\n<em>ʻO kēia ka ʻaoʻao kūkākūkā no kekahi mea ho‘ohana me ka inoa ʻole.</em>\nNo laila, pono mākou e ho‘ohana i ka IP no ka hōʻoia ʻana iā ia a hiki i kekahi mau mea hoʻohana ke hoʻokaʻana i kēia  IP.\nInā he mea ho‘ohana inoa ʻole ʻoe a loaʻa kekahi mau manaʻo nāuʻole, e ʻoluʻolu [[Special:CreateAccount|e kāinoa]] a i ʻole [[Special:UserLogin|e ʻeʻe]].''",
        "noarticletext": "ʻAʻohe kikokikona a kēia ʻaoʻao.\nHiki iā ʻoe ke [[Special:Search/{{PAGENAME}}|huli no kēia inoa ʻaoʻao]] i nā ʻaoʻao ʻē aʻe, <span class=\"plainlinks\">[{{fullurl:SpecialLog|page={{FULLPAGENAMEE}}}} huli i nā moʻolelo pili], a i ʻole [{{fullurl:{{FULLPAGENAME}}|action=edit}} hoʻololi i kēia ʻaoʻao]</span>.",
        "noarticletext-nopermission": "ʻAʻohe kikokikona a kēia ʻaoʻao.\nHiki iā ʻoe ke [[Special:Search/{{PAGENAME}}|huli no kēia inoa ʻaoʻao]] i nā ʻaoʻao ʻē aʻe aiʻole <span class=\"plainlinks\">[{{fullurl:SpecialLog|page={{FULLPAGENAMEE}}}} huli i nā moʻolelo pili]</span>, akā hiki ʻole iā ʻoe ke hoʻololi i kēia ʻaoʻao.",
        "userpage-userdoesnotexist-view": "ʻAʻole kāinoa ʻia ka moʻokāki mea hoʻohana ʻo \"$1\".",
        "upload-description": "Hōʻike ʻano waihona",
        "upload-options": "Koho hoʻouka",
        "watchthisupload": "E kiaʻi i kēia waihona",
-       "upload-warning-subj": "Akahele hoʻouka",
        "upload-file-error": "Hewa kūloko",
        "upload-http-error": "Ua loaʻa kekahi hewa HTTP: $1",
        "license": "Laikini:",
        "blocklogentry": "ua hoʻopale ʻia ʻo [[$1]] no ka manawa o $2 $3",
        "block-log-flags-nocreate": "ua hoʻopale ʻia ke kāinoa moʻokāki ʻana",
        "move-page-legend": "Hoʻoneʻe i ka ʻaoʻao",
-       "movearticle": "E hoʻoneʻe i ka ʻaoʻao:",
        "newtitle": "I ka inoa hou:",
        "move-watch": "Kiaʻi i ka ʻaoʻao kumu a me ka ʻaoʻao māka",
        "movepagebtn": "Hoʻoneʻe i ka ʻaoʻao",
        "movelogpage": "Hoʻoneʻe i ka moʻolelo",
        "movereason": "Kumu:",
        "revertmove": "hoʻihoʻi",
-       "delete_and_move": "Holoi a hoʻoneʻe",
        "delete_and_move_confirm": "‘Ae, e holoi i ka ‘ao‘ao",
        "export": "Kāpuka ʻaoʻao",
        "export-addcat": "Ho‘ohui",
        "version-poweredby-translators": "nā mea unuhi ma translatewiki.net",
        "version-software-version": "Mana",
        "redirect": "Kiahou e ka waihona, ka mea hoʻohana, ka ʻaoʻao aiʻole ka ID loihape",
-       "redirect-legend": "Kiahou i kekahi waihona aiʻole kekahi ʻaoʻao",
        "redirect-summary": "Kiahou kēia ʻaoʻao kūikawā i kekahi waihona (inā hāʻawi ʻia kekahi inoa waihona), kekahi ʻaoʻao (inā hāʻawi ʻia kekahi ID loihape aiʻole ID ʻaoʻao) aiʻole kekahi ʻaoʻao mea hoʻohana (inā hāʻawi ʻia ka ID mea hoʻohana helu). Ka hana ʻana: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], aiʻole [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Huli",
        "redirect-lookup": "Huli no:",
index db0e219..f63874f 100644 (file)
@@ -39,7 +39,7 @@
        },
        "tog-underline": "סימון קישורים בקו תחתי:",
        "tog-hideminor": "הסתרת עריכות משניות בדף השינויים האחרונים",
-       "tog-hidepatrolled": "×\94סתרת ×©×\99× ×\95×\99×\99×\9d ×\91×\93×\95ק×\99×\9d בדף השינויים האחרונים",
+       "tog-hidepatrolled": "×\94סתרת ×¢×¨×\99×\9b×\95ת ×\91×\93×\95ק×\95ת בדף השינויים האחרונים",
        "tog-newpageshidepatrolled": "הסתרת דפים בדוקים ברשימת הדפים החדשים",
        "tog-hidecategorization": "הסתרת שינויים בקטגוריות של דפים",
        "tog-extendwatchlist": "הרחבת רשימת המעקב כך שתציג את כל השינויים, לא רק את השינויים האחרונים בכל דף",
        "tog-showtoolbar": "הצגת סרגל העריכה",
        "tog-editondblclick": "עריכת דפים בלחיצה כפולה",
        "tog-editsectiononrightclick": "עריכת פסקאות באמצעות לחיצה ימנית על כותרות הפסקאות",
-       "tog-watchcreations": "×\94×\95ספת ×\93פ×\99×\9d ×©×\99צרת×\99 ×\95ק×\91צ×\99×\9d ×©×\94×¢×\9c×\99ת×\99 ×\9cרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\99",
-       "tog-watchdefault": "×\94×\95ספת ×\93פ×\99×\9d ×\95ק×\91צ×\99×\9d ×©×¢×¨×\9bת×\99 ×\9cרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\99",
-       "tog-watchmoves": "×\94×\95ספת ×\93פ×\99×\9d ×\95ק×\91צ×\99×\9d ×©×\94×¢×\91רת×\99 ×\9cרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\99",
-       "tog-watchdeletion": "×\94×\95ספת ×\93פ×\99×\9d ×\95ק×\91צ×\99×\9d ×©×\9e×\97קת×\99 ×\9cרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\99",
-       "tog-watchuploads": "×\94×\95ספת ×§×\91צ×\99×\9d ×\97×\93ש×\99×\9d ×©×\94×¢×\9c×\99ת×\99 ×\9cרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\99",
-       "tog-watchrollback": "×\94×\95ספת ×\93פ×\99×\9d ×©×\91×\99צעת×\99 ×\91×\94×\9d ×©×\97×\96×\95ר ×\9e×\94×\99ר ×\9cרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\99",
+       "tog-watchcreations": "×\94×\95ספת ×\93פ×\99×\9d ×\97×\93ש×\99×\9d ×©×\90× ×\99 {{GENDER:|×\99×\95צר|×\99×\95צרת}} ×\95ק×\91צ×\99×\9d ×©×\90× ×\99 ×\9e×¢×\9c×\94 ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
+       "tog-watchdefault": "×\94×\95ספת ×\93פ×\99×\9d ×\95ק×\91צ×\99×\9d ×©×\90× ×\99 {{GENDER:|×¢×\95ר×\9a|×¢×\95ר×\9bת}} ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
+       "tog-watchmoves": "×\94×\95ספת ×\93פ×\99×\9d ×\95ק×\91צ×\99×\9d ×©×\90× ×\99 {{GENDER:|×\9e×¢×\91×\99ר|×\9e×¢×\91×\99ר×\94}} ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
+       "tog-watchdeletion": "×\94×\95ספת ×\93פ×\99×\9d ×\95ק×\91צ×\99×\9d ×©×\90× ×\99 {{GENDER:|×\9e×\95×\97ק|×\9e×\95×\97קת}} ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
+       "tog-watchuploads": "×\94×\95ספת ×§×\91צ×\99×\9d ×\97×\93ש×\99×\9d ×©×\90× ×\99 ×\9e×¢×\9c×\94 ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
+       "tog-watchrollback": "×\94×\95ספת ×\93פ×\99×\9d ×©×\90× ×\99 {{GENDER:|×\9e×\91צע|×\9e×\91צעת}} ×\91×\94×\9d ×©×\97×\96×\95ר ×\9e×\94×\99ר ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
        "tog-minordefault": "סימון כל עריכה כמשנית כברירת מחדל",
        "tog-previewontop": "הצגת תצוגה מקדימה לפני תיבת העריכה",
        "tog-previewonfirst": "הצגת תצוגה מקדימה בעריכה הראשונה",
@@ -66,7 +66,7 @@
        "tog-fancysig": "התייחסות לחתימה כקוד ויקי (ללא קישור אוטומטי)",
        "tog-uselivepreview": "שימוש בתצוגה מקדימה מהירה",
        "tog-forceeditsummary": "הצגת אזהרה בעת הכנסת תקציר עריכה ריק",
-       "tog-watchlisthideown": "×\94סתרת ×\94ער×\99×\9b×\95ת ×©×\9c×\99 ×\91רש×\99×\9eת ×\94×\9eעק×\91",
+       "tog-watchlisthideown": "הסתרת עריכות שלי ברשימת המעקב",
        "tog-watchlisthidebots": "הסתרת עריכות של בוטים ברשימת המעקב",
        "tog-watchlisthideminor": "הסתרת עריכות משניות ברשימת המעקב",
        "tog-watchlisthideliu": "הסתרת עריכות של משתמשים רשומים ברשימת המעקב",
@@ -77,7 +77,7 @@
        "tog-ccmeonemails": "לשלוח אליי העתקים של הודעות דוא\"ל ששלחתי למשתמשים אחרים",
        "tog-diffonly": "ביטול הצגת תוכן הדף מתחת להשוואת הגרסאות",
        "tog-showhiddencats": "הצגת קטגוריות מוסתרות",
-       "tog-norollbackdiff": "×\94ש×\9e×\98ת ההבדלים בין הגרסאות לאחר ביצוע שחזור",
+       "tog-norollbackdiff": "×\9c×\90 ×\9c×\94צ×\99×\92 ×\90ת ההבדלים בין הגרסאות לאחר ביצוע שחזור",
        "tog-useeditwarning": "הצגת אזהרה בעת עזיבת דף עריכה עם שינויים שטרם נשמרו",
        "tog-prefershttps": "תמיד להשתמש בתקשורת מאובטחת לאחר הכניסה לחשבון",
        "underline-always": "תמיד",
        "mar": "מרץ",
        "apr": "אפר'",
        "may": "מאי",
-       "jun": "יונ'",
-       "jul": "יול'",
+       "jun": "יוני",
+       "jul": "יולי",
        "aug": "אוג'",
        "sep": "ספט'",
        "oct": "אוק'",
        "category_header": "דפים בקטגוריה \"$1\"",
        "subcategories": "קטגוריות משנה",
        "category-media-header": "קובצי מדיה בקטגוריה \"$1\"",
-       "category-empty": "'''קטגוריה זו אינה כוללת דפים או קובצי מדיה.'''",
+       "category-empty": "<strong>קטגוריה זו אינה כוללת דפים או קובצי מדיה.</strong>",
        "hidden-categories": "{{PLURAL:$1|קטגוריה מוסתרת|קטגוריות מוסתרות}}",
        "hidden-category-category": "קטגוריות מוסתרות",
        "category-subcat-count": "{{PLURAL:$2|קטגוריה זו כוללת את קטגוריית המשנה הבאה בלבד.|קטגוריה זו כוללת את {{PLURAL:$1|קטגוריית המשנה המוצגת להלן|$1 קטגוריות המשנה המוצגות להלן}}, וכוללת בסך־הכול $2 קטגוריות משנה.}}",
        "redirectedfrom": "(הופנה מהדף $1)",
        "redirectpagesub": "דף הפניה",
        "redirectto": "הפניה ל:",
-       "lastmodifiedat": "ש×\81וּנה לאחרונה ב־$1, בשעה $2.",
+       "lastmodifiedat": "×\93×£ ×\96×\94 ×©וּנה לאחרונה ב־$1, בשעה $2.",
        "viewcount": "דף זה נצפה {{PLURAL:$1|פעם אחת|פעמיים|$1 פעמים}}.",
        "protectedpage": "דף מוגן",
        "jumpto": "קפיצה אל:",
        "currentevents-url": "Project:אקטואליה",
        "disclaimers": "הבהרה משפטית",
        "disclaimerpage": "Project:הבהרה משפטית",
-       "edithelp": "×¢×\96ר×\94 ×\9cעריכה",
+       "edithelp": "×¢×\96ר×\94 ×\91עריכה",
        "helppage-top-gethelp": "עזרה",
        "mainpage": "עמוד ראשי",
        "mainpage-description": "עמוד ראשי",
        "confirmable-confirm": "האם {{GENDER:$1|ברצונך}} להמשיך?",
        "confirmable-yes": "כן",
        "confirmable-no": "לא",
-       "thisisdeleted": "×\9cש×\97×\96ר ×\90×\95 ×\9c×\94צ×\99×\92 $1?",
+       "thisisdeleted": "×\9c×\94צ×\99×\92 ×\90×\95 ×\9cש×\97×\96ר $1?",
        "viewdeleted": "להציג $1?",
        "restorelink": "{{PLURAL:$1|גרסה מחוקה אחת|$1 גרסאות מחוקות}}",
        "feedlinks": "הזנה:",
        "perfcachedts": "המידע הבא הוא עותק שמור בזיכרון המטמון של המידע, שעודכן לאחרונה ב־$1. לכל היותר {{PLURAL:$4|תוצאה אחת נשמרת|$4 תוצאות נשמרות}} בזיכרון המטמון.",
        "querypage-no-updates": "העדכונים לדף זה כרגע מופסקים, והמידע לא יעודכן באופן שוטף.",
        "viewsource": "הצגת מקור",
-       "viewsource-title": "הצגת המקור של $1",
+       "viewsource-title": "הצגת המקור של הדף \"$1\"",
        "actionthrottled": "הפעולה הוגבלה",
        "actionthrottledtext": "כאמצעי נגד שימוש לרעה, קיימת מגבלה על ביצוע פעולה זו פעמים רבות מדי בזמן קצר, וחרגת מהמגבלה הזאת.\nנא לנסות שוב בעוד מספר דקות.",
        "protectedpagetext": "דף זה מוגן כדי למנוע עריכה ופעולות אחרות.",
        "virus-badscanner": "הגדרות שגויות: סורק הווירוסים אינו ידוע: ''$1''",
        "virus-scanfailed": "הסריקה נכשלה (קוד: $1)",
        "virus-unknownscanner": "אנטי־וירוס בלתי ידוע:",
-       "logouttext": "<strong>×\99צ×\90ת×\9d ×\96×\94 ×¢×ª×\94 ×\9e×\94×\97ש×\91×\95×\9f.</strong>\n\nש×\99×\9e×\95 ×\9c×\91 ×\9b×\99 ×\99×\99ת×\9b×\9f ×©×\93פ×\99×\9d ×\90×\97×\93×\99×\9d ×\99×\9eש×\99×\9b×\95 ×\9c×\94×\99×\95ת ×\9e×\95צ×\92×\99×\9d ×\9b×\90×\99×\9c×\95 ×\90ת×\9d ×¢×\93×\99×\99×\9f ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f עד שתנקו את המטמון של הדפדפן שלכם.",
+       "logouttext": "<strong>×\99צ×\90ת×\9d ×\9e×\94×\97ש×\91×\95×\9f.</strong>\n\nש×\99×\9e×\95 ×\9c×\91 ×©×\99×\99ת×\9b×\9f ×©×\93פ×\99×\9d ×\9eס×\95×\99×\9e×\99×\9d ×\99×\9eש×\99×\9b×\95 ×\9c×\94×\99×\95ת ×\9e×\95צ×\92×\99×\9d ×\9b×\90×\99×\9c×\95 ×\90ת×\9d ×¢×\93×\99×\99×\9f ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f, עד שתנקו את המטמון של הדפדפן שלכם.",
        "cannotlogoutnow-title": "לא ניתן לצאת מהחשבון עכשיו",
        "cannotlogoutnow-text": "היציאה אינה אפשרית בעת שימוש ב{{GRAMMAR:תחילית|$1}}.",
        "welcomeuser": "ברוך בואך, $1!",
        "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-email-ph": "יש להקליד את כתובת הדוא\"ל שלך",
        "createacct-another-email-ph": "יש להקליד כתובת דוא\"ל",
        "createaccountmail": "שימוש בסיסמה זמנית אקראית ושליחתה לכתובת הדוא\"ל שצוינה",
+       "createaccountmail-help": "יכול לשמש ליצירת חשבון לאדם אחר בלי לברר מה הססמה.",
        "createacct-realname": "שם אמיתי (לא חובה)",
        "createaccountreason": "סיבה:",
        "createacct-reason": "סיבה",
        "createacct-reason-ph": "סיבה ליצירת חשבון נוסף",
+       "createacct-reason-help": "הודעה שמוצגת ביומן רישום המשתמשים",
        "createacct-submit": "יצירת החשבון שלך",
        "createacct-another-submit": "יצירת חשבון",
+       "createacct-continue-submit": "המשך ביצירת החשבון",
+       "createacct-another-continue-submit": "המשך ביצירת החשבון",
        "createacct-benefit-heading": "אנשים כמוך יוצרים את {{SITENAME}}.",
        "createacct-benefit-body1": "{{PLURAL:$1|עריכה|עריכות}}",
        "createacct-benefit-body2": "{{PLURAL:$1|דף|דפים}}",
        "nocookiesnew": "חשבון המשתמש שלכם נוצר, אך לא נכנסתם כמשתמשים רשומים.\nכדי להכניס משתמשים למערכת עושה {{SITENAME}} שימוש בעוגיות.\nבדפדפן שלכם העוגיות מבוטלות.\nאנא הפעילו אותן מחדש, ולאחר מכן תוכלו להיכנס למערכת עם שם המשתמש והסיסמה החדשים שלכם.",
        "nocookieslogin": "{{SITENAME}} משתמש בעוגיות כדי להכניס משתמשים למערכת.\nבדפדפן שלכם העוגיות מבוטלות.\nאנא הפעילו אותן מחדש ונסו שוב.",
        "nocookiesfornew": "חשבון המשתמש לא נוצר, כיוון שלא יכולנו לוודא את מקורו.\nודאו שהעוגיות מופעלות בדפדפן שלכם, העלו מחדש דף זה ונסו שוב.",
+       "createacct-loginerror": "החשבון נוצר בהצלחה, אבל לא ניתן היה להיכנס אליו באופן אוטומטי. נא [[Special:UserLogin|להיכנס באופן ידני]].",
        "noname": "לא הכנסת שם משתמש תקין",
        "loginsuccesstitle": "נכנסת לחשבון",
        "loginsuccess": "'''נכנסת ל{{grammar:תחילית|{{SITENAME}}}} בשם \"$1\".'''",
-       "nosuchuser": "×\90×\99×\9f ×\9eשת×\9eש ×\91ש×\9d \"$1\".\n×\90× ×\90 ×\95×\93×\90×\95 ×©×\94×\90×\99×\95ת × ×\9b×\95×\9f (×\9b×\95×\9c×\9c ×\90×\95ת×\99×\95ת ×¨×\99ש×\99×\95ת ×\95ק×\98× ×\95ת), ×\90×\95 [[Special:UserLogin/signup|צרו חשבון חדש]].",
+       "nosuchuser": "×\90×\99×\9f ×\9eשת×\9eש ×\91ש×\9d \"$1\".\nש×\99×\9e×\95 ×\9c×\91 ×©×©×\9e×\95ת ×\9eשת×\9eש×\99×\9d ×\94×\9d ×ª×\9c×\95×\99×\99×\9d־ר×\99ש×\99×\95ת.\n×\90× ×\90 ×\91Ö´×\93ק×\95 ×\90ת ×\94×\90×\99×\95ת ×©×\9c ×©×\9d ×\94×\9eשת×\9eש, ×\90×\95 [[Special:CreateAccount|צרו חשבון חדש]].",
        "nosuchusershort": "אין משתמש בשם \"$1\".\nאנא ודאו שהאיות נכון.",
        "nouserspecified": "יש לציין שם משתמש.",
        "login-userblocked": "משתמש זה חסום. אינכם מורשים להיכנס לחשבון.",
        "createacct-another-realname-tip": "השם האמיתי הוא אופציונאלי.\nאם תבחרו לספקו, הוא ישמש לייחוס עבודת המשתמש אליו.",
        "pt-login": "כניסה לחשבון",
        "pt-login-button": "כניסה לחשבון",
+       "pt-login-continue-button": "המשך כניסה לחשבון",
        "pt-createaccount": "יצירת חשבון",
        "pt-userlogout": "יציאה מהחשבון",
        "php-mail-error-unknown": "שגיאה לא ידועה בפונקציה mail()‎ של PHP",
        "botpasswords-invalid-name": "שם המשתמש שניתן אינו מכיל את תו הפרדת ססמאות הבוט (\"$1\").",
        "botpasswords-not-exist": "למשתמש \"$1\" אין ססמת בוט בשם \"$2\".",
        "resetpass_forbidden": "לא ניתן לשנות סיסמאות.",
+       "resetpass_forbidden-reason": "לא ניתן לשנות את הסיסמאות: $1",
        "resetpass-no-info": "נדרשת כניסה לחשבון כדי לגשת לדף זה באופן ישיר.",
        "resetpass-submit-loggedin": "שינוי סיסמה",
        "resetpass-submit-cancel": "ביטול",
        "passwordreset-emailsentusername": "אם יש כתובת דואר אלקטרוני שמשויכת לשם המשתמש הזה, אז יישלח דואר אלקטרוני לאיפוס הסיסמה.",
        "passwordreset-emailsent-capture": "נשלח דואר אלקטרוני לאיפוס הסיסמה, והוא מוצג להלן.",
        "passwordreset-emailerror-capture": "נוצר דואר אלקטרוני לאיפוס הסיסמה, והוא מוצג להלן, אך שליחתו ל{{GENDER:$2|משתמש|משתמשת}} נכשלה: $1",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|דוא\"ל איפוס הסיסמה נשלח|הודעות דוא\"ל של איפוס הסיסמה נשלחו}}. {{PLURAL:$1|שם המשתמשים והסיסמה מוצגים|רשימה של שמות המשתמשים והסיסמאות מוצגת}} להלן.",
+       "passwordreset-emailerror-capture2": "לא ניתן היה לשלוח דוא\"ל ל{{GENDER:$2|משתמש|משתמשת}}: $1 {{PLURAL:$3|שם המשתמש והסיסמה מוצגים|רשימה של שמות המשתמשים והסיסמאות מוצגת}} להלן.",
+       "passwordreset-nocaller": "לא סופק הקורא הנדרש",
+       "passwordreset-nosuchcaller": "הקורא אינו קיים: $1",
+       "passwordreset-ignored": "איפוס הסיסמה לא בוצע. ייתכן שלא הוגדר ספק.",
+       "passwordreset-invalideamil": "כתובת דוא\"ל לא תקינה",
+       "passwordreset-nodata": "לא סופק שם משתמש או כתובת דוא\"ל",
        "changeemail": "שינוי או הסרת כתובת דוא\"ל",
        "changeemail-header": "יש למלא את הטופס הזה כדי לשנות את כתובת הדוא\"ל שלך. אם ברצונך להימנע משיוך כתובת דוא\"ל כלשהי לחשבון שלך, יש להשאיר את שדה כתובת הדוא\"ל החדשה ריק בעת שליחת הטופס.",
        "changeemail-passwordrequired": "יש להקליד את הסיסמה שלך כדי לאשר את השינוי.",
        "subject": "נושא:",
        "minoredit": "זוהי עריכה משנית",
        "watchthis": "מעקב אחרי דף זה",
-       "savearticle": "ש×\9e×\99ר×\94",
+       "savearticle": "ש×\9e×\99רת ×\94×\93×£",
        "publishpage": "פרסום הדף",
        "preview": "תצוגה מקדימה",
        "showpreview": "תצוגה מקדימה",
        "accmailtext": "סיסמה אקראית עבור [[User talk:$1|$1]] נשלחה אל $2. ניתן לשנותה בדף '''[[Special:ChangePassword|שינוי הסיסמה]]''' לאחר הכניסה.",
        "newarticle": "(חדש)",
        "newarticletext": "הגעתם לדף שעדיין אינו קיים.\nכדי ליצור את הדף הזה, התחילו להקליד בתיבת הטקסט שלמטה (ראו את [$1 דף העזרה] למידע נוסף).\nאם הגעתם לכאן בטעות, לחצו על כפתור ה<strong>חזרה</strong> (Back) בדפדפן שלכם.",
-       "anontalkpagetext": "----\n<em>זהו דף שיחה של משתמש אנונימי שעדיין לא יצר חשבון במערכת, או שהוא לא משתמש בו.</em>\nלכן עלינו להשתמש בכתובת ה־IP המספרית כדי לזהותו.\nייתכן שכתובת IP זו תהיה משותפת למספר משתמשים.\nאם אתם משתמשים אנונימיים ומרגישים שקיבלתם הודעות בלתי רלוונטיות, אנא [[Special:UserLogin/signup|צרו חשבון]] או [[Special:UserLogin|היכנסו לחשבון]] כדי להימנע מבלבול עתידי עם משתמשים אנונימיים נוספים.",
+       "anontalkpagetext": "----\n<em>זהו דף שיחה של משתמש אנונימי שעדיין לא יצר חשבון במערכת, או שהוא לא משתמש בו.</em>\nלכן עלינו להשתמש בכתובת ה־IP המספרית כדי לזהותו.\nייתכן שכתובת IP זו תהיה משותפת למספר משתמשים.\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}}|לחפש את כותרת הדף]] בדפים אחרים או <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} לחפש את הדף ביומנים]</span>, אך אין לך הרשאה ליצור את הדף.",
        "missing-revision": "גרסה #$1 של הדף \"{{FULLPAGENAME}}\" אינה קיימת.\n\nזה נגרם בדרך כלל על־ידי לחיצה על קישור ישן לגרסה קודמת של דף שנמחק.\nאפשר למצוא פרטים ב[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} יומן המחיקות].",
-       "userpage-userdoesnotexist": "חשבון המשתמש \"$1\" אינו רשום.\nאנא בִּדקו אם ברצונכם ליצור/לערוך את הדף הזה.",
+       "userpage-userdoesnotexist": "חשבון המשתמש \"$1\" אינו רשום.\nאנא בִדקו אם ברצונכם ליצור/לערוך את הדף הזה.",
        "userpage-userdoesnotexist-view": "חשבון המשתמש \"$1\" אינו רשום.",
        "blocked-notice-logextract": "{{GENDER:$1|המשתמש הזה חסום|המשתמשת הזו חסומה}} כרגע.\nהפעולה האחרונה ביומן החסימות מוצגת להלן:",
-       "clearyourcache": "<strong>×\94ער×\94:</strong> ×\9c×\90×\97ר ×\94ש×\9e×\99ר×\94, ×\99×\99ת×\9b×\9f ×©×ª×¦×\98ר×\9b×\95 ×\9cנק×\95ת ×\90ת ×\96×\99×\9bר×\95×\9f ×\94×\9e×\98×\9e×\95×\9f (cache) ×©×\9c ×\94×\93פ×\93פ×\9f ×\9b×\93×\99 ×\9c×\94×\91×\97×\99×\9f ×\91ש×\99× ×\95×\99×\99×\9d.\n* <strong>פ×\99×\99רפ×\95קס / ×¡×¤×\90ר×\99:</strong> ×\9c×\97צ×\95 ×\95×\94×\97×\96×\99ק×\95 ×\90ת ×\94×\9eקש <em>Shift</em> ×\91עת ×\9c×\97×\99צת×\9b×\9d ×¢×\9c <strong>×\98×¢×\99× ×\94 ×\9e×\97×\93ש</strong> (Reload), ×\90×\95 ×\9c×\97צ×\95 ×¢×\9c ×¦×\99ר×\95×£ ×\94×\9eקש×\99×\9d <em>Ctrl-F5</em> ×\90×\95 <em>Ctrl-R</em>&rlm; (<em><span dir=\"ltr\">â\8c\98-R</span></em> ×\91×\9e×\97ש×\91 ×\9eק)\n* <strong>×\92×\95×\92×\9c ×\9bר×\95×\9d:</strong> ×\9c×\97צ×\95 ×¢×\9c ×¦×\99ר×\95×£ ×\94×\9eקש×\99×\9d <em>Ctrl-Shift-R</em>&rlm; (<em><span dir=\"ltr\">â\8c\98-Shift-R</span></em> ×\91×\9e×\97ש×\91 ×\9eק)\n* <strong>×\90×\99× ×\98רנ×\98 ×\90קספ×\9c×\95רר:</strong> ×\9c×\97צ×\95 ×\95×\94×\97×\96×\99ק×\95 ×\90ת ×\94×\9eקש <em>Ctrl</em> ×\91עת ×\9c×\97×\99צת×\9b×\9d ×¢×\9c <strong>רענ×\9f</strong> (Refresh), ×\90×\95 ×\9c×\97צ×\95 ×¢×\9c ×¦×\99ר×\95×£ ×\94×\9eקש×\99×\9d <em>Ctrl-F5</em>\n* <strong>×\90×\95פר×\94:</strong> ×¤×ª×\97×\95 ×\90ת <em>Menu â\86\92 Settings</em> (×\94נקר×\90 <em>Opera â\86\92 Preferences</em> ×\91×\9e×\97ש×\91 ×\9eק) ×\95×\90×\96 ×\9c×\97צ×\95 ×¢×\9c <em>Privacy & security â\86\92 Clear browsing data â\86\92 Cached images and files</em>.",
+       "clearyourcache": "<strong>×\94ער×\94:</strong> ×\9c×\90×\97ר ×\94ש×\9e×\99ר×\94, ×\99×\99ת×\9b×\9f ×©×\99×\94×\99×\94 ×¦×\95ר×\9a ×\9cנק×\95ת ×\90ת ×\96×\99×\9bר×\95×\9f ×\94×\9e×\98×\9e×\95×\9f (cache) ×©×\9c ×\94×\93פ×\93פ×\9f ×\9b×\93×\99 ×\9c×\94×\91×\97×\99×\9f ×\91ש×\99× ×\95×\99×\99×\9d.\n* <strong>פ×\99×\99רפ×\95קס / ×¡×¤×\90ר×\99:</strong> ×\9c×\94×\97×\96×\99ק ×\90ת ×\94×\9eקש <em>Shift</em> ×\91עת ×\9c×\97×\99צ×\94 ×¢×\9c <strong>×\98×¢×\99× ×\94 ×\9e×\97×\93ש</strong> (Reload), ×\90×\95 ×\9c×\9c×\97×\95×¥ ×¢×\9c ×¦×\99ר×\95×£ ×\94×\9eקש×\99×\9d <em>Ctrl-F5</em> ×\90×\95 <em>Ctrl-R</em> (×\91×\9e×\97ש×\91 ×\9eק: <em dir=\"ltr\">â\8c\98-R</em>)\n* <strong>×\92×\95×\92×\9c ×\9bר×\95×\9d:</strong> ×\9c×\9c×\97×\95×¥ ×¢×\9c ×¦×\99ר×\95×£ ×\94×\9eקש×\99×\9d <em>Ctrl-Shift-R</em> (×\91×\9e×\97ש×\91 ×\9eק: <em dir=\"ltr\">â\8c\98-Shift-R</em>)\n* <strong>×\90×\99× ×\98רנ×\98 ×\90קספ×\9c×\95רר:</strong> ×\9c×\9c×\97×\95×¥ ×\95×\9c×\94×\97×\96×\99ק ×\90ת ×\94×\9eקש <em>Ctrl</em> ×\91עת ×\9c×\97×\99צ×\94 ×¢×\9c <strong>רענ×\9f</strong> (Refresh), ×\90×\95 ×\9c×\9c×\97×\95×¥ ×¢×\9c ×¦×\99ר×\95×£ ×\94×\9eקש×\99×\9d <em>Ctrl-F5</em>\n* <strong>×\90×\95פר×\94:</strong> ×\9cפת×\95×\97 <em>תפר×\99×\98 â\86\90 ×\94×\92×\93ר×\95ת</em> (×\91×\9e×\97ש×\91 ×\9eק <em>Opera â\86\90 ×\94×¢×\93פ×\95ת</em>) ×\95×\90×\96 ×\9c×\9c×\97×\95×¥ ×¢×\9c <em>פר×\98×\99×\95ת ×\95×\90×\91×\98×\97×\94 â\86\90 ×\9e×\97ק ×\94×\99ס×\98×\95ר×\99×\99ת ×\92×\9c×\99ש×\94 â\86\90 Cached images and files</em>.",
        "usercssyoucanpreview": "<strong>עצה:</strong> השתמשו בכפתור \"{{int:showpreview}}\" כדי לבחון את גיליון ה־CSS החדש שלכם לפני השמירה.",
        "userjsyoucanpreview": "<strong>עצה:</strong> השתמשו בכפתור \"{{int:showpreview}}\" כדי לבחון את סקריפט ה־JavaScript החדש שלכם לפני השמירה.",
        "usercsspreview": "<strong>זִכרו שזו רק תצוגה מקדימה של גיליון ה־CSS שלכם.\nהוא עדיין לא נשמר!</strong>",
        "userinvalidcssjstitle": "'''אזהרה:''' העיצוב \"$1\" אינו קיים.\nדפי .css ו־.js מותאמים אישית משתמשים בכותרת עם אותיות קטנות – למשל, {{ns:user}}:דוגמה/vector.css ולא {{ns:user}}:דוגמה/Vector.css.",
        "updated": "(מעודכן)",
        "note": "'''הערה:'''",
-       "previewnote": "<strong>×\96Ö´×\9bר×\95 ×©×\96×\95 ×¨×§ ×ª×¦×\95×\92×\94 ×\9eק×\93×\99×\9e×\94.</strong>\n×\94ש×\99× ×\95×\99×\99×\9d ×©×\9c×\9b×\9d ×\98ר×\9d נשמרו!",
+       "previewnote": "<strong>×\96Ö´×\9bר×\95 ×©×\96×\95 ×¨×§ ×ª×¦×\95×\92×\94 ×\9eק×\93×\99×\9e×\94.</strong>\n×\94ש×\99× ×\95×\99×\99×\9d ×©×\9c×\9b×\9d ×¢×\93×\99×\99×\9f ×\9c×\90 נשמרו!",
        "continue-editing": "מעבר לאזור העריכה",
        "previewconflict": "תצוגה מקדימה זו מציגה כיצד ייראה הטקסט בחלון העריכה העליון, אם תבחרו לשמור אותו.",
        "session_fail_preview": "מצטערים! לא ניתן לבצע את עריכתכם עקב אובדן מידע הכניסה.\n\nייתכן שנותקתם מהחשבון. <strong>אנא ודאו שאתם עדיין מחוברים לחשבון ונסו שוב.</strong>\nאם זה עדיין לא עובד, נסו [[Special:UserLogout|לצאת מהחשבון]] ולהיכנס אליו שנית, וודאו שהדפדפן שלכם מאפשר קבלת עוגיות מאתר זה.",
        "editingsection": "עריכת הדף $1 (פסקה)",
        "editingcomment": "עריכת הדף $1 (פסקה חדשה)",
        "editconflict": "התנגשות עריכה: $1",
-       "explainconflict": "×\9eשת×\9eש ×\90×\97ר ×©×\99× ×\94 ×\90ת ×\94×\93×£ ×\9e×\90×\96 ×©×\94ת×\97×\9cת×\9d ×\9cער×\95×\9a ×\90×\95ת×\95.\n×\97×\9c×\95×\9f ×\94ער×\99×\9b×\94 ×\94×¢×\9c×\99×\95×\9f ×\9e×\9b×\99×\9c ×\90ת ×\94×\98קס×\98 ×\91×\93×£ ×\9bפ×\99 ×©×\94×\95×\90 ×¢×ª×\94.\n×\94ש×\99× ×\95×\99×\99×\9d ×©×\9c×\9b×\9d ×\9e×\95צ×\92×\99×\9d ×\91×\97×\9c×\95×\9f ×\94ער×\99×\9b×\94 ×\94ת×\97ת×\95×\9f.\n×¢×\9c×\99×\9b×\9d ×\9c×\9e×\96×\92 ×\90ת ×\94ש×\99× ×\95×\99×\99×\9d ×©×\9c×\9b×\9d ×\9cת×\95×\9a ×\94×\98קס×\98 ×\94ק×\99×\99×\9d.\n'''רק''' הטקסט בחלון העריכה העליון יישמר כשתלחצו על \"{{int:savearticle}}\".",
+       "explainconflict": "×\9eשת×\9eש ×\90×\97ר ×©×\99× ×\94 ×\90ת ×\94×\93×£ ×\9e×\90×\96 ×©×\94ת×\97×\9cת×\9d ×\9cער×\95×\9a ×\90×\95ת×\95.\n×\97×\9c×\95×\9f ×\94ער×\99×\9b×\94 ×\94×¢×\9c×\99×\95×\9f ×\9eצ×\99×\92 ×\90ת ×\94×\98קס×\98 ×\91×\93×£ ×\9bפ×\99 ×©×\94×\95×\90 ×\9bר×\92×¢.\n×\94ש×\99× ×\95×\99×\99×\9d ×©×\9c×\9b×\9d ×\9e×\95צ×\92×\99×\9d ×\91×\97×\9c×\95×\9f ×\94ער×\99×\9b×\94 ×\94ת×\97ת×\95×\9f.\n×¢×\9c×\99×\9b×\9d ×\9c×\9e×\96×\92 ×\90ת ×\94ש×\99× ×\95×\99×\99×\9d ×©×\9c×\9b×\9d ×\9cת×\95×\9a ×\94×\98קס×\98 ×\94ק×\99×\99×\9d.\n<strong>רק</strong> הטקסט בחלון העריכה העליון יישמר כשתלחצו על \"{{int:savearticle}}\".",
        "yourtext": "הטקסט שלך",
        "storedversion": "גרסה שמורה",
        "nonunicodebrowser": "'''אזהרה: הדפדפן שלך אינו תואם לתקן יוניקוד.'''\nכדי למנוע בעיות הנוצרות כתוצאה מכך ולאפשר לך לערוך דפים בבטחה, תווים שאינם ב־ASCII יוצגו בתיבת העריכה כקודים הקסדצימליים.",
-       "editingold": "'''אזהרה: אתם עורכים גרסה לא עדכנית של דף זה.'''\nאם תשמרו את הדף, כל השינויים שנעשו מאז גרסה זו יאבדו.",
+       "editingold": "<strong>אזהרה: אתם עורכים גרסה לא עדכנית של דף זה.</strong>\nאם תשמרו את הדף, כל השינויים שנעשו מאז גרסה זו יאבדו.",
        "yourdiff": "הבדלים",
        "copyrightwarning": "'''שימו לב:''' תרומתכם ל{{grammar:תחילית|{{SITENAME}}}} תפורסם תחת תנאי הרישיון $2 (ראו $1 לפרטים נוספים). אם אינכם רוצים שעבודתכם תהיה זמינה לעריכה על־ידי אחרים, שתופץ לעיני כול, ושאחרים יוכלו להעתיק ממנה בציון המקור – אל תפרסמו אותה פה. כמו־כן, אתם מבטיחים לנו כי כתבתם את הטקסט הזה בעצמכם, או העתקתם אותו ממקור שאינו מוגן בזכויות יוצרים. '''אל תעשו שימוש בחומר המוגן בזכויות יוצרים ללא רשות!'''",
        "copyrightwarning2": "'''שימו לב:''' תורמים אחרים עשויים לערוך או אף להסיר את תרומתכם ל{{grammar:תחילית|{{SITENAME}}}}. אם אינכם רוצים שעבודתכם תהיה זמינה לעריכה על־ידי אחרים, אל תפרסמו אותה פה. כמו־כן, אתם מבטיחים לנו כי כתבתם את הטקסט הזה בעצמכם, או העתקתם אותו ממקור שאינו מוגן בזכויות יוצרים (ראו $1 לפרטים נוספים). '''אל תעשו שימוש בחומר המוגן בזכויות יוצרים ללא רשות!'''",
        "edit-no-change": "המערכת התעלמה מעריכתך כיוון שלא נעשה שינוי בטקסט.",
        "postedit-confirmation-created": "הדף נוצר.",
        "postedit-confirmation-restored": "הדף שוחזר.",
-       "postedit-confirmation-saved": "ער×\99×\9bתך נשמרה.",
+       "postedit-confirmation-saved": "×\94ער×\99×\9b×\94 ×©×\9cך נשמרה.",
        "edit-already-exists": "לא ניתן ליצור דף חדש.\nהוא כבר קיים.",
        "defaultmessagetext": "טקסט ההודעה המקורי",
        "content-failed-to-parse": "פענוח $2 כתוכן מסוג $1 נכשל: $3",
        "editwarning-warning": "עזיבת דף זה עשויה לגרום לאובדן כל השינויים שביצעתם.\nאם אתם מחוברים לחשבון, תוכלו לבטל אזהרה זו בחלק \"{{int:prefs-editing}}\" שבהעדפות שלכם.",
        "editpage-notsupportedcontentformat-title": "סוג התוכן אינו נתמך",
        "editpage-notsupportedcontentformat-text": "תוכן מסוג $1 אינו נתמך על־ידי מודל התוכן $2.",
-       "content-model-wikitext": "×\98קס×\98 ויקי",
+       "content-model-wikitext": "ק×\95×\93 ויקי",
        "content-model-text": "טקסט פשוט",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
        "revdelete-unsuppress": "הסרת הגבלות בגרסאות המשוחזרות",
        "revdelete-log": "סיבה:",
        "revdelete-submit": "ביצוע על {{PLURAL:$1|הגרסה שנבחרה|הגרסאות שנבחרו}}",
-       "revdelete-success": "×\9eצ×\91 ×\94תצ×\95×\92×\94 ×©×\9c ×\94×\92רס×\94 ×©×\81×\95Ö¼× ×\94.",
+       "revdelete-success": "מצב התצוגה של הגרסה שוּנה.",
        "revdelete-failure": "לא ניתן היה לשנות את מצב התצוגה של הגרסה:\n$1",
-       "logdelete-success": "×\9eצ×\91 ×\94תצ×\95×\92×\94 ×©×\9c ×¤×¢×\95×\9cת ×\94×\99×\95×\9e×\9f ×©×\81×\95Ö¼× ×\94.",
+       "logdelete-success": "מצב התצוגה של פעולת היומן שוּנה.",
        "logdelete-failure": "לא ניתן היה לשנות את מצב התצוגה של היומן:\n$1",
        "revdel-restore": "שינוי מצב התצוגה",
        "pagehist": "היסטוריית הדף",
        "mergehistory-fail-permission": "הרשאות לא מספיקות למיזוג היסטוריה.",
        "mergehistory-fail-self-merge": "דף המקור זהה לדף היעד.",
        "mergehistory-fail-timestamps-overlap": "גרסאות המקור חופפות או מגיעות אחרי גרסאות היעד.",
-       "mergehistory-fail-toobig": "לא ניתן לבצע את מיזוג הגרסאות כיוון שצריך להעביר יותר גרסאות מהמגבלה, שהיא {{PLURAL:$1|גרסה אחת|‏‏֫$1 גרסאות}}.",
+       "mergehistory-fail-toobig": "לא ניתן לבצע את מיזוג הגרסאות כיוון שצריך להעביר יותר גרסאות מהמגבלה, שהיא {{PLURAL:$1|גרסה אחת|$1 גרסאות}}.",
        "mergehistory-no-source": "דף המקור $1 אינו קיים.",
        "mergehistory-no-destination": "דף היעד $1 אינו קיים.",
        "mergehistory-invalid-source": "דף המקור חייב להיות בעל כותרת תקינה.",
        "searchrelated": "קשור",
        "searchall": "הכול",
        "showingresults": "{{PLURAL:$1|מוצגת תוצאה <strong>אחת</strong>|מוצגות עד <strong>$1</strong> תוצאות}} החל ממספר <strong>$2</strong>:",
-       "showingresultsinrange": "{{PLURAL:$1|מוצגת תוצאה <strong>אחת</strong>|מוצגות עד <strong>$1</strong> תוצאות}} בין המספרים <strong>$2</strong> ו‏‏־<strong>$3</strong>:",
+       "showingresultsinrange": "{{PLURAL:$1|מוצגת תוצאה <strong>אחת</strong>|מוצגות עד <strong>$1</strong> תוצאות}} בין המספרים <strong>$2</strong> ו־<strong>$3</strong>:",
        "search-showingresults": "{{PLURAL:$4|תוצאה <strong>$1</strong> מתוך <strong>$3</strong>|תוצאות <strong>$1 - $2</strong> מתוך <strong>$3</strong>}}",
        "search-nonefound": "לא נמצאו תוצאות המתאימות לחיפוש.",
        "search-nonefound-thiswiki": "לא נמצאו תוצאות המתאימות לחיפוש באתר זה.",
        "prefs-watchlist": "רשימת המעקב",
        "prefs-editwatchlist": "עריכת רשימת המעקב",
        "prefs-editwatchlist-label": "עריכת דפים ברשימת המעקב שלך:",
-       "prefs-editwatchlist-edit": "הצגה או הסרה של דפים מרשימת המעקב שלך",
+       "prefs-editwatchlist-edit": "הצגה או הסרה של דפים מרשימת המעקב",
        "prefs-editwatchlist-raw": "עריכת רשימת המעקב הגולמית",
-       "prefs-editwatchlist-clear": "ניקוי רשימת המעקב שלך",
+       "prefs-editwatchlist-clear": "ניקוי רשימת המעקב",
        "prefs-watchlist-days": "מספר הימים שמוצגים ברשימת המעקב:",
        "prefs-watchlist-days-max": "לכל היותר {{PLURAL:$1|יום אחד|יומיים|$1 ימים}}",
        "prefs-watchlist-edits": "המספר המרבי של העריכות שמוצגות ברשימת המעקב המורחבת:",
        "right-override-export-depth": "ייצוא דפים כולל דפים מקושרים עד עומק של חמישה",
        "right-sendemail": "שליחת דואר אלקטרוני למשתמשים אחרים",
        "right-passwordreset": "צפייה בדואר אלקטרוני של איפוס סיסמה",
-       "right-managechangetags": "×\99צ×\99רת ×\95×\9e×\97×\99קת [[Special:Tags|ת×\92×\99×\95ת]] ×\9e×\91ס×\99ס ×\94נת×\95× ×\99×\9d",
+       "right-managechangetags": "×\99צ×\99ר×\94, ×\94פע×\9c×\94 ×\95×\91×\99×\98×\95×\9c ×©×\9c [[Special:Tags|ת×\92×\99×\95ת]]",
        "right-applychangetags": "החלת [[Special:Tags|תגיות]] יחד עם שינויים",
        "right-changetags": "הוספת והסרה של [[Special:Tags|תגיות]] כלשהן לגרסאות מסוימות ולרשומות יומן",
+       "right-deletechangetags": "מחיקת [[Special:Tags|תגיות]] מבסיס הנתונים",
        "grant-generic": "חבילת ההרשאות \"$1\"",
        "grant-group-page-interaction": "אינטראקציה עם דפים",
        "grant-group-file-interaction": "אינטראקציה עם קבצים",
        "grant-viewmywatchlist": "צפייה ברשימת המעקב שלך",
        "newuserlogpage": "יומן רישום משתמשים",
        "newuserlogpagetext": "זהו יומן המכיל הרשמות של משתמשים.",
-       "rightslog": "×\99×\95×\9e×\9f ×ª×¤×§×\99×\93×\99×\9d",
+       "rightslog": "×\99×\95×\9e×\9f ×\94רש×\90×\95ת",
        "rightslogtext": "זהו יומן השינויים בתפקידי המשתמשים.",
        "action-read": "לקרוא דף זה",
        "action-edit": "לערוך דף זה",
        "action-viewmyprivateinfo": "לצפות במידע הפרטי שלך",
        "action-editmyprivateinfo": "לערוך את המידע הפרטי שלך",
        "action-editcontentmodel": "לערוך את מודל התוכן של דף",
-       "action-managechangetags": "ליצור ולמחוק תגיות מבסיס הנתונים",
+       "action-managechangetags": "ליצור, להפעיל ולבטל תגיות",
        "action-applychangetags": "להחיל תגיות יחד עם השינויים שלכם",
        "action-changetags": "להוסיף ולהסיר תגיות כלשהן לגרסאות מסוימות ולרשומות יומן",
+       "action-deletechangetags": "למחוק תגיות מבסיס הנתונים",
        "nchanges": "{{PLURAL:$1|שינוי אחד|$1 שינויים}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|מאז ביקורך האחרון}}",
        "enhancedrc-history": "היסטוריה",
        "recentchanges-label-unpatrolled": "עריכה זו טרם נבדקה",
        "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-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "הצגה",
-       "rcnotefrom": "×\9c×\94×\9c×\9f {{PLURAL:$5|×\94ש×\99× ×\95×\99 ×©×\91×\95צע|×\94ש×\99× ×\95×\99×\99×\9d ×©×\91×\95צע×\95}} ×\94×\97×\9c ×\9eâ\80\8fâ\80\8fÖ«Ö¾<b>$2</b> (×¢×\93 <b>$1</b> ×\9e×\95צ×\92×\99×\9d).",
+       "rcnotefrom": "×\9c×\94×\9c×\9f {{PLURAL:$5|×\94ש×\99× ×\95×\99 ×©×\91×\95צע|×\94ש×\99× ×\95×\99×\99×\9d ×©×\91×\95צע×\95}} ×\9e×\90×\96 <strong>$3, $4</strong> (×\9e×\95צ×\92×\99×\9d ×¢×\93 <strong>$1</strong>).",
        "rclistfrom": "הצגת שינויים חדשים החל מ־$2, $3",
        "rcshowhideminor": "$1 עריכות משניות",
        "rcshowhideminor-show": "הצגת",
        "recentchangeslinked": "שינויים בדפים המקושרים",
        "recentchangeslinked-feed": "שינויים בדפים המקושרים",
        "recentchangeslinked-toolbox": "שינויים בדפים המקושרים",
-       "recentchangeslinked-title": "שינויים בדפים המקושרים מהדף $1",
+       "recentchangeslinked-title": "שינויים בדפים המקושרים מהדף \"$1\"",
        "recentchangeslinked-summary": "בדף מיוחד זה רשומים השינויים האחרונים בדפים המקושרים מתוך הדף (או בדפים הכלולים בקטגוריה).\nדפים ב[[Special:Watchlist|רשימת המעקב שלכם]] מוצגים ב'''הדגשה'''.",
        "recentchangeslinked-page": "שם הדף:",
        "recentchangeslinked-to": "הצגת השינויים בדפים המקשרים לדף הנתון במקום זאת",
        "recentchanges-page-removed-from-category": "הדף [[:$1]] הוסר מקטגוריה",
        "recentchanges-page-removed-from-category-bundled": "הדף [[:$1]] הוסר מקטגוריה, [[Special:WhatLinksHere/$1|והוא מוכלל בדפים אחרים]]",
        "autochange-username": "שינוי אוטומטי של מדיה־ויקי",
-       "upload": "העלאת קובץ לשרת",
-       "uploadbtn": "×\94×¢×\9c×\90×\94",
+       "upload": "העלאת קובץ",
+       "uploadbtn": "×\94×¢×\9c×\90ת ×\94ק×\95×\91×¥",
        "reuploaddesc": "ביטול ההעלאה וחזרה לטופס העלאת קבצים לשרת",
        "upload-tryagain": "שליחת התיאור החדש של הקובץ",
        "uploadnologin": "לא נכנסת לחשבון",
        "uploadvirus": "הקובץ מכיל וירוס!\nפרטים:\n<div dir=\"ltr\">$1</div>",
        "uploadjava": "קובץ זה הוא קובץ ZIP שמכיל קובץ &lrm;.class של Java.\nהעלאת קובצי Java אסורה, כיוון שהם יכולים לגרום לעקיפת מגבלות האבטחה.",
        "upload-source": "קובץ המקור",
-       "sourcefilename": "ש×\9d ×\94ק×\95×\91×¥:",
+       "sourcefilename": "ש×\9d ×§×\95×\91×¥ ×\94×\9eק×\95ר:",
        "sourceurl": "כתובת URL של המקור:",
        "destfilename": "שמירת הקובץ בשם:",
        "upload-maxfilesize": "גודל הקובץ המרבי: $1",
        "upload-form-label-infoform-description-tooltip": "תיאור קצר של כל העובדות החשובות על הקובץ.\nאם הקובץ הוא תמונה, יש לציין את הדברים העיקריים המתוארים בתמונה, את האירוע, או את המיקום.",
        "upload-form-label-usage-title": "שימושים",
        "upload-form-label-usage-filename": "שם הקובץ",
-       "foreign-structured-upload-form-label-own-work": "אני יצרתי את הקובץ",
-       "foreign-structured-upload-form-label-infoform-categories": "קטגוריות",
-       "foreign-structured-upload-form-label-infoform-date": "תאריך",
-       "foreign-structured-upload-form-label-own-work-message-local": "ההעלאה מבוצעת בהתאם לתנאי השירות ולמדיניות הרישיונות ב{{grammar:תחילית|{{SITENAME}}}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של {{SITENAME}}, עליך לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף ברירת המחדל להעלאת קבצים]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "ידוע לי שאני מעלה את הקובץ הזה למאגר משותף. ההעלאה מבוצעת בהתאם לתנאי השירות ולמדיניות הרישיונות שם.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של המאגר המשותף, עליך לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף העלאת הקבצים ב{{grammar:תחילית|{{SITENAME}}}}]], אם ניתן להעלות את הקובץ הזה לשם לפי מדיניות האתר.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "אני מאשר שאני מחזיק בזכויות היוצרים על הקובץ הזה, ואני מסכים לשחרר אותו באופן בלתי הפיך עבור ויקישיתוף תחת רישיון [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], ומסכים ל[https://wikimediafoundation.org/wiki/Terms_of_Use תנאי השימוש].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "אם זכויות היוצרים על הקובץ הזה אינן בבעלותך, או שברצונך לשחרר אותו תחת רישיון אחר, באפשרותך להשתמש ב[https://commons.wikimedia.org/wiki/Special:UploadWizard אשף ההעלאה לוויקישיתוף].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף העלאת הקבצים ב{{grammar:תחילית|{{SITENAME}}}}]], אם ניתן להעלות את הקובץ הזה לשם לפי מדיניות האתר.",
+       "upload-form-label-own-work": "אני יצרתי את הקובץ",
+       "upload-form-label-infoform-categories": "קטגוריות",
+       "upload-form-label-infoform-date": "תאריך",
+       "upload-form-label-own-work-message-generic-local": "ההעלאה מבוצעת בהתאם לתנאי השירות ולמדיניות הרישיונות ב{{grammar:תחילית|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של {{SITENAME}}, עליך לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
+       "upload-form-label-not-own-work-local-generic-local": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף ברירת המחדל להעלאת קבצים]].",
+       "upload-form-label-own-work-message-generic-foreign": "ידוע לי שאני מעלה את הקובץ הזה למאגר משותף. ההעלאה מבוצעת בהתאם לתנאי השירות ולמדיניות הרישיונות שם.",
+       "upload-form-label-not-own-work-message-generic-foreign": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של המאגר המשותף, עליך לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
+       "upload-form-label-not-own-work-local-generic-foreign": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף העלאת הקבצים ב{{grammar:תחילית|{{SITENAME}}}}]], אם ניתן להעלות את הקובץ הזה לשם לפי מדיניות האתר.",
        "backend-fail-stream": "לא הייתה אפשרות להזרים את הקובץ \"$1\".",
        "backend-fail-backup": "לא הייתה אפשרות לגבות את הקובץ \"$1\".",
        "backend-fail-notexists": "הקובץ \"$1\" אינו קיים.",
        "ntransclusions": "בשימוש {{PLURAL:$1|בדף אחד|ב־$1 דפים}}",
        "specialpage-empty": "אין תוצאות.",
        "lonelypages": "דפים יתומים",
-       "lonelypagestext": "×\94×\93פ×\99×\9d ×\94×\91×\90×\99×\9d ×\90×\99× ×\9d ×\9eק×\95שר×\99×\9d ×\9e×\93פ×\99×\9d ×\90×\97ר×\99×\9d ×\91×\90תר ×\96×\94 ×\95×\90×\99× ×\9d ×\9e×\95×\9b×\9c×\9c×\99×\9d ×\91×\94×\9d.",
+       "lonelypagestext": "×\94×\93פ×\99×\9d ×\94×\91×\90×\99×\9d ×\90×\99× ×\9d ×\9eק×\95שר×\99×\9d ×\95×\90×\99× ×\9d ×\9e×\95×\9b×\9c×\9c×\99×\9d ×\91×\93פ×\99×\9d ×\90×\97ר×\99×\9d ×\91×\90תר {{SITENAME}}.",
        "uncategorizedpages": "דפים חסרי קטגוריה",
        "uncategorizedcategories": "קטגוריות חסרות קטגוריה",
        "uncategorizedimages": "קבצים חסרי קטגוריה",
        "deadendpages": "דפים ללא קישורים",
        "deadendpagestext": "הדפים הבאים אינם מקשרים לדפים אחרים באתר {{SITENAME}}.",
        "protectedpages": "דפים מוגנים",
-       "protectedpages-indef": "×\94×\92× ×\95ת ×\9c×\96×\9e×\9f ×\91×\9cת×\99 ×\9e×\95×\92×\91×\9c בלבד",
+       "protectedpages-indef": "×\94×\92× ×\95ת ×\9c×\9c×\90 ×\94×\92×\91×\9cת ×\96×\9e×\9f בלבד",
        "protectedpages-summary": "בדף זה רשומים הדפים הקיימים שמוגנים כרגע. לרשימת הכותרות שמוגנות מפני יצירה, ראו [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "protectedpages-cascade": "הגנות מדורגות בלבד",
        "protectedpages-noredirect": "הסתרת הפניות",
        "protectedpages-page": "דף",
        "protectedpages-expiry": "זמן פקיעה",
        "protectedpages-performer": "הוגן על־ידי",
-       "protectedpages-params": "פר×\9e×\98ר×\99×\9d ×\9cהגנה",
+       "protectedpages-params": "ר×\9eת ×\94הגנה",
        "protectedpages-reason": "סיבה",
        "protectedpages-submit": "הצגת דפים",
        "protectedpages-unknown-timestamp": "לא ידוע",
        "listgrouprights-rights": "הרשאות",
        "listgrouprights-helppage": "Help:הרשאות",
        "listgrouprights-members": "(רשימת חברים)",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code dir=\"ltr\">($2)</code></span>",
        "listgrouprights-addgroup": "הוספת {{PLURAL:$2|הקבוצה|הקבוצות}}: $1",
        "listgrouprights-removegroup": "הסרת {{PLURAL:$2|הקבוצה|הקבוצות}}: $1",
        "listgrouprights-addgroup-all": "הוספת כל הקבוצות",
        "emailccsubject": "העתק של הודעתך למשתמש $1: $2",
        "emailsent": "הדואר נשלח",
        "emailsenttext": "הודעת הדואר האלקטרוני שלך נשלחה.",
-       "emailuserfooter": "$1 {{GENDER:$1|ש×\9c×\97|ש×\9c×\97×\94}} ×\90ת ×\94×\93×\95×\90\"×\9c ×\94×\96×\94 ×\9c{{GRAMMAR:ת×\97×\99×\9c×\99ת|$2}} ×\91×\90×\9eצע×\95ת ×¤×¢×\95×\9cת \"{{int:emailuser}}\" ב{{GRAMMAR:תחילית|{{SITENAME}}}}.",
+       "emailuserfooter": "$1 {{GENDER:$1|ש×\9c×\97|ש×\9c×\97×\94}} ×\90ת ×\94×\93×\95×\90\"×\9c ×\94×\96×\94 ×\9c{{GRAMMAR:ת×\97×\99×\9c×\99ת|$2}} ×\91×\90×\9eצע×\95ת ×\94ת×\9b×\95× ×\94 \"{{int:emailuser}}\" ב{{GRAMMAR:תחילית|{{SITENAME}}}}.",
        "usermessage-summary": "השארת הודעת מערכת.",
        "usermessage-editor": "שולח הודעות המערכת",
        "watchlist": "רשימת המעקב",
        "wlheader-showupdated": "דפים שהשתנו מאז ביקורך האחרון בהם מוצגים ב'''הדגשה'''.",
        "wlnote": "להלן {{PLURAL:$1|השינוי האחרון|<strong>$1</strong> השינויים האחרונים}} {{PLURAL:$2|בשעה האחרונה|בשעתיים האחרונות|ב־<strong>$2</strong> השעות האחרונות}}, עד $4, $3.",
        "wlshowlast": "הצגת $1 שעות אחרונות $2 ימים אחרונים",
-       "watchlist-hide": "×\94סתר×\94",
+       "watchlist-hide": "×\94סתרת",
        "watchlist-submit": "הצגה",
        "wlshowtime": "תקופת זמן לצפייה:",
        "wlshowhideminor": "עריכות משניות",
        "enotif_body": "לכבוד $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nתקציר העריכה: $PAGESUMMARY $PAGEMINOREDIT\n\nבאפשרותכם ליצור קשר עם העורך:\nבדואר האלקטרוני: $PAGEEDITOR_EMAIL\nבאתר: $PAGEEDITOR_WIKI\n\nלא תהיינה הודעות על פעולות נוספות עד שתבקרו בדף כשאתם מחוברים לחשבון. באפשרותכם גם לאפס את דגלי ההודעות בכל הדפים שברשימת המעקב.\n\nמערכת ההודעות של {{SITENAME}}\n\n--\nכדי לשנות את ההגדרות של הודעות הדוא\"ל הנשלחות אליכם, בקרו בדף\n{{canonicalurl:{{#special:Preferences}}}}\n\nכדי לשנות את הגדרות רשימת המעקב, בקרו בדף\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nכדי למחוק את הדף מרשימת המעקב שלכם, בקרו בדף\n$UNWATCHURL\n\nלמשוב ולעזרה נוספת:\n$HELPPAGE",
        "created": "נוצר",
        "changed": "שונה",
-       "deletepage": "×\9e×\97×\99ק×\94",
+       "deletepage": "×\9e×\97×\99קת ×\94×\93×£",
        "confirm": "אישור",
        "excontent": "התוכן היה: \"$1\"",
        "excontentauthor": "התוכן היה: \"$1\", {{GENDER:$2|והתורם היחיד היה|והתורמת היחידה הייתה}} \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|שיחה]])",
        "exbeforeblank": "התוכן לפני שרוקן היה: \"$1\"",
-       "delete-confirm": "מחיקת \"$1\"",
+       "delete-confirm": "מחיקת הדף \"$1\"",
        "delete-legend": "מחיקה",
        "historywarning": "<strong>אזהרה:</strong> לדף שאתם עומדים למחוק יש היסטוריית שינויים של {{PLURAL:$1|גרסה אחת|$1 גרסאות}}:",
        "historyaction-submit": "הצגה",
        "changecontentmodel-submit": "שינוי",
        "changecontentmodel-success-title": "מודל התוכן שוּנה",
        "changecontentmodel-success-text": "מודל התוכן של [[:$1]] שוּנה.",
-       "changecontentmodel-cannot-convert": "×\94ת×\95×\9b×\9f ×\91×\93×£ [[:$1]] ×\90×\99× ×\95 ×\99×\9b×\95×\9c ×\9c×\94×\99×\95ת ×\9e×\95×\9eר ×\9cס×\95×\92 ×©×\9c $2.",
+       "changecontentmodel-cannot-convert": "×\9c×\90 × ×\99ת×\9f ×\9c×\94×\9e×\99ר ×\90ת ×\94ת×\95×\9b×\9f ×©×\9c [[:$1]] ×\9cס×\95×\92 $2.",
        "changecontentmodel-nodirectediting": "מודל התוכן $1 אינו תומך בעריכה ישירה",
+       "changecontentmodel-emptymodels-title": "לא קיים מודל תוכן מתאים",
+       "changecontentmodel-emptymodels-text": "לא ניתן להמיר את התוכן של [[:$1]] לאף סוג.",
        "log-name-contentmodel": "יומן שינויי מודל תוכן",
        "log-description-contentmodel": "אירועים שקשורים למודל תוכן של דפים",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|יצר|יצרה}} את הדף $3 תוך שימוש במודל התוכן \"$5\" השונה ממודל ברירת המחדל",
        "protect-level-autoconfirmed": "רק משתמשים ותיקים מורשים",
        "protect-level-sysop": "רק מפעילי מערכת מורשים",
        "protect-summary-cascade": "מדורג",
-       "protect-expiring": "פוקעת $1 (UTC)",
-       "protect-expiring-local": "פוקעת $1",
+       "protect-expiring": "פוקעת ב{{GRAMMAR:תחילית|$1}} (UTC)",
+       "protect-expiring-local": "פוקעת ב{{GRAMMAR:תחילית|$1}}",
        "protect-expiry-indefinite": "בלתי מוגבלת בזמן",
        "protect-cascade": "הגנה על כל הדפים המוכללים בדף זה (הגנה מדורגת)",
        "protect-cantedit": "אין באפשרותך לשנות את רמת ההגנה על דף זה כיוון שאין לך הרשאה לערוך אותו.",
        "protect-edit-reasonlist": "עריכת סיבות ההגנה",
        "protect-expiry-options": "שעה:1 hour,יום:1 day,שבוע:1 week,שבועיים:2 weeks,חודש:1 month,שלושה חודשים:3 months,שישה חודשים:6 months,שנה:1 year,זמן בלתי מוגבל:infinite",
        "restriction-type": "הרשאה:",
-       "restriction-level": "ר×\9eת ×\94×\94×\92×\91×\9cה:",
+       "restriction-level": "ר×\9eת ×\94×\94×\92× ה:",
        "minimum-size": "גודל מינימלי",
        "maximum-size": "גודל מרבי:",
        "pagesize": "(בבתים)",
        "mycontris": "תרומות",
        "anoncontribs": "תרומות",
        "contribsub2": "עבור {{GENDER:$3|$1}} ($2)",
-       "contributions-userdoesnotexist": "×\94×\97ש×\91×\95×\9f \"$1\" אינו רשום.",
+       "contributions-userdoesnotexist": "×\97ש×\91×\95×\9f ×\94×\9eשת×\9eש \"$1\" אינו רשום.",
        "nocontribs": "לא נמצאו שינויים המתאימים לקריטריונים אלו.",
        "uctop": "(נוכחי)",
        "month": "עד החודש:",
        "sp-contributions-newonly": "הצגת עריכות שהן יצירות של דפים בלבד",
        "sp-contributions-submit": "חיפוש",
        "whatlinkshere": "דפים המקושרים לכאן",
-       "whatlinkshere-title": "דפים המקשרים לדף $1",
+       "whatlinkshere-title": "דפים המקשרים לדף \"$1\"",
        "whatlinkshere-page": "דף:",
        "linkshere": "הדפים שלהלן מקושרים לדף '''[[:$1]]''':",
        "nolinkshere": "אין דפים המקושרים לדף '''[[:$1]]'''.",
        "whatlinkshere-prev": "{{PLURAL:$1|הקודם|$1 הקודמים}}",
        "whatlinkshere-next": "{{PLURAL:$1|הבא|$1 הבאים}}",
        "whatlinkshere-links": "→ קישורים",
-       "whatlinkshere-hideredirs": "$1 הפניות",
-       "whatlinkshere-hidetrans": "$1 הכללות",
-       "whatlinkshere-hidelinks": "$1 קישורים",
-       "whatlinkshere-hideimages": "$1 קישורים לקובץ",
+       "whatlinkshere-hideredirs": "הסתרת הפניות",
+       "whatlinkshere-hidetrans": "הסתרת הכללות",
+       "whatlinkshere-hidelinks": "הסתרת קישורים",
+       "whatlinkshere-hideimages": "הסתרת קישורי קבצים",
        "whatlinkshere-filters": "מסננים",
        "whatlinkshere-submit": "הצגה",
        "autoblockid": "חסימה אוטומטית #$1",
        "ipb-hardblock": "ביטול האפשרות של משתמשים רשומים לערוך מכתובת IP זו",
        "ipbcreateaccount": "חסימה של יצירת חשבונות",
        "ipbemailban": "חסימה של שליחת דואר אלקטרוני",
-       "ipbenableautoblock": "×\97ס×\99×\9e×\94 ×\90×\95×\98×\95×\9e×\98×\99ת ×\92×\9d ×©×\9c ×\9bת×\95×\91ת ×\94Ö¾IP ×\94×\90×\97ר×\95× ×\94 ×©×\94שת×\9eש ×\91×\94 ×\95של כל כתובת IP שינסה להשתמש בה בעתיד",
-       "ipbsubmit": "×\97ס×\99×\9e×\94",
+       "ipbenableautoblock": "×\97ס×\99×\9e×\94 ×\90×\95×\98×\95×\9e×\98×\99ת ×©×\9c ×\9bת×\95×\91ת ×\94Ö¾IP ×\94×\90×\97ר×\95× ×\94 ×©×\94×\9eשת×\9eש ×¢×©×\94 ×\91×\94 ×©×\99×\9e×\95ש ×\95×\92×\9d של כל כתובת IP שינסה להשתמש בה בעתיד",
+       "ipbsubmit": "×\97ס×\99×\9eת ×\94×\9eשת×\9eש",
        "ipbother": "זמן אחר:",
        "ipboptions": "שעתיים:2 hours,יום:1 day,שלושה ימים:3 days,שבוע:1 week,שבועיים:2 weeks,חודש:1 month,שלושה חודשים:3 months,שישה חודשים:6 months,שנה:1 year,זמן בלתי מוגבל:infinite",
        "ipbhidename": "הסתרת שם המשתמש מהעריכות ומהרשימות",
        "ipb-confirmhideuser": "אתם עומדים לחסום משתמש עם האפשרות \"הסתרת משתמש\". זה יעלים את שם המשתמש בכל הרשימות ופעולות היומן. האם אתם בטוחים שברצונכם לעשות זאת?",
        "ipb-confirmaction": "אם אתם באמת בטוחים שברצונכם לעשות זאת, אנא סמנו את השדה \"{{int:ipb-confirm}}\" שבתחתית.",
        "ipb-edit-dropdown": "עריכת סיבות החסימה",
-       "ipb-unblock-addr": "×\94סרת חסימה של $1",
-       "ipb-unblock": "×\94סרת ×\97ס×\99×\9e×\94 ×©×\9c ×©×\9d ×\9eשת×\9eש ×\90×\95 כתובת IP",
+       "ipb-unblock-addr": "ש×\97ר×\95ר חסימה של $1",
+       "ipb-unblock": "ש×\97ר×\95ר ×\97ס×\99×\9e×\94 ×©×\9c ×\9eשת×\9eש ×\90×\95 ×©×\9c כתובת IP",
        "ipb-blocklist": "הצגת החסימות הנוכחיות",
-       "ipb-blocklist-contribs": "×\94תר×\95×\9e×\95ת ×©×\9c {{GENDER:$1|$1}}",
+       "ipb-blocklist-contribs": "תרומות של {{GENDER:$1|$1}}",
        "ipb-blocklist-duration-left": "נותרו $1",
-       "unblockip": "שחרור חסימה",
+       "unblockip": "שחרור חסימה של משתמש",
        "unblockiptext": "השתמשו בטופס שלהלן כדי להחזיר את הרשאות הכתיבה למשתמש או כתובת IP חסומים.",
-       "ipusubmit": "ש×\97ר×\95ר ×\97ס×\99×\9e×\94",
+       "ipusubmit": "×\94סרת ×\97ס×\99×\9e×\94 ×\96×\95",
        "unblocked": "[[User:$1|$1]] {{GENDER:$1|שוחרר מחסימתו|שוחררה מחסימתה}}.",
        "unblocked-range": "$1 שוחרר מחסימתו.",
        "unblocked-id": "חסימה מספר $1 שוחררה.",
        "blocklist-tempblocks": "הסתרת חסימות זמניות",
        "blocklist-addressblocks": "הסתרת חסימות IP בודד",
        "blocklist-rangeblocks": "הסתרת חסימות טווחים",
-       "blocklist-timestamp": "×\96×\9e×\9f",
+       "blocklist-timestamp": "ת×\90ר×\99×\9a ×\95שע×\94",
        "blocklist-target": "יעד",
-       "blocklist-expiry": "פקיעה",
-       "blocklist-by": "×\9eפע×\99×\9c ×\97×\95ס×\9d",
-       "blocklist-params": "הגדרות חסימה",
+       "blocklist-expiry": "×\96×\9e×\9f ×¤×§×\99×¢×\94",
+       "blocklist-by": "× ×\97ס×\9d ×¢×\9cÖ¾×\99×\93×\99",
+       "blocklist-params": "×\94×\92×\93ר×\95ת ×\94×\97ס×\99×\9e×\94",
        "blocklist-reason": "סיבה",
        "ipblocklist-submit": "חיפוש",
        "ipblocklist-localblock": "חסימה מקומית",
        "ipblocklist-otherblocks": "{{PLURAL:$1|חסימה אחרת|חסימות אחרות}}",
-       "infiniteblock": "×\91×\9cת×\99 ×\9e×\95×\92×\91×\9c ×\91זמן",
-       "expiringblock": "פ×\95קע ×\91Ö¾$2, $1",
+       "infiniteblock": "×\9c×\9c×\90 ×\94×\92×\91×\9cת זמן",
+       "expiringblock": "×\94×\97ס×\99×\9e×\94 ×¤×\95קעת ×\91{{GRAMMAR:ת×\97×\99×\9c×\99ת|$1}} ×\91שע×\94 $2",
        "anononlyblock": "משתמשים אנונימיים בלבד",
        "noautoblockblock": "חסימה אוטומטית מבוטלת",
        "createaccountblock": "יצירת חשבונות נחסמה",
        "lockdbsuccesstext": "בסיס הנתונים ננעל.<br />\nיש לזכור [[Special:UnlockDB|לשחרר את הנעילה]] לאחר שפעולת התחזוקה תסתיים.",
        "unlockdbsuccesstext": "שוחררה הנעילה של בסיס הנתונים",
        "lockfilenotwritable": "קובץ נעילת בסיס הנתונים אינו ניתן לכתיבה. כדי שאפשר יהיה לנעול את בסיס הנתונים או לבטל את נעילתו, שרת האינטרנט צריך לקבל הרשאות לכתוב אליו.",
+       "databaselocked": "בסיס הנתונים כבר נעול.",
        "databasenotlocked": "בסיס הנתונים אינו נעול.",
        "lockedbyandtime": "(על־ידי $1 ב־$3, $2)",
-       "move-page": "העברת $1",
+       "move-page": "העברת הדף \"$1\"",
        "move-page-legend": "העברת דף",
        "movepagetext": "שימוש בטופס שלהלן ישנה את שמו של דף, ויעביר את כל ההיסטוריה שלו לשם חדש.\nהשם הישן יהפוך לדף הפניה אל הדף עם השם החדש.\nבאפשרותכם לעדכן אוטומטית דפי הפניה לכותרת המקורית.\nאם תבחרו לא לעשות זאת, אנא ודאו שאין [[Special:DoubleRedirects|הפניות כפולות]] או [[Special:BrokenRedirects|שבורות]].\nאתם אחראים לוודא שכל הקישורים ימשיכו להצביע למקום שאליו הם אמורים להצביע.\n\nשימו לב: הדף <strong>לא</strong> יועבר אם כבר יש דף תחת השם החדש, אלא אם הדף השני הוא הפניה ואין לו היסטוריית עריכות קודמות.\nפירוש הדבר שאפשר לשנות חזרה את שמו של דף לשם המקורי אם נעשתה טעות, ושלא ניתן לדרוס דף קיים.\n\n<strong>לתשומת לבכם:</strong>\nשינוי זה עשוי להיות שינוי דרסטי ובלתי צפוי לדף פופולרי;\nאנא ודאו שאתם מבינים את השלכות המעשה לפני שאתם ממשיכים.",
        "movepagetext-noredirectfixer": "שימוש בטופס שלהלן ישנה את שמו של דף, ויעביר את כל ההיסטוריה שלו לשם חדש.\nהשם הישן יהפוך לדף הפניה אל הדף עם השם החדש.\nאנא ודאו שאין [[Special:DoubleRedirects|הפניות כפולות]] או [[Special:BrokenRedirects|שבורות]].\nאתם אחראים לוודא שכל הקישורים ימשיכו להצביע למקום שאליו הם אמורים להצביע.\n\nשימו לב: הדף <strong>לא</strong> יועבר אם כבר יש דף תחת השם החדש, אלא אם הדף הזה הוא הפניה ואין לו היסטוריית עריכות קודמות.\nפירוש הדבר שאפשר לשנות חזרה את שמו של דף לשם המקורי אם נעשתה טעות, ושלא ניתן לדרוס דף קיים.\n\n<strong>לתשומת לבכם:</strong>\nשינוי זה עשוי להיות שינוי דרסטי ובלתי צפוי לדף פופולרי;\nאנא ודאו שאתם מבינים את השלכות המעשה לפני שאתם ממשיכים.",
        "cant-move-to-category-page": "אין לך הרשאה להעביר דף לדף קטגוריה.",
        "newtitle": "השם החדש:",
        "move-watch": "מעקב אחרי דף המקור ואחרי דף היעד",
-       "movepagebtn": "×\94×¢×\91ר×\94",
+       "movepagebtn": "×\94×¢×\91רת ×\94×\93×£",
        "pagemovedsub": "ההעברה הושלמה בהצלחה",
        "movepage-moved": "הדף \"$1\" הועבר לשם \"$2\".",
        "movepage-moved-redirect": "נוצרה הפניה.",
        "tooltip-pt-mytalk": "דף השיחה שלך",
        "tooltip-pt-anontalk": "שיחה על תרומות המשתמש האנונימי",
        "tooltip-pt-preferences": "ההעדפות שלך",
-       "tooltip-pt-watchlist": "רשימת הדפים שאתם עוקבים אחרי השינויים בהם",
+       "tooltip-pt-watchlist": "רשימת הדפים ש{{GENDER:|אתה עוקב|את עוקבת}} אחרי השינויים בהם",
        "tooltip-pt-mycontris": "רשימת התרומות שלך",
        "tooltip-pt-anoncontribs": "רשימת העריכות שנעשו מכתובת ה־IP הזאת",
        "tooltip-pt-login": "מומלץ להיכנס לחשבון; עם זאת, אין חובה לעשות זאת",
        "tooltip-pt-logout": "יציאה מהחשבון",
        "tooltip-pt-createaccount": "מומלץ ליצור חשבון ולהיכנס אליו; עם זאת, אין חובה לעשות זאת",
        "tooltip-ca-talk": "שיחה על דף זה",
-       "tooltip-ca-edit": "עריכת דף זה באמצעות קוד ויקי",
+       "tooltip-ca-edit": "עריכת דף זה",
        "tooltip-ca-addsection": "הוספת פסקה חדשה",
-       "tooltip-ca-viewsource": "×\93×£ ×\96×\94 ×\9e×\95×\92×\9f.\n×\91×\90פשר×\95ת×\9a ×\9cצפ×\95ת ×\91×\98קס×\98 המקור שלו",
+       "tooltip-ca-viewsource": "×\93×£ ×\96×\94 ×\9e×\95×\92×\9f.\n×\91×\90פשר×\95ת×\9a ×\9cצפ×\95ת ×\91ק×\95×\93 המקור שלו",
        "tooltip-ca-history": "גרסאות קודמות של דף זה",
-       "tooltip-ca-protect": "הגנה על דף זה",
+       "tooltip-ca-protect": "×\94פע×\9cת ×\94×\92× ×\94 ×¢×\9c ×\93×£ ×\96×\94",
        "tooltip-ca-unprotect": "שינוי ההגנה על דף זה",
        "tooltip-ca-delete": "מחיקת דף זה",
        "tooltip-ca-undelete": "שחזור עריכות שנעשו בדף זה לפני שנמחק",
        "tooltip-ca-move": "העברת דף זה",
-       "tooltip-ca-watch": "הוספת דף זה לרשימת המעקב",
-       "tooltip-ca-unwatch": "הסרת דף זה מרשימת המעקב",
+       "tooltip-ca-watch": "הוספת דף זה לרשימת המעקב שלך",
+       "tooltip-ca-unwatch": "הסרת דף זה מרשימת המעקב שלך",
        "tooltip-search": "חיפוש ב{{grammar:תחילית|{{SITENAME}}}}",
        "tooltip-search-go": "מעבר לדף בשם הזה בדיוק, אם הוא קיים",
        "tooltip-search-fulltext": "חיפוש טקסט זה בדפים",
        "tooltip-save": "שמירת השינויים שלך",
        "tooltip-publish": "פרסום השינויים שלך",
        "tooltip-preview": "תצוגה מקדימה של השינויים שלך. נא להשתמש באפשרות זו לפני השמירה.",
-       "tooltip-diff": "צפ×\99×\99×\94 ×\91ש×\99× ×\95×\99×\99×\9d ×©×¢×¨×\9bת×\9d בטקסט",
+       "tooltip-diff": "×\94צ×\92ת ×\94ש×\99× ×\95×\99×\99×\9d ×©×\91×\99צעת בטקסט",
        "tooltip-compareselectedversions": "צפייה בהשוואת שתי גרסאות של דף זה",
        "tooltip-watch": "הוספת דף זה לרשימת המעקב שלך",
        "tooltip-watchlistedit-normal-submit": "הסרת הדפים",
        "anonymous": "{{PLURAL:$1|משתמש אנונימי|משתמשים אנונימיים}} של {{SITENAME}}",
        "siteuser": "משתמש {{SITENAME}} $1",
        "anonuser": "משתמש אנונימי של {{SITENAME}} $1",
-       "lastmodifiedatby": "דף זה שונה לאחרונה ב־$2, $1 על־ידי $3.",
+       "lastmodifiedatby": "דף זה שוּנה לאחרונה ב־$2, $1 על־ידי $3.",
        "othercontribs": "מבוסס על העבודה של $1.",
        "others": "אחרים",
        "siteusers": "{{PLURAL:$2|{{GENDER:$1|משתמש}}|משתמשי}} {{SITENAME}} $1",
        "spam_blanking": "כל הגרסאות כוללות קישורים ל־$1, מרוקן את הדף",
        "spam_deleting": "כל הגרסאות כוללות קישורים ל־$1, מוחק את הדף",
        "simpleantispam-label": "בדיקת אנטי־ספאם.\n<strong>אל</strong> תמלאו שדה זה!",
-       "pageinfo-title": "מידע על \"$1\"",
+       "pageinfo-title": "מידע על הדף \"$1\"",
        "pageinfo-not-current": "מצטערים, לא ניתן להציג את המידע הזה לגרסאות ישנות.",
        "pageinfo-header-basic": "מידע בסיסי",
        "pageinfo-header-edits": "היסטוריית עריכות",
        "filedelete-old-unregistered": "גרסת הקובץ \"$1\" אינה רשומה בבסיס הנתונים.",
        "filedelete-current-unregistered": "הקובץ \"$1\" אינו רשום בבסיס הנתונים.",
        "filedelete-archive-read-only": "השרת אינו יכול לכתוב לתיקיית הארכיון \"$1\".",
-       "previousdiff": "â\86\92 ×\9e×¢×\91ר ×\9c×\94ש×\95×\95×\90ת ×\94×\92רס×\90×\95ת הקודמת",
-       "nextdiff": "×\9e×¢×\91ר ×\9c×\94ש×\95×\95×\90ת ×\94×\92רס×\90×\95ת הבאה ←",
+       "previousdiff": "â\86\92 ×\94ער×\99×\9b×\94 הקודמת",
+       "nextdiff": "×\94ער×\99×\9b×\94 הבאה ←",
        "mediawarning": "<strong>אזהרה:</strong> סוג קובץ זה עלול להכיל קוד זדוני.\nהרצת הקוד עלולה לסכן את המחשב שלך.",
        "imagemaxsize": "גודל תמונה מרבי:<br /><em>(בדפי תיאור של קבצים)</em>",
        "thumbsize": "גודל של תמונות ממוזערות:",
        "watchlisttools-clear": "ניקוי רשימת המעקב",
        "watchlisttools-view": "הצגת השינויים הרלוונטיים",
        "watchlisttools-edit": "הצגה ועריכה של רשימת המעקב",
-       "watchlisttools-raw": "ער×\99×\9bת ×\94רש×\99×\9e×\94 הגולמית",
+       "watchlisttools-raw": "ער×\99×\9bת ×¨×©×\99×\9eת ×\94×\9eעק×\91 הגולמית",
        "iranian-calendar-m1": "פרברדין",
        "iranian-calendar-m2": "אורדיבהשט",
        "iranian-calendar-m3": "חורדאד",
        "timezone-local": "מקומי",
        "duplicate-defaultsort": "'''אזהרה:''' המיון הרגיל \"$2\" דורס את המיון הרגיל המוקדם ממנו \"$1\".",
        "duplicate-displaytitle": "<strong>אזהרה:</strong> כותרת התצוגה \"$2\" דורסת את כותרת התצוגה הקודמת \"$1\".",
+       "restricted-displaytitle": "<strong>אזהרה:</strong> כותרת התצוגה \"$1\" לא הופעלה כי היא אינה תואמת לכותרת האמיתית של הדף.",
        "invalid-indicator-name": "<strong>שגיאה:</strong> התכונה <code>name</code> של מצייני מצב הדף אינה יכולה להיות ריקה.",
        "version": "גרסת התוכנה",
        "version-extensions": "הרחבות מותקנות",
        "tags-delete-not-found": "התגית \"$1\" אינה קיימת.",
        "tags-delete-too-many-uses": "התגית \"$1\" מוחלת על יותר {{PLURAL:$2|מגרסה אחת|מ־$2 גרסאות}}, ולכן לא ניתן למחוק אותה.",
        "tags-delete-warnings-after-delete": "התגית \"$1\" נמחקה, אבל {{PLURAL:$2|התקבלה האזהרה הבאה|התקבלו האזהרות הבאות}}:",
+       "tags-delete-no-permission": "אין לך הרשאה למחוק תגיות שינויים.",
        "tags-activate-title": "הפעלת תגית",
        "tags-activate-question": "אתם עומדים להפעיל את התגית \"$1\".",
        "tags-activate-reason": "הסבר:",
        "dberr-usegoogle": "באפשרותך לנסות לחפש באמצעות גוגל בינתיים.",
        "dberr-outofdate": "שימו לב שהתוכן שלנו כפי שנשמר במאגר שם עשוי שלא להיות מעודכן.",
        "dberr-cachederror": "זהו עותק שמור של המידע, והוא עשוי שלא להיות מעודכן.",
-       "htmlform-invalid-input": "×\99ש ×\91×¢×\99×\95ת ×¢×\9d ×\97×\9cק ×\9e×\94ק×\9c×\98 ×©×\94×\9bנסת",
+       "htmlform-invalid-input": "×\99ש ×\91×¢×\99×\95ת ×¢×\9d ×\97×\9cק ×\9e×\94ק×\9c×\98 ×©×\94×\95×\9bנס.",
        "htmlform-select-badoption": "הערך שציינת אינו אפשרות תקינה.",
        "htmlform-int-invalid": "הערך שציינת אינו מספר שלם.",
        "htmlform-float-invalid": "הערך שציינת אינו מספר.",
        "htmlform-int-toolow": "הערך שציינת הוא מתחת למינימום, $1",
        "htmlform-int-toohigh": "הערך שציינת הוא מעל למקסימום, $1",
-       "htmlform-required": "ער×\9a ×\96×\94 ×\93ר×\95ש",
+       "htmlform-required": "ש×\93×\94 ×\96×\94 ×\93ר×\95ש.",
        "htmlform-submit": "שליחה",
        "htmlform-reset": "ביטול השינויים",
        "htmlform-selectorother-other": "אחר",
        "logentry-import-upload": "$1 {{GENDER:$2|ייבא|ייבאה}} את $3 באמצעות העלאת קובץ",
        "logentry-import-upload-details": "$1 {{GENDER:$2|ייבא|ייבאה}} את $3 באמצעות העלאת קובץ ({{PLURAL:$4|גרסה אחת|$4 גרסאות}})",
        "logentry-import-interwiki": "$1 {{GENDER:$2|ייבא|ייבאה}} את $3 מאתר ויקי אחר",
-       "logentry-import-interwiki-details": "$1 {{GENDER:$2|ייבא|ייבאה}} את $3 מ־$5&rlm; ({{PLURAL:$4|גרסה אחת|$4 גרסאות}})&rlm;",
+       "logentry-import-interwiki-details": "$1 {{GENDER:$2|ייבא|ייבאה}} את $3 מ־$5&rlm; ({{PLURAL:$4|גרסה אחת|$4 גרסאות}})",
        "logentry-merge-merge": "$1 {{GENDER:$2|מיזג|מיזגה}} את $3 לתוך $4 (גרסאות עד $5)",
        "logentry-move-move": "$1 {{GENDER:$2|העביר|העבירה}} את הדף $3 לשם $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|העביר|העבירה}} את הדף $3 לשם $4 בלי להשאיר הפניה",
        "logentry-protect-modify": "$1 {{GENDER:$2|שינה|שינתה}} את רמת ההגנה של הדף $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|שינה|שינתה}} את רמת ההגנה של הדף $3 $4 [מדורג]",
        "logentry-rights-rights": "$1 {{GENDER:$2|שינה|שינתה}} את ההרשאות של {{GENDER:$6|$3}} מ{{GRAMMAR:תחילית|$4}} ל{{GRAMMAR:תחילית|$5}}",
-       "logentry-rights-rights-legacy": "$1 {{GENDER:$2|שינה|שינתה}} את ההרשאות של $3&rlm;",
+       "logentry-rights-rights-legacy": "$1 {{GENDER:$2|שינה|שינתה}} את ההרשאות של $3",
        "logentry-rights-autopromote": "$1 קודם אוטומטית מ{{GRAMMAR:תחילית|$4}} ל{{GRAMMAR:תחילית|$5}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|העלה|העלתה}} את $3",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|העלה|העלתה}} גרסה חדשה של $3",
        "feedback-useragent": "User agent:",
        "searchsuggest-search": "חיפוש",
        "searchsuggest-containing": "כולל...",
+       "api-error-autoblocked": "כתובת ה־IP שלך נחסמה אוטומטית, כי היא הייתה בשימוש על־ידי משתמש חסום.",
        "api-error-badaccess-groups": "אינך מורשה להעלות קבצים לאתר הוויקי הזה.",
        "api-error-badtoken": "שגיאה פנימית: אסימון שבור.",
+       "api-error-blocked": "נחסמת מעריכה.",
        "api-error-copyuploaddisabled": "העלאה לפי כתובת כובתה בשרת זה.",
        "api-error-duplicate": "כבר יש באתר הזה {{PLURAL:$1|קובץ אחר|קבצים אחרים}} עם אותו תוכן.",
        "api-error-duplicate-archive": "כבר {{PLURAL:$1|היה|היו}} באתר הזה {{PLURAL:$1|קובץ|קבצים}} עם אותו תוכן, אבל {{PLURAL:$1|הוא נמחק|הם נמחקו}}.",
        "api-error-nomodule": "שגיאה פנימית: מודול ההעלאה אינו מוגדר.",
        "api-error-ok-but-empty": "שגיאה פנימית: אין תשובה מהשרת.",
        "api-error-overwrite": "לא מותרת החלפת קובץ קיים.",
+       "api-error-ratelimited": "ניסית להעלות בזמן קצר יותר קבצים מהכמות המירבית המותרת באתר הוויקי הזה.\nנא לנסות שוב בעוד מספר דקות.",
        "api-error-stashfailed": "שגיאה פנימית: השרת נכשל באחסון הקובץ הזמני.",
        "api-error-publishfailed": "שגיאה פנימית: השרת נכשל בפרסום הקובץ הזמני.",
        "api-error-stasherror": "הייתה שגיאה בהעלאת הקובץ לסליק.",
        "log-action-filter-upload": "סוג ההעלאות:",
        "log-action-filter-all": "הכול",
        "log-action-filter-block-block": "חסימות",
-       "log-action-filter-block-reblock": "ש×\99× ×\95×\99×\99 ×\97ס×\99×\9e×\94",
-       "log-action-filter-block-unblock": "ש×\97ר×\95ר×\99 ×\97ס×\99×\9e×\94",
+       "log-action-filter-block-reblock": "ש×\99× ×\95×\99×\99 ×\97ס×\99×\9e×\95ת",
+       "log-action-filter-block-unblock": "ש×\97ר×\95ר×\99 ×\97ס×\99×\9e×\95ת",
        "log-action-filter-contentmodel-change": "שינויים במודל תוכן",
        "log-action-filter-contentmodel-new": "יצירות דפים עם מודל תוכן לא־סטנדרטי",
        "log-action-filter-delete-delete": "מחיקת דפים",
        "log-action-filter-suppress-block": "העלמות של משתמשים באמצעות חסימה",
        "log-action-filter-suppress-reblock": "העלמות של משתמשים באמצעות חסימה מחדש",
        "log-action-filter-upload-upload": "העלאות חדשות",
-       "log-action-filter-upload-overwrite": "דריסת קבצים קיימים"
+       "log-action-filter-upload-overwrite": "דריסת קבצים קיימים",
+       "authmanager-authn-not-in-progress": "האימות נכשל או שנתוני הפעולה נאבדו. נא להתחיל את התהליך מחדש.",
+       "authmanager-authn-no-primary": "לא ניתן היה לאמת את האישורים שסופקו.",
+       "authmanager-authn-no-local-user": "האישורים שסופקו אינם שייכים לשום משתמש באתר זה.",
+       "authmanager-authn-no-local-user-link": "נתוני ההאמנה שניתנו תקינים, אבל אינם משויכים לשום משתמש בוויקי הזה. נא להיכנס לחשבון באופן שונה, או ליצור משתמש חדש ואז תהיה לך אפשרות לקשר את נתוני ההאמנה הקודמים שלך לחשבון ההוא.",
+       "authmanager-authn-autocreate-failed": "יצירה אוטומטית של חשבון מקומי נכשלה: $1",
+       "authmanager-change-not-supported": "לא ניתן לשנות את נתוני ההאמנה שניתנו, כי שום דבר לא ישתמש בהם.",
+       "authmanager-create-disabled": "אפשרות יצירת החשבונות מבוטלת.",
+       "authmanager-create-from-login": "כדי ליצור את החשבון, נא למלא את השדות שלהלן.",
+       "authmanager-create-not-in-progress": "יצירת החשבון נכשלה או שנתוני הפעולה נאבדו. נא להתחיל את התהליך מחדש.",
+       "authmanager-create-no-primary": "האישורים שסופקו לא יכולים להיות בשימוש ביצירת חשבון.",
+       "authmanager-link-no-primary": "האישורים שסופקו לא יכולים להיות בשימוש בקישור חשבונות.",
+       "authmanager-link-not-in-progress": "קישור החשבונות נכשל או שנתוני הפעולה נאבדו. נא להתחיל את התהליך מחדש.",
+       "authmanager-authplugin-setpass-failed-title": "שינוי הסיסמה נכשל",
+       "authmanager-authplugin-setpass-failed-message": "תוסף האימות דחה את שינוי הסיסמה.",
+       "authmanager-authplugin-create-fail": "תוסף האימות דחה את יצירת החשבון.",
+       "authmanager-authplugin-setpass-denied": "תוסף האימות אינו מאפשר לשנות סיסמאות.",
+       "authmanager-authplugin-setpass-bad-domain": "דומיין לא תקין.",
+       "authmanager-autocreate-noperm": "אין אפשרות ליצור חשבונות באופן אוטומטי.",
+       "authmanager-autocreate-exception": "יצירת חשבונות אוטומטית מבוטלת באופן אוטומטי בשל שגיאות קודמות.",
+       "authmanager-userdoesnotexist": "חשבון המשתמש \"$1\" אינו רשום.",
+       "authmanager-userlogin-remembermypassword-help": "האם לזכור את הסיסמה למשך זמן ארוך יותר מאורך הפעולה.",
+       "authmanager-username-help": "שם המשתמש לאימות.",
+       "authmanager-password-help": "הסיסמה לאימות.",
+       "authmanager-domain-help": "שם מתחם לאימות חיצוני.",
+       "authmanager-retype-help": "חזרה על הסיסמה.",
+       "authmanager-email-label": "דוא\"ל",
+       "authmanager-email-help": "כתובת דוא\"ל",
+       "authmanager-realname-label": "שם אמיתי",
+       "authmanager-realname-help": "השם האמיתי של המשתמש",
+       "authmanager-provider-password": "אימות שמבוסס על סיסמה",
+       "authmanager-provider-password-domain": "אימות מבוסס מתחם וססמה.",
+       "authmanager-provider-temporarypassword": "סיסמה זמנית",
+       "authprovider-confirmlink-message": "בהתבסס על ניסיונות הכניסה האחרונים שלך, ניתן לקשר את החשבונות הבאים לחשבון שלך. לאחר שהחשבונות יקושרו, ניתן יהיה להיכנס לחשבון באמצעותם. נא לבחור את החשבונות שברצונך לקשר.",
+       "authprovider-confirmlink-request-label": "החשבונות שיקושרו",
+       "authprovider-confirmlink-success-line": "$1: הקישור בוצע בהצלחה.",
+       "authprovider-confirmlink-failed": "קישור החשבונות לא הושלם: $1",
+       "authprovider-confirmlink-ok-help": "להמשיך אחרי הודעות שגיאת קישור.",
+       "authprovider-resetpass-skip-label": "דילוג",
+       "authprovider-resetpass-skip-help": "לדלג על איפוס הסיסמה.",
+       "authform-nosession-login": "האימות הושלם בהצלחה, אבל הדפדפן שלך אינו \"זוכר\" את הכניסה שלך לחשבון.\n\n$1",
+       "authform-nosession-signup": "החשבון נוצר, אבל הדפדפן שלך אינו \"זוכר\" את הכניסה שלך לחשבון.\n\n$1",
+       "authform-newtoken": "אסימון חסר. $1",
+       "authform-notoken": "אסימון חסר",
+       "authform-wrongtoken": "אסימון שגוי",
+       "specialpage-securitylevel-not-allowed-title": "הגישה נדחתה",
+       "specialpage-securitylevel-not-allowed": "מצטערים, אין באפשרותך להשתמש בדף זה כי הזהות שלך לא אומתה.",
+       "authpage-cannot-login": "לא ניתן להתחיל את תהליך הכניסה לחשבון.",
+       "authpage-cannot-login-continue": "לא ניתן היה להיכנס לחשבון. כנראה שזמן ההמתנה של הפעולה חלף.",
+       "authpage-cannot-create": "לא ניתן להתחיל את תהליך יצירת החשבון.",
+       "authpage-cannot-create-continue": "לא ניתן להמשיך בתהליך יצירת החשבון. כנראה שזמן ההמתנה של הפעולה חלף.",
+       "authpage-cannot-link": "לא ניתן להתחיל את תהליך קישור החשבונות.",
+       "authpage-cannot-link-continue": "לא ניתן להמשיך בתהליך קישור החשבונות. כנראה שזמן ההמתנה של הפעולה חלף.",
+       "cannotauth-not-allowed-title": "הגישה נדחתה",
+       "cannotauth-not-allowed": "אינך מורשה להשתמש בדף זה",
+       "changecredentials": "שינוי האישורים",
+       "changecredentials-submit": "שינוי",
+       "changecredentials-submit-cancel": "ביטול",
+       "changecredentials-invalidsubpage": "$1 אינו סוג אישור תקין.",
+       "changecredentials-success": "האישורים שלך שונו.",
+       "removecredentials": "הסרת האישורים",
+       "removecredentials-submit": "הסרה",
+       "removecredentials-submit-cancel": "ביטול",
+       "removecredentials-invalidsubpage": "$1 אינו סוג אישור תקין.",
+       "removecredentials-success": "האישורים שלך הוסרו.",
+       "credentialsform-provider": "סוג האישורים:",
+       "credentialsform-account": "שם החשבון:",
+       "cannotlink-no-provider-title": "אין חשבונות שניתן לקשר",
+       "cannotlink-no-provider": "אין חשבונות שניתן לקשר.",
+       "linkaccounts": "קישור חשבונות",
+       "linkaccounts-success-text": "החשבון קושר.",
+       "linkaccounts-submit": "קישור החשבונות",
+       "unlinkaccounts": "ביטול הקישור של החשבונות",
+       "unlinkaccounts-success": "קישור החשבון בוטל."
 }
index b5f461d..8d8024f 100644 (file)
        "noname": "आपने वैध सदस्यनाम नहीं दिया है।",
        "loginsuccesstitle": "प्रवेश हुआ",
        "loginsuccess": "'''आप {{SITENAME}} में \"$1\" सदस्यनाम से लॉग इन हो {{GENDER:$1|चुके|चुकी|चुके}} हैं।'''",
-       "nosuchuser": "\"$1\" नाम का कोई सदस्य नहीं है।\nसदस्यनाम में लघु और दीर्घ अक्षरों से फ़र्क पड़ता है।\nअपनी वर्तनी जाँचें, या [[Special:UserLogin/signup|नया खाता खोलें]]।",
+       "nosuchuser": "\"$1\" नाम का कोई सदस्य नहीं है।\nसदस्यनाम में लघु और दीर्घ अक्षरों से फ़र्क पड़ता है।\nअपनी वर्तनी जाँचें, या [[Special:CreateAccount|नया खाता खोलें]]।",
        "nosuchusershort": "\"$1\" नाम का कोई सदस्य नहीं है।\nकृपया अपनी दी हुई वर्तनी जाँचें।",
        "nouserspecified": "सदस्यनाम देना अनिवार्य है।",
        "login-userblocked": "यह सदस्य प्रतिबन्धित है। सत्रारम्भ की अनुमति नहीं है।",
        "accmailtext": "[[User talk:$1|$1]] के लिए एक यंत्र जनित कूटशब्द $2 को भेज दिया गया है। लॉगिन करने के बाद इसे '''[[Special:ChangePassword|कूटशब्द बदलें]]'' वाले पृष्ठ पर बदला जा सकता है।",
        "newarticle": "(नया)",
        "newarticletext": "आप ऐसे पृष्ठ पर आए हैं जो अभी तक बनाया नहीं गया है।\nपृष्ठ बनाने के लिये नीचे के बौक्स में पाठ लिखें। अधिक जानकारी के लिये [$1 सहायता पृष्ठ] देखें।\nअगर आप यहाँ पर गलती से आए हैं तो अपने ब्राउज़र के बैक ('''back''') बटन पर क्लिक करें।",
-       "anontalkpagetext": "----''यह वार्ता पृष्ठ उन बेनामी सदस्यों के लिये है जिन्होंने या तो खाता नहीं खोला है या खाते का प्रयोग नहीं कर रहे हैं।\nइसलिये उनकी पहचान के लिये हमें उनका आइ॰पी पता प्रयोग करना पड़ता है।\nआइ॰पी पता कई सदस्यों के लिए साझा हो सकता है।\nयदि आप एक बेनामी सदस्य हैं और आपको लगता है कि आपके बारे में अप्रासंगिक टीका टिप्पणी की गई है तो कृपया [[Special:UserLogin/signup|सदस्यता लें]] या [[Special:UserLogin|सत्रारंभ करें]] ताकि अन्य बेनामी सदस्यों में से आपको अलग से पहचाना जा सके।''",
+       "anontalkpagetext": "----''यह वार्ता पृष्ठ उन बेनामी सदस्यों के लिये है जिन्होंने या तो खाता नहीं खोला है या खाते का प्रयोग नहीं कर रहे हैं।\nइसलिये उनकी पहचान के लिये हमें उनका आइ॰पी पता प्रयोग करना पड़ता है।\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}}}} संबंधित लॉग खोज सकते हैं]</span>, परन्तु आपको यह पृष्ठ बनाने की अनुमति नहीं है।",
        "missing-revision": "\"{{FULLPAGENAME}}\" पृष्ठ का अवतरण #$1 मौजूद नहीं है।\n\nआम तौर पर यह एक हटाए गए पृष्ठ के पुराने लिंक पर क्लिक करने से होता है।\nअधिक जानकारी के लिए आप [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटाने का लॉग] देख सकते हैं।",
        "upload-form-label-infoform-description-tooltip": "छोटे रूप में बतायें इस के उलेखनीयता के बारे में। \n चित्र हेतु, जो मुख्य वस्तु नहीं दिखाया गया है आदि के स्थान।",
        "upload-form-label-usage-title": "उपयोग",
        "upload-form-label-usage-filename": "फ़ाइल का नाम",
-       "foreign-structured-upload-form-label-own-work": "यह मेरा कार्य है",
-       "foreign-structured-upload-form-label-infoform-categories": "श्रेणियाँ",
-       "foreign-structured-upload-form-label-infoform-date": "दिनांक",
-       "foreign-structured-upload-form-label-own-work-message-local": "मैं यह सत्यापित करता हूँ कि मेरे द्वारा डाला गया फ़ाइल {{SITENAME}} सेवा के शर्तों और अधिकार नियम के अनुकूल है।",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "यदि आप {{SITENAME}} के नियमों के अंतर्गत फ़ाइल नहीं डाल सकते, तो आप इसे हटा कर किसी दूसरे विधि का उपयोग करें।",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "आप [[Special:Upload|मूल डालने वाले पृष्ठ]] का भी उपयोग कर सकते हो।",
-       "foreign-structured-upload-form-label-own-work-message-default": "मैं यह समझता हूँ कि यहाँ सभी फ़ाइल सांझा होते हैं। मैं यह सत्यापित करता हूँ कि में सेवा के शर्तों और नियम के अनुरूप ही कार्य कर रहा हूँ।",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "यदि आप इस नियम के अंतर्गत फ़ाइल नहीं डालना चाहते तो अभी इसे बन्द कर दें और कोई दूसरे विधि को खोजें।",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "यदि आप चाहें तो आप [[Special:Upload|{{SITENAME}} के पृष्ठ]] पर फ़ाइल डाल सकते हैं, यदि यह फ़ाइल वहाँ के नियम के अंतर्गत हो तो।",
-       "foreign-structured-upload-form-label-own-work-message-shared": "कम से कम इस फ़ाइल का प्रतिकृति अधिकार मेरे पास है और यह [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] के अंतर्गत है, व [https://wikimediafoundation.org/wiki/Terms_of_Use विकि उपयोग की शर्तों] का भी पालन करता है।",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "यदि आपके पास इस फ़ाइल का प्रतिकृति अधिकार नहीं है और आप इसे किसी और अधिकार के तहत प्रदर्शित करना चाहते हैं तो आप [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard] का उपयोग करें।",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "यदि आप चाहें तो आप [[Special:Upload|{{SITENAME}} के पृष्ठ]] पर फ़ाइल डाल सकते हैं, यदि यह फ़ाइल वहाँ के नियम के अंतर्गत हो तो।",
+       "upload-form-label-own-work": "यह मेरा कार्य है",
+       "upload-form-label-infoform-categories": "श्रेणियाँ",
+       "upload-form-label-infoform-date": "दिनांक",
+       "upload-form-label-own-work-message-generic-local": "मैं यह सत्यापित करता हूँ कि मेरे द्वारा डाला गया फ़ाइल {{SITENAME}} सेवा के शर्तों और अधिकार नियम के अनुकूल है।",
+       "upload-form-label-not-own-work-message-generic-local": "यदि आप {{SITENAME}} के नियमों के अंतर्गत फ़ाइल नहीं डाल सकते, तो आप इसे हटा कर किसी दूसरे विधि का उपयोग करें।",
+       "upload-form-label-not-own-work-local-generic-local": "आप [[Special:Upload|मूल डालने वाले पृष्ठ]] का भी उपयोग कर सकते हो।",
+       "upload-form-label-own-work-message-generic-foreign": "मैं यह समझता हूँ कि यहाँ सभी फ़ाइल सांझा होते हैं। मैं यह सत्यापित करता हूँ कि में सेवा के शर्तों और नियम के अनुरूप ही कार्य कर रहा हूँ।",
+       "upload-form-label-not-own-work-message-generic-foreign": "यदि आप इस नियम के अंतर्गत फ़ाइल नहीं डालना चाहते तो अभी इसे बन्द कर दें और कोई दूसरे विधि को खोजें।",
+       "upload-form-label-not-own-work-local-generic-foreign": "यदि आप चाहें तो आप [[Special:Upload|{{SITENAME}} के पृष्ठ]] पर फ़ाइल डाल सकते हैं, यदि यह फ़ाइल वहाँ के नियम के अंतर्गत हो तो।",
        "backend-fail-stream": "फ़ाइल $1 स्ट्रीम नहीं हो पाई।",
        "backend-fail-backup": "फ़ाइल $1 बैकअप नहीं हो पाई।",
        "backend-fail-notexists": "फ़ाइल $1 मौजूद नहीं है।",
index 6586e89..6da450d 100644 (file)
        "noname": "Aap achchha user name ke nai specify karaa hai.",
        "loginsuccesstitle": "Login safal bhais",
        "loginsuccess": "'''Aap \"$1\" ke naam pe {{SITENAME}} me logged in hai.'''",
-       "nosuchuser": "\"$1\" naam ke koi sadasya nai hai.\nSadasya ke naam case sensitive hai.\nAapan spelling check karo nai to [[Special:UserLogin/signup|nawaa account banao]].",
+       "nosuchuser": "\"$1\" naam ke koi sadasya nai hai.\nSadasya ke naam case sensitive hai.\nAapan spelling check karo nai to [[Special:CreateAccount|nawaa account banao]].",
        "nosuchusershort": "\"$1\" naam ke koi sadasya nai hai.\nAapan spelling check karo.",
        "nouserspecified": "Aap ke aapan username de ke parri.",
        "login-userblocked": "Ii sadasya ke rok dewa gais hae.  Login kare ke ijajat nai hae.",
        "accmailtext": "Ek randomly banawal password ke [[User talk:$1|$1]] ke khatir $2 ke lage bhaja gais hai.\nIi nawa account ke password ke ''[[Special:ChangePassword|change password]]''  panna pe badla jaae sake hai jab aap login karta hai.",
        "newarticle": "(Nawaa)",
        "newarticletext": "Aap ek panna ke jorr ke follow kara hae jon ki abhi nai hae.\nIi panna banae khatir, niche box me type karo (see the [$1 help page] for more info).\nAgar jo aap hian par galti se aae hai tab aapan browser ke '''back''' button pe click karo.",
-       "anontalkpagetext": "----''Ii salah kare waala panna uu anonymous sadasya ke baare me jon abhi account nai banais hai, nai to account ke kaam me nai lawe hai.\nIi kaaran se ham log ke IP address kaam me lae ke ii sadasya ke jaana jae hai.\n\nIi rakam ke IP address ke dher sadasya kaam me lae sake hai.\nAgar aap ek anonymous user hai aur ii sochta hai ki bekar baat aap ke baare me karaa gais hai, tab\n[[Special:UserLogin/signup|create an account]] or [[Special:UserLogin|log in]] aage ke garrbarri roke khatir aur duusra anonymous users se mistake nai kare ke khatir .''",
+       "anontalkpagetext": "----''Ii salah kare waala panna uu anonymous sadasya ke baare me jon abhi account nai banais hai, nai to account ke kaam me nai lawe hai.\nIi kaaran se ham log ke IP address kaam me lae ke ii sadasya ke jaana jae hai.\n\nIi rakam ke IP address ke dher sadasya kaam me lae sake hai.\nAgar aap ek anonymous user hai aur ii sochta hai ki bekar baat aap ke baare me karaa gais hai, tab\n[[Special:CreateAccount|create an account]] or [[Special:UserLogin|log in]] aage ke garrbarri roke khatir aur duusra anonymous users se mistake nai kare ke khatir .''",
        "noarticletext": "Abhi ii panna me kuchh likhaa nai hai.\nAap saktaa hai [[Special:Search/{{PAGENAME}}|ii panna ke title khoje]] duusra panna me,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nnai to [{{fullurl:{{FULLPAGENAME}}|action=edit}} ii panna ke badlo]</span>.",
        "noarticletext-nopermission": "Abhi ii panna me koi chij likha nai hae.\nAap sakta hae [[Special:Search/{{PAGENAME}}|ii panna ke title ke khoje]] duusra panna me,\nnai to <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>, lekin aap ke ii panna ke banae ke ijaaja tnai hae.",
        "missing-revision": "Panna \"{{FULLPAGENAME}}\" me #$1 badlao nai hae.\nIske kaaran ii hoe sake hae ki ek mitawa gais panna se link karaa jaawe hae.\nIske baare me aur jaankari [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log] me paawa jaae sake hae.",
index 333b58c..1e7f53f 100644 (file)
        "noname": "Wala ka nagbutang sang tood-tood nga gamit-pangalan.",
        "loginsuccesstitle": "Madinalag-on nga pagsulod",
        "loginsuccess": "'''Nakasulod ka na subong sa {{SITENAME}} bilang si \"$1\".'''",
-       "nosuchuser": "Wala sang manug-gamit nga iya pangalan \"$1\".\nAng mga gamit-pangalan sensitibo sa kadakoon sang letra.\nSiguradoha ang pagbaybay sang pumolongon, ukon  [[Special:UserLogin/signup|magbuhat sang bag-o nga akawnt]].",
+       "nosuchuser": "Wala sang manug-gamit nga iya pangalan \"$1\".\nAng mga gamit-pangalan sensitibo sa kadakoon sang letra.\nSiguradoha ang pagbaybay sang pumolongon, ukon  [[Special:CreateAccount|magbuhat sang bag-o nga akawnt]].",
        "nosuchusershort": "Wala sang manug-gamit nga iya pangalan \"$1\".\nSiguradoha ang pagbaybay sang pumolongon.",
        "nouserspecified": "Kinahanglan mo magbutang sa gamit-pangalan.",
        "login-userblocked": "Ang ini nga manug-gamit ginapungga. Indi ka mahimo nga magsulod.",
        "accmailtext": "May pasword nga wala ginpilian nga ginhimo para kay [[User talk:$1|$1]] nga ginpadala sa $2.\n\nAng pasword para sa sini nga bag-o nga akawnt mahimo ma-ilisan sa ''[[Special:ChangePassword|ilisan ang pasword]]'' nga panid pagkatapos magsulod.",
        "newarticle": "(Bag-o)",
        "newarticletext": "Nagbukas ka sang isa ka tabid padulong sa isa ka panid nga wala pa nahimo.\nAgud mahimo ang panid, magsugod ka lang sa pagsulat sa sulod sang kahon nga makit-an mo sa idalum (tan-awa ang [$1 bulig nga pahina] para sa dugang nga ihibalo).\nUgaling kon ikaw nagtalang lamang diri, palihog lang tum-uka ang pityong nga <strong>balik</strong> sa imo nga palalayagan.",
-       "anontalkpagetext": "----''Ini ang panid para sa pagtalakay sa wala makilala-an nga manuggamit nga wala pa nakatuga sang akawnt, ukon wala nagagamit sang isa.\nAmo nga kinahanglan naton mag-gamit sang IP adres nga de numero agod nga mahibaluan naton siya.\nAng amo sini nga adres sang IP mahimo nga pagasaluhan sang madamo nga manuggamit.\nKon ikaw manuggamit nga wala makilal-i kag nabatyagan mo nga may mga komento nga wala man sing labot nga ginapakadto sa imo, palihog [[Special:UserLogin/signup|maghimo ka sang akawnt]] ukon [[Special:UserLogin|magsulod]] para malikawan ang iban pa nga pagsala sa iban pa nga wala makilal-an nga manuggamit.''",
+       "anontalkpagetext": "----''Ini ang panid para sa pagtalakay sa wala makilala-an nga manuggamit nga wala pa nakatuga sang akawnt, ukon wala nagagamit sang isa.\nAmo nga kinahanglan naton mag-gamit sang IP adres nga de numero agod nga mahibaluan naton siya.\nAng amo sini nga adres sang IP mahimo nga pagasaluhan sang madamo nga manuggamit.\nKon ikaw manuggamit nga wala makilal-i kag nabatyagan mo nga may mga komento nga wala man sing labot nga ginapakadto sa imo, palihog [[Special:CreateAccount|maghimo ka sang akawnt]] ukon [[Special:UserLogin|magsulod]] para malikawan ang iban pa nga pagsala sa iban pa nga wala makilal-an nga manuggamit.''",
        "noarticletext": "Wala unod ining panid.\nSarang ka [[Special:Search/{{PAGENAME}}|magpangita sining panig-ulo]] sa iban nga mga panid,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pangitaa sa nagakaangot nga pagkitan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} ilisan ining panid]</span>.",
        "noarticletext-nopermission": "Wala unod ining panid.\nSarang ka mag-[[Special:Search/{{PAGENAME}}|pangitaa ang ini nga panig-ulo]] sa iban nga mga panid,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pangitaa sa nagakaangot nga pagkitan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} ilisan ining panid]</span>, ugaling wala ka ginapahanugutan nga maghimo sining panid.",
        "missing-revision": "Ang ini nga pag-ilis nga #$1 sang panid nga ginhinanglan nga \"{{FULLPAGENAME}}\" wala naga-eksister.\n\nIni kalabanan ginabuhat sang nagasunod nga wala na mabag-o nga link sang hisayranay sa isa ka panid nga gindula na.\nAng mga detalye mahimo nga makita sa [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].",
        "import-rootpage-nosubpage": "Ang espasyo sang ngalan nga \"$1\" nga gingikanang panid indi ginapasugtan ang kaupod nga mga panid.",
        "importlogpage": "Listahan sang mga importe",
        "importlogpagetext": "Mga importeng administratibo sang mga panid nga may maragtas sang pagbag-o halin sa iban nga wiki.",
-       "javascripttest-pagetext-frameworks": "Palihug pilion ang isa sa mga masunod nga mga testing frameworks: $1",
-       "javascripttest-pagetext-skins": "Pilion ang isa ka panit para magdalagan sa imo nga eksamin:",
        "tooltip-pt-userpage": "Ang imo kaugalingon nga pahina",
        "tooltip-pt-anonuserpage": "Ang panid sang tiggamit para sa IP address imo ginbag-o bilang",
        "tooltip-pt-mytalk": "Ang imo pahina sang paghisayranay",
index 0c33094..209dd30 100644 (file)
        "thursday": "četvrtak",
        "friday": "petak",
        "saturday": "subota",
-       "sun": "Ned",
-       "mon": "Pon",
-       "tue": "Uto",
-       "wed": "Sri",
-       "thu": "Ä\8cet",
-       "fri": "Pet",
-       "sat": "Sub",
+       "sun": "ned",
+       "mon": "pon",
+       "tue": "uto",
+       "wed": "sri",
+       "thu": "Ä\8det",
+       "fri": "pet",
+       "sat": "sub",
        "january": "siječnja",
        "february": "veljače",
        "march": "ožujka",
        "noname": "Niste unijeli valjano suradničko ime.",
        "loginsuccesstitle": "Prijava uspješna",
        "loginsuccess": "Prijavili ste se na wiki kao \"$1\".",
-       "nosuchuser": "Ne postoji suradnik s imenom \"$1\".\nSuradnička imena su osjetljiva na veličinu slova.\nProvjerite jeste li točno upisali, ili [[Special:UserLogin/signup|otvorite novi suradnički račun]].",
+       "nosuchuser": "Ne postoji suradnik s imenom \"$1\".\nSuradnička imena su osjetljiva na veličinu slova.\nProvjerite jeste li točno upisali, ili [[Special:CreateAccount|otvorite novi suradnički račun]].",
        "nosuchusershort": "Ne postoji suradnik s imenom \"$1\". Provjerite Vaš unos.",
        "nouserspecified": "Molimo navedite suradničko ime.",
        "login-userblocked": "Ovaj je suradnik blokiran. Prijava nije dopuštena.",
        "accmailtext": "Nova lozinka za [[User talk:$1|$1]] je poslana na $2.\n\nNakon prijave, lozinka za ovaj novi račun može biti promijenjena na stranici ''[[Special:ChangePassword|promijeni lozinku]]'' nakon prijave.",
        "newarticle": "(Novo)",
        "newarticletext": "Došli ste na stranicu koja još ne postoji.\nAko želite stvoriti tu stranicu, počnite tipkati u prozor ispod ovog teksta (pogledajte [$1 stranicu za pomoć]).\nAko ste ovamo dospjeli slučajno, kliknite gumb '''natrag''' (back) u svom pregledniku.",
-       "anontalkpagetext": "----''Ovo je stranica za razgovor s neprijavljenim suradnikom koji još nije otvorio suradnički račun ili se njime ne koristi. Zbog toga se moramo služiti brojčanom IP adresom kako bismo ga identificirali. Takvu adresu često može dijeliti više ljudi. Ako ste neprijavljeni suradnik i smatrate da su Vam upućeni irelevantni komentari, molimo Vas da [[Special:UserLogin/signup|otvorite suradnički račun]] ili [[Special:UserLogin|se prijavite]] te tako u budućnosti izbjegnete zamjenu s drugim neprijavljenim suradnicima.''",
+       "anontalkpagetext": "----''Ovo je stranica za razgovor s neprijavljenim suradnikom koji još nije otvorio suradnički račun ili se njime ne koristi. Zbog toga se moramo služiti brojčanom IP adresom kako bismo ga identificirali. Takvu adresu često može dijeliti više ljudi. Ako ste neprijavljeni suradnik i smatrate da su Vam upućeni irelevantni komentari, molimo Vas da [[Special:CreateAccount|otvorite suradnički račun]] ili [[Special:UserLogin|se prijavite]] te tako u budućnosti izbjegnete zamjenu s drugim neprijavljenim suradnicima.''",
        "noarticletext": "Na ovoj stranici trenutačno nema sadržaja.\nMožete [[Special:Search/{{PAGENAME}}|potražiti ovaj naslov]] na drugim stranicama,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane evidencije]\nili [{{fullurl:{{FULLPAGENAME}}|action=edit}} stvoriti ovu stranicu]</span>.",
        "noarticletext-nopermission": "Ova stranica nema sadržaja.\nMožete [[Special:Search/{{PAGENAME}}|tražiti naslov ove stranice]] na drugim stranicama ili <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane evidencije]</span>, ali ne možete stvoriti ovu stranicu.",
        "missing-revision": "Uređivanje broj $1 na stranici \"{{FULLPAGENAME}}\" ne postoji.\n\nOvo je obično uzrokovano kada kliknete na zastarjelu poveznicu na stranice koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} evidenciji brisanja].",
        "upload-form-label-infoform-description": "Opis",
        "upload-form-label-usage-title": "Korištenje",
        "upload-form-label-usage-filename": "Ime datoteke",
-       "foreign-structured-upload-form-label-own-work": "Ovo je moje djelo",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorije",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Možete pokušati [[Special:Upload|postaviti datoteku na projektu {{SITENAME}}]], pod uvjetom da može biti tamo postavljena, sukladno pravilima projekta.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Potvrđujem da posjedujem autorska prava ove datoteke i slažem se da je nepozivo postavljam na Zajednički poslužitelj pod licencijom  [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], i pristajem na [https://wikimediafoundation.org/wiki/Terms_of_Use Uvjete uporabe].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Ako ne posjedujete autorska prava za ovu datoteku, ili je želite objaviti pod drugom licencijom, razmislite o uporabi [https://commons.wikimedia.org/wiki/Special:UploadWizard Čarobnjaka za postavljanje] na Zajedničkom poslužitelju.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Možete pokušati [[Special:Upload|postaviti datoteku na projektu {{SITENAME}}]], pod uvjetom da je dopušteno postavljanje ove datoteke, sukladno pravilima projekta.",
+       "upload-form-label-own-work": "Ovo je moje djelo",
+       "upload-form-label-infoform-categories": "Kategorije",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-not-own-work-local-generic-foreign": "Možete pokušati [[Special:Upload|postaviti datoteku na projektu {{SITENAME}}]], pod uvjetom da može biti tamo postavljena, sukladno pravilima projekta.",
        "backend-fail-stream": "Ne mogu prikazati datoteku $1.",
        "backend-fail-backup": "Izrada sigurnosne kopije datoteke \"$1\" nije uspjela.",
        "backend-fail-notexists": "Datoteka $1 ne postoji.",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|promjena|promjene|promjena}} od $2",
        "javascripttest": "Testiranje JavaScripta",
        "javascripttest-qunit-intro": "Pogledajte [$1 testnu dokumentaciju] na mediawiki.org.",
-       "tooltip-pt-userpage": "Stranica suradnika {{GENDER:|Your user}}",
+       "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",
        "mw-widgets-dateinput-placeholder-day": "GGGG-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "GGGG-MM",
        "mw-widgets-titleinput-description-new-page": "stranica još ne postoji",
-       "mw-widgets-titleinput-description-redirect": "preusmjeravanje na $1"
+       "mw-widgets-titleinput-description-redirect": "preusmjeravanje na $1",
+       "log-action-filter-upload": "Vrsta postavljanja:",
+       "log-action-filter-all": "sve",
+       "log-action-filter-upload-upload": "novo postavljanje",
+       "log-action-filter-upload-overwrite": "ponovno postavljanje"
 }
index af3840c..4821bb6 100644 (file)
        "noname": "Du musst en gültiche Benutzernoome oongewe.",
        "loginsuccesstitle": "Oonmeldung erfollichgreich",
        "loginsuccess": "Du bist jetzt als \"$1\" bei {{SITENAME}} oongemeldt.",
-       "nosuchuser": "Der Benutzernoome „$1“ existiert net.\nÜwerprüf die Schreibweis (Gross-/Klenschreibung beachte) orrer [[Special:UserLogin/signup|meld dich als neier Benutzer an]].",
+       "nosuchuser": "Der Benutzernoome „$1“ existiert net.\nÜwerprüf die Schreibweis (Gross-/Klenschreibung beachte) orrer [[Special:CreateAccount|meld dich als neier Benutzer an]].",
        "nosuchusershort": "Der Benutzernoome \"$1\" ist net voarhand. Bitte üwerprüf die Schreibweis.",
        "nouserspecified": "Bittschön geb en Benutzernoome an.",
        "login-userblocked": "{{GENDER:$1|Der Benutzer|Die Benutzrin|Der Benutzer}} ist gesperrt. Die Oonmeldung ist net erlaubt.",
        "accmailtext": "En zufällich generiertes Passwort für [[User talk:$1|$1]] woard an $2 versandt. Es kann uff der Seit ''[[Special:ChangePassword|Passwort ännre]]'' noh der Oonmeldung geännert sin.",
        "newarticle": "(Nei)",
        "newarticletext": "Du bist en Link zu en Seit noh gang, wo net voarhand ist.\nUm die Seit oonzulehn, trooh dein Text in das unnestehend Beoorbeitungsfeld ren (weitre Informatione uff der [$1 Hellefseit]).\nSoweit du fälschlicherweise hier bist, klick uff dein Browser sein Schaltfläch '''Retuar'''.",
-       "anontalkpagetext": "----''Dies Seit dient dozu, enem net oongemeldete Benutzer Nachrichte zu hinnerlosse. Es weard sein IP-Adress zur Identifizierung verwenndt. IP-Adresse könne von mehrere Benutzer gemeinsam verwendt sin. Wenn du mit den Kommentare uff der Seit nix oonfänge kannst, richte die sich vermutlich an en frühre Inhaber von deiner IP-Adresse und du kannst se ignoriere. Du kannst dir ooch en [[Special:UserLogin/signup|Benutzerkonto erstelle]] orrer dich [[Special:UserLogin|oonmelde]], um künftich Verwechslunge mit annre anonyme Benutzer zu vermeide.''",
+       "anontalkpagetext": "----''Dies Seit dient dozu, enem net oongemeldete Benutzer Nachrichte zu hinnerlosse. Es weard sein IP-Adress zur Identifizierung verwenndt. IP-Adresse könne von mehrere Benutzer gemeinsam verwendt sin. Wenn du mit den Kommentare uff der Seit nix oonfänge kannst, richte die sich vermutlich an en frühre Inhaber von deiner IP-Adresse und du kannst se ignoriere. Du kannst dir ooch en [[Special:CreateAccount|Benutzerkonto erstelle]] orrer dich [[Special:UserLogin|oonmelde]], um künftich Verwechslunge mit annre anonyme Benutzer zu vermeide.''",
        "noarticletext": "Die Seit dohie enthält momentan noch ken Text.\nDu kannst sie <span class=\"plainlinks\">[{{fullurl:{{FULLPAGENAME}}|action=edit}} beoorbeite]</span>,\nehre Titel uff annre Seite [[Special:Search/{{PAGENAME}}|suche]]\norrer die zugehöriche <span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbücher betrachte]</span>.",
        "noarticletext-nopermission": "Die Seit dohie enthält momentan noch ken Text.\nDu kannst sie <span class=\"plainlinks\">[{{fullurl:{{FULLPAGENAME}}|action=edit}} beoorbeite]</span>,\nehre Titel uff annre Seite [[Special:Search/{{PAGENAME}}|suche]]\norrer die zugehöriche <span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbücher betrachte]</span>.",
        "missing-revision": "Die Version $1 von der Seit mit der noomen \"{{FULLPAGENAME}}\" ist net voarhand.\n\nDer Fehler weard normalerweis von enem veraltete Link zur Versionsgeschicht von en Seit verursacht, wo zwischichzeitlich gelöscht woard.\nEnzelheite sind im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lösch-Logbuch] einsehbar (visibel).",
index f8069bf..ac27ce7 100644 (file)
        "noname": "Njejsy płaćiwe wužiwarske mjeno podał.",
        "loginsuccesstitle": "Přizjewjenje wuspěšne",
        "loginsuccess": "'''Sy nětko jako \"$1\" w {{GRAMMAR:lokatiw|{{SITENAME}}}} {{GENDER:|přizjewjeny|přizjewjena|přizjewjene}}.'''",
-       "nosuchuser": "Njeje wužiwar z mjenom \"$1\".\nWužiwarske mjena wobkedźbuja wulkopisanje.\nPřepruwuj swój prawopis abo [[Special:UserLogin/signup|wutwor nowe konto]].",
+       "nosuchuser": "Njeje wužiwar z mjenom \"$1\".\nWužiwarske mjena wobkedźbuja wulkopisanje.\nPřepruwuj swój prawopis abo [[Special:CreateAccount|wutwor nowe konto]].",
        "nosuchusershort": "Wužiwarske mjeno „$1” njeeksistuje. Prošu skontroluj prawopis.",
        "nouserspecified": "Dyrbiš wužiwarske mjeno podać",
        "login-userblocked": "Tutón wužiwar je zablokowany. Přizjewjenje njedowolene.",
        "accmailtext": "Připadnje spłodźene hesło za [[User talk:$1|$1]] bu na $2 pósłane. Daj so na stronje ''[[Special:ChangePassword|hesło změnić]]'' při přizjewjenju změnić.",
        "newarticle": "(Nowy)",
        "newarticletext": "Sy wotkaz k stronje slědował, kotraž hišće njeeksistuje. Zo by stronu wutworił, wupjelń slědowace tekstowe polo (hlej [$1 stronu pomocy] za dalše informacije). Jeli sy zmylnje tu, klikń prosće na tłóčatko <b>Wróćo</b> we swojim wobhladowaku.",
-       "anontalkpagetext": "---- ''To je diskusijna strona za anonymneho wužiwarja, kiž hišće konto wutworił njeje abo je njewužiwa. Dyrbimy tohodla numerisku IP-adresu wužiwać, zo bychmy jeho/ju identifikowali. Tajka IP-adresa hodźi so wot wjacorych wužiwarjow zhromadnje wužiwać. Jeli sy anonymny wužiwar a měniš, zo buchu irelewantne komentary k tebi pósłane, [[Special:UserLogin/signup|wutwor prošu konto]] abo [[Special:UserLogin|přizjew so]], zo by přichodnu šmjatańcu z anonymnymi wužiwarjemi wobešoł.''",
+       "anontalkpagetext": "---- ''To je diskusijna strona za anonymneho wužiwarja, kiž hišće konto wutworił njeje abo je njewužiwa. Dyrbimy tohodla numerisku IP-adresu wužiwać, zo bychmy jeho/ju identifikowali. Tajka IP-adresa hodźi so wot wjacorych wužiwarjow zhromadnje wužiwać. Jeli sy anonymny wužiwar a měniš, zo buchu irelewantne komentary k tebi pósłane, [[Special:CreateAccount|wutwor prošu konto]] abo [[Special:UserLogin|přizjew so]], zo by přichodnu šmjatańcu z anonymnymi wužiwarjemi wobešoł.''",
        "noarticletext": "Tuchwilu tuta strona žadyn tekst njewobsahuje. Móžeš [[Special:Search/{{PAGENAME}}|tutón titul strony na druhich stronach pytać]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} wotpowědne protokole pytać] abo [{{fullurl:{{FULLPAGENAME}}|action=edit}} tutu stronu wobdźěłać]</span>.",
        "noarticletext-nopermission": "Tuchwilu žadyn tekst na tutej stronje njeje.\nMóžeš [[Special:Search/{{PAGENAME}}|tutón titul strony]] na druhich stronach pytać abo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pytaj wotpowědne protokole]</span>, ale nimaš prawo, strou wutworić.",
        "missing-revision": "Wersija #$1 strony z mjenom \"{{FULLPAGENAME}}\" njeeksistuje.\n\nPřičina je zwjetša zestarjeny wotkaz w stawiznach k stronje, kotraž je so zhašała.\nPodrobnosće móžeš w  [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protokolu wušmórnjenjow] namakać.",
index 0a51bcd..193b5bb 100644 (file)
@@ -9,10 +9,11 @@
                        "아라",
                        "Tisave",
                        "Bfpage",
-                       "Macofe"
+                       "Macofe",
+                       "Lucas"
                ]
        },
-       "tog-underline": "Souliyen lyen yo :",
+       "tog-underline": "Souliyen lyen yo&nbsp;:",
        "tog-hideminor": "Kache tout modifikasyon resan yo ki tou piti",
        "tog-hidepatrolled": "Kache modifikasyon yo ki fèk fèt pou moun ki ap veye yo",
        "tog-newpageshidepatrolled": "Kache paj ki siveye yo nan mitan lis nouvo paj yo",
@@ -27,7 +28,7 @@
        "tog-watchmoves": "Ajoute paj mwen deplase yo nan lis swivi mwen",
        "tog-watchdeletion": "Ajoute paj mwen efase yo nan lis swivi mwen",
        "tog-watchrollback": "Ajoute paj kote mwen ranvèse chanjman yo nan lis swivi mwen",
-       "tog-minordefault": "Make tout modifikasyon mwen yo \"tou piti\" pa defo",
+       "tog-minordefault": "Make tout modifikasyon kòm «&nbsp;tou piti&nbsp;» pa defo",
        "tog-previewontop": "Montre kout je anvan zòn modifikasyon",
        "tog-previewonfirst": "Montre kout je pou chak premye modifikasyon",
        "tog-enotifwatchlistpages": "Voye yon imèl ban mwen lè youn nan paj m ap swiv yo chanje",
        "morenotlisted": "Lis sa a pa konplè.",
        "mypage": "Paj",
        "mytalk": "Diskisyon",
-       "anontalk": "Paj diskisyon pou adrès IP sa",
+       "anontalk": "Diskite",
        "navigation": "Navigasyon",
        "and": "&#32;epi",
        "qbfind": "Chache",
        "otherlanguages": "Nan lòt lang yo",
        "redirectedfrom": "(Redirije depi $1)",
        "redirectpagesub": "Paj pou redireksyon",
-       "redirectto": "Voye sou:",
+       "redirectto": "Redireksyon sou&nbsp;:",
        "lastmodifiedat": "Paj sa te modifye pou dènye fwa $1 a $2.<br />",
        "viewcount": "Paj sa te konsilte {{PLURAL:$1|yon fwa|$1 fwa}}.",
        "protectedpage": "Paj pwoteje",
        "versionrequired": "Vèsion $1 de MediaWiki nesesè",
        "versionrequiredtext": "Vèzion $1 de MediaWiki nesesè pou itilize paj sa. Wè [[Special:Version|version page]].",
        "ok": "OK",
-       "retrievedfrom": "Rekipere depi « $1 »",
+       "retrievedfrom": "Rekipere depi «&nbsp;$1&nbsp;»",
        "youhavenewmessages": "Ou genyen $1 ($2).",
        "youhavenewmessagesmanyusers": "Ou gen $2 de plizyè itilizatè $2.",
        "youhavenewmessagesmulti": "Ou genyen nouvo mesaj sou $1.",
        "viewsourceold": "Wè kòd paj la",
        "editlink": "modifye",
        "viewsourcelink": "wè kòd paj la",
-       "editsectionhint": "Modifye seksyon : $1",
+       "editsectionhint": "Modifye seksyon&nbsp;: $1",
        "toc": "Kontni yo",
        "showtoc": "montre",
        "hidetoc": "kache",
        "confirmable-yes": "Wi",
        "confirmable-no": "Non",
        "thisisdeleted": "Ou vle wè oubyen restore $1 ?",
-       "viewdeleted": "Wè $1 ?",
+       "viewdeleted": "Wè $1&nbsp;?",
        "restorelink": "{{PLURAL:$1|yon revizion efase|$1 revizion efase yo}}",
        "feedlinks": "Fil:",
        "feed-invalid": "Kalite fil sa envalid.",
        "virus-scanfailed": "Rechèch an pa ritounen pyès rezilta (kòd $1)",
        "virus-unknownscanner": "antiviris nou pa konnen :",
        "logouttext": "'''Ou dekonekte kounye a.'''\n\nOu mèt kontinye itilize {{SITENAME}} san ou pa idantifye, oubyen ou ka <span class='plainlinks'>[$1 rekonekte]</span> w ankò ak menm non an oubyen yon lòt.\nNote ke kèk paj gendwa afiche tankou ou te toujou konekte tank ou pa efase kach nan navigatè ou.",
+       "welcomeuser": "Byenveni, $1&nbsp;!",
        "yourname": "Non itilizatè ou an :",
        "userlogin-yourname": "Non itilizatè",
        "userlogin-yourname-ph": "Rantre non itilizatè w",
        "noname": "Ou pa bay sistèm an yon non itilizatè ki bon.",
        "loginsuccesstitle": "Ou byen konekte nan sistèm la",
        "loginsuccess": "Ou konekte kounye a nan {{SITENAME}} ak idantifyan sa a « $1 ».",
-       "nosuchuser": "Itilizatè \"$1\" pa ekziste.\nMajiskil ak miniskil chanje non itilizatè.\nByen gade ke ou te byen ekri non ou, oubyen [[Special:UserLogin/signup|kreye yon nouvo kont]].",
+       "nosuchuser": "Itilizatè \"$1\" pa ekziste.\nMajiskil ak miniskil chanje non itilizatè.\nByen gade ke ou te byen ekri non ou, oubyen [[Special:CreateAccount|kreye yon nouvo kont]].",
        "nosuchusershort": "Pa genyen itilizatè ak non « $1 » sa a. Byen gade lòtograf ou an.",
        "nouserspecified": "Ou dwe mete non itilizatè ou an.",
        "login-userblocked": "Itilizatè sa bloke.  Li pa gendwa konekte.",
        "resetpass-submit-cancel": "Anile",
        "resetpass-wrong-oldpass": "Mopas sa pa bon ditou; li te mèt mopas ou an kounye a oubyen yon mopas tanporè.\nGendwa ou te deja modifye li oubyen ou te mande yon nouvo mopas tanporè.",
        "resetpass-temp-password": "Mopas tanporè yo ba ou an:",
+       "passwordreset-username": "Non itilizatè&nbsp;:",
        "bold_sample": "Tèks fonse",
        "bold_tip": "Tèks fonse",
        "italic_sample": "Tèks italik",
        "accmailtext": "Nou fè yon mopas o aza pou [[User talk:$1|$1]] epi nou voye l nan adrès $2.\nOu ka chanje mopas pou kont sa a nan paj ''[[Special:ChangePassword|chanje mopas]]'' aprè ou konekte ou.",
        "newarticle": "(Nouvo)",
        "newarticletext": "Ou swiv on lyen pou yon paj ki poko egziste.\nPou ou kapab kreye paj sa a, komanse ekri nan bwat ki anba (gade [$1 paj èd nan] pou konnen plis, pou plis enfòmasyon).\nSi se paske ou fè yon erè ke ou rive nan paj sa a, klike anlè bouton '''fè back''' nan navigatè ou a.",
-       "anontalkpagetext": "---- ''Ou nan paj diskisyon yon itilizatè anonim, ki pa gen non, ki poko kreye yon kont oubyen ki pa itilize pyès kont nan sistèm sa. Pou rezon sa, nou dwe itilize adrès IP l pou nou kapab lokalize l, sitye l, montre kote l rete, idantifye l. Yon adrès IP kapab pataje ant plizyè moun, plizyè itilizatè. Si ou se yon itilizatè anonim e si ou wè ke ou resevwa komantè ki pa t pou ou, ou mèt [[Special:UserLogin/signup|kreye yon kont]] oubyen [[Special:UserLogin|konekte ou]] pou ou kapab anpeche konfizyon ak kontribitè anonim yo.''",
+       "anontalkpagetext": "---- ''Ou nan paj diskisyon yon itilizatè anonim, ki pa gen non, ki poko kreye yon kont oubyen ki pa itilize pyès kont nan sistèm sa. Pou rezon sa, nou dwe itilize adrès IP l pou nou kapab lokalize l, sitye l, montre kote l rete, idantifye l. Yon adrès IP kapab pataje ant plizyè moun, plizyè itilizatè. Si ou se yon itilizatè anonim e si ou wè ke ou resevwa komantè ki pa t pou ou, ou mèt [[Special:CreateAccount|kreye yon kont]] oubyen [[Special:UserLogin|konekte ou]] pou ou kapab anpeche konfizyon ak kontribitè anonim yo.''",
        "noarticletext": "Poko genyen tèks nan paj sa a.\nOu mèt [[Special:Search/{{PAGENAME}}|fè yon rechèch, fouye ak non paj sa a]] nan lòt paj yo, oubyen <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chache jounal modifikasyon yo ki an relasyon ak paj sa] oubyen tou [{{fullurl:{{FULLPAGENAME}}|action=edit}} modifye paj sa]</span>.",
        "noarticletext-nopermission": "Poko genyen tèks nan paj sa a.\nOu mèt [[Special:Search/{{PAGENAME}}|fè yon rechèch, fouye ak non paj sa a]] nan lòt paj yo, oubyen <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chache jounal modifikasyon yo ki an relasyon ak paj sa].",
        "userpage-userdoesnotexist": "Kont itilizatè « <nowiki>$1</nowiki> » sa pa anrejistre. Verifye toutbon ke ou vle kreye paj sa.",
        "allpagesprefix": "Montre paj yo ki ap komanse pa prefiks sa a :",
        "categories": "Kategori yo",
        "categoriespagetext": "Kategori ki ap swiv {{PLURAL:$1|la|yo}} gen lòt paj oubien medya nan yo.\n[[Special:UnusedCategories|Kategori ki pa itilize]] pa parèt la.\nGade tou [[Special:WantedCategories|kategori moun mande]].",
-       "special-categories-sort-count": "klase pa valè",
-       "special-categories-sort-abc": "klase alfabetikalman",
        "linksearch": "Lyen andeyò",
        "listgrouprights-members": "(lis manm yo)",
        "emailuser": "Voye yon mesaj (imèl) pou itilizatè sa a",
+       "noemailtext": "Itilizatè sa pa te espesifye yon adrès imel ki valab.",
        "watchlist": "Lis swivi mwen",
        "mywatchlist": "Lis swivi mwen",
        "addedwatchtext": "Paj « [[:$1]] » te byen ajoute nan [[Special:Watchlist|lis swivi ou an]].\nDepi kounye a, tout modifikasyon nan paj sa a ak nan paj diskisyon li pral parèt <b>fonse</b> nan [[Special:RecentChanges|lis chanjman ki fèk fèt]] pou ou ka wè yo pi byen.",
        "whatlinkshere-page": "Paj :",
        "linkshere": "Paj yo ki anba ap mene nan <b>[[:$1]]</b> :",
        "nolinkshere": "Pyès paj genyen lyen pou paj sa a <b>[[:$1]]</b>.",
-       "isredirect": "Paj redireksyon",
+       "isredirect": "paj redireksyon",
        "istemplate": "anndan",
        "isimage": "lyen fichye a",
        "whatlinkshere-prev": "{{PLURAL:$1|presedan|$1 presedan yo}}",
index e0e4843..f477ccb 100644 (file)
        "noname": "Érvénytelen szerkesztőnevet adtál meg.",
        "loginsuccesstitle": "Sikeres bejelentkezés",
        "loginsuccess": "'''Sikeresen bejelentkeztél a(z) {{SITENAME}} wikibe „$1” néven.'''",
-       "nosuchuser": "Nem létezik „$1” nevű szerkesztő.\nA szerkesztőnevek kis- és nagybetű-érzékenyek.\nEllenőrizd, hogy helyesen írtad-e be, vagy [[Special:UserLogin/signup|hozz létre egy új fiókot]].",
+       "nosuchuser": "Nem létezik „$1” nevű szerkesztő.\nA szerkesztőnevek kis- és nagybetű-érzékenyek.\nEllenőrizd, hogy helyesen írtad-e be, vagy [[Special:CreateAccount|hozz létre egy új fiókot]].",
        "nosuchusershort": "Nem létezik „$1” nevű szerkesztő.\nEllenőrizd, hogy helyesen írtad-e be.",
        "nouserspecified": "Meg kell adnod a felhasználói nevet.",
        "login-userblocked": "Ez a szerkesztő blokkolva van, a bejelentkezés nem engedélyezett.",
        "accmailtext": "A(z) [[User talk:$1|$1]] fiókhoz egy véletlenszerűen generált jelszót küldünk a(z) $2 címre.\n\nAz új fiók jelszava a ''[[Special:ChangePassword|jelszó megváltoztatása]]'' lapon módosítható a bejelentkezés után.",
        "newarticle": "(Új)",
        "newarticletext": "Egy olyan lapra mutató hivatkozást követtél, ami még nem létezik.\nA lap létrehozásához csak gépeld be a szövegét a lenti szövegdobozba. Ha kész vagy, az „Előnézet megtekintése” gombbal ellenőrizheted, hogy úgy fog-e kinézni, ahogy szeretnéd, és a „Lap mentése” gombbal tudod elmenteni. (További információkat a [$1 súgólapon] találsz).\nHa tévedésből jutottál ide, kattints a böngésződ '''vissza''' vagy '''back''' gombjára.",
-       "anontalkpagetext": "----''Ez egy olyan anonim szerkesztő vitalapja, aki még nem regisztrált, vagy csak nem jelentkezett be.\nEzért az IP-címét használjuk az azonosítására.\nUgyanazon az IP-címen számos szerkesztő osztozhat az idők folyamán.\nHa úgy látod, hogy az üzenetek, amiket ide kapsz, nem neked szólnak, [[Special:UserLogin/signup|regisztrálj]] vagy ha már regisztráltál, [[Special:UserLogin|jelentkezz be]], hogy ne keverjenek össze másokkal.''",
+       "anontalkpagetext": "----''Ez egy olyan anonim szerkesztő vitalapja, aki még nem regisztrált, vagy csak nem jelentkezett be.\nEzért az IP-címét használjuk az azonosítására.\nUgyanazon az IP-címen számos szerkesztő osztozhat az idők folyamán.\nHa úgy látod, hogy az üzenetek, amiket ide kapsz, nem neked szólnak, [[Special:CreateAccount|regisztrálj]] vagy ha már regisztráltál, [[Special:UserLogin|jelentkezz be]], hogy ne keverjenek össze másokkal.''",
        "noarticletext": "Ez a lap jelenleg nem tartalmaz szöveget.\n[[Special:Search/{{PAGENAME}}|Rákereshetsz erre a címszóra]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplókat],\nvagy [{{fullurl:{{FULLPAGENAME}}|action=edit}} létrehozhatod a lapot].</span>",
        "noarticletext-nopermission": "Ez a lap jelenleg nem tartalmaz szöveget.\n[[Special:Search/{{PAGENAME}}|Rákereshetsz a lap címére]] más lapok tartalmában, vagy <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplófájlokat]</span>.",
        "missing-revision": "A(z) \"{{FULLPAGENAME}}\" nevű oldal #$1 változata nem létezik.\n\nEzt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.",
        "upload-form-label-infoform-description-tooltip": "Röviden írj le minden említésre méltót a műről.\nFénykép esetén említsd meg a főbb látható dolgokat, a készítés alkalmát vagy helyszínét.",
        "upload-form-label-usage-title": "Használat",
        "upload-form-label-usage-filename": "Fájlnév",
-       "foreign-structured-upload-form-label-own-work": "Ez a saját munkám",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategóriák",
-       "foreign-structured-upload-form-label-infoform-date": "Dátum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Kijelentem, hogy a fájlt a(z) {{SITENAME}} következő felhasználási feltételei és licencirányelvei alapján töltöm fel.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Ha nem tudod feltölteni a fájlt a(z) {{SITENAME}} irányelvei értelmében, zárd be ezt a panelt és próbálkozz egy másik módszerrel.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Az [[Special:Upload|alapértelmezett feltöltőoldalt]] is kipróbálhatod.",
-       "foreign-structured-upload-form-label-own-work-message-default": "Megértettem, hogy a megosztott tárhelyre töltöm fel a fájlt. Kijelentem, hogy az ottani felhasználási feltételek és licencirányelvek szerint teszem ezt.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Ha nem tudod feltölteni a fájlt a megosztott tárhely irányelvei értelmében, zárd be ezt a panelt és próbálkozz egy másik módszerrel.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Megpróbálhatod használni [[Special:Upload|a(z) {{SITENAME}} feltöltési lapját]] is, ha ezt a fájlt fel lehet tölteni az itteni irányelvek szerint.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Igazolom, hogy én birtoklom a fájl szerzői jogait, és egyetértek azzal, hogy visszavonhatatlanul közzéteszem a fájlt a Wikimédia Commonson a [https://creativecommons.org/licenses/by-sa/4.0/deed.hu Creative Commons Nevezd meg! – Így add tovább! 4.0] licenc alatt, és egyetértek a [https://wikimediafoundation.org/wiki/Terms_of_Use felhasználási feltételekkel].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Ha nem birtoklod a fájl szerzői jogait vagy más licenc alatt szeretnéd közzétenni, fontold meg a [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Feltöltésvarázslójának] használatát.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Megpróbálhatod használni [[Special:Upload|a(z) {{SITENAME}} feltöltési lapját]] is, ha ezt a fájlt fel lehet tölteni az itteni irányelvek szerint.",
+       "upload-form-label-own-work": "Ez a saját munkám",
+       "upload-form-label-infoform-categories": "Kategóriák",
+       "upload-form-label-infoform-date": "Dátum",
+       "upload-form-label-own-work-message-generic-local": "Kijelentem, hogy a fájlt a(z) {{SITENAME}} következő felhasználási feltételei és licencirányelvei alapján töltöm fel.",
+       "upload-form-label-not-own-work-message-generic-local": "Ha nem tudod feltölteni a fájlt a(z) {{SITENAME}} irányelvei értelmében, zárd be ezt a panelt és próbálkozz egy másik módszerrel.",
+       "upload-form-label-not-own-work-local-generic-local": "Az [[Special:Upload|alapértelmezett feltöltőoldalt]] is kipróbálhatod.",
+       "upload-form-label-own-work-message-generic-foreign": "Megértettem, hogy a megosztott tárhelyre töltöm fel a fájlt. Kijelentem, hogy az ottani felhasználási feltételek és licencirányelvek szerint teszem ezt.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Ha nem tudod feltölteni a fájlt a megosztott tárhely irányelvei értelmében, zárd be ezt a panelt és próbálkozz egy másik módszerrel.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Megpróbálhatod használni [[Special:Upload|a(z) {{SITENAME}} feltöltési lapját]] is, ha ezt a fájlt fel lehet tölteni az itteni irányelvek szerint.",
        "backend-fail-stream": "Nem sikerült sugározni ezt a fájlt: $1.",
        "backend-fail-backup": "Nem lehet elmenteni ezt a fájlt: $1.",
        "backend-fail-notexists": "Ez a fájl nem létezik: $1 .",
index 9429f01..20a247b 100644 (file)
        "noname": "Դուք չեք նշել թույլատրելի մասնակցային անուն։",
        "loginsuccesstitle": "Բարեհաջող մուտք",
        "loginsuccess": "'''Դուք մուտք գործեցիք {{SITENAME}}, որպես \"$1\"։'''",
-       "nosuchuser": "$1 անունով մասնակից գոյություն չունի։\nՄասնակիցների անունները զգայուն են մեծատառերի նկատմամբ։\nՍտուգեք ձեր ուղղագրությունը կամ [[Special:UserLogin/signup|ստեղծեք նոր մասնակցի հաշիվ]]։",
+       "nosuchuser": "$1 անունով մասնակից գոյություն չունի։\nՄասնակիցների անունները զգայուն են մեծատառերի նկատմամբ։\nՍտուգեք ձեր ուղղագրությունը կամ [[Special:CreateAccount|ստեղծեք նոր մասնակցի հաշիվ]]։",
        "nosuchusershort": "$1 անունով մասնակից գոյություն չունի։ Ստուգեք ձեր ուղղագրությունը։",
        "nouserspecified": "Հարկավոր է նշել մասնակցային անուն։",
        "login-userblocked": "Այս մասնակիցը արգելափակված է: Մուտքը արգելված է:",
        "accmailtext": "[[User talk:$1|$1]] մասնակցի համար պատահական նշերից կազմված գաղտնաբառը ուղարկված է $2 հասցեին։\n\nՀամակարգ մուտք գործելուն պես կարող եք ''[[Special:ChangePassword|փոխել գաղտնաբառը]]''։",
        "newarticle": "(Նոր)",
        "newarticletext": "Դուք հղվել եք դեռևս գոյություն չունեցող էջի։ \nՆոր էջ ստեղծելու համար ստորև գտնվող խմբագրման դաշտում ավելացրեք տեքստ, այնուհետև սեղմեք '''Հիշել էջը''' (այցելեք [$1 օգնության էջը]՝ մանրամասն տեղեկությունների համար)։ \n\nԵթե դուք սխալմամբ եք այստեղ հայտնվել, ապա սեղմեք ձեր դիտարկչի '''հետ''' (back) կոճակը։",
-       "anontalkpagetext": "----\n''Այս քննարկման էջը պատկանում է անանուն մասնակցին, որը դեռ չի ստեղծել մասնակցային հաշիվ կամ չի մտել համակարգ մասնակցի անունով։''\nԱյդ իսկ պատճառով օգտագործվում է թվային IP-հասցեն։\nՆման IP-հասցեից կարող են օգտվել մի քանի մասնակիցներ։\nԵթե դուք անանուն մասնակից եք, բայց կարծում եք, որ ուրիշներին վերաբերող դիտողությունները արվում են ձեր հասցեով, ապա խնդրում ենք պարզապես [[Special:UserLogin/signup|գրանցվել]] կամ [[Special:UserLogin|մտնել համակարգ]], որպեսզի հետագայում ձեզ չշփոթեն այլ անանուն մասնակիցների հետ։",
+       "anontalkpagetext": "----\n''Այս քննարկման էջը պատկանում է անանուն մասնակցին, որը դեռ չի ստեղծել մասնակցային հաշիվ կամ չի մտել համակարգ մասնակցի անունով։''\nԱյդ իսկ պատճառով օգտագործվում է թվային IP-հասցեն։\nՆման IP-հասցեից կարող են օգտվել մի քանի մասնակիցներ։\nԵթե դուք անանուն մասնակից եք, բայց կարծում եք, որ ուրիշներին վերաբերող դիտողությունները արվում են ձեր հասցեով, ապա խնդրում ենք պարզապես [[Special:CreateAccount|գրանցվել]] կամ [[Special:UserLogin|մտնել համակարգ]], որպեսզի հետագայում ձեզ չշփոթեն այլ անանուն մասնակիցների հետ։",
        "noarticletext": "Ներկայումս այս էջում որևէ տեքստ չկա։\nԴուք կարող եք [[Special:Search/{{PAGENAME}}|որոնել այս անվանումը]] այլ էջերում, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} որոնել համապատասխան տեղեկամատյանները] կամ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ստեղծել նոր էջ այս անվանմամբ]</span>։",
        "noarticletext-nopermission": "Ներկայումս այս էջում որևէ տեքստ չկա։\nԴուք կարող եք [[Special:Search/{{PAGENAME}}|որոնել այս անվանունը]] այլ էջերում կամ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} որոնել այն տեղեկամատյաններում]</span>։ Դուք չունեք թույլտվություն ստեղծել այս էջը։",
        "userpage-userdoesnotexist": "«<nowiki>$1</nowiki>» անվանմամբ մասնակից գոյություն չունի։\nԽնդրում ենք հավաստիանալ նրանում, թե արդյոք ուզում եք ստեղծել/խմբագրել այս էջը։",
        "upload-form-label-infoform-title": "Մանրամասներ",
        "upload-form-label-infoform-name": "Անուն",
        "upload-form-label-usage-filename": "Նիշքի անուն",
-       "foreign-structured-upload-form-label-own-work": "Սա իմ անձնական աշխատանքն է",
-       "foreign-structured-upload-form-label-infoform-categories": "Կատեգորիաներ",
-       "foreign-structured-upload-form-label-infoform-date": "Ամսաթիվ",
+       "upload-form-label-own-work": "Սա իմ անձնական աշխատանքն է",
+       "upload-form-label-infoform-categories": "Կատեգորիաներ",
+       "upload-form-label-infoform-date": "Ամսաթիվ",
        "upload-curl-error6": "URL-հասցեն անհասանելի է",
        "upload-curl-error6-text": "Նշված URL-հասցեն անհասանելի է։ Խնդրում ենք ստուգել հասցեի ճշգրտությունը և կայքի գործունությունը։",
        "upload-curl-error28": "Բեռնման ժամհատնում",
        "sp-contributions-search": "Որոնել ներդրումները",
        "sp-contributions-username": "IP-հասցե կամ մասնակցի անուն.",
        "sp-contributions-toponly": "Ցույց տալ միայն այն խմբագրումները, որոնք վերջին փոփոխություն են",
-       "sp-contributions-newonly": "Õ\91Õ¸Ö\82ÕµÖ\81 Õ¿Õ¡Õ¬ Õ´Õ«Õ¡ÕµÕ¶ Õ¡ÕµÕ¶ Õ­Õ´Õ¢Õ¡Õ£Ö\80Õ¸Ö\82Õ´Õ¶Õ¥Ö\80Õ¨, Õ¸Ö\80Õ¸Õ¶Ö\84 Õ§Õ» Õ¥Õ¶ Õ½Õ¿Õ¥Õ²Õ®Õ¥Õ¬",
+       "sp-contributions-newonly": "Õ\91Õ¸Ö\82ÕµÖ\81 Õ¿Õ¡Õ¬ Õ´Õ«Õ¡ÕµÕ¶ Õ¶Õ¸Ö\80 Õ§Õ»Õ¥Ö\80Õ« Õ­Õ´Õ¢Õ¡Õ£Ö\80Õ¸Ö\82Õ´Õ¶Õ¥Ö\80Õ¨",
        "sp-contributions-submit": "Որոնել",
        "whatlinkshere": "Այստեղ հղվող էջերը",
        "whatlinkshere-title": "Էջեր, որոնք հղում են դեպի «$1»",
index ebba631..c012d7b 100644 (file)
@@ -32,6 +32,7 @@
        "tog-watchdefault": "Adder le paginas e files que io modifica a mi observatorio",
        "tog-watchmoves": "Adder le paginas e files que io renomina a mi observatorio",
        "tog-watchdeletion": "Adder le paginas e files que io dele a mi observatorio",
+       "tog-watchuploads": "Adder le nove files que io incarga a mi observatorio",
        "tog-watchrollback": "Adder a mi observatorio le paginas in que io ha effectuate un revocation",
        "tog-minordefault": "Marcar omne modificationes initialmente como minor",
        "tog-previewontop": "Monstrar previsualisation ante le quadro de modification",
@@ -56,7 +57,7 @@
        "tog-ccmeonemails": "Inviar me copias del messages de e-mail que io invia a altere usatores",
        "tog-diffonly": "Non monstrar le contento del pagina sub le comparation de duo versiones",
        "tog-showhiddencats": "Monstrar categorias celate",
-       "tog-norollbackdiff": "Omitter le diff post le execution de un revocation",
+       "tog-norollbackdiff": "Non monstrar differentias post exequer un revocation",
        "tog-useeditwarning": "Advertir me quando io quita un pagina de modification sin publicar le cambiamentos",
        "tog-prefershttps": "Sempre usar un connexion secur in session aperte",
        "underline-always": "Sempre",
        "noname": "Tu non specificava un nomine de usator valide.",
        "loginsuccesstitle": "Session aperite",
        "loginsuccess": "'''Tu es ora authenticate in {{SITENAME}} como \"$1\".'''",
-       "nosuchuser": "Non existe un usator con le nomine \"$1\".\nIn le nomines de usator se distingue inter majusculas e minusculas.\nVerifica le orthographia, o [[Special:UserLogin/signup|crea un nove conto]].",
+       "nosuchuser": "Non existe un usator con le nomine \"$1\".\nIn le nomines de usator se distingue inter majusculas e minusculas.\nVerifica le orthographia, o [[Special:CreateAccount|crea un nove conto]].",
        "nosuchusershort": "Non existe un usator con le nomine \"$1\".\nVerifica le orthographia.",
        "nouserspecified": "Tu debe specificar un nomine de usator.",
        "login-userblocked": "Iste usator es blocate. Apertura de session non permittite.",
        "minoredit": "Isto es un modification minor",
        "watchthis": "Observar iste pagina",
        "savearticle": "Publicar pagina",
+       "publishpage": "Publicar pagina",
        "preview": "Previsualisation",
        "showpreview": "Monstrar previsualisation",
        "showdiff": "Detaliar modificationes",
        "accmailtext": "Un contrasigno generate aleatorimente pro [[User talk:$1|$1]] ha essite inviate a $2. Illo pote esser cambiate in le pagina ''[[Special:ChangePassword|Cambiar contrasigno]]'' post que tu ha aperite un session.",
        "newarticle": "(Nove)",
        "newarticletext": "Tu ha sequite un ligamine verso un pagina que non existe ancora.\nPro crear iste pagina, comencia a scriber in le quadro infra (consulta le [$1 pagina de adjuta] pro plus informationes).\nSi tu ha arrivate a iste pagina per error, clicca le button '''Retornar''' de tu navigator.",
-       "anontalkpagetext": "---- ''Isto es le pagina de discussion pro un usator anonyme qui non ha ancora create un conto, o qui non lo usa. Consequentemente nos debe usar le adresse IP numeric pro identificar le/la.\nUn tal adresse IP pote esser usate in commun per varie personas.\nSi tu es un usator anonyme e pensa que commentos irrelevante ha essite dirigite a te, per favor [[Special:UserLogin/signup|crea un conto]] o [[Special:UserLogin|aperi un session]] pro evitar futur confusiones con altere usatores anonyme.''",
+       "anontalkpagetext": "---- ''Isto es le pagina de discussion pro un usator anonyme qui non ha ancora create un conto, o qui non lo usa. Consequentemente nos debe usar le adresse IP numeric pro identificar le/la.\nUn tal adresse IP pote esser usate in commun per varie personas.\nSi tu es un usator anonyme e pensa que commentos irrelevante ha essite dirigite a te, per favor [[Special:CreateAccount|crea un conto]] o [[Special:UserLogin|aperi un session]] pro evitar futur confusiones con altere usatores anonyme.''",
        "noarticletext": "Al momento il non ha texto in iste pagina.\nTu pote [[Special:Search/{{PAGENAME}}|cercar le titulo de iste pagina]] in altere paginas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercar in le registros pertinente],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear iste pagina]</span>.",
        "noarticletext-nopermission": "In iste momento il non ha texto in iste pagina.\nTu pote [[Special:Search/{{PAGENAME}}|cercar le titulo de iste pagina]] in altere paginas,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercar in le registros pertinente], ma tu non ha le permission de crear iste pagina.</span>",
        "missing-revision": "Le version №$1 del pagina nominate \"{{FULLPAGENAME}}\" non existe.\n\nIsto es generalmente causate per sequer un ligamine de historia obsolete a un pagina que ha essite delite.\nDetalios se trova in le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de deletiones].",
        "userpage-userdoesnotexist": "Le conto de usator \"<nowiki>$1</nowiki>\" non es registrate. Per favor verifica que tu vole crear/modificar iste pagina.",
        "userpage-userdoesnotexist-view": "Le conto de usator \"$1\" non es registrate.",
        "blocked-notice-logextract": "Iste usator es actualmente blocate.\nLe ultime entrata del registro de blocadas es reproducite ci infra pro information:",
-       "clearyourcache": "'''Nota:''' Post confirmar, il pote esser necessari refrescar le ''cache'' de tu navigator pro vider le cambiamentos.\n* '''Firefox / Safari:''' Tenente ''Shift'' clicca ''Reload (Recargar)'', o preme ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' sur Mac)\n* '''Google Chrome:''' Preme ''Ctrl-Shift-R'' (''⌘-Shift-R'' sur Mac)\n* '''Internet Explorer:''' Tenente ''Ctrl'' clicca ''Refresh (Refrescar)'', o preme ''Ctrl-F5'' \n* '''Opera:''' Vacua le ''cache'' in ''Tools → Preferences (Utensiles → Preferentias)''",
+       "clearyourcache": "<strong>Nota:</strong> Post confirmar, il pote esser necessari refrescar le <em>cache</em> de tu navigator pro vider le cambiamentos.\n* <strong>Firefox / Safari:</strong> Tenente <em>Shift</em> clicca <em>Reload (Recargar)</em>, o preme <em>Ctrl-F5</em> o <em>Ctrl-R</em> (<em>⌘-R</em> sur Mac)\n* <strong>Google Chrome:</strong> Preme <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> sur Mac)\n* <strong>Internet Explorer:</strong> Tenente <em>Ctrl</em> clicca <em>Refresh (Refrescar)</em>, o preme <em>Ctrl-F5</em> \n* <strong>Opera:</strong> Vade a <em>Menu → Configurationes</em> (<em>Opera → Preferentias</em> sur un Mac) e alora a <em>Privacy & securitate → Rader datos de navigation → Files e imagines in cache</em>.",
        "usercssyoucanpreview": "'''Consilio:''' Usa le button \"{{int:showpreview}}\" pro testar tu nove CSS ante de salveguardar lo.",
        "userjsyoucanpreview": "'''Consilio:''' Usa le button \"{{int:showpreview}}\" pro testar tu nove JavaScript ante de salveguardar lo.",
        "usercsspreview": "'''Non oblida que isto es solmente un previsualisation de tu CSS personalisate.'''\n'''Le modificationes non ha ancora essite salveguardate!'''",
        "right-override-export-depth": "Exportar paginas includente paginas ligate usque a un profunditate de 5",
        "right-sendemail": "Inviar e-mail a altere usatores",
        "right-passwordreset": "Vider le e-mails pro reinitialisar le contrasigno",
-       "right-managechangetags": "Crear e deler [[Special:Tags|etiquettas]] in le base de datos",
+       "right-managechangetags": "Crear e (de)activar [[Special:Tags|etiquettas]]",
        "right-applychangetags": "Applicar [[Special:Tags|etiquettas]] al proprie modificationes",
        "right-changetags": "Adder e remover qualcunque [[Special:Tags|etiquettas]] sur individual versiones e entratas de registro",
+       "right-deletechangetags": "Deler [[Special:Tags|etiquettas]] del base de datos",
        "grant-generic": "Gruppo de derectos \"$1\"",
        "grant-group-page-interaction": "Interager con paginas",
        "grant-group-file-interaction": "Interager con multimedia",
        "action-viewmyprivateinfo": "vider le proprie information private",
        "action-editmyprivateinfo": "modificar le proprie information private",
        "action-editcontentmodel": "modificar le modello de contento de un pagina",
-       "action-managechangetags": "crear e deler etiquettas in le base de datos",
+       "action-managechangetags": "crear e (de)activar etiquettas",
        "action-applychangetags": "applicar etiquettas al proprie modificationes",
        "action-changetags": "adder e remover qualcunque etiquettas sur individual versiones e entratas de registro",
+       "action-deletechangetags": "deler etiquettas del base de datos",
        "nchanges": "$1 {{PLURAL:$1|modification|modificationes}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|desde le ultime visita}}",
        "enhancedrc-history": "historia",
        "recentchangeslinked-page": "Nomine del pagina:",
        "recentchangeslinked-to": "Monstrar modificationes in paginas con ligamines al pagina specificate",
        "recentchanges-page-added-to-category": "[[:$1]] addite al categoria",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] e [[Special:WhatLinksHere/$1|{{PLURAL:$2|un pagina|$2 paginas}}]] addite al categoria",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] addite al categoria, [[Special:WhatLinksHere/$1|iste pagina es includite in altere paginas]]",
        "recentchanges-page-removed-from-category": "[[:$1]] removite del categoria",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] e [[Special:WhatLinksHere/$1|{{PLURAL:$2|un pagina|$2 paginas}}]] removite del categoria",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] removite del categoria, [[Special:WhatLinksHere/$1|iste pagina es includite in altere paginas]]",
        "autochange-username": "Cambiamento automatic de MediaWiki",
        "upload": "Incargar file",
        "uploadbtn": "Incargar file",
        "upload-form-label-infoform-description-tooltip": "Describe brevemente tote le aspectos notabile de iste obra. Pro un photo, mentiona le cosas principal que es representate, le occasion o le loco.",
        "upload-form-label-usage-title": "Uso",
        "upload-form-label-usage-filename": "Nomine del file",
-       "foreign-structured-upload-form-label-own-work": "Iste es mi proprie obra",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorias",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Io confirma que io incarga iste file secundo le conditiones de servicio e politicas de licentia de {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Si tu non pote incargar iste file in concordantia con le politicas de {{SITENAME}}, per favor claude iste dialogo e essaya un altere methodo.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Tu pote etiam essayar [[Special:Upload|le pagina de incargamento normal]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Io comprende que io incarga iste file in un repositorio commun. Io confirma que io lo face secundo le conditiones de servicio e politicas de licentia de illo.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Si tu non pote incargar iste file in concordantia con le politicas del repositorio commun, per favor claude iste dialogo e essaya un altere methodo.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Tu pote anque probar [[Special:Upload|le pagina de incargamento in {{SITENAME}}]], si le politicas de ille sito permitte incargar iste file.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Io certifica que io possede le derecto de autor sur iste file, io consenti le publication irrevocabile de iste file a Wikimedia Commons sub le licentia [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], e io accepta le [https://wikimediafoundation.org/wiki/Terms_of_Use conditiones de uso].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Si tu non possede le derecto de autor sur iste file, o si tu prefere publicar lo sub un altere licentia, considera usar le [https://commons.wikimedia.org/wiki/Special:UploadWizard assistente de incargamento de Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Tu pote anque probar [[Special:Upload|le pagina de incargamento in {{SITENAME}}]], si le politicas de ille sito permitte incargar iste file.",
+       "upload-form-label-own-work": "Iste es mi proprie obra",
+       "upload-form-label-infoform-categories": "Categorias",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Io confirma que io incarga iste file secundo le conditiones de servicio e politicas de licentia de {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Si tu non pote incargar iste file in concordantia con le politicas de {{SITENAME}}, per favor claude iste dialogo e essaya un altere methodo.",
+       "upload-form-label-not-own-work-local-generic-local": "Tu pote etiam essayar [[Special:Upload|le pagina de incargamento normal]].",
+       "upload-form-label-own-work-message-generic-foreign": "Io comprende que io incarga iste file in un repositorio commun. Io confirma que io lo face secundo le conditiones de servicio e politicas de licentia de illo.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Si tu non pote incargar iste file in concordantia con le politicas del repositorio commun, per favor claude iste dialogo e essaya un altere methodo.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Tu pote anque probar [[Special:Upload|le pagina de incargamento in {{SITENAME}}]], si le politicas de ille sito permitte incargar iste file.",
        "backend-fail-stream": "Non poteva transmitter le file $1.",
        "backend-fail-backup": "Non poteva facer un copia de reserva del file $1.",
        "backend-fail-notexists": "Le file $1 non existe.",
        "changecontentmodel-success-text": "Le typo de contento de [[:$1]] ha essite cambiate.",
        "changecontentmodel-cannot-convert": "Le contento de [[:$1]] non pote esser convertite a un typo de $2.",
        "changecontentmodel-nodirectediting": "Le modello de contento $1 non supporta le modification directe",
+       "changecontentmodel-emptymodels-title": "Nulle modello de contento disponibile",
+       "changecontentmodel-emptymodels-text": "Le contento in [[:$1]] non pote esser convertite in alcun typo.",
        "log-name-contentmodel": "Registro de cambiamentos de modello de contento",
        "log-description-contentmodel": "Eventos relative al modellos de contento de un pagina",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|creava}} le pagina $3 con le modello de contento non predefinite \"$5\"",
        "whatlinkshere-prev": "{{PLURAL:$1|precedente|precedente $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|sequente|sequente $1}}",
        "whatlinkshere-links": "← ligamines",
-       "whatlinkshere-hideredirs": "$1 redirectiones",
-       "whatlinkshere-hidetrans": "$1 transclusiones",
-       "whatlinkshere-hidelinks": "$1 ligamines",
-       "whatlinkshere-hideimages": "$1 le ligamines a files",
+       "whatlinkshere-hideredirs": "Celar redirectiones",
+       "whatlinkshere-hidetrans": "Celar transclusiones",
+       "whatlinkshere-hidelinks": "Celar ligamines",
+       "whatlinkshere-hideimages": "Celar le ligamines a files",
        "whatlinkshere-filters": "Filtros",
        "whatlinkshere-submit": "Va",
        "autoblockid": "Auto-blocada №$1",
        "lockdbsuccesstext": "Le base de datos de {{SITENAME}} ha essite blocate.\n<br />Rememora te de disblocar lo post completar tu mantenentia.",
        "unlockdbsuccesstext": "Le base de datos de {{SITENAME}} ha essite disblocate.",
        "lockfilenotwritable": "Impossibile scriber al file de blocada del base de datos.\nPro blocar o disblocar le base de datos, le servitor web debe poter scriber a iste file.",
+       "databaselocked": "Le base de datos es jam blocate.",
        "databasenotlocked": "Le base de datos non es blocate.",
        "lockedbyandtime": "(per $1 le $2 a $3)",
        "move-page": "Renominar $1",
        "tooltip-ca-nstab-category": "Vider le pagina del categoria",
        "tooltip-minoredit": "Marcar iste modification como minor",
        "tooltip-save": "Confirmar tu modificationes",
+       "tooltip-publish": "Publicar tu cambiamentos",
        "tooltip-preview": "Per favor verifica tu modificationes ante que tu los publica!",
        "tooltip-diff": "Detaliar le modificationes que tu ha facite in le texto.",
        "tooltip-compareselectedversions": "Vider le differentias inter le seligite duo versiones de iste pagina.",
        "exif-sublocationcreated": "Sublocalitate del citate ubi le photo esseva prendite",
        "exif-worldregiondest": "Region del mundo monstrate",
        "exif-countrydest": "Pais monstrate",
-       "exif-countrycodedest": "Codice pro pais monstrate",
+       "exif-countrycodedest": "Codice del pais monstrate",
        "exif-provinceorstatedest": "Provincia o stato monstrate",
        "exif-citydest": "Citate monstrate",
        "exif-sublocationdest": "Sublocalitate del citate monstrate",
        "confirmemail_body_set": "Un persona, probabilemente tu, usante le adresse IP $1,\nha specificate que iste adresse de e-mail pertine al conto \"$2\" in {{SITENAME}}.\n\nPro confirmar que iste conto vermente pertine a te, e pro activar le functionalitate\nde e-mail in {{SITENAME}}, visita iste ligamine in tu navigator:\n\n$3\n\nSi le conto *non* pertine a te, seque iste ligamine\npro cancellar le confirmation del adresse de e-mail:\n\n$5\n\nIste codice de confirmation expirara le $6 a $7.",
        "confirmemail_invalidated": "Confirmation del adresse de e-mail cancellate",
        "invalidateemail": "Cancellar confirmation del adresse de e-mail",
+       "notificationemail_subject_changed": "Le adresse de e-mail registrate sur {{SITENAME}} ha essite cambiate",
+       "notificationemail_subject_removed": "Le adresse de e-mail registrate sur {{SITENAME}} ha essite removite",
+       "notificationemail_body_changed": "Qualcuno, probabilemente tu, ab le adresse IP $1, ha cambiate le adresse de e-mail del conto \"$2\" in \"$3\" sur {{SITENAME}}.\n\nSi isto non esseva tu, contacta immediatemente un administrator del sito.",
+       "notificationemail_body_removed": "Qualcuno, probabilemente tu, ab le adresse IP $1, ha removite le adresse de e-mail del conto \"$2\" sur {{SITENAME}}.\n\nSi isto non esseva tu, contacta immediatemente un administrator del sito.",
        "scarytranscludedisabled": "[Le transclusion interwiki es disactivate]",
        "scarytranscludefailed": "[Falleva de obtener le patrono pro $1]",
        "scarytranscludefailed-httpstatus": "[Obtention de patrono fallite pro $1: HTTP $2]",
        "watchlistedit-raw-done": "Tu observatorio ha essite actualisate.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 titulo|$1 titulos}} ha essite addite:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 titulo|$1 titulos}} ha essite removite:",
-       "watchlistedit-clear-title": "Observatorio radite",
+       "watchlistedit-clear-title": "Rader observatorio",
        "watchlistedit-clear-legend": "Rader observatorio",
        "watchlistedit-clear-explain": "Tote le titulos essera removite de tu observatorio",
        "watchlistedit-clear-titles": "Titulos:",
        "timezone-local": "Local",
        "duplicate-defaultsort": "Attention: Le clave de ordination predefinite \"$2\" supplanta le anterior clave de ordination predefinite \"$1\".",
        "duplicate-displaytitle": "<strong>Attention:</strong> Le titulo a monstrar \"$2\" supplanta le ancian titulo a monstrar \"$1\".",
+       "restricted-displaytitle": "<strong>Attention:</strong> Le titulo a monstrar \"$1\" ha essite ignorate perque illo non es equivalente al titulo real del pagina.",
        "invalid-indicator-name": "<strong>Error:</strong> Le attributo <code>name</code> del indicatores del stato del pagina non pote esser vacue.",
        "version": "Version",
        "version-extensions": "Extensiones installate",
        "version-libraries-description": "Description",
        "version-libraries-authors": "Autores",
        "redirect": "Rediriger per ID de file, usator, pagina, version o registro",
-       "redirect-summary": "Iste pagina special redirige a un file (si es date le nomine de un file), a un pagina (si es date un ID de version o ID de pagina) o a un pagina de usator (si es date un ID de usator numeric). Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] o [[{{#Special:Redirect}}/user/101]].",
+       "redirect-summary": "Iste pagina special redirige a un file (si es date le nomine de un file), a un pagina (si es date un ID de version o ID de pagina), a un pagina de usator (si es date un ID de usator numeric) o a un entrata de registro (si es date le ID de un registro). Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] o [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Va",
        "redirect-lookup": "Cercar:",
        "redirect-value": "Valor:",
        "redirect-page": "ID del pagina",
        "redirect-revision": "Version de pagina",
        "redirect-file": "Nomine de file",
+       "redirect-logid": "ID de registro",
        "redirect-not-exists": "Valor non trovate",
        "fileduplicatesearch": "Cercar files duplicate",
        "fileduplicatesearch-summary": "Cercar files duplicate a base de lor summas de verification ''(hash).''",
        "tags-deactivate": "disactivar",
        "tags-hitcount": "$1 {{PLURAL:$1|modification|modificationes}}",
        "tags-manage-no-permission": "Tu non ha le permission de gerer le etiquettas de modification.",
+       "tags-manage-blocked": "Tu non pote gerer etiquettas de cambiamento durante que tu es blocate.",
        "tags-create-heading": "Crear un nove etiquetta",
        "tags-create-explanation": "Per configuration predefinite, le etiquettas novemente create essera disponibile pro le uso per usatores e robots.",
        "tags-create-tag-name": "Nomine del etiquetta:",
        "tags-delete-not-found": "Le etiquetta \"$1\" non existe.",
        "tags-delete-too-many-uses": "Le etiquetta \"$1\" es applicate a plus de $2 {{PLURAL:$2|version|versiones}}, e per isto non pote esser delite.",
        "tags-delete-warnings-after-delete": "Le etiquetta \"$1\" ha essite delite, ma le sequente {{PLURAL:$2|advertimento|advertimentos}} ha essite incontrate:",
+       "tags-delete-no-permission": "Tu non ha le permission de deler etiquettas de modification.",
        "tags-activate-title": "Activar etiquetta",
        "tags-activate-question": "Tu es sur le puncto de activar le etiquetta \"$1\".",
        "tags-activate-reason": "Motivo:",
        "tags-deactivate-not-allowed": "Non es possibile disactivar le etiquetta \"$1\".",
        "tags-deactivate-submit": "Disactivar",
        "tags-apply-no-permission": "Tu non ha le permission de adjunger etiquettas de cambiamento a tu cambiamentos.",
+       "tags-apply-blocked": "Tu non pote applicar etiquettas de cambiamento con tu cambiamentos durante que tu es blocate.",
        "tags-apply-not-allowed-one": "Non es permittite applicar le etiquetta \"$1\" manualmente.",
        "tags-apply-not-allowed-multi": "Le sequente {{PLURAL:$2|etiquetta|etiquettas}} non es autorisate a esser manualmente applicate: $1",
        "tags-update-no-permission": "Tu non ha le permission de adder o remover etiquettas de cambiamento sur individual versiones o entratas de registro.",
+       "tags-update-blocked": "Tu non pote adder o remover etiquettas de cambiamento durante que tu es blocate.",
        "tags-update-add-not-allowed-one": "Non es permittite adjunger le etiquetta \"$1\" manualmente.",
        "tags-update-add-not-allowed-multi": "Le sequente {{PLURAL:$2|etiquetta|etiquettas}} non es autorisate a esser manualmente adjungite: $1",
        "tags-update-remove-not-allowed-one": "Non es permittite remover le etiquetta \"$1\".",
        "tags-edit-revision-legend": "Adder o remover etiquettas de {{PLURAL:$1|iste version|tote le $1 versiones}}",
        "tags-edit-logentry-legend": "Adder o remover etiquettas de {{PLURAL:$1|iste entrata|tote le $1 entratas}} de registro",
        "tags-edit-existing-tags": "Etiquettas existente:",
-       "tags-edit-existing-tags-none": "\"Nulle\"",
+       "tags-edit-existing-tags-none": "<em>Nulle</em>",
        "tags-edit-new-tags": "Nove etiquettas:",
        "tags-edit-add": "Adder iste etiquettas:",
        "tags-edit-remove": "Remover iste etiquettas:",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|protegeva}} $3 $4 [in cascada]",
        "logentry-protect-modify": "$1 {{GENDER:$2|cambiava}} le nivello de protection de $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|cambiava}} le nivello de protection de $3 $4 [in cascada]",
-       "logentry-rights-rights": "$1 {{GENDER:$2|cambiava}} le appertinentia a gruppos pro $3 de $4 a $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2|cambiava}} le appertinentia a gruppos pro {{GENDER:$6|$3}} de $4 a $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|cambiava}} le appertinentia a gruppos pro $3",
        "logentry-rights-autopromote": "$1 ha essite automaticamente {{GENDER:$2|promovite}} de $4 a $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|ha incargate}} $3",
        "feedback-useragent": "Agente usator:",
        "searchsuggest-search": "Cercar",
        "searchsuggest-containing": "continente...",
+       "api-error-autoblocked": "Tu adresse IP ha essite blocate automaticamente, perque illo ha essite usate per un usator blocate.",
        "api-error-badaccess-groups": "Tu non ha le permission de incargar files in iste wiki.",
        "api-error-badtoken": "Error interne: indicio invalide.",
+       "api-error-blocked": "Le modification ha essite blocate pro te.",
        "api-error-copyuploaddisabled": "Le incargamentos per URL es disactivate in iste servitor.",
        "api-error-duplicate": "Existe jam {{PLURAL:$1|un altere file|altere files}} in le wiki con le mesme contento.",
        "api-error-duplicate-archive": "Il habeva jam {{PLURAL:$1|un altere file|altere files}} in le sito con le mesme contento, ma {{PLURAL:$1|illo|illos}} ha essite delite.",
        "api-error-nomodule": "Error interne: nulle modulo de incargamento definite.",
        "api-error-ok-but-empty": "Error interne: nulle responsa del servitor.",
        "api-error-overwrite": "Superscriber un file existente non es permittite.",
+       "api-error-ratelimited": "Tu tenta incargar plus files in curte tempore que iste wiki permitte.\nPer favor, reproba in alcun minutas.",
        "api-error-stashfailed": "Error interne: le servitor non poteva immagazinar le file temporari.",
        "api-error-publishfailed": "Error interne: le servitor non poteva publicar le file temporari.",
        "api-error-stasherror": "Un error ha occurrite durante le incargamento del file in \"stash\".",
        "api-error-unknownerror": "Error incognite: \"$1\".",
        "api-error-uploaddisabled": "Le incargamento es disactivate in iste wiki.",
        "api-error-verification-error": "Le file pote esser corrumpite o su nomine pote haber un extension errate.",
+       "api-error-was-deleted": "Un file con iste nomine ha jam essite incargate e postea delite.",
        "duration-seconds": "$1 {{PLURAL:$1|secunda|secundas}}",
        "duration-minutes": "$1 {{PLURAL:$1|minuta|minutas}}",
        "duration-hours": "$1 {{PLURAL:$1|hora|horas}}",
        "expand_templates_generate_xml": "Monstrar arbore syntactic XML",
        "expand_templates_generate_rawhtml": "Monstrar HTML brute",
        "expand_templates_preview": "Previsualisation",
-       "expand_templates_preview_fail_html": "<em>Perque {{SITENAME}} ha HTML crude activate e il habeva un perdita de datos de session, le previsualisation es celate como precaution contra attaccos con JavaScript.</em>\n\n<strong>Si isto es un tentativa de previsualisation legitime, per favor essaya lo de novo.</strong>\nSi illo ancora non functiona, essaya [[Special:UserLogout|clauder le session]] e aperir un nove session.",
+       "expand_templates_preview_fail_html": "<em>Perque {{SITENAME}} ha HTML crude activate e il habeva un perdita de datos de session, le previsualisation es celate como precaution contra attaccos con JavaScript.</em>\n\n<strong>Si isto es un tentativa de previsualisation legitime, per favor essaya lo de novo.</strong>\nSi illo ancora non functiona, essaya [[Special:UserLogout|clauder le session]] e aperir un nove session, e verifica que tu navigator permitte le cookies de iste sito.",
        "expand_templates_preview_fail_html_anon": "<em>Perque {{SITENAME}} ha HTML crude activate e tu non ha aperite session, le previsualisation es celate como precaution contra attaccos con JavaScript.</em>\n\n<strong>Si isto es un tentativa de previsualisation legitime, per favor [[Special:UserLogin|aperi session]] e essaya lo de novo.</strong>",
-       "pagelanguage": "Selector de lingua de pagina",
+       "expand_templates_input_missing": "Tu debe scriber alcun texto de entrata.",
+       "pagelanguage": "Cambiar lingua del pagina",
        "pagelang-name": "Pagina",
        "pagelang-language": "Lingua",
        "pagelang-use-default": "Usar lingua predefinite",
        "pagelang-select-lang": "Selige lingua",
+       "pagelang-submit": "Submitter",
        "right-pagelang": "Cambiar lingua del pagina",
        "action-pagelang": "cambiar le lingua del pagina",
        "log-name-pagelang": "Registro de cambios de lingua",
        "log-description-pagelang": "Isto es un registro de cambios de lingua in paginas.",
-       "logentry-pagelang-pagelang": "$1 {{GENDER:$2|cambiava}} le lingua del pagina $3 de $4 a $5.",
+       "logentry-pagelang-pagelang": "$1 {{GENDER:$2|cambiava}} le lingua de $3 de $4 a $5.",
        "default-skin-not-found": "Attention! Le apparentia predefinite de tu wiki, definite in <code dir=\"ltr\">$wgDefaultSkin</code> como <code>$1</code>, non es disponibile.\n\nLe installation pare includer le sequente {{PLURAL:$4|apparentia|apparentias}}. Vide [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] pro saper como activar {{PLURAL:$4|lo|los e seliger le predefinite}}.\n\n$2\n\n; Si tu ha justo installate MediaWiki:\n: Tu lo ha probabilemente installate a partir de git, o directemente del codice fonte con un altere methodo. Isto es normal. Essaya installar alcun apparentias desde [https://www.mediawiki.org/wiki/Category:All_skins le directorio de apparentias de mediawiki.org], per:\n:* Discargar le [https://www.mediawiki.org/wiki/Download archivo tar del installator], que include plure apparentias e extensiones. Tu pote copiar e collar le directorio <code>skins/</code> de illo.\n:* Discargar archivos tar con apparentias indidivual ab [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Usar Git pro discargar apparentias].\n: Facer isto non deberea interferer con tu repositorio git si tu es un disveloppator de MediaWiki.\n\n; Si tu ha justo actualisate MediaWiki:\n: MediaWiki a partir del version 1.24 non plus activa automaticamente le apparentias installate (vide [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual: Skin autodiscovery]). Tu pote collar le sequente {{PLURAL:$5|linea|lineas}} in <code>LocalSettings.php</code> pro activar {{PLURAL:$5|le apparentia|tote le apparentias}} actualmente installate:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Si tu ha justo modificate <code>LocalSettings.php</code>:\n: Verifica meticulosemente que le nomines del apparentias non ha errores.",
        "default-skin-not-found-no-skins": "Attention! Le apparentia predefinite de tu wiki, definite in <code>$wgDefaultSkin</code> como <code>$1</code>, non es disponibile.\n\nTu non ha apparentias installate.\n\n; Si tu ha justo installate o actualisate MediaWiki:\n: Tu lo ha probabilemente installate a partir de git, o directemente del codice fonte con un altere methodo. Isto es normal. Essaya installar alcun apparentias desde [https://www.mediawiki.org/wiki/Category:All_skins le directorio de apparentias de mediawiki.org], per:\n:* Discargar le [https://www.mediawiki.org/wiki/Download archivo tar del installator], que include plure apparentias e extensiones. Tu pote copiar e collar le directorio <code>skins/</code> de illo.\n:* Discargar archivos tar con apparentias individual ab [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Usar Git pro discargar apparentias].\n: Facer isto non deberea interferer con tu repositorio git si tu es un disveloppator de MediaWiki. Vide [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] pro saper como activar apparentias e seliger le predefinite.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (activate)",
        "mediastatistics": "Statisticas de multimedia",
        "mediastatistics-summary": "Statisticas sur le typos de file incargate. Isto include solmente le version le plus recente de un file. Versiones ancian o delite de files es excludite.",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 byte|$1 bytes}} ($2; $3%)",
+       "mediastatistics-bytespertype": "Dimension total de files pro iste section: {{PLURAL:$1|$1 byte|$1 bytes}} ($2; $3%).",
+       "mediastatistics-allbytes": "Dimension total de tote le files: {{PLURAL:$1|$1 byte|$1 bytes}} ($2).",
        "mediastatistics-table-mimetype": "Typo MIME",
        "mediastatistics-table-extensions": "Extensiones possibile",
        "mediastatistics-table-count": "Numero de files",
        "mediastatistics-header-text": "Textual",
        "mediastatistics-header-executable": "Executabiles",
        "mediastatistics-header-archive": "Formatos comprimite",
+       "mediastatistics-header-total": "Tote le files",
        "json-warn-trailing-comma": "$1 {{PLURAL:$1|comma|commas}} final ha essite removite de JSON",
        "json-error-unknown": "Il habeva un problema con le JSON. Error: $1",
        "json-error-depth": "Le profunditate maxime del pila ha essite excedite",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Symbolos",
        "special-characters-group-greek": "Greco",
+       "special-characters-group-greekextended": "Greco extense",
        "special-characters-group-cyrillic": "Cyrillic",
        "special-characters-group-arabic": "Arabe",
        "special-characters-group-arabicextended": "Arabe extendite",
        "mw-widgets-titleinput-description-new-page": "pagina non existe ancora",
        "mw-widgets-titleinput-description-redirect": "redirection a $1",
        "api-error-blacklisted": "Per favor elige un altere titulo, plus descriptive.",
-       "randomrootpage": "Pagina-radice aleatori"
+       "sessionmanager-tie": "Impossibile combinar plure typos de authentication de requesta: $1.",
+       "sessionprovider-generic": "sessiones $1",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "sessiones basate sur cookies",
+       "sessionprovider-nocookies": "Le cookies pote esser disactivate. Assecura te de haber activate le cookies e recomencia.",
+       "randomrootpage": "Pagina radice aleatori",
+       "log-action-filter-block": "Typo de blocada:",
+       "log-action-filter-contentmodel": "Typo de modification de modello de contento:",
+       "log-action-filter-delete": "Typo de deletion:",
+       "log-action-filter-import": "Typo de importation:",
+       "log-action-filter-managetags": "Typo de action de gestion de etiquettas:",
+       "log-action-filter-move": "Typo de renomination:",
+       "log-action-filter-newusers": "Typo de creation de conto:",
+       "log-action-filter-patrol": "Typo de patrulia:",
+       "log-action-filter-protect": "Typo de protection:",
+       "log-action-filter-rights": "Typo de cambio de derecto",
+       "log-action-filter-suppress": "Typo de suppression",
+       "log-action-filter-upload": "Typo de incargamento:",
+       "log-action-filter-all": "Toto",
+       "log-action-filter-block-block": "Blocar",
+       "log-action-filter-block-reblock": "Modification de blocada",
+       "log-action-filter-block-unblock": "Disblocar",
+       "log-action-filter-contentmodel-change": "Cambio de modello de contento",
+       "log-action-filter-contentmodel-new": "Creation de pagina con modello de contento non standard",
+       "log-action-filter-delete-delete": "Deletion de pagina",
+       "log-action-filter-delete-restore": "Restauration de pagina",
+       "log-action-filter-delete-event": "Deletion de registro",
+       "log-action-filter-delete-revision": "Deletion de version",
+       "log-action-filter-import-interwiki": "Importation trans wiki",
+       "log-action-filter-import-upload": "Importation per incargamento XML",
+       "log-action-filter-managetags-create": "Creation de etiquetta",
+       "log-action-filter-managetags-delete": "Deletion de etiquetta",
+       "log-action-filter-managetags-activate": "Activation de etiquetta",
+       "log-action-filter-managetags-deactivate": "Disactivation de etiquetta",
+       "log-action-filter-move-move": "Renomination sin superscriber redirectiones",
+       "log-action-filter-move-move_redir": "Renomination superscribente redirectiones",
+       "log-action-filter-newusers-create": "Creation per usator anonyme",
+       "log-action-filter-newusers-create2": "Creation per usator registrate",
+       "log-action-filter-newusers-autocreate": "Creation automatic",
+       "log-action-filter-newusers-byemail": "Creation con contrasigno inviate per e-mail",
+       "log-action-filter-patrol-patrol": "Patrulia manual",
+       "log-action-filter-patrol-autopatrol": "Patrulia automatic",
+       "log-action-filter-protect-protect": "Protection",
+       "log-action-filter-protect-modify": "Modification de protection",
+       "log-action-filter-protect-unprotect": "Disprotection",
+       "log-action-filter-protect-move_prot": "Protection displaciate",
+       "log-action-filter-rights-rights": "Cambiamento manual",
+       "log-action-filter-rights-autopromote": "Cambiamento automatic",
+       "log-action-filter-suppress-event": "Suppression de registro",
+       "log-action-filter-suppress-revision": "Suppression de version",
+       "log-action-filter-suppress-delete": "Suppression de pagina",
+       "log-action-filter-suppress-block": "Suppression de usator per blocada",
+       "log-action-filter-suppress-reblock": "Suppression de usator per re-blocada",
+       "log-action-filter-upload-upload": "Nove file incargate",
+       "log-action-filter-upload-overwrite": "File re-incargate"
 }
index e19b63b..20abcbd 100644 (file)
@@ -63,6 +63,7 @@
        "tog-watchdefault": "Tambahkan halaman yang saya sunting ke daftar pantauan",
        "tog-watchmoves": "Tambahkan halaman yang saya pindahkan ke daftar pantauan",
        "tog-watchdeletion": "Tambahkan halaman yang saya hapus ke daftar pantauan",
+       "tog-watchuploads": "Tambahkan berkas baru yang saya unggah ke daftar pantauan",
        "tog-watchrollback": "Tambahkan halaman yang pernah saya kembalikan ke dalam daftar pantauan saya",
        "tog-minordefault": "Tandai semua suntingan sebagai suntingan kecil secara baku",
        "tog-previewontop": "Perlihatkan pratayang sebelum kotak sunting dan tidak sesudahnya",
        "databaseerror-query": "Kueri: $1",
        "databaseerror-function": "Fungsi: $1",
        "databaseerror-error": "Kesalahan: $1",
+       "transaction-duration-limit-exceeded": "Untuk mencegah penundaan replikasi yang tinggi, pengiriman ini dibatalkan karena durasi tulis ($1) melebihi batas $2 {{PLURAL:$2|detik|detik}}.\nJika Anda ingin mengganti banyak butir sekaligus, cobalah melakukan dalam operasi yang lebih kecil.",
        "laggedslavemode": "Peringatan: Halaman mungkin tidak berisi perubahan terbaru.",
        "readonly": "Basis data dikunci",
        "enterlockreason": "Masukkan alasan penguncian, termasuk perkiraan kapan kunci akan dibuka",
        "noname": "Nama pengguna yang Anda masukkan tidak sah.",
        "loginsuccesstitle": "Berhasil masuk log",
        "loginsuccess": "'''Anda sekarang masuk log di {{SITENAME}} sebagai \"$1\".'''",
-       "nosuchuser": "Tidak ada pengguna dengan nama \"$1\".\nNama pengguna membedakan kapitalisasi.\nPeriksa kembali ejaan Anda, atau [[Special:UserLogin/signup|buat akun baru]].",
+       "nosuchuser": "Tidak ada pengguna dengan nama \"$1\".\nNama pengguna membedakan kapitalisasi.\nPeriksa kembali ejaan Anda, atau [[Special:CreateAccount|buat akun baru]].",
        "nosuchusershort": "Tidak ada pengguna dengan nama \"$1\".\nSilakan periksa kembali ejaan Anda.",
        "nouserspecified": "Anda harus memasukkan nama pengguna.",
        "login-userblocked": "Pengguna ini diblokir. Tidak diizinkan/diperbolehkan untuk masuk log.",
        "noemail": "Tidak ada alamat surel yang tercatat untuk pengguna \"$1\".",
        "noemailcreate": "Anda perlu menyediakan alamat surel yang sah",
        "passwordsent": "Kata sandi baru telah dikirimkan ke alamat surel yang didaftarkan untuk \"$1\".\nSilakan masuk log kembali setelah menerima surel tersebut.",
-       "blocked-mailpassword": "Alamat IP Anda diblokir dari penyuntingan sehingga tidak diizinkan menggunakan fungsi pengingat kata sandi untuk mencegah penyalahgunaan.",
+       "blocked-mailpassword": "Alamat IP Anda diblokir dari penyuntingan. Untuk mencegah penyalahgunaan, Anda tidak diperkenankan untuk memulihkan kata sandi Anda melalui alamat IP ini.",
        "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",
        "createaccount-title": "Pembuatan akun untuk {{SITENAME}}",
        "createaccount-text": "Seseorang telah membuat sebuah akun untuk alamat surel Anda di {{SITENAME}} ($4) dengan nama \"$2\" dan kata sandi \"$3\". Anda dianjurkan untuk masuk log dan mengganti kata sandi Anda sekarang.\n\nAnda dapat mengabaikan pesan ini jika akun ini dibuat karena suatu kesalahan.",
        "login-throttled": "Anda sudah terlalu sering mencoba masuk log.\nSilakan menunggu $1 sebelum mencoba lagi.",
-       "login-abort-generic": "Proses masuk Anda tidak berhasil - Dibatalkan",
+       "login-abort-generic": "Proses masuk log Anda tidak berhasil - Dibatalkan",
        "login-migrated-generic": "Akun Anda telah dimigrasi, dan nama pengguna Anda tidak lagi terdaftar di wiki ini.",
        "loginlanguagelabel": "Bahasa: $1",
        "suspicious-userlogout": "Permintaan Anda untuk keluar log ditolak karena tampaknya dikirim oleh penjelajah yang rusak atau proksi penyinggah.",
        "newpassword": "Kata sandi baru:",
        "retypenew": "Ketik ulang kata sandi baru:",
        "resetpass_submit": "Atur kata sandi dan masuk log",
-       "changepassword-success": "Kata sandi Anda telah berhasil diubah!",
+       "changepassword-success": "Kata sandi Anda telah diubah!",
        "changepassword-throttled": "Anda terlalu sering mencoba masuk log.\nMohon tunggu $1 sebelum mencoba lagi.",
        "botpasswords": "Kata sandi bot",
+       "botpasswords-summary": "<em>Kata sandi bot</em> memungkinkan akses ke akun pengguna menggunakan API tanpa menggunakan kredensial masuk log utama akun tersebut. Hak pengguna yang tersedia ketika masuk log dengan kata sandi bot mungkin akan dibatasi.\n\nJika Anda tidak tahu kenapa Anda ingin melakukan hal ini, sebaiknya jangan lakukan. Semestinya tidak ada orang lain yang boleh meminta Anda untuk menciptakan dan menyerahkan kata sandi bot ini kepadanya.",
        "botpasswords-disabled": "Kata sandi bot dinonaktifkan.",
        "botpasswords-no-central-id": "Untuk menggunakan kata sandi bot, Anda harus masuk log ke akun yang telah tersentralisasi.",
        "botpasswords-existing": "Kata sandi bot tersedia",
        "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-label-restrictions": "Batasan penggunaan:",
        "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-update-failed": "Gagal memperbarui nama bot \"$1\". Apakah sebelumnya sudah pernah dihapus?",
        "botpasswords-created-title": "Kata sandi bot dibuat",
-       "botpasswords-created-body": "Kata sandi bot \"$1\" sukses dibuat.",
+       "botpasswords-created-body": "Kata sandi bot \"$1\" berhasil dibuat.",
        "botpasswords-updated-title": "Kata sandi bot diperbarui",
-       "botpasswords-updated-body": "Kata sandi bot \"$1\" sukses diperbarui.",
+       "botpasswords-updated-body": "Kata sandi bot \"$1\" berhasil diperbarui.",
        "botpasswords-deleted-title": "Kata sandi bot dihapus",
        "botpasswords-deleted-body": "Kata sandi bot \"$1\" telah dihapus.",
        "botpasswords-newpassword": "Kata sandi baru untuk masuk log dengan '''$1''' adalah '''$2'''. ''Mohon simpan untuk referensi di kemudian hari.''",
        "resetpass-no-info": "Anda harus masuk log untuk mengakses halaman ini secara langsung.",
        "resetpass-submit-loggedin": "Ganti kata sandi",
        "resetpass-submit-cancel": "Batalkan",
-       "resetpass-wrong-oldpass": "Kata sandi tidak sah.\nAnda mungkin telah berhasil mengganti kata sandi Anda atau telah meminta kata sandi sementara yang baru.",
+       "resetpass-wrong-oldpass": "Kata sandi tidak sah.\nAnda mungkin telah mengganti kata sandi Anda atau telah meminta kata sandi sementara yang baru.",
        "resetpass-recycled": "Mohon menyetel ulang kata sandi Anda ke sesuatu yang berbeda dari kata sandi Anda sekarang.",
        "resetpass-temp-emailed": "Anda masuk log dengan kode sementara yang disurel.\nUntuk menyelesaikan masuk log, Anda harus mengatur sandi baru di sini:",
        "resetpass-temp-password": "Kata sandi sementara:",
        "minoredit": "Ini adalah suntingan kecil.",
        "watchthis": "Pantau halaman ini",
        "savearticle": "Simpan halaman",
+       "publishpage": "Terbitkan halaman",
        "preview": "Pratayang",
        "showpreview": "Lihat pratayang",
        "showdiff": "Lihat perubahan",
        "accmailtext": "Sebuah kata sandi acak untuk [[User talk:$1|$1]] telah dikirimkan ke $2.\n\nKata sandi untuk akun baru ini dapat diubah di halaman ''[[Special:ChangePassword|pengubahan kata sandi]]'' setelah masuk log.",
        "newarticle": "(Baru)",
        "newarticletext": "Anda mengikuti pranala ke halaman yang belum tersedia. Untuk membuat halaman tersebut, ketiklah isi halaman di kotak di bawah ini (lihat [$1 halaman bantuan] untuk informasi lebih lanjut). Jika Anda tanpa sengaja sampai ke halaman ini, klik tombol '''back''' di penjelajah web Anda.",
-       "anontalkpagetext": "----''Ini adalah halaman pembicaraan seorang pengguna anonim yang belum membuat akun atau tidak menggunakannya.\nDengan demikian, kami terpaksa harus memakai alamat IP yang bersangkutan untuk mengidentifikasikannya.\nAlamat IP seperti ini mungkin dipakai bersama oleh beberapa pengguna yang berbeda.\nJika Anda adalah seorang pengguna anonim dan merasa mendapatkan komentar-komentar yang tidak relevan yang ditujukan langsung kepada Anda, silakan [[Special:UserLogin/signup|membuat akun]] atau [[Special:UserLogin|masuk log]] untuk menghindari kerancuan dengan pengguna anonim lainnya di lain waktu.''",
+       "anontalkpagetext": "----''Ini adalah halaman pembicaraan seorang pengguna anonim yang belum membuat akun atau tidak menggunakannya.\nDengan demikian, kami terpaksa harus memakai alamat IP yang bersangkutan untuk mengidentifikasikannya.\nAlamat IP seperti ini mungkin dipakai bersama oleh beberapa pengguna yang berbeda.\nJika Anda adalah seorang pengguna anonim dan merasa mendapatkan komentar-komentar yang tidak relevan yang ditujukan langsung kepada Anda, silakan [[Special:CreateAccount|membuat akun]] atau [[Special:UserLogin|masuk log]] untuk menghindari kerancuan dengan pengguna anonim lainnya di lain waktu.''",
        "noarticletext": "Saat ini tidak ada teks di halaman ini.\nAnda dapat [[Special:Search/{{PAGENAME}}|melakukan pencarian untuk judul halaman ini]] di halaman-halaman lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log terkait], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} membuat halaman ini]</span>.",
        "noarticletext-nopermission": "!Saat ini tidak ada teks di halaman ini.\nAnda dapat [[Special:Search/{{PAGENAME}}|melakukan pencarian untuk judul halaman ini]] di halaman-halaman lain, atau <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log terkait]</span>, tapi Anda tidak memiliki izin untuk membuat halaman ini",
        "missing-revision": "Revisi #$1 halaman berjudul \"{{FULLPAGENAME}}\" tidak eksis.\n\nHal ini biasanya disebabkan oleh tautan versi terdahulu menuju halaman yang sudah dihapus.\nRinciannya dapat ditemukan di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log penghapusan].",
        "userpage-userdoesnotexist": "Akun pengguna \"<nowiki>$1</nowiki>\" tidak terdaftar.",
        "userpage-userdoesnotexist-view": "Pengguna \"$1\" tidak terdaftar.",
        "blocked-notice-logextract": "Pengguna ini sedang diblokir.\nEntri log pemblokiran terakhir tersedia di bawah ini sebagai rujukan:",
-       "clearyourcache": "'''Catatan:''' Setelah menyimpan, Anda mungkin harus memintas singgahan peramban Anda untuk melihat perubahan.\n* '''Firefox / Safari:''' Tahan ''Shift'' sambil mengeklik ''Reload'', atau tekan ''Ctrl-F5'' atau ''Ctrl-R'' (''⌘-R'' di Mac)\n* '''Google Chrome:''' Tekan ''Ctrl-Shift-R'' (''⌘-Shift-R'' di Mac)\n* '''Internet Explorer:''' Tahan ''Ctrl'' sambl mengeklik ''Refresh'', atau tekan ''Ctrl-F5''\n* '''Opera:''' Bersihkan tembolok di ''Tools → Preferences''",
+       "clearyourcache": "'''Catatan:''' Setelah menyimpan, Anda mungkin harus memintas isi singgahan peramban Anda untuk melihat perubahan.\n* '''Firefox / Safari:''' Tahan ''Shift'' sambil mengeklik ''Reload'', atau tekan ''Ctrl-F5'' atau ''Ctrl-R'' (''⌘-R'' di Mac)\n* '''Google Chrome:''' Tekan ''Ctrl-Shift-R'' (''⌘-Shift-R'' di Mac)\n* '''Internet Explorer:''' Tahan ''Ctrl'' sambl mengeklik ''Refresh'', atau tekan ''Ctrl-F5''\n* '''Opera:''' Buka <em> Menu → Settings</em> (<em>Opera → Preferences</em>Privacy & Security  → Clear browsing data  → Cached images and files</em>.",
        "usercssyoucanpreview": "'''Tips:''' Gunakan tombol \"{{int:showpreview}}\" untuk menguji CSS baru Anda sebelum menyimpannya.",
        "userjsyoucanpreview": "'''Tips:''' Gunakan tombol \"{{int:showpreview}}\" untuk menguji JS baru Anda sebelum menyimpannya.",
        "usercsspreview": "'''Ingatlah bahwa Anda sedang menampilkan pratayang dari CSS Anda.\nPratayang ini belum disimpan!'''",
        "userrights-unchangeable-col": "Kelompok yang tidak dapat Anda ubah",
        "userrights-irreversible-marker": "$1*",
        "userrights-conflict": "Konflik perubahan hak pengguna! Silakan tinjau ulang dan konfirmasi perubahan Anda.",
-       "userrights-removed-self": "Anda berhasil mencabut hak-hak Anda. Anda tidak bisa lagi mengakses halaman ini.",
+       "userrights-removed-self": "Anda telah mencabut hak-hak Anda sendiri. Anda tidak bisa lagi mengakses halaman ini.",
        "group": "Kelompok:",
        "group-user": "Pengguna",
        "group-autoconfirmed": "Pengguna terkonfirmasi otomatis",
        "uploaded-script-svg": "Terdapat elemen terskrip \"$1\" dalam berkas SVG yang diunggah.",
        "uploaded-hostile-svg": "Terdapat CSS yang tidak aman dalam elemen gaya berkas SVG yang diunggah.",
        "uploaded-event-handler-on-svg": "Penetapan atribut <i>event-handler</i> $1=\"$2\" tidak diizinkan dalam berkas SVG.",
+       "uploaded-href-attribute-svg": "Atribut href pada berkas SVG hanya dibolehkan untuk ditautkan ke target http:// atau https://, ditemukan <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-href-unsafe-target-svg": "Menemukan href ke data tidak aman: URI menarget <code>&lt;$1 $2=\"$3\"&gt;</code> dalam berkas SVG yang diunggah.",
+       "uploaded-setting-event-handler-svg": "Penyetelan atribut event-handler diblokir, menemukan <code>&lt;$1 $2=\"$3\"&gt;</code> dalam berkas SVG yang diunggah.",
        "uploadscriptednamespace": "Berkas SVG ini memuat ruang nama ilegal \"$1\"",
        "uploadinvalidxml": "XML dalam berkas yang diunggah tidak bisa diuraikan.",
        "uploadvirus": "Berkas tersebut mengandung virus! Rincian: $1",
        "upload-options": "Opsi pengunggahan",
        "watchthisupload": "Pantau berkas ini",
        "filewasdeleted": "Suatu berkas dengan nama ini pernah dimuat dan selanjutnya dihapus. Harap cek $1 sebelum memuat lagi berkas tersebut.",
+       "filename-thumb-name": "Tampaknya ini adalah judul gambar mini. Mohon jangan mengunggah gambar mini kembali ke wiki yang sama. Bila tidak, silakan perbaiki nama berkas sehingga lebih bermakna, dan tidak memiliki awalan seperti judul gambar mini.",
        "filename-bad-prefix": "Nama berkas yang Anda muat diawali dengan '''\"$1\"''', yang merupakan nama non-deskriptif yang biasanya diberikan secara otomatis oleh kamera digital. Harap pilih nama lain yang lebih deskriptif untuk berkas Anda.",
        "filename-prefix-blacklist": " #<!-- biarkan baris ini seperti adanya --> <pre>\n# Contohnya sebagai berikut:\n#   * Semuanya dari karekter \"#\" sampai akhir baris ini adalah komentar\n#   * Setiap garis \"_\" adalah awalan untuk nama file khas yang diberikan secara otomatis oleh kamera digital\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # beberapa model telpon seluler\nIMG # generik\nJD # Jenoptik\nMGP # Pentax\nPICT # lainnya.\n #</pre> <!-- biarkan baris ini seperti adanya -->",
        "upload-proto-error": "Protokol tak tepat",
        "upload-form-label-infoform-description-tooltip": "Jelaskan dengan singkat hal-hal penting tentang karya ini.\nUntuk foto, sebutkan hal-hal utama yang ditampilkan, kesempatan atau tempat yang ditampilkan di foto.",
        "upload-form-label-usage-title": "Penggunaan",
        "upload-form-label-usage-filename": "Nama berkas",
-       "foreign-structured-upload-form-label-own-work": "Ini adalah karya saya sendiri",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategori",
-       "foreign-structured-upload-form-label-infoform-date": "Tanggal",
+       "upload-form-label-own-work": "Ini adalah karya saya sendiri",
+       "upload-form-label-infoform-categories": "Kategori",
+       "upload-form-label-infoform-date": "Tanggal",
        "backend-fail-stream": "Tidak bisa mengalikan berkas $1.",
        "backend-fail-backup": "Tidak dapat mencadangkan berkas $1.",
        "backend-fail-notexists": "Berkas $1 tidak ada.",
        "uploadstash-summary": "Halaman ini memberikan akses terhadap berkas yang diunggah (atau dalam proses pengunggahan), namun belum diterbitkan ke wiki. Berkas-berkas ini tidak dapat dilihat oleh siapa pun kecuali pengunggahnya.",
        "uploadstash-clear": "Hapus berkas simpanan",
        "uploadstash-nofiles": "Anda tidak memiliki berkas simpanan.",
-       "uploadstash-badtoken": "Pelaksanaan tindakan tersebut gagal. Mungkin karena hak penyuntingan Anda telah kedaluwarsa. Coba lagi.",
+       "uploadstash-badtoken": "Pelaksanaan tindakan tersebut gagal. Mungkin karena hak penyuntingan Anda telah kedaluwarsa. Silakan coba lagi.",
        "uploadstash-errclear": "Penghapusan berkas gagal.",
        "uploadstash-refresh": "Segarkan daftar berkas.",
+       "uploadstash-thumbnail": "lihat miniatur",
        "invalid-chunk-offset": "Ofset potongan tidak valid",
        "img-auth-accessdenied": "Akses ditolak",
        "img-auth-nopathinfo": "PATH_INFO hilang.\nServer Anda tidak diatur untuk melewatkan informasi ini.\nServer tersebut mungkin berbasis CGI dan tidak dapat mendukung img_auth.\nLihat https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "changecontentmodel-title-label": "Judul halaman",
        "changecontentmodel-model-label": "Model konten baru",
        "changecontentmodel-reason-label": "Alasan:",
+       "changecontentmodel-submit": "Ubah",
        "changecontentmodel-success-title": "Model konten ini telah diubah",
        "changecontentmodel-success-text": "Jenis konten [[:$1]] telah diubah",
        "changecontentmodel-cannot-convert": "Isi pada [[:$1]] tidak dapat ditukar kepada jenis $2.",
        "whatlinkshere-prev": "{{PLURAL:$1|sebelumnya $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|selanjutnya $1}}",
        "whatlinkshere-links": "← pranala",
-       "whatlinkshere-hideredirs": "$1 pengalihan",
-       "whatlinkshere-hidetrans": "$1 transklusi",
-       "whatlinkshere-hidelinks": "$1 pranala",
-       "whatlinkshere-hideimages": "$1 pranala berkas",
+       "whatlinkshere-hideredirs": "Sembunyikan pengalihan",
+       "whatlinkshere-hidetrans": "Sembunyikan transklusi",
+       "whatlinkshere-hidelinks": "Sembunyikan pranala",
+       "whatlinkshere-hideimages": "Sembunyikan pranala berkas",
        "whatlinkshere-filters": "Penyaring",
        "whatlinkshere-submit": "Tuju ke",
        "autoblockid": "Blokir otomatis #$1",
        "ipb-unblock": "Hilangkan blokir seorang pengguna atau suatu alamat IP",
        "ipb-blocklist": "Lihat blokir yang diterapkan",
        "ipb-blocklist-contribs": "Kontribusi untuk {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "Tersisa $1",
        "unblockip": "Buka blokir pengguna",
        "unblockiptext": "Gunakan formulir di bawah untuk mengembalikan kemampuan menulis sebuah alamat IP atau pengguna yang sebelumnya telah diblokir.",
        "ipusubmit": "Hilangkan blokir ini",
        "lockdbsuccesstext": "Basis data telah dikunci.<br />\nPastikan Anda [[Special:UnlockDB|membuka kuncinya]] setelah pemeliharaan selesai.",
        "unlockdbsuccesstext": "Kunci basis data telah dibuka.",
        "lockfilenotwritable": "Berkas kunci basis data tidak dapat ditulis. Untuk mengunci atau membuka basis data, berkas ini harus dapat ditulis oleh server web.",
+       "databaselocked": "Basis data telah terkunci.",
        "databasenotlocked": "Basis data tidak terkunci.",
        "lockedbyandtime": "(oleh $1 pada $2 $3)",
        "move-page": "Pindahkan $1",
        "tooltip-ca-nstab-category": "Lihat halaman kategori",
        "tooltip-minoredit": "Tandai ini sebagai suntingan kecil",
        "tooltip-save": "Simpan perubahan Anda",
+       "tooltip-publish": "Terbitkan perubahan Anda",
        "tooltip-preview": "Pratayang perubahan Anda, harap gunakan ini sebelum menyimpan!",
        "tooltip-diff": "Lihat perubahan yang telah Anda lakukan.",
        "tooltip-compareselectedversions": "Lihat perbedaan antara dua versi halaman yang dipilih.",
        "watchlistedit-raw-done": "Daftar pantauan Anda telah diperbarui.",
        "watchlistedit-raw-added": "{{PLURAL:$1|$1 judul telah}} ditambahkan:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|$1 judul telah}} dikeluarkan:",
-       "watchlistedit-clear-title": "Daftar pantauan dihapus",
+       "watchlistedit-clear-title": "Hapus daftar pantauan",
        "watchlistedit-clear-legend": "Hapus daftar pantauan",
        "watchlistedit-clear-explain": "Semua judul akan dihapus dari daftar pantauan Anda",
        "watchlistedit-clear-titles": "Judul:",
        "tags-create-invalid-chars": "Nama tag tidak boleh mengandung koma (<code>,</code>) atau garis miring (<code>/</code>).",
        "tags-create-invalid-title-chars": "Nama tag tidak boleh mengandung karakter yang tidak bisa digunakan dalam judul halaman.",
        "tags-create-already-exists": "Tag \"$1\" sudah ada.",
+       "tags-create-warnings-below": "Apakah Anda ingin melanjutkan pembuatan tanda ini?",
        "tags-delete-reason": "Alasan:",
        "tags-activate-reason": "Alasan:",
        "tags-activate-submit": "Aktifkan",
        "feedback-bugornote": "Jika Anda sudah siap untuk mendeskripsikan masalah teknis secara rinci silakan [$1 melaporkan bug].\nJika tidak, Anda dapat menggunakan formulir mudah di bawah ini. Komentar Anda akan ditambahkan ke halaman \"[$3 $2]\", bersama dengan nama pengguna Anda dan apa browser yang Anda gunakan.",
        "feedback-cancel": "Batal",
        "feedback-close": "Selesai",
+       "feedback-dialog-title": "Kirimkan saran dan tanggapan",
+       "feedback-dialog-intro": "Anda bisa menggunakan formulir sederhana di bawah untuk mengirimkan saran dan masukan. Komentar Anda akan ditambahkan pada laman \"$1\" bersama nama pengguna Anda.",
        "feedback-error-title": "Kesalahan",
        "feedback-error1": "Galat: Hasil tidak dikenal dari API",
        "feedback-error2": "Galat: Penyuntingan gagal",
        "mw-widgets-dateinput-placeholder-day": "TTTT-BB-HH",
        "mw-widgets-dateinput-placeholder-month": "TTTT-BB",
        "api-error-blacklisted": "Pilih judul lain yang deskriptif",
-       "randomrootpage": "Halaman dasar sembarang"
+       "randomrootpage": "Halaman dasar sembarang",
+       "log-action-filter-block": "Jenis pemblokiran:",
+       "log-action-filter-all": "Semua",
+       "log-action-filter-block-block": "Blokir",
+       "log-action-filter-suppress-block": "Perahasiaan pengguna menurut pemblokiran"
 }
index e54d6cf..2dff4b6 100644 (file)
        "noname": "Saanmo a nainaganan ti umisu a nagan ti agar-aramat.",
        "loginsuccesstitle": "Nakastrek",
        "loginsuccess": "<strong>Nakastrekkan iti {{SITENAME}} a kas ni \"$1\".</strong>",
-       "nosuchuser": "Awan ti agar-aramat nga agnagan iti \"$1\". \n\nDagiti nagan ti agar-aramat ket sensitibo ti kadakkel ti letra.\n\nKitaem ti panangiletram, wenno [[Special:UserLogin/signup|agpartuat iti baro a pakabilangan]].",
+       "nosuchuser": "Awan ti agar-aramat nga agnagan iti \"$1\". \n\nDagiti nagan ti agar-aramat ket sensitibo ti kadakkel ti letra.\n\nKitaem ti panangiletram, wenno [[Special:CreateAccount|agpartuat iti baro a pakabilangan]].",
        "nosuchusershort": "Awan ti agar-aramat nga agnagan iti \"$1\".\nKitaem ti panangiletram.",
        "nouserspecified": "Nasken nga inaganam ti nagan ti agar-aramat.",
        "login-userblocked": "Naserraan daytoy nga agar-aramat. Saan a mapalubosan ti sumrek.",
        "accmailtext": "Ti pugto a napartuat a kontrasenias para kenni [[User talk:$1|$1]] ket naipatuloden iti $2. Mabalin a masukatan iti\n<em>[[Special:ChangePassword|pagsukatan ti kontrasenias]]</em> a panid no sumrekka.",
        "newarticle": "(Baro)",
        "newarticletext": "Nasurotmo ti silpo ti awan pay a panid. \nTi mangpartuat ti panid, rugiamon ti agmakinilia iti kahon dita baba (kitaen ti [$1 panid ti tulong] para iti adu pay a pakaammo). \nNo addaka ditoy babaen ti biddut, pindutem ti buton ti <strong>back</strong> ti pagbasabasam.",
-       "anontalkpagetext": "----\n<em>Daytoy ti pakitungtungan a panid para iti di ammo nga agar-aramat a saan pay a nakapartuat iti pakabilangan, wenno saanna nga us-usaren.</em>\nIsu nga agusarkami ti numero nga IP a pagtaengan tapno mailasin isuda a lalaki/babai.\nTi kastoy nga IP a pagtaengan ket us-usaren a bingayan babaen ti nadumaduma nga agar-aramat.\nNo sika ket maysa a di ammo nga agar-aramat ken dagiti awan ti pategna a komentario ket napaitudo kenka, pangngaasi nga [[Special:UserLogin/signup|agpartuatka iti pakabilangam]] wenno [[Special:UserLogin|sumrekka]] \ntapno maliklikan ti pannakaiyallilaw kadagiti sabali a di ammo nga agar-aramat.",
+       "anontalkpagetext": "----\n<em>Daytoy ti pakitungtungan a panid para iti di ammo nga agar-aramat a saan pay a nakapartuat iti pakabilangan, wenno saanna nga us-usaren.</em>\nIsu nga agusarkami ti numero nga IP a pagtaengan tapno mailasin isuda a lalaki/babai.\nTi kastoy nga IP a pagtaengan ket us-usaren a bingayan babaen ti nadumaduma nga agar-aramat.\nNo sika ket maysa a di ammo nga agar-aramat ken dagiti awan ti pategna a komentario ket napaitudo kenka, pangngaasi nga [[Special:CreateAccount|agpartuatka iti pakabilangam]] wenno [[Special:UserLogin|sumrekka]] \ntapno maliklikan ti pannakaiyallilaw kadagiti sabali a di ammo nga agar-aramat.",
        "noarticletext": "Awan ti agdama a teksto iti daytoy a panid.\nMabalinmo ti [[Special:Search/{{PAGENAME}}|agbiruk iti kastoy a titulo ti panid]] kadagiti sabali a panid,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} agbiruk kadagiti mainaig a listaan],\nwenno [{{fullurl:{{NAMESPACE}}:{{PAGENAME}}|action=edit}} partuaten daytoy a panid]</span>.",
        "noarticletext-nopermission": "Awan ti agdama  a linaon daytoy a panid.\nMabalinmo ti [[Special:Search/{{PAGENAME}}|agbiruk para iti titulo ti daytoy a panid]] kadagiti sabali a panid, wenno <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} agbiruk kadagiti mainaig a listaan]</span>, ngem awan ti pammalubosmo a mangpartuat iti daytoy a panid.",
        "missing-revision": "Ti panagbalbaliw ti #$1 iti daytoy a panid a nanaganan ti \"{{FULLPAGENAME}}\" ket awan.\n\nDaytoy ket kadawyan a gapuanan babaen ti sumaganad a silpo ti baak a pakasaritaan iti maysa a naikkaten a panid.\nDagiti salaysay ket mabalin a mabirukan idiay [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} listaan ti panagikkat].",
        "upload-form-label-infoform-description": "Deskripsion",
        "upload-form-label-usage-title": "Panagusar",
        "upload-form-label-usage-filename": "Nagan ti papeles",
-       "foreign-structured-upload-form-label-own-work": "Daytoy ket bukodko nga obra",
-       "foreign-structured-upload-form-label-infoform-categories": "Katkategoria",
-       "foreign-structured-upload-form-label-infoform-date": "Petsa",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Mabalinmo pay a padasen [[Special:Upload|ti kasisigud a pagikargaan a panid]].",
+       "upload-form-label-own-work": "Daytoy ket bukodko nga obra",
+       "upload-form-label-infoform-categories": "Katkategoria",
+       "upload-form-label-infoform-date": "Petsa",
+       "upload-form-label-not-own-work-local-generic-local": "Mabalinmo pay a padasen [[Special:Upload|ti kasisigud a pagikargaan a panid]].",
        "backend-fail-stream": "Saan a maipan ti papeles $1.",
        "backend-fail-backup": "Saan a makaidulin ti kapada ti papeles ti $1.",
        "backend-fail-notexists": "Awan ti papeles ti $1.",
index da89813..8ac3982 100644 (file)
                        "Adam-Yourist"
                ]
        },
-       "tog-underline": "Ð¥Ñ\8cожадеÑ\80га |ок|алÑ\82акадар:",
-       "tog-hideminor": "Ð¥Ñ\8cаÑ\82|аÑ\8fздаÑ\80а Ñ\87Ñ\83 ÐºÐµÑ\80даÑ\87а Ñ\85Ñ\83вÑ\86амаÑ\88a Ð·|амига Ð´Ð¾Ð»Ð° Ñ\85Ñ\83вÑ\86амаÑ\88 ÐºÑ\8aайладаккÑ\85а",
-       "tog-hidepatrolled": "Ð¥Ñ\8cаÑ\82|аÑ\8fздаÑ\80а Ñ\87Ñ\83 ÐºÐµÑ\80даÑ\87а Ñ\85Ñ\83вÑ\86амаÑ\88a Ð´|анийÑ\81адаÑ\8c Ð´Ð¾Ð»Ð° Ñ\85Ñ\83вÑ\86амаÑ\88 ÐºÑ\8aайладаккÑ\85а",
-       "tog-newpageshidepatrolled": "Ð¥Ñ\8cаÑ\82|аÑ\8fздаÑ\80а Ñ\87Ñ\83 ÐºÐµÑ\80даÑ\87а Ñ\85Ñ\83вÑ\86амаÑ\88a Ñ\85Ñ\8cанийÑ\81адаÑ\8c Ð´Ð¾Ð»Ð° Ð¾Ð°Ð³|онаÑ\88 ÐºÑ\8aайлаÑ\8fккÑ\85а",
-       "tog-hidecategorization": "Къайлаяккха оагӀонай категореш",
-       "tog-extendwatchlist": "ШеÑ\80адаÑ\8c Ñ\82еÑ\80кама Ñ\85Ñ\8cаÑ\82|аÑ\8fздаÑ\80, Ð¼Ð°Ñ\81Ñ\81адола Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\87Ñ\83лоаÑ\86аÑ\88 Ð´Ð¾Ð»Ð°, Ð°Ð»Ñ\85Ñ\85а Ñ\82|еÑ\85Ñ\8cаÑ\80а Ð´Ð°Ñ\8cÑ\80аÑ\88 Ð¼Ð°Ñ\80а Ð° Ð´Ð¾Ð°Ñ\86аÑ\88",
+       "tog-underline": "ТIаÑ\85Ñ\8cожаÑ\8fÑ\80га ÐºIала Ñ\82ака Ñ\85Ñ\8cакÑ\85ар:",
+       "tog-hideminor": "Ð\9aÑ\8aайладаккÑ\85а Ð·|амига Ð´Ð¾Ð»Ð° Ñ\85Ñ\83вÑ\86амаÑ\88 ÐºÐµÑ\80да Ñ\85Ñ\83вÑ\86амаÑ\88Ñ\82а Ñ\8eкÑ\8aеÑ\80а",
+       "tog-hidepatrolled": "Ð\9aÑ\8aайладаккÑ\85а Ñ\85а Ð´ÐµÑ\80а Ñ\87акÑ\85даÑ\8cнна Ð´Ð¾Ð»Ð° Ñ\85Ñ\83вÑ\86амаÑ\88 ÐºÐµÑ\80да Ñ\85Ñ\83вÑ\86амаÑ\88Ñ\82а Ñ\8eкÑ\8aеÑ\80а",
+       "tog-newpageshidepatrolled": "Ð\9aÑ\8aайлаÑ\8fÑ\8cккÑ\85а Ñ\85а Ð´ÐµÑ\80а Ñ\87акÑ\85Ñ\8aÑ\8fнна Ð¹Ð¾Ð»Ð° Ð¾Ð°Ð³IонаÑ\88 ÐºÐµÑ\80да Ð¾Ð°Ð³IонаÑ\88Ñ\82а Ñ\8eкÑ\8aеÑ\80а",
+       "tog-hidecategorization": "Къайлаяха оагӀонай категореш",
+       "tog-extendwatchlist": "Ð¥Ñ\8cаÑ\88еÑ\80Ñ\8aÑ\8fÑ\8c Ð¹Ð¾Ð»Ð° Ð·ÐµÐ¼ Ð±Ð°Ñ\80а Ñ\81пиÑ\81ок, Ð¼Ð°Ñ\81Ñ\81адола Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\88е Ñ\87Ñ\83лоаÑ\86аÑ\88, Ñ\82|еÑ\85Ñ\8cаÑ\80а Ð´Ð°Ñ\8c Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\85инна Ñ\86а IеÑ\88.",
        "tog-usenewrc": "Керда хувцамашка а хьат|аяздара зембаккхарга а эргадаккхараш тоабаде (JavaScript эша)",
-       "tog-numberheadings": "Ð\9aеÑ\80Ñ\82аÑ\88каÑ\88Ñ\82а Ð°Ð»Ð°Ð½Ð·Ð° Ñ\82аÑ\8cÑ\80аÑ\85Ñ\8cа Ñ\85оÑ\82Ñ\82а",
-       "tog-showtoolbar": "Г|алатнийcдара г|ирсагартакх хьахьокха (JavaScript)",
-       "tog-editondblclick": "Шозза Ð´|аÑ\82о|амÑ\86a oаг|Ñ\83в Ñ\85Ñ\83вÑ\86а (JavaScript)",
-       "tog-editsectiononrightclick": "РалÑ\81декÑ\8aаÑ\80аÑ\88 Ñ\85Ñ\83вÑ\86а Ð´Ð°Ñ\85каÑ\86а Ð°Ñ\8cÑ\82Ñ\82а Ð´|аÑ\82о|амÑ\86а  ÐºÐµÑ\80Ñ\82аÑ\88ка Ñ\82|а (JavaScript)",
-       "tog-watchcreations": "Tеркама хьат|аяздар т|а аз яь оаг|онаши чуяьккха паьлаши т|атоха",
-       "tog-watchdefault": "Tеркама хьат|аяздар т|а аз хийца оаг|онаши паьлаша кустяздараши т|атоха",
-       "tog-watchmoves": "Tеркама хьат|аяздар т|а аз ц|ихийца оагӀонаши паьлаши т|атоха",
-       "tog-watchdeletion": "Tеркама хьат|аяздар т|а аз д|аяьккха оагӀонаши паьлаши т|атоха",
-       "tog-minordefault": "ТеÑ\80камза Ñ\85Ñ\83вÑ\86амаÑ\88Ñ\82а Ð»Ð¾Ð°Ñ\80Ñ\85Ó\80амза Ð¼Ð¾ Ð±ÐµÐ»Ð³Ð°Ð»Ð¾ Ñ\85оÑ\82Ñ\82а",
-       "tog-previewontop": "Ð\93Ó\80алаÑ\82нийÑ\81даÑ\80а ÐºÐ¾Ñ\80а Ñ\85Ñ\8cалÑ\85е Ð±Ó\80аÑ\80гÑ\82аÑ\81Ñ\81ам Ð¾Ñ\82Ñ\82ае",
-       "tog-previewonfirst": "Ð\93Ó\80алаÑ\82нийÑ\81даÑ\80е Ð´ÐµÑ\85Ñ\8cавоалаÑ\88/йоалаÑ\88 Ð±Ó\80аÑ\80гÑ\82аÑ\81Ñ\81ам гойта",
+       "tog-numberheadings": "Ð\90вÑ\82омаÑ\82иÑ\87еÑ\81ки Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ°Ñ\88Ñ\82а Ð½Ñ\83меÑ\80аÑ\86и Ñ\85Ñ\8cае",
+       "tog-showtoolbar": "ГIирсай панель хьахьокха хувцам беч хана",
+       "tog-editondblclick": "Ð\9dиÑ\81Ñ\8aе Ð¾Ð°Ð³Ó\80онаÑ\88 Ñ\88озза IоÑ\82Ó\80аÑ\82оÓ\80аеÑ\87а (JavaScript)",
+       "tog-editsectiononrightclick": "Ð\9dийÑ\81де Ð´Ð°ÐºÑ\8aа Ñ\88озза Ð´Ð°Ñ\85ка Ð°Ñ\8cÑ\82Ñ\82а Ñ\82оIаеÑ\80 Ñ\82Ó\80аÑ\82оÓ\80айиÑ\87а Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ° Ñ\82Iа (JavaScript)",
+       "tog-watchcreations": "Зем бара списка т|атоха аз хьаяь оаг|онаши чуяьккха файлаши",
+       "tog-watchdefault": "Зем бара списка т|атоха аз хийца оаг|онаши файлай йоазонца сурташ оттадари",
+       "tog-watchmoves": "Зем бара списка т|атоха аз цIи хийца оаг|онаши файлаши",
+       "tog-watchdeletion": "Зем бара списка т|атоха аз дIаяьккха оаг|онаши файлаши",
+       "tog-minordefault": "Ð\9cаÑ\81Ñ\81аза Ð·Iамига Ð´Ð°Ñ\80аÑ\88 Ñ\81анна Ð±ÐµÐ»Ð³Ð°Ð»Ð´Ðµ Ñ\85Ñ\83вÑ\86амаÑ\88.",
+       "tog-previewontop": "Ð¥Ñ\8cалÑ\85Ñ\85е Ð±Ó\80аÑ\80гÑ\82оÑ\85аÑ\80 Ñ\85Ñ\8cагойÑ\82а Ñ\85Ñ\83вÑ\86ама ÐºÐ¾Ñ\80а Ñ\85Ñ\8cалÑ\85аÑ\88каÑ\85Ñ\8c",
+       "tog-previewonfirst": "Ð¥Ñ\83вÑ\86ама Ð´ÐµÑ\85Ñ\8cавоалаÑ\88 Ñ\85ан Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±Ó\80аÑ\80гÑ\82оÑ\85аÑ\80 Ñ\85Ñ\8cагойта",
        "tog-enotifwatchlistpages": "Tеркама хьат|аяздар чура оаг|онаши паьлаши хувцамаех лаьца д-хоамне т|а дайта хьа",
        "tog-enotifusertalkpages": "Са дувцама оаг|он т|а хувцамаш хилча, д-хоамнец хьахоам бе",
        "tog-enotifminoredits": "Геттара з|амига хувцамаш хилча а, д-хоамнец хьахоам бе",
        "thursday": "ера",
        "friday": "пӀаьраска",
        "saturday": "шоатта",
-       "sun": "К|и",
+       "sun": "КIиранди",
        "mon": "Ор",
        "tue": "Шин",
-       "wed": "Кха",
+       "wed": "Кх",
        "thu": "Ер",
-       "fri": "П|аь",
+       "fri": "ПI",
        "sat": "Шоа",
        "january": "АгIой бутт",
        "february": "Саь-кур бутт",
        "april": "Тушоли бутт",
        "may_long": "Села бутт",
        "june": "Этинга бутт",
-       "july": "Баьцамеа\\Меа бутт",
+       "july": "Баьцамеа бутт",
        "august": "Мяцхали бутт",
        "september": "Тов\\Михий бутт",
        "october": "Ардарий\\АьрхIий бутт",
        "november": "Лай чилла бутт",
        "december": "Чан-тар бутт",
-       "january-gen": "Ð\9dажгамÑ\81Ñ\85ой бетт",
+       "january-gen": "Ð\90гIой бетт",
        "february-gen": "Саь-кур бетт",
-       "march-gen": "Муттхьол бетт",
+       "march-gen": "Мутт-хьал бетт",
        "april-gen": "Тушоли бетт",
        "may-gen": "Села бетт",
        "june-gen": "Этинга бетт",
-       "july-gen": "Баьцамеа\\Меа бетт",
+       "july-gen": "Баьцамеа бетт",
        "august-gen": "Мецхали бетт",
        "september-gen": "Тов\\Михий бетт",
        "october-gen": "Ардарий\\АьрхIий бетт",
        "november-gen": "Лай чилла бетт",
-       "december-gen": "Чантар бетт",
+       "december-gen": "Чан-тар бетт",
        "jan": "АгIой",
        "feb": "Саь-кур",
        "mar": "Мутт-хьал",
        "oct": "Ардарий\\АьрхIий",
        "nov": "Лай чилла",
        "dec": "Чан-тар",
-       "january-date": "Ð\9dажгамÑ\81Ñ\85ой $1",
+       "january-date": "Ð\90гIой Ð±Ñ\83Ñ\82Ñ\82 $1",
        "february-date": "Саь-кур бутт $1",
        "march-date": "Муттхьол $1",
        "april-date": "Тушоли $1",
        "period-am": "ДЦ",
        "period-pm": "ДТ",
        "pagecategories": "{{PLURAL:$1|1=Категори|Категореш}}",
-       "category_header": "\"$1\" Катага чура оаг|онаш",
-       "subcategories": "ЧÑ\83Ñ\80акаÑ\82агаш",
+       "category_header": "«$1» категори чура оагIонаш",
+       "subcategories": "Ð\9aIалкаÑ\82егоÑ\80еш",
        "category-media-header": "\"$1\" Категори чура файлаш",
-       "category-empty": "''УкÑ\85 ÐºÐ°Ñ\82ага Ñ\87Ñ\83 Ñ\86Ñ\85Ñ\8cаккÑ\85а Ð¾Ð°Ð³|онаÑ\88 Ðµ Ð¿Ð°Ñ\8cлаÑ\88 Ñ\8fÑ\86.''",
+       "category-empty": "''Ð\95Ñ\80 ÐºÐ°Ñ\82егоÑ\80и Ñ\85Ó\80анза Ñ\8fÑ\8cÑ\81Ñ\81а Ñ\8f (Ñ\86Ñ\85Ñ\8cаккÑ\85а Ð¾Ð°Ð³IонаÑ\88 Ðµ Ñ\84айлаÑ\88 Ð¹Ð¾Ð°Ñ\86аÑ\88).''",
        "hidden-categories": "{{PLURAL:$1|1=Къайла категори|Къайла категореш}}",
-       "hidden-category-category": "Ð\9aÑ\8aайла ÐºÐ°Ñ\82агаш",
-       "category-subcat-count": "{{PLURAL:$2|УкÑ\85 ÐºÐ°Ñ\82агa Ñ\82|еÑ\85Ñ\8cаÑ\80а Ð±Ñ\83Ñ\85каÑ\82аг Ñ\87Ñ\83лоаÑ\86.|{{PLURAL:$1|1=$1 Ð±Ñ\83Ñ\85каÑ\82аг Ñ\85Ñ\8cаÑ\85Ñ\8cекÑ\85а Ñ\8f|$1 Ð±Ñ\83Ñ\85каÑ\82агаÑ\88 Ñ\85Ñ\8cаÑ\85Ñ\8cекÑ\85а Ñ\8f}} $2 Ð¹Ð¾Ð»Ð°Ñ\87аÑ\80ex.}}",
-       "category-subcat-count-limited": "УкÑ\85 ÐºÐ°Ñ\82агa Ñ\87Ñ\83 {{PLURAL:$1|1=$1 Ðº|алкаÑ\82аг|$1 Ðº|алкаÑ\82агаÑ\88}}.",
-       "category-article-count": "{{PLURAL:$2|УкÑ\85 ÐºÐ°Ñ\82ага Ñ\86Ñ\85Ñ\8cа Ð¾Ð°Ð³|Ñ\83в Ð¼Ð°Ñ\80а Ñ\87Ñ\83лоаÑ\86аÑ\86.|{{PLURAL:$1|1=$1 Ð¾Ð°Ð³|Ñ\83в Ñ\85Ñ\8cаÑ\85екÑ\85а Ñ\8f|$1 Ð¾Ð°Ð³|oнаÑ\88 Ñ\85Ñ\8cаÑ\85екÑ\85а Ñ\8f}} Ñ\83кÑ\85 ÐºÐ°Ñ\82ага $2 Ð¹Ð¾Ð»Ð°Ñ\87аÑ\80\85.}}",
-       "category-article-count-limited": "УкÑ\85 ÐºÐ°Ñ\82ага Ñ\87Ñ\83 {{PLURAL:$1|1=$1 Ð¾Ð°Ð³|Ñ\83в|$1 Ð¾Ð°Ð³|oнаÑ\88}}.",
+       "hidden-category-category": "Ð\9aÑ\8aайла ÐºÐ°Ñ\82егоÑ\80еш",
+       "category-subcat-count": "{{PLURAL:$2|УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 Ñ\8f Ñ\83кÑ\85ан ÐºIалÑ\85аÑ\80а ÐºÐ°Ñ\82егоÑ\80и.|УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 Ñ\8f $1 {{PLURAL:$1|кIалÑ\85аÑ\80а ÐºÐ°Ñ\82егоÑ\80и|кIалÑ\85аÑ\80а ÐºÐ°Ñ\82егоÑ\80еÑ\88}} $2 Ð¼Ð°Ñ\81Ñ\81айолÑ\87аÑ\80еÑ\85.}}",
+       "category-subcat-count-limited": "УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 {{PLURAL:$1|кIалÑ\85аÑ\80а ÐºÐ°Ñ\82егоÑ\80и|$1 ÐºIалÑ\85аÑ\80а ÐºÐ°Ñ\82егоÑ\80еÑ\88}} Ñ\8f.",
+       "category-article-count": "{{PLURAL:$2|УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 Ñ\86аI Ð¼Ð°Ñ\80а Ð¾Ð°Ð³IÑ\83в Ñ\8fÑ\86.|УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 Ñ\8f $2 Ð¾Ð°Ð³Ó\80Ñ\83в, Ñ\86аÑ\80еÑ\85 Ð¾Ð°Ð³Ó\80онгаÑ\85Ñ\8c {{PLURAL:$1|Ñ\85Ñ\8cагойÑ\82а $1 Ð¾Ð°Ð³Ó\80Ñ\83в}}}}",
+       "category-article-count-limited": "УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 {{PLURAL:$1|$1 Ð¾Ð°Ð³Ó\80Ñ\83в Ñ\8f|1=Ñ\86аI Ð¾Ð°Ð³Ó\80Ñ\83в Ð¼Ð°Ñ\80а Ñ\8fÑ\86}}.",
        "category-file-count": "{{PLURAL:$2|Укх катагори чу цаI мара файл яц.|{{PLURAL:$1|1=$1 файл хьахьокхаш я|$1 файл хьахьокхаш я}} укх категори $2 долачарeх.}}",
-       "category-file-count-limited": "УкÑ\85 ÐºÐ°Ñ\82ага Ñ\87Ñ\83 {{PLURAL:$1|1=$1 Ð»Ñ\83Ñ\80даÑ\80|$1 Ð»Ñ\83Ñ\80даÑ\80аÑ\88}}.",
-       "listingcontinuesabbrev": "д|ахо",
-       "index-category": "Ð\94|аÑ\85Ñ\8cожама Ð¾Ð°Ð³|онаш",
-       "noindex-category": "Ð\94|аÑ\85Ñ\8cожаманза Ð¾Ð°Ð³|онаш",
-       "broken-file-category": "Ð\9fаÑ\8cла Ñ\85Ñ\8cожадеÑ\80гаÑ\88Ñ\86а Ð±Ð¾Ð»Ñ\85беÑ\88 Ð¹Ð¾Ð°Ñ\86а Ð¾Ð°Ð³|онаш",
-       "about": "Ð\9bоаÑ\86ам",
+       "category-file-count-limited": "УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 {{PLURAL:$1|$1 Ñ\84айл|$1 Ñ\84айлаÑ\88|1=Ñ\86аI Ð¼Ð°Ñ\80а Ñ\84айл Ñ\8fÑ\86}}.",
+       "listingcontinuesabbrev": "(дIахо)",
+       "index-category": "Ð\98ндекÑ\81 Ð¾Ñ\82Ñ\82аеÑ\88 Ð¾Ð°Ð³Iонаш",
+       "noindex-category": "Ð\98ндекÑ\81 Ñ\86а Ð¾Ñ\82Ñ\82аеÑ\88 Ð¾Ð°Ð³Iонаш",
+       "broken-file-category": "Файла Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80гаÑ\88 Ð±Ð¾Ð»Ñ\85беÑ\88 Ð¹Ð¾Ð°Ñ\86а Ð¾Ð°Ð³Iонаш",
+       "about": "СÑ\83Ñ\80Ñ\82 Ð¾Ñ\82Ñ\82адаÑ\80",
        "article": "Йоазув",
-       "newwindow": "(кердача коре)",
+       "newwindow": "&nbsp;(керда кора чу)",
        "cancel": "Эшац",
-       "moredotdotdot": "Д|ахо",
-       "morenotlisted": "Ер |ояздар хьалдиззанз да.",
-       "mypage": "Oаг|ув",
-       "mytalk": "Дувцам",
-       "anontalk": "Дувцар",
+       "moredotdotdot": "ДIахо...",
+       "morenotlisted": "Ер список хьалйиза яц.",
+       "mypage": "ОагIув",
+       "mytalk": "Дувца оттадар",
+       "anontalk": "Дувца оттадар",
        "navigation": "Навигаци",
        "and": "&#32;а",
        "qbfind": "Лахар",
-       "qbbrowse": "Б|аргтасса",
-       "qbedit": "Ð¥Ñ\83вÑ\86а",
-       "qbpageoptions": "Оаг|он оттамаш",
+       "qbbrowse": "БIаргтохар",
+       "qbedit": "Ð\9dиÑ\81Ñ\8aе",
+       "qbpageoptions": "ОагIон оттамаш",
        "qbmyoptions": "Са оттамаш",
        "faq": "КТХ",
        "faqpage": "Project:КТХ",
-       "actions": "Ð¥|амдаÑ\80аш",
+       "actions": "Ð\90Ñ\80дамаш",
        "namespaces": "ЦIерий мотташ",
-       "variants": "Ð\9aеÌ\81паш",
+       "variants": "Ð\92аÑ\80ианÑ\82аш",
        "navigation-heading": "Навигацен меню",
-       "errorpagetitle": "Г|алат",
-       "returnto": "цу $1 оаг|он т|а юхаг|о",
+       "errorpagetitle": "ГӀалат",
+       "returnto": "Укх $1 оагIона тIа юхагӀо.",
        "tagline": "Кечал укхазара: {{grammar:genitive|{{SITENAME}}}}",
-       "help": "Ð\93Ó\80о",
+       "help": "Ð\9dовкÑ\8a\81Ñ\82ал",
        "search": "Лахаp",
        "searchbutton": "Хьалáха",
-       "go": "Дехьа г|о",
+       "go": "Дехьавала",
        "searcharticle": "Дехьавала",
        "history": "Истори",
        "history_short": "Истори",
-       "updatedmarker": "Со Ñ\85анаÑ\87а Ð´ÐµÐ½Ñ\86а Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\85иннaд",
+       "updatedmarker": "Со Ñ\82IеÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\83кÑ\85аз Ñ\85иннаÑ\87Ñ\83л Ñ\82IеÑ\85Ñ\8cагIа ÐºÐµÑ\80дадаÑ\8cккÑ\85ад",
        "printableversion": "Зарба тохара верси",
-       "permalink": "Даиман латташ йола хьожаярг",
-       "print": "Ð\9aепаÑ\82оÑ\85аÑ\80",
+       "permalink": "Ð\94аиман Ð»Ð°Ñ\82Ñ\82аÑ\88 Ð¹Ð¾Ð»Ð° Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80г",
+       "print": "Ð\97аÑ\80ба Ñ\82оÑ\85а",
        "view": "Хьажар",
-       "view-foreign": "Ð\9cазаоаг|он Ñ\87Ñ\83 $1 хьажа",
+       "view-foreign": "УкÑ\85 $1 Ñ\81айÑ\82а Ñ\87Ñ\83 хьажа",
        "edit": "Нийсде",
        "edit-local": "Хувца локальни йоазонца сурт оттадар",
-       "create": "Ð¥Ñ\8cаде",
+       "create": "Ð¥Ñ\8cакÑ\85олла",
        "create-local": "ТIатоха локальни йоазонца сурт оттадар",
-       "editthispage": "Ð\95Ñ\80 Ð¾Ð°Ð³|Ñ\83в Ñ\85Ñ\83вÑ\86а",
-       "create-this-page": "Ep oаг|ув хьае",
-       "delete": "Д|аяккха",
-       "deletethispage": "Ð\95Ñ\80 Ð¾Ð°Ð³|Ñ\83в Ð´|аÑ\8fÑ\8cккÑ\85а",
-       "undeletethispage": "Ð\95Ñ\80 Ð¾Ð°Ð³|Ñ\83в Ð´|аÑ\8fккÑ\85анз Ð¹Ð¸Ñ\82а",
-       "undelete_short": "Ð\9cеÑ\82Ñ\82аоÑ\82Ñ\82ае {{PLURAL:$1|1=Ñ\85Ñ\83вÑ\86ам|$1 Ñ\85Ñ\83вÑ\86амаÑ\88}}",
-       "viewdeleted_short": "Б|аргтасса {{PLURAL:$1|1=д|адаьккха хувцам|$1 д|адаьккха хувцамаш}}",
-       "protect": "Ð\9bоÑ\80аде",
+       "editthispage": "Ð\9dийÑ\81Ñ\8aе ÐµÑ\80 Ð¾Ð°Ð³IÑ\83в",
+       "create-this-page": "Хьакхолла ер оагӀув",
+       "delete": "ДӀаяккха",
+       "deletethispage": "Ð\94Ó\80аÑ\8fккÑ\85а ÐµÑ\80 Ð¾Ð°Ð³Ó\80Ñ\83в",
+       "undeletethispage": "ЮÑ\85амеÑ\82Ñ\82аоÑ\82Ñ\82ае ÐµÑ\80 Ð¾Ð°Ð³Ó\80Ñ\83в",
+       "undelete_short": "ЮÑ\85амеÑ\82Ñ\82аоÑ\82Ñ\82ае {{PLURAL:$1|$1 Ð½Ð¸Ð¹Ñ\81даÑ\80|$1 Ð½Ð¸Ð¹Ñ\81даÑ\80аÑ\88|1=нийÑ\81даÑ\80}}",
+       "viewdeleted_short": "{{PLURAL:$1|$1 дIадаьккха нийсдарга|дIадаьккха нийсдарга|$1 дIадаьккха нийсдарашга}} хьажар",
+       "protect": "Ð\93Iо Ð´Ð°Ñ\80",
        "protect_change": "хувца",
-       "protectthispage": "Ð\9bоÑ\80ае ÐµÑ\80 Ð¾Ð°Ð³|Ñ\83в",
-       "unprotect": "Ð\9bоÑ\80ам хувца",
-       "unprotectthispage": "Ð\9bоÑ\80ам хувца",
+       "protectthispage": "Ð\93Iо Ð´Ðµ Ñ\83кÑ\85 Ð¾Ð°Ð³Iон",
+       "unprotect": "Ð\93Iо хувца",
+       "unprotectthispage": "УкÑ\85 Ð¾Ð°Ð³Iон Ð³Iо хувца",
        "newpage": "Керда оагӀув",
-       "talkpage": "УкÑ\85 Ð¾Ð°Ð³|он Ñ\82|а Ð´Ñ\83вÑ\86ам Ð±Ðµ",
+       "talkpage": "Ð\95Ñ\80 Ð¾Ð°Ð³IÑ\83в Ñ\8eвÑ\86а",
        "talkpagelinktext": "дувца оттадар",
-       "specialpage": "Ð\93\83лакÑ\85адаÑ\80а Ð¾Ð°Ð³|ув",
+       "specialpage": "Ð\91алÑ\85а Ð¾Ð°Ð³Ó\80ув",
        "personaltools": "Доакъашхочун гӀирсаш",
        "articlepage": "Йоазон т|а б|аргтасса",
        "talk": "Дувца оттадар",
        "categorypage": "Катага оаг|oн т|а б|аргтасса",
        "viewtalkpage": "Дувцамага б|аргтасса",
        "otherlanguages": "Кхыча меттаех",
-       "redirectedfrom": "($1 Ñ\82IaÑ\80а Ñ\85Ñ\8cаÑ\85Ñ\8cожадаÑ\8c Ð´Ð°)",
+       "redirectedfrom": "($1 Ñ\82IaÑ\80а Ñ\83кÑ\85аз Ñ\85Ñ\8cаÑ\85Ñ\8cожаÑ\8fÑ\8c Ñ\8f)",
        "redirectpagesub": "Д|а-хьа дайта оаг|ув",
-       "redirectto": "ТIахьожадар укхаза:",
+       "redirectto": "Ð\94Iа-Ñ\81ахьожадар укхаза:",
        "lastmodifiedat": "Укх оагIoн тIеххьара хувцам: $2, $1.",
        "viewcount": "Укх оаг|oн т|а б|аргтассаб {{PLURAL:$1|цхьааца\n|$1 times}}. {{PLURAL:$1|1=цхьазза|$1за}}.",
        "protectedpage": "Лорама оаг|ув",
        "aboutsite": "{{grammar:genitive|{{SITENAME}}}} лаьца",
        "aboutpage": "Project:Лоацам",
        "copyright": "$1 чулоацамаца тIакхоачаш да.",
-       "copyrightpage": "{{ns:project}}:ЯздаÑ\8cÑ\87Ñ\83нна Ð±Ð¾ÐºÑ\8aо",
+       "copyrightpage": "{{ns:project}}:Ð\90вÑ\82оÑ\80а Ð±Ð¾ÐºÑ\8aонаÑ\88",
        "currentevents": "ХӀанзара хинна хIамаш",
        "currentevents-url": "Project:ХӀанзара хинна хIамаш",
        "disclaimers": "Бехктокхам хьацаэцар",
        "disclaimerpage": "Project:Бехктокхам хьацаэцар",
-       "edithelp": "Хувцама гӀо",
+       "edithelp": "Хувцам бара новкъостал",
        "helppage-top-gethelp": "ГӀо",
        "mainpage": "Кертера оагӀув",
        "mainpage-description": "Кертера оагӀув",
        "page-atom-feed": "«$1» — Atom-мугI",
        "red-link-title": "$1 (укх тайпара оагӀув яц)",
        "nstab-main": "Йоазув",
-       "nstab-user": "Ð\94акÑ\8aалаÑ\8cÑ\86аÑ\80хо",
+       "nstab-user": "Ð\94оакÑ\8aаÑ\88хо",
        "nstab-media": "Медифаг",
-       "nstab-special": "Ð\93Ó\80Ñ\83лакха оагӀув",
+       "nstab-special": "Ð\91алха оагӀув",
        "nstab-project": "Проектах лаьца",
        "nstab-image": "Файл",
        "nstab-mediawiki": "Хоам",
-       "nstab-template": "Ð\9aеп",
+       "nstab-template": "Ð\9bо",
        "nstab-help": "ГӀо",
        "nstab-category": "Категори",
        "mainpage-nstab": "Кертера оагӀув",
        "internalerror_info": "Чура гӀалат: $1",
        "cannotdelete-title": "ОагIув дIаяккха йиш яц \"$1\"",
        "badtitle": "Мегаш йоаца цӀи",
-       "badtitletext": "Езаш йола оагӀува цӀи нийса яц, яьсса я, е харцахь я меттаюкъара цIи е интервики цӀи. Иштта, цӀера юкъе оттаде мегаш доаца хьаракъаш нийсаденна хила тарлуш да.",
-       "viewsource": "Ð\91IаÑ\80гÑ\82аÑ\81Ñ\81ам",
+       "badtitletext": "Езаш йола оагӀон цӀи нийса яц, яьсса я, е харцахь йоалаяй меттай юкъера цIи е интервики цӀи. Иштта, цӀера юкъе оттаде мегаш доаца хьаракъаш нийсаденна хила тарлуш да.",
+       "viewsource": "Ð¥Ñ\8cажаÑ\80",
        "actionthrottled": "Сихален овзамал",
        "protectedpagetext": "Ер оаг|ув къайла я хувцамаш дергдоацаш е кхы дола х|амдараш.",
        "virus-unknownscanner": "довзашдоаца мазаундохьалург:",
        "welcomeuser": "Маьрша доаг|алд, $1!",
        "yourname": "Дакъалаьцархочунна цӀи:",
-       "userlogin-yourname": "Доакъашхочунна цӀи",
-       "userlogin-yourname-ph": "Чуйоалае доакъашхочун цӀи",
+       "userlogin-yourname": "Доакъашхочун цӀи",
+       "userlogin-yourname-ph": "Iочуязъе хьай учёта яздара (доакъашхочун) цӀи",
        "createacct-another-username-ph": "Чуйоалае доакъашхочун цӀи",
        "yourpassword": "КъайладIоагӀа:",
        "userlogin-yourpassword": "Пароль",
        "notloggedin": "Оаш шоай цӀи хьааьннадац",
        "nologin": "Леламе дIаяздар дац? '''$1'''.",
        "nologinlink": "Леламе дIаяздар кхолла",
-       "createaccount": "Учёта яздар кхолла",
+       "createaccount": "Учёта яздар хьакхолла",
        "gotaccount": "Укхаза дӀаязабенна дий шо? '''$1'''.",
        "gotaccountlink": "Чувала/яла",
        "userlogin-resetlink": "Чувала/яла цӀии дIоагӀаи дийцаденнадий?",
        "emailconfirmlink": "Доаржален хоамни хьожадорг дIачIоагIаде",
        "loginlanguagelabel": "Мотт: $1",
        "pt-login": "Чувала/яла",
-       "pt-createaccount": "Учёта яздар кхолла",
+       "pt-createaccount": "Учёта яздар хьакхолла",
        "pt-userlogout": "Аравала/яла",
        "changepassword": "КъайладIоaгIа дIахувцар",
        "oldpassword": "Къаьна къайладIоагӀа:",
        "resetpass-submit-cancel": "Юхавал/ялa",
        "passwordreset-username": "Дакъалаьцархочунна цӀи:",
        "passwordreset-email": "Д-хоамни моттиг:",
-       "bold_sample": "Сома Ñ\8fздам",
-       "bold_tip": "Сома Ñ\8fздам",
-       "italic_sample": "Ð\9aÑ\83лга Ñ\8fздам",
-       "italic_tip": "Ð\9aÑ\83лга Ñ\8fздам",
-       "link_sample": "Ӏинка кортале",
-       "link_tip": "Чура хьожаярг",
-       "extlink_sample": "Ӏинка кортале http://www.example.com",
-       "extlink_tip": "Ð\90Ñ\80ен Ó\80инка (http:// Ñ\82амагÓ\80аÑ\85 Ð´Ð¸Ð¹Ñ\86а Ð¼Ð° Ð»Ðµ)",
-       "headline_sample": "Ð\9aоÑ\80Ñ\82ален Ñ\8fздам",
-       "headline_tip": "2-гӀа лагӀарлен кортале",
-       "nowiki_sample": "Укхаза кийчаде дезаш доаца яздам оттаде",
-       "nowiki_tip": "Ð\9cаÑ\81Ñ\81а-бÑ\83Ñ\81Ñ\82амлоÑ\80г Ñ\82еÑ\80камза Ð´Ð¸Ñ\82а",
-       "image_tip": "ЧÑ\83Ñ\8fÑ\8cккÑ\85а Ð¿Ð°Ñ\8cла",
-       "media_tip": "Файлан тIахьожавар",
-       "sig_tip": "ШÑ\83н кулгаяздар а, хӀанзара ха а",
-       "hr_tip": "Ð\9cÑ\83Ñ\85ала Ð¼Ñ\83гÓ\80 (могаÑ\88 Ñ\82айпаÑ\80а Ðº|еззига Ñ\85айÑ\80аде)",
-       "summary": "Ð¥Ñ\83вÑ\86амий Ð±ÐµÐ»Ð³Ð°Ð»Ð´Ðµр",
+       "bold_sample": "Ð\90Ñ\85\81ома Ñ\82екÑ\81Ñ\82",
+       "bold_tip": "Ð\90Ñ\85\81ома Ñ\82екÑ\81Ñ\82",
+       "italic_sample": "СиÑ\85а Ð¹Ð¾Ð°Ð·Ð¾Ð½ Ñ\82екÑ\81Ñ\82",
+       "italic_tip": "СиÑ\85а Ð¹Ð¾Ð°Ð·Ð¾Ð½ Ñ\82екÑ\81Ñ\82",
+       "link_sample": "ТIахьожаярга заголовок",
+       "link_tip": "ЧÑ\83Ñ\80а Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80г",
+       "extlink_sample": "http://www.example.com тIахьожаярга заголовок",
+       "extlink_tip": "Ð\90Ñ\80аÑ\85Ñ\8cаÑ\80а Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80г (йиÑ\86 Ð¼Ð° Ñ\8fлийÑ\82Ñ\82а Ð¿Ñ\80еÑ\84икÑ\81 http://)",
+       "headline_sample": "Ð\97аголовка Ñ\82екÑ\81Ñ\82",
+       "headline_tip": "2-гӀа лагӀа заголовок",
+       "nowiki_sample": "Укхаза хувца езаш йоаца текст хьачуоттае",
+       "nowiki_tip": "ТеÑ\80кал Ð¼Ð° Ðµ Ð²Ð¸ÐºÐ¸-Ñ\84оÑ\80маÑ\82иÑ\80овани",
+       "image_tip": "Ð\94IаÑ\87Ñ\83оÑ\82Ñ\82аÑ\8fÑ\8c Ñ\84айл",
+       "media_tip": "Файла тIахьожавар",
+       "sig_tip": "Ð¥Ñ\8cа кулгаяздар а, хӀанзара ха а",
+       "hr_tip": "Ð\9fÑ\85Ñ\8cоÑ\80агIен Ñ\82ака (Ñ\86оÑ\85 Ð¿Ð°Ð¹Ð´Ð° Ñ\8dÑ\86аÑ\80 Ñ\82IеÑ\85даÑ\8cнна ÐºÐ°Ñ\81Ñ\82Ñ\82а Ð¼Ð° де)",
+       "summary": "Ð¥Ñ\83вÑ\86амай Ñ\81Ñ\83Ñ\80Ñ\82 Ð¾Ñ\82Ñ\82адар",
        "subject": "БӀагал/кортале:",
        "minoredit": "ЗӀамига хувцам",
-       "watchthis": "УкÑ\85 Ð¾Ð°Ð³Ó\80Ñ\83ва Ñ\82еÑ\80кам Ð±Ðµ",
-       "savearticle": "ОагӀув хьаязъе",
+       "watchthis": "Ð\97ем Ð±Ðµ Ñ\83кÑ\85 Ð¾Ð°Ð³Ó\80он",
+       "savearticle": "ОагӀув дIаязъе",
        "preview": "Хьалхе бӀаргтассар",
-       "showpreview": "Хьалхе бӀаргтaссам",
+       "showpreview": "Хьалххе бIаргтохар",
        "showdiff": "Даь хувцамаш",
-       "anoneditwarning": "Зем хила! Шо кхы чудаьннадац. Шун IP-моттиг укх хийца оагӀув искаречу дӀаяздаь хургья.",
+       "anoneditwarning": "<strong>Теркам бе!</strong> Хьо автор хинна система чуваьннавац. Нагахьа санна Iа моллагIа хувцам бой, Хьа IP-адрес дийла массанен бIаргагуш хургда. Нагахьа санна Хьо <strong>[$1 хьачувоале]</strong> е <strong>[$2 учёта яздар хьакхолле]</strong>, нийсдараш (хувцамаш) бувзам болаш хургда Хьа доакъашхой цIерца, иштта кхыдола толажагIи гIойленагIи дола дикаьш хургда Хьона.",
        "summary-preview": "Лоацам ба:",
        "subject-preview": "Кортале хургья:",
        "blockedtitle": "Дакъалаьцархо чӀега бела ва/я",
        "loginreqpagetext": "Кхыйола оагӀувнашка хьожаргдолаш, оаш $1 де деза.",
        "accmailtitle": "КъайладIоагӀа дӀадахьийтад",
        "newarticle": "(Kерда)",
-       "newarticletext": "Шо хьожаяргаца дехьадаьннад йоаца оагӀув тӀа.\nИз кхолларгьйолаш кӀалхагӀа доала корачу текст Iочуязаде (нагахьа кхетаде хала дале [$1 новкъосталан оагӀувага] хьажа).\nЦа ховш укхаза нийсденнадале, шоай браузера кнопка '''Юха''' тӀа пӀелга тоӀабе.",
+       "newarticletext": "Шо хьожаяргаца дехьадаьннад йоаца оагӀон тӀа.\nИз кхолларгьйолаш кӀалхагӀа доала корачу текст Iочуязаде (нагахьа кхетаде хала дале [$1 новкъосталан оагӀонга] хьажа).\nЦа ховш укхаза нийсденнадале, шоай браузера '''Юха''' (назад) кнопка тӀа пӀелга тоӀабе.",
        "noarticletext": "ХIанз укх оагӀув тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи хьоаяр кораде]] кхыйола йоазуваш чу, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тара дола тептарий яздаьраш], е\n'''[{{fullurl:{{FULLPAGENAME}}|action=edit}} изза мо цӀи йолаш оагӀув кхолла]'''</span>.",
-       "noarticletext-nopermission": "ХIанз укх оагӀув тӀа яздам дац.\nШун йиш я, кхыдола йоазувнашках [[Special:Search/{{PAGENAME}}|дола цӀерий хаттам корае]] е <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} нийсамий тептара йоазувнаш корае].</span>",
+       "noarticletext-nopermission": "ХIанз укх оагӀон тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи белгалъяр хьалаха]] кхыйола оагIонаш тIа, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} цун тара дола тептарай яздаьраш].</span> Иштта йола (Ер) оагӀув хьакхолла Хьа бокъо яц.",
        "note": "'''ХӀамоалар:'''",
        "previewnote": "'''Хьалхе б|аргтассам мара бац.'''\nЯздам кхы яздаь дац!",
        "editing": "Хувцам: $1",
-       "editingsection": "Хувцам: $1 (оагӀува дáкъа)",
+       "editingsection": "Хувцам: $1 (оагӀон дáкъа)",
        "editingcomment": "ГӀалатнийсдар $1 (керда декъам)",
        "editconflict": "ГӀалатнийсдара къовсам: $1",
        "yourtext": "Хьа яздам",
        "copyrightwarning": "Теркам бе, $2 ($1 хьажа) бокъонаца лорадеш, тӀахьежама кӀала уллаш, оаш мел чуяккхаш дола хоамаш, яздамаш долга.\nНаггахь санна шоай яздамаш пурам доацаш мала волашву саго хувца е кхы дола моттиге яздердолаш, безам беци, укхаз Ӏочуцаяздеча, дикаьгӀа да.<br />\nОаш дош лу, даь дола хувцама да волга/йолга, е оаш пурам долаш Ӏочуяздеш да кхычера меттигара шоай яздамаш/хоамаш.\n'''Яздархой бокъоца лорадеш дола хӀамаш, цара пурам доацаш, Ӏочумаязаде!'''",
-       "templatesused": "УкÑ\85 Ð±Ó\80аÑ\80гоагÓ\80Ñ\83вни Ð¾Ð°Ð³Ó\80Ñ\83в Ñ\82Ó\80а Ð»ÐµÐ»Ð°Ñ\8fÑ\8c {{PLURAL:$1|1=Ð\9aÑ\83Ñ\86кеп|Ð\9aÑ\83Ñ\86кепаш}}:",
+       "templatesused": "УкÑ\85 Ð¾Ð°Ð³Iон Ñ\82Iа {{PLURAL:$1|1=пайда Ñ\8dÑ\86а Ð\9bо|пайда Ñ\8dÑ\86а Ð\9bонаш}}:",
        "templatesusedpreview": "Хьалхе бӀаргтассама оагӀув тӀа леладеш дола {{PLURAL:$1|1=Куцкеп|Куцкепаш}}:",
        "template-protected": "(лорадаь да)",
-       "template-semiprotected": "(дакъа-лорам)",
-       "hiddencategories": "Ер оагӀув укх {{PLURAL:$1|1=къайла цатегаца|къайла цатегашца}} дакъа лоаца:",
-       "permissionserrorstext-withaction": "$2 де бокъо яц {{PLURAL:$1|1=из бахьан долаш|из бахьанаш долаш}}:",
+       "template-semiprotected": "(цхьа долча даькъе гIо оттадаь да)",
+       "hiddencategories": "Ер оагIув {{PLURAL:$1|$1 къайла категориех|1=цаI къайла категорех}} я:",
+       "permissionserrorstext-withaction": "Ер $2 де Хьа бокъо яц {{PLURAL:$1|1=из бахьан долаш|из бахьанаш долаш}}:",
        "recreate-moveddeleted-warn": "'''Зем бе! Шо хьалххе дIайоаккхаш хинна оагӀув хьае гӀерта.'''\n\nХьажа, бокъонцахь езаш йолга.\nКӀалхагIа укх оагӀуви дӀадаккхами цӀи хувцами тептараш хьекха да.",
-       "moveddeleted-notice": "Ер оагӀув дӀаяккха хиннай.\nНовкъостала, кӀалха хьахьекха да дӀадаккхама а хувцама а тептарашкара яздараш.",
+       "moveddeleted-notice": "Ер оагӀув дӀаяккха хиннай.\nНовкъостала, кӀалха хьахьекха да дӀадаккхама а хувцама а тептарашкара дIаяздаьраш.",
        "log-fulllog": "Деррига таптара бӀаргтасса",
        "edit-conflict": "Хувцамий къовсам.",
        "post-expand-template-inclusion-warning": "Зембаккхам: жамIан чIабалаш чулоаца дустам геттара доккха да.\nЦхьадола чIабалаш чулоацалургдац.",
        "post-expand-template-inclusion-category": "Чулоаца чIабала мегаш дола дустам дукхалена тӀехьайоала оагӀувнаш",
        "post-expand-template-argument-warning": "Зем бе! Ер оагӀув цаӀ куцкепа |аьлдош мара чулоацац, юхадастара сел доккха дустам йолаш.\nЦу тайпара |аьлдешаш ӀокӀаладаькха да.",
        "post-expand-template-argument-category": "Куцкепий теркамза |аьлдешаш чулоаца оагӀувнаш",
-       "viewpagelogs": "Укх оагӀон тептараш хьокха",
+       "viewpagelogs": "УкÑ\85 Ð¾Ð°Ð³Ó\80он Ñ\82епÑ\82аÑ\80аÑ\88 Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а",
        "currentrev-asof": "тӀеххьара верси $1",
        "revisionasof": "Верси $1",
-       "revision-info": "$1; $2 хувцам",
+       "revision-info": "Верси $1; {{GENDER:$6|$2}}$7",
        "previousrevision": "← Xьалхарча",
        "nextrevision": "ТIехьайоагIараш →",
        "currentrevisionlink": "ХIанзара верси",
-       "cur": "хӀанз.",
+       "cur": "хӀанза.",
        "next": "тӀехь.",
-       "last": "хьалх.",
+       "last": "хьалха.",
        "page_first": "хьалхара",
        "page_last": "тӀехьара",
        "histlegend": "Кхетам: (хӀанз.) = хӀанза йолачунна бӀаргоагӀувни хьакъоастам ба; (хьалх.) = хьалха хинначунна бӀаргоагӀувни хьакъоастам ба; '''зӀ''' = зӀамига хьахувцам ба.",
        "searchresults": "Лахар чакхдоалаш корадаьр",
        "searchresults-title": "«$1» лахар",
        "notextmatches": "ОагIувнаша яздамий вIашагIакхетараш дац",
-       "prevn": "{{PLURAL:$1|хьалхйоагlар $1|хьалхйоагlараш $1|хьалхйоагlараш $1}}",
+       "prevn": "{{PLURAL:$1|1=хьалхайогIар|хьалхайогIараш}} $1",
        "nextn": "{{PLURAL:$1|1=тIехьайоагIар|тIехьайоагIараш}} $1",
        "prevn-title": "{{PLURAL:$1|1=$1 хьалхара йоазув|$1 хьалхара йоазувнаш}}",
        "nextn-title": "{{PLURAL:$1|ТIадоагIа $1 яздар|ТIадоагIа $1 яздараш}}",
        "shown-title": "Хьóкха $1 {{PLURAL:$1|даь йоазо|даь йоазонаш}} укх оáгIувна тIа",
-       "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) хьажа",
+       "viewprevnext": "ДIахьажа ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''Укх масса-хьахьоадайтамач ер оаг|ув \"[[:$1]]\" я'''",
-       "searchmenu-new": "<strong>Ð\9aÑ\85олла Ð¾Ð°Ð³IÑ\83в Â«[[:$1]]» Ñ\83кÑ\85 Ð²Ð¸ÐºÐ¸-пÑ\80оекÑ\82е!</strong>\n{{PLURAL:$2|0=|Ð\98Ñ\88Ñ\82Ñ\82а Ñ\85Ñ\8cажа Ñ\85Ñ\8cай Ð»Ð¸Ð¹Ñ\85а Ð¾Ð°Ð³IÑ\83внага.|Иштта хьажа хьай лахара хьахиннарашка.}}",
+       "searchmenu-new": "<strong>Ð¥Ñ\8cакÑ\85олла Ð¾Ð°Ð³IÑ\83в Â«[[:$1]]» Ñ\83кÑ\85 Ð²Ð¸ÐºÐ¸-пÑ\80оекÑ\82е!</strong>\n{{PLURAL:$2|0=|Ð\98Ñ\88Ñ\82Ñ\82а Ñ\85Ñ\8cажа IайÑ\85а Ð»Ð¸Ð¹Ñ\85а Ð¾Ð°Ð³Iонга.|Иштта хьажа хьай лахара хьахиннарашка.}}",
        "searchprofile-articles": "Кертера оагIонаш",
        "searchprofile-images": "Мультимедиа",
        "searchprofile-everything": "Массанахьа",
        "searchprofile-advanced-tooltip": "Iочуязаяь цIераренашках лаха",
        "search-result-size": "$1 ({{PLURAL:$2|$2 дош|$2 дешаш}})",
        "search-result-category-size": "{{PLURAL:$1|1=$1 дакъа|$1 дакъаш}} ({{PLURAL:$2|1=$2 кIалцатег|$2 кIалцатегаш}}, {{PLURAL:$3|1=$3 паьла|$3 паьлий}})",
-       "search-redirect": "($1 дехьачуяьккхар)",
-       "search-section": " (дакъа $1)",
+       "search-redirect": "(дIа-сахьожадар $1 тIара)",
+       "search-section": "(дакъа «$1»)",
        "search-suggest": "Хьона эшар ер хила мега: $1",
        "search-interwiki-caption": "Гаргалон хьахьоадайтамаш",
        "search-interwiki-default": "$1 хьахиннараш:",
        "search-relatedarticle": "шоайл дола",
        "searchrelated": "гаргара",
        "searchall": "деррига",
-       "search-nonefound": "Ð\94IаÑ\85аÑ\82Ñ\82ама Ð½Ð¸Ð¹Ñ\81амаÑ\88 корадаьдац.",
+       "search-nonefound": "Ð¥Ñ\8cа Ð´ÐµÑ\85аÑ\80 Ð´Ð°Ñ\80а Ð²IаÑ\88и Ð½Ð¸Ð¹Ñ\81а Ð´Ð¾Ð°Ð³IаÑ\88 Ñ\85илаÑ\80 корадаьдац.",
        "powersearch-legend": " Доккха тахкар",
        "powersearch-ns": " ЦIерий аренашкахь лахар",
        "powersearch-toggleall": "Деррига",
        "powersearch-togglenone": "Цхьаккха",
        "preferences": "Оттамаш",
-       "mypreferences": "Ð\9eÑ\82Ñ\82амаш",
+       "mypreferences": "Ð\93IиÑ\80Ñ\81аш",
        "prefs-skin": "БIагала куц",
        "skin-preview": "Хьажа",
        "prefs-personal": "Хьа хьай далам",
        "nchanges": "$1 {{PLURAL:$1|1=хувцам|хувцамаш}}",
        "enhancedrc-history": "истори",
        "recentchanges": "Керда хувцамаш",
-       "recentchanges-legend": "Ð\9aеÑ\80да Ñ\85Ñ\83вÑ\86амий оттамаш",
-       "recentchanges-summary": "КIалхагIа лоарамий доаламе тIехьара оагIувний хувцамаш дIаязадаь да {{grammar:genitive|{{SITENAME}}}}.",
+       "recentchanges-legend": "Ð\9aеÑ\80да Ñ\85Ñ\83вÑ\86амай оттамаш",
+       "recentchanges-summary": "КIалхагIа ханашца нийсдаь дIаяьздаь да {{grammar:genitive|{{SITENAME}}}}  оагIонай тIеххьара хувцамаш.",
        "recentchanges-feed-description": "Укх ларамца тIехьара массахувцамашт теркам бе.",
        "recentchanges-label-newpage": "Укх хувцамаца керда оагIув кхелла хиннай",
        "recentchanges-label-minor": "Ер зIамига хувцам ба",
        "recentchanges-label-bot": "Ер хувцам ботаца баь ба",
-       "recentchanges-label-unpatrolled": "Ð\95Ñ\80 Ñ\85Ñ\83вÑ\86ам Ñ\88ий Ð¼Ð¾Ñ\82Ñ\82иге ÐºÑ\85Ñ\8b Ð´IадеÑ\85Ñ\8cаÑ\8fÑ\8cккÑ\85аÑ\8fц.",
+       "recentchanges-label-unpatrolled": "УкÑ\85 Ñ\85Ñ\83вÑ\86ама Ñ\85Iанза Ñ\86Ñ\85Ñ\8cанно Ñ\85а (паÑ\82Ñ\80Ñ\83лиÑ\80оваÑ\82Ñ\8c) Ð´Ð°Ñ\8c Ð´Ð°ц.",
        "recentchanges-label-plusminus": "байташкахь боарам хувцар",
        "recentchanges-legend-heading": "<strong>Легенда:&nbsp;</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (хьажа иштта [[Special:NewPages|керда оагIувнашка]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (хьажа иштта [[Special:NewPages|керда оагIонашка]])",
        "rcnotefrom": "КIалхагIа хувцамаш хьахьекха я <strong>$2</strong> денза (<strong>$1</strong> кхачалца).",
-       "rclistfrom": "$3 $2 тIара хувцамаш хьахьокха",
-       "rcshowhideminor": "$1 зIамига хувцамаш",
-       "rcshowhideminor-hide": "Ð\9aÑ\8aайлдаккха",
+       "rclistfrom": "$3 $2 денза даь хувцамаш хьахьокха",
+       "rcshowhideminor": "$1 зIамига нийсдараш",
+       "rcshowhideminor-hide": "Ð\94IакÑ\8aайладаккха",
        "rcshowhidebots": "$1 боташ",
        "rcshowhidebots-show": "Хьахьокха",
        "rcshowhideliu": "$1 бовзийтарчара доакъашхой",
-       "rcshowhideliu-hide": "Ð\9aÑ\8aайлдаккха",
-       "rcshowhideanons": "$1 цIияьккханза дакъалаьцархой",
+       "rcshowhideliu-hide": "Ð\9aÑ\8aайлабаха",
+       "rcshowhideanons": "$1 цIияккханза доакъашхой",
        "rcshowhideanons-show": "Хьахьокха",
-       "rcshowhideanons-hide": "Ð\9aÑ\8aайлдаккха",
+       "rcshowhideanons-hide": "Ð\9aÑ\8aайлабаха",
        "rcshowhidepatr": "$1 теркам даь хувцамаш",
-       "rcshowhidemine": "$1 Ñ\81ай Ñ\85Ñ\83вÑ\86амаш",
-       "rcshowhidemine-hide": "Ð\9aÑ\8aайлдаккха",
-       "rclinks": "$2 динах<br />$3 $1 хинна тIехьара хувцамаш хьахьокха",
+       "rcshowhidemine": "$1 Ñ\85Ñ\8cа Ð½Ð¸Ð¹Ñ\81даÑ\80аш",
+       "rcshowhidemine-hide": "Ð\94IакÑ\8aайладаккха",
+       "rclinks": "Хьахьокха $2 дийнахь даь хинна тIеххьара $1 хувцамаш\n<br />$3",
        "diff": "башхало",
        "hist": "истори",
-       "hide": "Къайлдаккха",
+       "hide": "Ð\9aÑ\8aайладаккÑ\85а",
        "show": "Хьахьокха",
        "minoreditletter": "зI",
        "newpageletter": "К",
        "rc-change-size-new": "Хувцам баьнначул тӀехьагIа бола боарам: $1 {{PLURAL:$1|байт}}",
        "rc-enhanced-expand": "Ма дарра чулоацамаш хьахьокха (JavaScriptаца)",
        "rc-enhanced-hide": "Ма дарра чулоацамаш къайладаккха",
-       "recentchangeslinked": "Ð\93аÑ\80галон Ñ\85Ñ\83вÑ\86амаш",
+       "recentchangeslinked": "Ð\92IаÑ\88агIдÑ\83взаденна Ð½Ð¸Ð¹Ñ\81даÑ\80аш",
        "recentchangeslinked-feed": "Гаргалон хувцамаш",
        "recentchangeslinked-toolbox": "Укханца вIашагIдувзаденна хувцамаш",
-       "recentchangeslinked-title": "$1ца хьалаьца хувцамаш",
+       "recentchangeslinked-title": "$1ца вIашидувзаденна хувцамаш",
        "recentchangeslinked-summary": "Ер, Iинк яь йола оагIув (е укх цатегачу чуйоагIараш), дукха ха йоацаш хьийца оагIувнашкий дагарле я.\n[[Special:Watchlist|Шун теркама дагарленашках]] чуйоагIа оагIувнаш '''белгалаяь я'''.",
-       "recentchangeslinked-page": "ОагIува цIи",
-       "recentchangeslinked-to": "Ð\9eагIÑ\83внаÑ\88 Ñ\82Iа Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а, Ñ\85Ñ\8cаÑ\85Ñ\8cекÑ\85а Ð¹Ð¾Ð»Ð° Ð¾Ð°Ð³IÑ\83в Ñ\82Iа IинкаÑ\88 ÐµÑ\88 Ð¹Ð¾Ð»а.",
+       "recentchangeslinked-page": "ОагIон цIи",
+       "recentchangeslinked-to": "Ð\92еÑ\88Ñ\82а, Ð±ÐµÐ»Ð³Ð°Ð»Ñ\8fÑ\8cккÑ\85а Ð¾Ð°Ð³Iон Ñ\82IаÑ\85Ñ\8cожавеÑ\88 Ð´Ð¾Ð»Ð° Ð¾Ð°Ð³IонаÑ\88Ñ\82а Ð´Ð°Ñ\8c Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а.",
        "upload": "Файл чуяккха",
        "uploadbtn": "Паьл чуяьккха",
        "uploadlogpage": "Чуяьккхамий тептар",
-       "filedesc": "Ð\9bоаÑ\86а Ð»Ð¾Ð°Ñ\86ам",
+       "filedesc": "Ð\9bоаÑ\86а Ð¹Ð¾Ð°Ð·Ð¾Ð½Ñ\86а Ñ\81Ñ\83Ñ\80Ñ\82 Ð¾Ñ\82Ñ\82адаÑ\80",
        "fileuploadsummary": "Лоаца лоацам:",
        "license": "ЦIийяздар",
-       "license-header": "ЦIийÑ\8fздаÑ\80",
+       "license-header": "Ð\9bиÑ\86ензиÑ\80ование",
        "imgfile": "файл",
        "listfiles": "Паьлий дагарче",
        "listfiles_date": "Денха",
        "filehist-dimensions": "Файлан боарам",
        "filehist-filesize": "Паьла юстарал",
        "filehist-comment": "Белгалдаккхар",
-       "imagelinks": "Файла пайда эцар",
+       "imagelinks": "Файлах пайда эцар",
        "linkstoimage": "{{PLURAL:$1|1=ТIехьайоагIача $1 оагIуво тIахьожаву|ТIехьайоагIача $1 оагIувнаша тIахьожаву}} укх файла тIа:",
-       "nolinkstoimage": "Ð\99ола Ð¿Ð°Ñ\8cла Ñ\82Iа  Iинк Ñ\8e Ð¾Ð°Ð³IÑ\83внаÑ\88 Ð´Ð°Ñ\86",
+       "nolinkstoimage": "УкÑ\85 Ñ\84айла Ñ\82IаÑ\85Ñ\8cожавеÑ\88 Ð¹Ð¾Ð»Ð° Ð¾Ð°Ð³IонаÑ\88 Ñ\8fÑ\86.",
        "sharedupload": "Ер паьла $1чера я, кхыча хьахьоадайтамча хьахайраде йийшайолаш я.",
        "sharedupload-desc-here": "Ер файл $1 чура я, иштта кхыйола проекташ чу пайда эца аьттув болаш я.\nЦун [$2 йоазонца сурт оттадара оагIон] информаци кIалхахь хьайоалаяй.",
        "uploadnewversion-linktext": "Укх паьлий керда бIаса чуяьккха",
        "brokenredirects-delete": "дIадаккха",
        "withoutinterwiki-submit": "Хьахьокха",
        "nbytes": "$1 {{PLURAL:$1|байт}}",
-       "nmembers": "$1 {{PLURAL:$1|1=дакъалаьцархо|дакъалаьцархой}}",
+       "nmembers": "$1 {{PLURAL:$1|объект}}",
        "prefixindex": "ОагIувнаший хьалхера цIи хьагойтар",
        "shortpages": "Лоаца оагIувнаш",
        "longpages": "Доккхий оагIувнаш",
        "move": "ЦIи хувца",
        "movethispage": "Укх оагIува цIи хувца",
        "pager-newer-n": "{{PLURAL:$1|кердагIа дара|кердагIа дараш|кердагIа долачаьрахь}} $1",
-       "pager-older-n": "{{PLURAL:$1|кÑ\8aаÑ\8cнаÑ\80а Ð´Ð°Ñ\80а|кÑ\8aаÑ\8cнаÑ\80а Ð´Ð°Ñ\80аÑ\88|кÑ\8aаÑ\8cнаÑ\80а Ð´Ð¾Ð»aÑ\87аÑ\8cÑ\80аÑ\85Ñ\8c}} $1",
+       "pager-older-n": "{{PLURAL:$1|кÑ\8aаÑ\8cнаÑ\80а Ð´Ð°Ñ\80а|кÑ\8aаÑ\8cнаÑ\80а Ð´Ð°Ñ\80аÑ\88|кÑ\8aаÑ\8cнаÑ\80а Ð´Ð¾Ð»aÑ\87аÑ\80еÑ\85}} $1",
        "booksources": "Джейнай хьасташ (источники)",
        "booksources-search-legend": "Джейнах лаьца хоам лахар",
        "booksources-search": "Хьалáха",
        "listgrouprights-members": "(тоабий дагарче)",
        "emailuser": "Дакъалаьцархочоа д-хоамни:",
        "watchlist": "Теркама дагарче",
-       "mywatchlist": "ТеÑ\80кама Ð´Ð°Ð³Ð°Ñ\80ле",
+       "mywatchlist": "Ð\97ем Ð±Ð°Ñ\80а Ñ\81пиÑ\81ок",
        "watchlistfor2": "$1 $2 царна",
        "addedwatchtext": "\"[[:$1]]\" оагIув, шун [[Special:Watchlist|теркама дагаршкахь]] чуяккха я. \nТехьара мел йола укх оагIувни хувцамаш цу дагаршкахь хоам беш хургья. Вешта [[Special:RecentChanges|керда хувцама дагаршкаехь]] сома къоалмаца хьакъоастлуш хургья.",
        "removedwatchtext": "\"[[:$1]]\" оагIув, шун [[Special:Watchlist|теркама дарагчера]] дIаяккха хиннай.",
-       "watch": "Зе",
+       "watch": "Зем бе",
        "watchthispage": "Укх оагIува теркам бе",
        "unwatch": "Лора ма де",
        "watchlist-details": "Шун теркама дагарченгахь йола  $1 {{PLURAL:$1|1=оагIув|оагIувнаш}}, дувцама оагIувнаш ца лоархIаш.",
        "deleteotherreason": "Кхыдола бахьан/тIатохар:",
        "deletereasonotherlist": "Кхыдола бахьан",
        "rollbacklink": "юхаяккха",
-       "protectlogpage": "Ð\9bоÑ\80адаÑ\80а тептар",
+       "protectlogpage": "Ð\93Iон тептар",
        "protectedarticle": "\"[[$1]]\" оагIув лорам деж я",
        "modifiedarticleprotection": "\"[[$1]]\" оагIувни лорама лагIа хувцаяьннай",
        "protectcomment": "Бахьан:",
        "invert": "Харжар юхадаккха",
        "namespace_association": "Ювзаенна мотт",
        "blanknamespace": "(Кертера)",
-       "contributions": "{{GENDER:$1|Доакъашхочунна}} къахьегам",
+       "contributions": "{{GENDER:$1|Доакъашхочун}} къахьегам",
        "contributions-title": "$1 дакъалаьцархочунна къахьегам",
        "mycontris": "Са къахьегам",
        "anoncontribs": "Къахьегам",
        "sp-contributions-toponly": "ТIехьара доржамаш лоархаш дола хувцамаш мара ма хьокха",
        "sp-contributions-submit": "Хьалáха",
        "whatlinkshere": "Хьожаяргаш укхаза",
-       "whatlinkshere-title": "\"$1\" тIа Iинкаш еш йола оагIувнаш",
+       "whatlinkshere-title": "\"$1\" тIахьожавеш йола оагIонаш",
        "whatlinkshere-page": "ОагIув",
-       "linkshere": "ТIехьайоагIа оагIувнаш тIахьожаву «'''[[:$1]]'''»:",
+       "linkshere": "ТIехьайоагIа оагIонаш тIахьожаву «'''[[:$1]]'''»:",
        "nolinkshere": "'''[[:$1]]''' оагIув тIа, кхыдола оагIувашкара Iинкаш йоацаш я",
-       "isredirect": "ТIаÑ\85Ñ\8cожадаÑ\80ан Ð¾Ð°Ð³IÑ\83в",
+       "isredirect": "оагIÑ\83в-дIа-Ñ\81аÑ\85Ñ\8cожадаÑ\80",
        "istemplate": "юкъейоалаяр",
-       "isimage": "Файлан хьожаярг",
+       "isimage": "Файлови Ñ\82Iахьожаярг",
        "whatlinkshere-prev": "{{PLURAL:$1|1=хьалхайоагIа|хьалхайоагIараш}} $1",
-       "whatlinkshere-next": "{{PLURAL:$1|1=тIехьайоагIа|тIехьайоагIараш}} $1",
-       "whatlinkshere-links": "← хьожаяргаш",
-       "whatlinkshere-hideredirs": "$1 дIа-хьа чуяьккхамаш",
-       "whatlinkshere-hidetrans": "$1 чуяьккхамаш",
-       "whatlinkshere-hidelinks": "$1 Iинкаш",
+       "whatlinkshere-next": "{{PLURAL:$1|1=тIехьайоагIар|тIехьайоагIараш}} $1",
+       "whatlinkshere-links": "â\86\90 Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80гаÑ\88",
+       "whatlinkshere-hideredirs": "ДIакъайладаккха дIа-сахьожадар",
+       "whatlinkshere-hidetrans": "ДIакъайладаккха юкъедахараш",
+       "whatlinkshere-hidelinks": "ДIакъайлаяккха тIахьожаяргаш",
        "whatlinkshere-hideimages": "$1 суртIинкаш",
-       "whatlinkshere-filters": "ЦIенÑ\8aеÑ\80аÑ\88",
+       "whatlinkshere-filters": "ФилÑ\8cÑ\82Ñ\80Ñ\8b",
        "blockip": "Укх {{GENDER:$1|доакъошхочоа}} ч|ега бола",
        "ipboptions": "2 сахьат:2 hours,1 ди:1 day,3 ди:3 days,1 кIира:1 week,2 кIира:2 weeks,1 бутт:1 month,3 бутт:3 months,6 бутт:6 months,1 шу:1 year,сиха ца луш:infinite",
        "ipblocklist": "ЧIега бела дакъалаьцархой",
        "movelogpage": "ЦIи хувцара тептар",
        "movereason": "Бахьан",
        "revertmove": "юхаяьккха",
-       "export": "ОагIувий эхфортам",
+       "export": "ОагIонай экспорт",
        "allmessagesname": "ЦIи",
        "allmessagesdefault": "Сатийна улла яздам",
        "allmessages-filter-all": "Дерригаш",
        "thumbnail-more": "Доккха де",
        "thumbnail_error": "ЗIамигасуртанчий кхеллама гIалат: $1",
        "import-upload-filename": "ПаьлацIи:",
-       "tooltip-pt-userpage": "{{GENDER:|Хьа}} доакъашхочунна оагIув",
-       "tooltip-pt-mytalk": "Шун дувцамий оагIув",
-       "tooltip-pt-preferences": "{{GENDER:|Ð¥Ñ\8cа Ð¾Ñ\82Ñ\82амаш}}",
-       "tooltip-pt-watchlist": "ОоагIувна дагарле, шо бIаргалокхаш йола",
-       "tooltip-pt-mycontris": "Шун хувцамаш",
+       "tooltip-pt-userpage": "{{GENDER:|Хьа}} доакъашхочун оагIув",
+       "tooltip-pt-mytalk": "{{GENDER:|Хьа}} дувца оттадара оагIув",
+       "tooltip-pt-preferences": "{{GENDER:|Ð¥Ñ\8cа Ð³IиÑ\80Ñ\81аш}}",
+       "tooltip-pt-watchlist": "Iа зем бу оагIонаш",
+       "tooltip-pt-mycontris": "{{GENDER:|хьа}} хувцамаш",
        "tooltip-pt-login": "Укхаза хьай цIи аьле чувала/яла йиша я, амма из параз дац",
        "tooltip-pt-logout": "Аравала/яла",
        "tooltip-pt-createaccount": "Хьа бокъо я учёта яздар кхелла система чу вала, амма параз долаш дац из.",
        "tooltip-ca-talk": "ОагIон чулоацам дувца оттадар",
        "tooltip-ca-edit": "Нийсъе ер оагIув",
        "tooltip-ca-addsection": "Керда дакъа хьаде",
-       "tooltip-ca-viewsource": "Ð\95Ñ\80 Ð¾Ð°Ð³IÑ\83в Ñ\85Ñ\83вÑ\86амаÑ\85 Ð»Ð¾Ñ\80аÑ\8fÑ\8c Ñ\8f, Ð°Ð¼Ð¼Ð° Ñ\88Ñ\83н Ñ\86Ñ\83нна Ð³IÑ\83вамага Ñ\85Ñ\8cажа бокъо я.",
+       "tooltip-ca-viewsource": "Ð\95Ñ\80 Ð¾Ð°Ð³IÑ\83в Ñ\85Ñ\83вÑ\86амбаÑ\80аÑ\85 Ð³Iо Ñ\82еÑ\85а (лоÑ\80аÑ\8f) Ñ\8f, Ð°Ð¼Ð¼Ð° Ñ\86Ñ\83нна Ð´IадолалÑ\83 Ñ\82екÑ\81Ñ\82ага Ñ\85Ñ\8cажа Ð°, Ð¸Ð· Ñ\82IеÑ\80Ñ\85Ñ\8cаÑ\8fзÑ\8aе а бокъо я.",
        "tooltip-ca-history": "Укх оагIон даь хувцамаш тIа дола тептар",
        "tooltip-ca-protect": "Eр оагIув лорае",
        "tooltip-ca-delete": "Ер оагIув дIаяькха",
        "tooltip-ca-move": "Укх оагIон цIи хувца",
-       "tooltip-ca-watch": "Ер оагIув хьай теркам беча каьхата тIа тIаяьккха",
+       "tooltip-ca-watch": "Ер оагIув Iайха зувш йолча оагIонашта юкъеяккха",
        "tooltip-ca-unwatch": "Ер оагIув теркам беча каьхата тIара дIаяькха",
        "tooltip-search": "Хьалáха {{grammar:prepositional|{{SITENAME}}}} чу",
        "tooltip-search-go": "Изза мо цӀи йолаш оагӀув тӀa дехьавала",
        "tooltip-t-recentchangeslinked": "Укх оагIуво тIахьожавеш йолча оагIонай тIеххьара хувцамаш",
        "tooltip-feed-rss": "Укх оагIувна RSSчу гойтар",
        "tooltip-feed-atom": "Укх оаг|увна Atomчу гойтар",
-       "tooltip-t-contributions": "{{GENDER:$1|Укх доакъашхочо хийца}} йола оагIувнаш",
+       "tooltip-t-contributions": "{{GENDER:$1|Укх доакъашхочо хийца}} йола оагIонаш",
        "tooltip-t-emailuser": "Укх дакъалаьцархочоа зIы яхьийта",
        "tooltip-t-upload": "Файлаш чуяккха",
        "tooltip-t-specialpages": "ГIулакха оагIувнаш",
        "tooltip-t-print": "Укх оагIон зарба тохара верси",
        "tooltip-t-permalink": "ОагIон укх версин тIахьожавеш йола даим латташ йола хьожаярг",
        "tooltip-ca-nstab-main": "ОагIон чурадар",
-       "tooltip-ca-nstab-user": "Ð\94акÑ\8aалаÑ\8cÑ\86аÑ\80Ñ\85оÑ\87Ñ\83нна Ñ\88ий оагIув",
+       "tooltip-ca-nstab-user": "Ð\94оакÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ñ\88е Ð´Ð¾Ð°Ð»Ð°Ñ\85Ñ\8c Ð¹Ð¾Ð»Ð° оагIув",
        "tooltip-ca-nstab-special": "Ер гIулакха оагIув я, из хувца бокъо яц",
        "tooltip-ca-nstab-project": "Проектан оагIув",
        "tooltip-ca-nstab-image": "Файлан оагӀув",
-       "tooltip-ca-nstab-template": "Ð\9aепан оагIув",
+       "tooltip-ca-nstab-template": "Ð\9bеÑ\80а оагIув",
        "tooltip-ca-nstab-help": "ГӀон оагIув",
        "tooltip-ca-nstab-category": "Категорий оагӀув",
        "tooltip-minoredit": "Ер хувцар башха доаца санна белгалде",
-       "tooltip-save": "Ð¥Ñ\83вÑ\86амаÑ\88 ÐºÑ\85оде",
-       "tooltip-preview": "Ð\9eагIÑ\83в Ñ\82Iа Ñ\85Ñ\8cалÑ\85е Ð±IаÑ\80гÑ\82аÑ\81Ñ\81аÑ\80, Ð´ÐµÑ\85аÑ\80 Ð´Ð°, Ð¾Ð°Ð³IÑ\83в Ð´IаÑ\8fзÑ\8aелаÑ\8cÑ\85, Ñ\86Ñ\83н Ñ\82еÑ\80кам Ð±Ðµ.",
-       "tooltip-diff": "Яздам Ñ\82Iа Ñ\8fÑ\8c Ð¹Ð¾Ð»Ð° хувцамаш хьахьокха",
+       "tooltip-save": "Ð¥Ñ\8cай Ñ\85Ñ\83вÑ\86амаÑ\88 Ð»Ð¾Ñ\80адеÑ\88 Ð´IаÑ\8fзде",
+       "tooltip-preview": "Ð\94еÑ\85аÑ\80 Ð´Ð°, Ð¾Ð°Ð³Ó\80Ñ\83в Ð»Ð¾Ñ\80аеÑ\88Ñ\8c Ð´IаÑ\8fзÑ\8aелеÑ\85Ñ\8c Ð¸Ð· Ð¼Ð¸Ñ\88Ñ\82а Ñ\8f Ñ\82аÑ\85ка Ñ\85Ñ\8cалÑ\85Ñ\85е Ñ\85Ñ\8cажаÑ\80аÑ\85 Ð¿Ð°Ð¹Ð´Ð° Ñ\8dÑ\86аÑ\88!",
+       "tooltip-diff": "Ð\94IадолалÑ\83 Ñ\82екÑ\81Ñ\82аÑ\86а Ð´Ð°Ñ\8c хувцамаш хьахьокха",
        "tooltip-compareselectedversions": "Укх оагIувни шин доржамаш тIа юкъера хувцамаш зе.",
        "tooltip-watch": "Ер оагIув теркам беча каьхата тIа яькха",
        "tooltip-rollback": "Цкъа пIелг тоIабе дIадаккха тIехьара редакторас даь хувцамаш",
-       "tooltip-undo": "Даь хувцар дIадаьккха, хьалххе хьажар хьахьокха, дIадаккхара бахьан Iочуязаде аьттув болаш.",
-       "tooltip-summary": "Лоаца чулоацам Iочуязаде",
+       "tooltip-undo": "Даь хувцар дIадаьккха, хьалххе бIаргтохар хьахьокха, дIадаккхара бахьан Iочуязде аьттув болаш.",
+       "tooltip-summary": "Лоаца йоазонца сурт оттадар Iочуязде",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|1=Къайла категори|Къайла категореш}} ($1)",
        "pageinfo-toolboxlink": "ОагIонах бола хоам",
        "previousdiff": "← Хьалхара нийсдар",
        "nextdiff": "ТIайоагIа нийсъар",
        "ilsubmit": "Хьалáха",
        "bad_image_list": "Бустам цу тайпара хила беза:\n\nДагарлен хьаракъаш мара лоарх|аш хургьяц (укх тамагIалгацa * дувлашду мугIараш).\nМугIарен хьалхара Iинк, сурт Iоттае пурам доаца Iинка, хила еза. \nЦу мугIара тIехьайоагIа Iинкаш, арадаккхар мо лоарх|аш хургья, вешта аьлча, йоазувашка чуIоттаде мегаш дола сурт санна ларх|а мега.",
        "metadata": "Метахоамаш",
-       "metadata-help": "Ð\9fаÑ\8cлаÑ\81 Ñ\87Ñ\83лоаÑ\86а, ÐºÑ\85Ñ\8bдола Ñ\85IамаÑ\88, Ñ\82аÑ\8cÑ\80аÑ\85Ñ\8cа Ñ\81Ñ\83Ñ\80Ñ\82доакÑ\85аÑ\80гÑ\86а Ðµ Ñ\82IагIолладоакÑ\85аÑ\80гÑ\86а Ñ\87Ñ\83дакÑ\85аÑ\88 Ð´Ð¾Ð»Ð°. Ð¥Ñ\8cаÑ\8fÑ\8c Ð¿Ð°Ñ\8cл, Ð³IалаÑ\82аÑ\85Ñ\8c Ð¼Ñ\83кÑ\8aадаÑ\8cкÑ\85а Ñ\85инна Ð´Ð°Ð»Ðµ, Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85аÑ\88 Ð´Ð¾Ð»Ð° Ñ\81Ñ\83Ñ\80Ñ\82, Ð´ÐµÑ\80Ñ\80ига Ñ\85IамаÑ\88 Ñ\87Ñ\83лоаÑ\86аÑ\80гдаÑ\86.",
+       "metadata-help": "ФайлаÑ\81 ÐºÑ\85Ñ\8bдола Ñ\85оамаÑ\88 Ñ\87Ñ\83лоаÑ\86а, Ñ\86иÑ\84Ñ\80овой Ñ\81Ñ\83Ñ\80Ñ\82доакÑ\85аÑ\80го Ðµ Ñ\81канеÑ\80о Ñ\82IаÑ\82оÑ\85аÑ\88 Ð´Ð¾Ð»Ð°. Ð\9dагаÑ\85Ñ\8cа Ñ\84айл Ñ\87Ñ\83Ñ\8fкÑ\85аÑ\87Ñ\83л Ñ\82IеÑ\85Ñ\8cа Ñ\85ийÑ\86а Ñ\85инна Ð´Ð°Ð»Ðµ, Ñ\86Ñ\85Ñ\8cаÑ\86Ñ\86айола Ð¿Ð°Ñ\80амеÑ\82Ñ\80аÑ\88 Ñ\85IанзаÑ\80а Ñ\81Ñ\83Ñ\80Ñ\82а Ñ\82аÑ\80а Ð¹Ð¾Ð°Ñ\86аÑ\88 Ñ\85ила Ð¼ÐµÐ³Ð°Ñ\88 Ñ\8f.",
        "metadata-expand": "Кхыдола хIамаш хьахьокха",
        "metadata-collapse": "Кхыдола хIамаш къайладаккха",
        "metadata-fields": "Укх списке дагaрадаь суртай метахоамай йистош, хьахьекха хургда суртан оагIон тIа, метахоамай таблица хьоарчая йолаш. Юхедиса йистош къайла хургда.\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-orientation": "Суртан белгало",
+       "exif-orientation": "Сурта белгало",
        "exif-imagedescription": "Сурта цIи",
        "exif-model": "Камера модель",
        "exif-software": "Программни Iалашдар",
        "exif-artist": "Яздархо",
        "exif-exifversion": "Верси Exif",
-       "exif-colorspace": "Ð\91аÑ\81аÑ\80а Ð°Ñ\80е",
+       "exif-colorspace": "Ð\91еÑ\81ай Ð¼Ð¾Ñ\82Ñ\82",
        "exif-pixelxdimension": "Сурта шорал",
        "exif-pixelydimension": "Сурта лакхал",
        "exif-datetimedigitized": "Оцифровк яь таьрахь а, ха а",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|дувца оттадар]])",
        "duplicate-defaultsort": "Зем бе. Сатийна дIа-хьа хьоржама доагI \"$2\" хьалхара сатийна дIа-хьа хьоржама доагI \"$1\" хьахьоржа.",
        "version": "Доржам",
-       "version-specialpages": "Ð\93\83лакÑ\85ий Ð¾Ð°Ð³IÑ\83внаш",
+       "version-specialpages": "Ð\91алÑ\85а Ð¾Ð°Ð³Ó\80онаш",
        "version-version": "($1)",
        "version-software-version": "Доржам",
        "fileduplicatesearch-filename": "ПаьлацIи:",
index ff60316..3da7522 100644 (file)
        "noname": "Vu ne donis valida uzantonomo.",
        "loginsuccesstitle": "Eniro sucesoza",
        "loginsuccess": "'''Vu eniris a {{SITENAME}} kom \"$1\".'''",
-       "nosuchuser": "Ne existas uzanto \"$1\".\nUzanto-nomi esas mayu/minuskulo-distingenda.\nKontrolez vua espelado, o [[Special:UserLogin/signup|krear nova konto]].",
+       "nosuchuser": "Ne existas uzanto \"$1\".\nUzanto-nomi esas mayu/minuskulo-distingenda.\nKontrolez vua espelado, o [[Special:CreateAccount|krear nova konto]].",
        "nosuchusershort": "Esas nula uzanto \"$1\".\nKontrolez la espelado.",
        "nouserspecified": "Vu mustas specigar uzantonomo.",
        "wrongpassword": "La skribita pasovorto esis nekorekta. Voluntez probar itere.",
index 8e3c46f..8908443 100644 (file)
        "noname": "Þú hefur ekki tilgreint gilt notandanafn.",
        "loginsuccesstitle": "Innskráning tókst",
        "loginsuccess": "'''Þú ert nú innskráð(ur) á {{SITENAME}} sem „$1“.'''",
-       "nosuchuser": "Það er enginn notandi með þetta nafn: \"$1\".\nGerður er greinarmunur á há- og lágstöfum.\nAthugaðu hvort um innsláttavillu er að ræða eða [[Special:UserLogin/signup|búðu til nýtt notandanafn]].",
+       "nosuchuser": "Það er enginn notandi með þetta nafn: \"$1\".\nGerður er greinarmunur á há- og lágstöfum.\nAthugaðu hvort um innsláttavillu er að ræða eða [[Special:CreateAccount|búðu til nýtt notandanafn]].",
        "nosuchusershort": "Það er enginn notandi með nafnið „$1“. Athugaðu hvort nafnið sé ritað rétt.",
        "nouserspecified": "Þú verður að taka fram notandanafn.",
        "login-userblocked": "Þessi notandi hefur verið settur í bann.  Innskráning ekki leyfð.",
        "noemail": "Það er ekkert netfang skráð fyrir notandan \"$1\".",
        "noemailcreate": "Þú verður að skrá gilt netfang",
        "passwordsent": "Nýtt lykilorð var sent á netfangið sem er skráð á „$1“.\nSkráðu þig inn á ný þegar þú hefur móttekið það.",
-       "blocked-mailpassword": "Þér er ekki heimilt að gera breytingar frá þessu netfangi og  því getur þú ekki fengið nýtt lykilorð í pósti.  Þetta er gert til þess að koma í veg fyrir skemmdarverk.",
+       "blocked-mailpassword": "IP-vistfangið þitt hefur verið útilokað frá því að gera breytingar. Til þess að koma í veg fyrir misnotkun er því ekki hægt að nýta sér endurheimtingu lykilorðs frá þessu IP-vistfangi.",
        "eauthentsent": "Staðfestingarpóstur hefur verið sendur á uppgefið netfang. Þú verður að fylgja leiðbeiningunum í póstinum til þess að virkja netfangið og staðfesta að það sé örugglega þitt.",
        "throttled-mailpassword": "Tölvupóstur til að endursetja lykilorðið hefur þegar verið sent, innan við $1 {{PLURAL:$1|síðasta klukkutímans|síðustu klukkutímanna}}.\nTil að koma í veg fyrir misnotkun, er aðeins einn tölvupóstur sendur {{PLURAL:$1|hvern $1 klukkutíma|hverja $1 klukkutíma}}.",
        "mailerror": "Upp kom villa við sendingu tölvupósts: $1",
        "resetpass_submit": "Skrifaðu aðgangsorðið og skráðu þig inn",
        "changepassword-success": "Það tókst að breyta lykilorðinu þínu!",
        "changepassword-throttled": "Þú hefur gert of margar tilraunir til innskráningar að undanförnu.\nBíddu í $1 áður en þú reynir aftur.",
+       "botpasswords": "Lykilorð róbóta",
        "botpasswords-label-create": "Búa til",
        "botpasswords-label-update": "Uppfæra",
        "botpasswords-label-cancel": "Hætta við",
        "minoredit": "Þetta er minniháttar breyting",
        "watchthis": "Vakta þessa síðu",
        "savearticle": "Vista síðu",
+       "publishpage": "Gefa út síðu",
        "preview": "Forskoða",
        "showpreview": "Forskoða",
        "showdiff": "Sýna breytingar",
        "accmailtext": "Lykilorðið fyrir [[User talk:$1|$1]] hefur verið sent á $2. Hægt er að breyta því á síðunni ''[[Special:ChangePassword|breyta lykilorði]]'' þegar notandinn hefur skráð sig inn.",
        "newarticle": "(Ný)",
        "newarticletext": "Þú hefur fylgt tengli á síðu sem ekki er til ennþá.\nÞú getur búið til síðu með þessu nafni með því að skrifa í formið fyrir neðan\n(meiri upplýsingar í [$1 hjálpinni]).\nEf þú hefur óvart villst hingað geturðu notað '''til baka'''-hnappinn í vafranum þínum.",
-       "anontalkpagetext": "----''Þetta er spjallsíða fyrir óþekktan notanda sem hefur ekki búið til aðgang ennþá, eða notar hann ekki.\nÞar af leiðandi þurfum við að nota vistfang til að bera kennsli á hann/hana.\nNokkrir notendur geta deilt sama vistfangi.\nEf þú ert óþekktur notandi og finnst að óviðkomandi athugasemdum hafa verið beint að þér, gjörðu svo vel og [[Special:UserLogin/signup|búðu til aðgang]] eða [[Special:UserLogin|skráðu þig inn]] til þess að koma í veg fyrir þennan rugling við aðra óþekkta notendur í framtíðinni.''",
+       "anontalkpagetext": "----''Þetta er spjallsíða fyrir óþekktan notanda sem hefur ekki búið til aðgang ennþá, eða notar hann ekki.\nÞar af leiðandi þurfum við að nota vistfang til að bera kennsli á hann/hana.\nNokkrir notendur geta deilt sama vistfangi.\nEf þú ert óþekktur notandi og finnst að óviðkomandi athugasemdum hafa verið beint að þér, gjörðu svo vel og [[Special:CreateAccount|búðu til aðgang]] eða [[Special:UserLogin|skráðu þig inn]] til þess að koma í veg fyrir þennan rugling við aðra óþekkta notendur í framtíðinni.''",
        "noarticletext": "Enginn texti er á þessari síðu enn sem komið er.\nÞú getur [[Special:Search/{{PAGENAME}}|leitað að þessum titli]], í öðrum síðum,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} leitað í tengdum skrám], eða [{{fullurl:{{FULLPAGENAME}}|action=edit}} búið hana til]</span>.",
        "noarticletext-nopermission": "Það er enginn texti á þessari síðu eins og er.\nÞú getur [[Special:Search/{{PAGENAME}}|leitað að þessum titli]] í öðrum síðum, eða <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} leitað í tengdum skrám]</span>, en þú hefur ekki réttindi til þess að stofna þessa síðu.",
        "missing-revision": "Útgáfa #$1 síðunnar „{{FULLPAGENAME}}\" er ekki til.\n\nÞetta gerist oftast þegar úreld breytingaskrá tengir á síðu sem hefur verið eytt. Frekari upplýsingar eru í [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} eyðingarskránni].",
        "upload-form-label-infoform-description-tooltip": "Lýstu stuttlega öllu því sem er markvert um verkið.\nFyrir ljósmyndir, nefndu aðalatriði myndarinnar, tilefni eða staðsetningu.",
        "upload-form-label-usage-title": "Notkun",
        "upload-form-label-usage-filename": "Skráarheiti",
-       "foreign-structured-upload-form-label-own-work": "Það er mitt eigið verk",
-       "foreign-structured-upload-form-label-infoform-categories": "Flokkar",
-       "foreign-structured-upload-form-label-infoform-date": "Dagsetning",
-       "foreign-structured-upload-form-label-own-work-message-local": "Ég skil að ég sé að hlaða inn skrá samkvæmt notkunarskilmálum og leyfisskilmálum {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Ef þú getur ekki hlaðið inn þessari skrá samkvæmt reglum {{SITENAME}}, lokaðu þá þessum glugga og reyndu aðra aðferð.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Þú gætir einnig prófað að nota [[Special:Upload|sjálfgefnu innhleðslusíðuna]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Ég skil að ég sé að hlaða inn skrá á sameiginlegt vefsvæði. Ég staðfesti að ég sé að gera það samkvæmt notkunarskilmálum og leyfisskilmálum þess.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Ef þú getur ekki hlaðið inn þessari skrá samkvæmt reglum sameiginlega vefsvæðisins, lokaðu þá þessum glugga og reyndu aðra aðferð.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Þú gætir einnig prófað að nota [[Special:Upload|innhleðslusíðuna á {{SITENAME}}]], ef það má hlaða þessari skrá inn samkvæmt reglum þeirra.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Ég staðfesti að ég eigi höfundarréttinn að þessari skrá og samþykki óafturkræft að gefa þessa skrá til Wikimedia Commons undir  [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] leyfi. Ég samþykki [https://wikimediafoundation.org/wiki/Terms_of_Use notendaskilmálana].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Ef þú átt ekki höfundarréttinn að þessari skrá, eða þú vilt gefa það út undir öðru leyfi, prófaðu  [https://commons.wikimedia.org/wiki/Special:UploadWizard Innsendingaálfinn á Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Þú gætir einnig prófað að nota [[Special:Upload|innhleðslusíðuna á {{SITENAME}}]], ef það má hlaða þessari skrá inn samkvæmt reglum þeirra.",
+       "upload-form-label-own-work": "Það er mitt eigið verk",
+       "upload-form-label-infoform-categories": "Flokkar",
+       "upload-form-label-infoform-date": "Dagsetning",
+       "upload-form-label-own-work-message-generic-local": "Ég skil að ég sé að hlaða inn skrá samkvæmt notkunarskilmálum og leyfisskilmálum {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Ef þú getur ekki hlaðið inn þessari skrá samkvæmt reglum {{SITENAME}}, lokaðu þá þessum glugga og reyndu aðra aðferð.",
+       "upload-form-label-not-own-work-local-generic-local": "Þú gætir einnig prófað að nota [[Special:Upload|sjálfgefnu innhleðslusíðuna]].",
+       "upload-form-label-own-work-message-generic-foreign": "Ég skil að ég sé að hlaða inn skrá á sameiginlegt vefsvæði. Ég staðfesti að ég sé að gera það samkvæmt notkunarskilmálum og leyfisskilmálum þess.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Ef þú getur ekki hlaðið inn þessari skrá samkvæmt reglum sameiginlega vefsvæðisins, lokaðu þá þessum glugga og reyndu aðra aðferð.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Þú gætir einnig prófað að nota [[Special:Upload|innhleðslusíðuna á {{SITENAME}}]], ef það má hlaða þessari skrá inn samkvæmt reglum þeirra.",
        "backend-fail-stream": "Gat ekki streymt skránni „$1“.",
        "backend-fail-backup": "Öryggisafritun skrárinnar $1 mistókst.",
        "backend-fail-notexists": "Skráin $1 er ekki til.",
index 1277ab6..330e083 100644 (file)
        "noname": "Il nome utente indicato non è valido.",
        "loginsuccesstitle": "Accesso effettuato",
        "loginsuccess": "'''Sei stato connesso al server di {{SITENAME}} con il nome utente di \"$1\".'''",
-       "nosuchuser": "Non è registrato alcun utente di nome \"$1\".\nI nomi utente sono sensibili alle maiuscole.\nVerificare il nome inserito o [[Special:UserLogin/signup|creare una nuova utenza]].",
+       "nosuchuser": "Non è registrato alcun utente di nome \"$1\".\nI nomi utente sono sensibili alle maiuscole.\nVerificare il nome inserito o [[Special:CreateAccount|creare una nuova utenza]].",
        "nosuchusershort": "Non è registrato alcun utente di nome \"$1\". Verificare il nome inserito.",
        "nouserspecified": "È necessario specificare un nome utente.",
        "login-userblocked": "Questa utenza è bloccata. Non è possibile effettuare il login.",
        "botpasswords-invalid-name": "Il nome utente indicato non contiene il separatore per password bot (\"$1\").",
        "botpasswords-not-exist": "L'utente \"$1\" non dispone di una password bot chiamata \"$2\".",
        "resetpass_forbidden": "Non è possibile modificare le password",
+       "resetpass_forbidden-reason": "Non è possibile modificare le password: $1",
        "resetpass-no-info": "Devi aver effettuato l'accesso per accedere a questa pagina direttamente.",
        "resetpass-submit-loggedin": "Cambia password",
        "resetpass-submit-cancel": "Annulla",
        "accmailtext": "Una password generata casualmente per [[User talk:$1|$1]] è stata inviata a $2. Questa password può essere modificata nella pagina per ''[[Special:ChangePassword|cambiare la password]]'' subito dopo l'accesso.",
        "newarticle": "(Nuovo)",
        "newarticletext": "Il collegamento appena seguito corrisponde ad una pagina non ancora esistente.\nSe vuoi creare la pagina ora, basta cominciare a scrivere il testo nella casella qui sotto (vedi la [$1 pagina di aiuto] per maggiori informazioni).\nSe il collegamento è stato aperto per errore, è sufficiente fare clic sul pulsante <strong>Indietro</strong> del proprio browser.",
-       "anontalkpagetext": "----\n''Questa è la pagina di discussione di un utente anonimo, che non ha ancora creato un'utenza o comunque non la sta usando. Per identificarlo è quindi necessario usare il numero del suo indirizzo IP. Gli indirizzi IP possono però essere condivisi da più utenti. Se sei un utente anonimo e ritieni che i commenti presenti in questa pagina non si riferiscano a te, [[Special:UserLogin/signup|crea una nuova utenza]] o [[Special:UserLogin|entra con quella che già hai]] per evitare di essere confuso con altri utenti anonimi in futuro.''",
+       "anontalkpagetext": "----\n<em>Questa è la pagina di discussione di un utente anonimo, che non ha ancora creato un'utenza o comunque non la sta usando.</em>\nPer identificarlo è quindi necessario usare il numero del suo indirizzo IP.\nGli indirizzi IP possono però essere condivisi da più utenti.\nSe sei un utente anonimo e ritieni che i commenti presenti in questa pagina non si riferiscano a te, [[Special:CreateAccount|crea una nuova utenza]] o [[Special:UserLogin|entra con quella che già hai]] per evitare di essere confuso con altri utenti anonimi in futuro.",
        "noarticletext": "In questo momento la pagina richiesta è vuota.\nPuoi [[Special:Search/{{PAGENAME}}|cercare questo titolo]] nelle altre pagine del sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercare nei registri correlati] oppure [{{fullurl:{{FULLPAGENAME}}|action=edit}} creare questa pagina]</span>.",
        "noarticletext-nopermission": "In questo momento la pagina richiesta è vuota. È possibile [[Special:Search/{{PAGENAME}}|cercare questo titolo]] nelle altre pagine del sito o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercare nei registri correlati]</span>, ma non hai i permessi per creare questa pagina.",
        "missing-revision": "La versione #$1 della pagina \"{{FULLPAGENAME}}\" non esiste.\n\nQuesto si verifica solitamente seguendo un collegamento a una pagina cancellata, in una cronologia non aggiornata.\nI dettagli possono essere trovati nel [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro delle cancellazioni].",
        "right-override-export-depth": "Esporta le pagine includendo le pagine collegate fino ad una profondità di 5",
        "right-sendemail": "Invia email ad altri utenti",
        "right-passwordreset": "Vede i messaggi di reimpostazione della password",
-       "right-managechangetags": "Crea ed elimina dal database i [[Special:Tags|tag]]",
+       "right-managechangetags": "Crea e attiva/disattiva le [[Special:Tags|etichette]]",
        "right-applychangetags": "Applica delle [[Special:Tags|etichette]] alle proprie modifiche",
        "right-changetags": "Aggiunge e rimuove specifiche [[Special:Tags|etichette]] su singole versioni o voci di registro",
+       "right-deletechangetags": "Cancella le [[Special:Tags|etichette]] dal database",
        "grant-generic": "Pacchetto diritti \"$1\"",
        "grant-group-page-interaction": "Interagisce con le pagine",
        "grant-group-file-interaction": "Interagisce con i file multimediali",
        "action-viewmyprivateinfo": "vedere i propri dati personali",
        "action-editmyprivateinfo": "modificare i propri dati personali",
        "action-editcontentmodel": "modificare il modello di contenuto di una pagina",
-       "action-managechangetags": "crea ed elimina i tag dal database",
+       "action-managechangetags": "creare e attivare/disattivare le etichette",
        "action-applychangetags": "applicare delle etichette alle tue modifiche",
        "action-changetags": "aggiungere o rimuovere specifiche etichette su singole versioni o voci di registro",
+       "action-deletechangetags": "cancellare le etichette dal database",
        "nchanges": "$1 {{PLURAL:$1|modifica|modifiche}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|dall'ultima visita}}",
        "enhancedrc-history": "cronologia",
        "upload-form-label-infoform-description-tooltip": "Descrivi sinteticamente tutto quanto sia degno di nota a proposito di quest'opera.\nPer le foto, indica le cose principali che vi sono rappresentate, l'occasione e/o il luogo in cui sono state scattate.",
        "upload-form-label-usage-title": "Utilizzo",
        "upload-form-label-usage-filename": "Nome del file",
-       "foreign-structured-upload-form-label-own-work": "Questo è un mio lavoro",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorie",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confermo che sto caricando questo file seguendo le condizioni di servizio e le politiche sulle license di {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Se non si è in grado di caricare il file in base alle politiche di {{SITENAME}}, chiudi questa finestra e prova un altro metodo.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Puoi anche provare la [[Special:Upload|pagina di caricamento predefinita]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Ho capito che sto caricando questo file in un archivio condiviso. Confermo che lo sto facendo secondo le condizioni di servizio e le politiche sulle license di lì.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Se non si è in grado di caricare il file in base alle politiche dell'archivio condiviso, chiudi questa finestra e prova un altro metodo.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Puoi anche provare ad usare la [[Special:Upload|pagina di caricamento su {{SITENAME}}]], se questo file può essere caricato in base alle sue politiche.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Attesto che possiedo i diritti d'autore su questo file, e accetto irrevocabilmente il rilascio di questo file su Wikimedia Commons in base alla licenza [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribuzione-Condividi allo stesso modo 4.0], e accetto le [https://wikimediafoundation.org/wiki/Terms_of_Use Condizioni d'uso].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Se non possiedi i diritti d'autore su questo file, o se lo vuoi rilasciare con una licenza diversa, usa il [https://commons.wikimedia.org/wiki/Special:UploadWizard caricamento guidato di Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Puoi anche provare ad usare la [[Special:Upload|pagina di caricamento su {{SITENAME}}]], se il sito consente il caricamento di questo file in base alle sue politiche.",
+       "upload-form-label-own-work": "Questo è un mio lavoro",
+       "upload-form-label-infoform-categories": "Categorie",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Confermo che sto caricando questo file seguendo le condizioni di servizio e le politiche sulle license di {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Se non si è in grado di caricare il file in base alle politiche di {{SITENAME}}, chiudi questa finestra e prova un altro metodo.",
+       "upload-form-label-not-own-work-local-generic-local": "Puoi anche provare la [[Special:Upload|pagina di caricamento predefinita]].",
+       "upload-form-label-own-work-message-generic-foreign": "Ho capito che sto caricando questo file in un archivio condiviso. Confermo che lo sto facendo secondo le condizioni di servizio e le politiche sulle license di lì.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se non si è in grado di caricare il file in base alle politiche dell'archivio condiviso, chiudi questa finestra e prova un altro metodo.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Puoi anche provare ad usare la [[Special:Upload|pagina di caricamento su {{SITENAME}}]], se questo file può essere caricato in base alle sue politiche.",
        "backend-fail-stream": "Impossibile trasmettere il file $1.",
        "backend-fail-backup": "Impossibile eseguire il backup del file $1 .",
        "backend-fail-notexists": "Il file $1 non esiste.",
        "changecontentmodel-success-text": "Il tipo di contenuto di [[:$1]] è stato modificato.",
        "changecontentmodel-cannot-convert": "Il contenuto di [[:$1]] non può essere convertito in tipo $2.",
        "changecontentmodel-nodirectediting": "Il modello di contenuto $1 non supporta la modifica diretta",
+       "changecontentmodel-emptymodels-title": "Nessun modello di contenuto disponibile",
+       "changecontentmodel-emptymodels-text": "Il contenuto di [[:$1]] non può essere convertito in alcun tipo.",
        "log-name-contentmodel": "Modifiche del modello contenuti",
        "log-description-contentmodel": "Eventi relativi al modello di contenuto di una pagina",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|ha creato}} la pagina $3 utilizzando un modello di contenuto non predefinito \"$5\"",
        "whatlinkshere-prev": "{{PLURAL:$1|precedente|precedenti $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|successivo|successivi $1}}",
        "whatlinkshere-links": "← collegamenti",
-       "whatlinkshere-hideredirs": "$1 redirect",
-       "whatlinkshere-hidetrans": "$1 inclusioni",
-       "whatlinkshere-hidelinks": "$1 collegamenti",
-       "whatlinkshere-hideimages": "$1 link da file",
+       "whatlinkshere-hideredirs": "Nascondi redirect",
+       "whatlinkshere-hidetrans": "Nascondi inclusioni",
+       "whatlinkshere-hidelinks": "Nascondi collegamenti",
+       "whatlinkshere-hideimages": "Nascondi collegamenti da file",
        "whatlinkshere-filters": "Filtri",
        "whatlinkshere-submit": "Vai",
        "autoblockid": "Autoblocco #$1",
        "lockdbsuccesstext": "Il database è stato bloccato.<br />\nRicordare di [[Special:UnlockDB|rimuovere il blocco]] dopo aver terminato le operazioni di manutenzione.",
        "unlockdbsuccesstext": "Il database è stato sbloccato.",
        "lockfilenotwritable": "Impossibile scrivere sul file di ''lock'' del database. L'accesso in scrittura a tale file da parte del server web è necessario per bloccare e sbloccare il database.",
+       "databaselocked": "Il database è già bloccato.",
        "databasenotlocked": "Il database non è bloccato.",
        "lockedbyandtime": "(da $1 il $2 alle $3)",
        "move-page": "Spostamento di $1",
        "autoredircomment": "Redirect alla pagina [[$1]]",
        "autosumm-new": "Creata pagina con \"$1\"",
        "autosumm-newblank": "Creata pagina vuota",
-       "size-bytes": "$1 byte",
+       "size-bytes": "$1 {{PLURAL:$1|byte}}",
        "lag-warn-normal": "Le modifiche apportate {{PLURAL:$1|nell'ultimo secondo|negli ultimi $1 secondi}} potrebbero non apparire in questa lista.",
        "lag-warn-high": "A causa di un eccessivo ritardo nell'aggiornamento del server di database, le modifiche apportate {{PLURAL:$1|nell'ultimo secondo|negli ultimi $1 secondi}} potrebbero non apparire in questa lista.",
        "watchlistedit-normal-title": "Modifica osservati speciali",
        "tags-activate": "attiva",
        "tags-deactivate": "disattiva",
        "tags-hitcount": "$1 {{PLURAL:$1|modifica|modifiche}}",
-       "tags-manage-no-permission": "Non hai il permesso di gestire il cambiamento tag.",
+       "tags-manage-no-permission": "Non si dispone dei permessi necessari per gestire le etichette di modifica.",
        "tags-manage-blocked": "Non puoi gestire le etichette alle modifiche mentre sei bloccato.",
        "tags-create-heading": "Crea un nuovo tag",
        "tags-create-explanation": "Per impostazione predefinita, i tag appena creati saranno disponibili per l'utilizzo di utenti e bot.",
        "tags-delete-not-found": "Il tag \"$1\" non esiste.",
        "tags-delete-too-many-uses": "Il tag \"$1\" è applicato a più di $2 {{PLURAL:$2|revisione|revisioni}}, il che significa che non può essere eliminato.",
        "tags-delete-warnings-after-delete": "L'etichetta \"$1\" è stata cancellata, ma fai attenzione {{PLURAL:$2|al seguente avviso|ai seguenti avvisi}}:",
+       "tags-delete-no-permission": "Non si dispone dei permessi necessari per cancellare le etichette di modifica.",
        "tags-activate-title": "Attiva tag",
        "tags-activate-question": "Stai per attivare il tag \"$1\".",
        "tags-activate-reason": "Motivo:",
        "tags-apply-blocked": "Non puoi applicare le etichette alle modifiche mentre sei bloccato.",
        "tags-apply-not-allowed-one": "L'etichetta \"$1\" non può essere applicata manualmente.",
        "tags-apply-not-allowed-multi": "{{PLURAL:$2|La seguente etichetta non può essere applicata|Le seguenti etichette non possono essere applicate}}  manualmente: $1",
-       "tags-update-no-permission": "Non hai il permesso di aggiungere o rimuovere modifiche di tag dalle singole revisioni o voci di registro.",
+       "tags-update-no-permission": "Non si dispone dei permessi necessari per aggiungere o rimuovere le etichette di modifica dalle singole versioni o voci di registro.",
        "tags-update-blocked": "Non puoi aggiungere o rimuovere le etichette alle modifiche mentre sei bloccato.",
        "tags-update-add-not-allowed-one": "Il tag \"$1\" non può essere aggiunto manualmente.",
        "tags-update-add-not-allowed-multi": "{{PLURAL:$2|Il seguente tag non può essere aggiunto|I seguenti tag non possono essere aggiunti}} manualmente: $1",
        "feedback-useragent": "Agente utente:",
        "searchsuggest-search": "Ricerca",
        "searchsuggest-containing": "contenente...",
+       "api-error-autoblocked": "Il tuo indirizzo IP è stato bloccato automaticamente, perché è stato utilizzato da un utente bloccato.",
        "api-error-badaccess-groups": "Non sei autorizzato a caricare documenti su questa wiki.",
        "api-error-badtoken": "Errore interno: token errato.",
+       "api-error-blocked": "Sei stato bloccato, non puoi fare modifiche.",
        "api-error-copyuploaddisabled": "Il caricamento tramite URL è disabilitato su questo server.",
        "api-error-duplicate": "Sul sito {{PLURAL:$1|c'è già un altro documento|ci sono già altri documenti}} con lo stesso contenuto.",
        "api-error-duplicate-archive": "{{PLURAL:$1|C'era un altro file|C'erano altri file}} già nel sito con lo stesso contenuto, ma {{PLURAL:$1|è stato cancellato|sono stati cancellati}}.",
        "api-error-nomodule": "Errore interno: non è stato impostato il modulo di caricamento.",
        "api-error-ok-but-empty": "Errore interno: nessuna risposta dal server.",
        "api-error-overwrite": "Sovrascrivere un file esistente non è consentito.",
+       "api-error-ratelimited": "Stai cercando di caricare più file in meno tempo di quanto questo wiki permette.\nRiprova tra pochi minuti.",
        "api-error-stashfailed": "Errore interno: il server non è riuscito a memorizzare il documento temporaneo.",
        "api-error-publishfailed": "Errore interno: il server non è riuscito a pubblicare il documento temporaneo.",
        "api-error-stasherror": "Si è verificato un errore durante il caricamento del file in stash.",
        "log-action-filter-suppress-block": "Soppressione utente da blocco",
        "log-action-filter-suppress-reblock": "Soppressione utente da ri-blocco",
        "log-action-filter-upload-upload": "Nuovo caricamento",
-       "log-action-filter-upload-overwrite": "Ricaricamento"
+       "log-action-filter-upload-overwrite": "Ricaricamento",
+       "authmanager-userdoesnotexist": "L'utenza \"$1\" non è registrata.",
+       "authmanager-email-help": "Indirizzo email",
+       "authmanager-realname-help": "Nome reale dell'utente",
+       "authform-newtoken": "Token mancante. $1",
+       "changecredentials-submit-cancel": "Annulla",
+       "removecredentials-submit": "Rimuovi",
+       "removecredentials-submit-cancel": "Annulla"
 }
index 1546fb8..4f85d2c 100644 (file)
@@ -69,7 +69,8 @@
                        "Sujiniku",
                        "Azeha",
                        "Kana Higashikawa",
-                       "Shield-9"
+                       "Shield-9",
+                       "Waiesu"
                ]
        },
        "tog-underline": "リンクの下線:",
        "tog-ccmeonemails": "他の利用者に送信したメールの控えを自分にも送信",
        "tog-diffonly": "差分の下にページ内容を表示しない",
        "tog-showhiddencats": "隠しカテゴリを表示",
-       "tog-norollbackdiff": "巻き戻し後の差分を表示しない",
+       "tog-norollbackdiff": "ロールバック後の差分を表示しない",
        "tog-useeditwarning": "変更を保存せずに編集画面から離れようとしたら警告",
        "tog-prefershttps": "ログインする際、常に安全な接続を使用する",
        "underline-always": "常に付ける",
        "noname": "有効な利用者名が指定されていません。",
        "loginsuccesstitle": "ログイン済み",
        "loginsuccess": "<strong>{{SITENAME}}に「$1」としてログインしました。</strong>",
-       "nosuchuser": "「$1」という名前の利用者は見当たりません。\n利用者名では大文字と小文字を区別します。\n綴りが正しいことを確認するか、[[Special:UserLogin/signup|新たにアカウントを作成]]してください。",
+       "nosuchuser": "「$1」という名前の利用者は見当たりません。\n利用者名では大文字と小文字を区別します。\n綴りが正しいことを確認するか、[[Special:CreateAccount|新たにアカウントを作成]]してください。",
        "nosuchusershort": "「$1」という名前の利用者は存在しません。\n綴りを確認してください。",
        "nouserspecified": "利用者名を指定してください。",
        "login-userblocked": "この利用者はブロックされています。ログインは拒否されます。",
        "minoredit": "細部の編集",
        "watchthis": "このページをウォッチ",
        "savearticle": "ページを保存",
+       "publishpage": "ページを公開",
        "preview": "プレビュー",
        "showpreview": "プレビューを表示",
        "showdiff": "差分を表示",
        "accmailtext": "[[User talk:$1|$1]]のために無作為に生成したパスワードを、$2に送信しました。パスワードは、ログインした際に<em>[[Special:ChangePassword|パスワード変更]]</em>ページで変更できます。",
        "newarticle": "(新)",
        "newarticletext": "まだ存在しないページへのリンクをたどりました。\nこのページを新規作成するには、ページの内容を以下のボックスに記入してください (詳しくは[$1 ヘルプページ]を参照してください)。\n誤ってこのページにたどり着いた場合には、ブラウザーの<strong>戻る</strong>ボタンで前のページに戻ってください。",
-       "anontalkpagetext": "----\n<em>このページはアカウントをまだ作成していないか使用していない匿名利用者のための議論ページです。</em>\n\n匿名利用者を識別するために、利用者名の代わりにIPアドレスが使用されています。IP アドレスは複数の利用者で共有されている場合があります。もし、あなたが匿名利用者であり、自分に関係のないコメントが寄せられていると考えられる場合は、[[Special:UserLogin/signup|アカウントを作成する]]か[[Special:UserLogin|ログインして]]他の匿名利用者と間違えられないようにしてください。",
+       "anontalkpagetext": "----\n<em>このページはアカウントをまだ作成していないか使用していない匿名利用者のための議論ページです。</em>\n\n匿名利用者を識別するために、利用者名の代わりにIPアドレスが使用されています。IP アドレスは複数の利用者で共有されている場合があります。もし、あなたが匿名利用者であり、自分に関係のないコメントが寄せられていると考えられる場合は、[[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}}|このページ名を検索]]、または<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 関連する記録を検索]</span>できますが、あなたにはこのページを作成する権限がありません。",
        "missing-revision": "「{{FULLPAGENAME}}」というページの版番号 $1 の版は存在しません。\n\n通常、削除されたページの版への古い差分表示や固定リンクをたどった際に、このようなことが起きます。 \n詳細は[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 削除記録]を参照してください。",
        "content-model-css": "CSS",
        "content-json-empty-object": "空のオブジェクト",
        "content-json-empty-array": "空の配列",
-       "duplicate-args-warning": "<strong>警告:</strong> [[:$1]]は「$3」パラメータの値が複数存在する[[:$2]]を呼び出しています。提供されている最後の値のみが使用されます。",
+       "duplicate-args-warning": "<strong>警告:</strong> [[:$1]]は複数の「$3」パラメータを伴って[[:$2]]を呼び出しています。提供されている最後の値のみが使用されます。",
        "duplicate-args-category": "テンプレート呼び出しで引数が重複しているページ",
        "duplicate-args-category-desc": "引数が重複したテンプレート呼び出しを含むページ。例: <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code>、<code><nowiki>{{foo|bar|1=baz}}</nowiki></code>",
        "expensive-parserfunction-warning": "<strong>警告:</strong> このページでは、高負荷なパーサー関数の呼び出し回数が多過ぎます。\n\n{{PLURAL:$2|呼び出しを $2 回}}未満にしてください ({{PLURAL:$1|現在は $1 回}})。",
        "recentchangeslinked-page": "ページ名:",
        "recentchangeslinked-to": "このページへのリンク元での変更の表示に切り替え",
        "recentchanges-page-added-to-category": "[[:$1]]をカテゴリに追加",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]]ã\81¨ä»\96[[Special:WhatLinksHere/$1|{{PLURAL:$2|1ã\83\9aã\83¼ã\82¸|$2ã\83\9aã\83¼ã\82¸}}]]ã\82\92ã\82«ã\83\86ã\82´ã\83ªã\81«è¿½å\8a ",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]]ã\82\92ã\82«ã\83\86ã\82´ã\83ªã\81«è¿½å\8a ã\80\82[[Special:WhatLinksHere/$1|ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81¯ä»\96ã\81®ã\83\9aã\83¼ã\82¸ã\81«å\90«ã\81¾ã\82\8cã\81¦ã\81\84ã\81¾ã\81\99ã\80\82]]",
        "recentchanges-page-removed-from-category": "[[:$1]]をカテゴリから除外",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]]ã\81¨ä»\96 [[Special:WhatLinksHere/$1|{{PLURAL:$2|1ã\83\9aã\83¼ã\82¸|$2ã\83\9aã\83¼ã\82¸}}]]ã\82\92ã\82«ã\83\86ã\82´ã\83ªã\81\8bã\82\89é\99¤å¤\96",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]]ã\82\92ã\82«ã\83\86ã\82´ã\83ªã\81\8bã\82\89é\99¤å¤\96ã\80\82[[Special:WhatLinksHere/$1|ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81¯ä»\96ã\81®ã\83\9aã\83¼ã\82¸ã\81«å\90«ã\81¾ã\82\8cã\81¦ã\81\84ã\81¾ã\81\99]]",
        "autochange-username": "メディアウィキ自動変更",
        "upload": "ファイルをアップロード",
        "uploadbtn": "ファイルをアップロード",
        "upload-form-label-infoform-description-tooltip": "この作品に対して特筆すべきことをすべて説明します。\n写真であれば、主に何が写っているのか、いつ、どこで撮ったものなのかについて述べてください。",
        "upload-form-label-usage-title": "使用法",
        "upload-form-label-usage-filename": "ファイル名",
-       "foreign-structured-upload-form-label-own-work": "これはあなた自身による作業です",
-       "foreign-structured-upload-form-label-infoform-categories": "カテゴリ",
-       "foreign-structured-upload-form-label-infoform-date": "日付",
-       "foreign-structured-upload-form-label-own-work-message-local": "私は {{SITENAME}} 上での以下の利用規約とライセンス方針で、このファイルをアップロードしていることを確認します。",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "もし、あなたは {{SITENAME}} の方針の下で、このファイルをアップロードすることができない場合には、このダイアログを閉じて、別の方法をお試しください。",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "また、[[Special:Upload|デフォルトのアップロードページ]]を試してみてください。",
-       "foreign-structured-upload-form-label-own-work-message-default": "私は共有リポジトリにこのファイルをアップロードしていることを理解しています。私は、そこにサービスやライセンス方針を以下のようにやっていることを、確認します。",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "もし、あなたは共有リポジトリの方針の下で、このファイルをアップロードすることができない場合には、このダイアログを閉じて、別の方法をお試しください。",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "このファイルはその方針の下でそこにアップロードすることができれば、また、 [[Special:Upload|the upload page on {{SITENAME}}]]を使用してみてください",
-       "foreign-structured-upload-form-label-own-work-message-shared": "私は、このファイルの著作権を所有していることを宣誓し、取消し不能な形で  [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] ライセンスのもとでウィキメディア・コモンズに、このファイルを解放することに同意します。そして私は、  [https://wikimediafoundation.org/wiki/Terms_of_Use Terms of Use] に同意します。",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "このファイルの著作権を所有していない場合、または別のライセンスの下でそれをリリースしたい場合には、 [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard] を使用することを検討してください。",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "もしサイトが、それらの方針の下にて、このファイルのアップロードを許可している場合は、[[Special:Upload|{{SITENAME}}上でのアップロードページ]]の利用も検討できます。",
+       "upload-form-label-own-work": "これはあなた自身による作業です",
+       "upload-form-label-infoform-categories": "カテゴリ",
+       "upload-form-label-infoform-date": "日付",
+       "upload-form-label-own-work-message-generic-local": "私は {{SITENAME}} 上での以下の利用規約とライセンス方針で、このファイルをアップロードしていることを確認します。",
+       "upload-form-label-not-own-work-message-generic-local": "もし、あなたは {{SITENAME}} の方針の下で、このファイルをアップロードすることができない場合には、このダイアログを閉じて、別の方法をお試しください。",
+       "upload-form-label-not-own-work-local-generic-local": "また、[[Special:Upload|デフォルトのアップロードページ]]を試してみてください。",
+       "upload-form-label-own-work-message-generic-foreign": "私は共有リポジトリにこのファイルをアップロードしていることを理解しています。私は、そこにサービスやライセンス方針を以下のようにやっていることを、確認します。",
+       "upload-form-label-not-own-work-message-generic-foreign": "もし、あなたは共有リポジトリの方針の下で、このファイルをアップロードすることができない場合には、このダイアログを閉じて、別の方法をお試しください。",
+       "upload-form-label-not-own-work-local-generic-foreign": "このファイルはその方針の下でそこにアップロードすることができれば、また、 [[Special:Upload|the upload page on {{SITENAME}}]]を使用してみてください",
        "backend-fail-stream": "ファイル $1 をストリームできませんでした。",
        "backend-fail-backup": "ファイル $1 をバックアップできませんでした。",
        "backend-fail-notexists": "ファイル $1 は存在しません。",
        "whatlinkshere-prev": "前の$1件",
        "whatlinkshere-next": "次の$1件",
        "whatlinkshere-links": "← リンク",
-       "whatlinkshere-hideredirs": "転送ページを$1",
-       "whatlinkshere-hidetrans": "参照読み込みを$1",
-       "whatlinkshere-hidelinks": "リンクを$1",
-       "whatlinkshere-hideimages": "ファイルへのリンクを$1",
+       "whatlinkshere-hideredirs": "転送ページを非表示",
+       "whatlinkshere-hidetrans": "参照読み込みを非表示",
+       "whatlinkshere-hidelinks": "リンクを非表示",
+       "whatlinkshere-hideimages": "ファイルへのリンクを非表示",
        "whatlinkshere-filters": "絞り込み",
        "whatlinkshere-submit": "実行",
        "autoblockid": "自動ブロック #$1",
        "ipb-unblock": "利用者またはIPアドレスのブロックを解除",
        "ipb-blocklist": "現在有効なブロックを表示",
        "ipb-blocklist-contribs": "{{GENDER:$1|$1}}の投稿の一覧",
+       "ipb-blocklist-duration-left": "残り $1",
        "unblockip": "ブロックを解除",
        "unblockiptext": "以下のフォームで利用者またはIPアドレスのブロックを解除できます。",
        "ipusubmit": "このブロックを解除",
        "tags-delete-not-found": "タグ「$1」は存在しません。",
        "tags-delete-too-many-uses": "タグ「$1」は少なくとも$2版に付与されており、削除できません。",
        "tags-delete-warnings-after-delete": "タグ「$1」の削除しましたが、以下の{{PLURAL:$2|警告}}が発生しました:",
+       "tags-delete-no-permission": "変更タグを削除する権限がありません。",
        "tags-activate-title": "タグの有効化",
        "tags-activate-question": "タグ「$1」を有効化しようとしています。",
        "tags-activate-reason": "理由:",
index 34bd816..34241c6 100644 (file)
        "noname": "Yu no spesifai no valid yuuza niem.",
        "loginsuccesstitle": "Lagiin soksesful",
        "loginsuccess": "'''Yu nou lag iin tu {{SITENAME}} az \"$1\".'''",
-       "nosuchuser": "No yuuza no de bai di niem \"$1\".\nYuuza niem kies sensitiv.\nChek yu spelin, ar [[Special:UserLogin/signup|kriet a nyuu akount]].",
+       "nosuchuser": "No yuuza no de bai di niem \"$1\".\nYuuza niem kies sensitiv.\nChek yu spelin, ar [[Special:CreateAccount|kriet a nyuu akount]].",
        "nosuchusershort": "No yuuza no de bai di niem \"$1\".\nChek yu spelin.",
        "nouserspecified": "Yu afi spesifai a yuuzaniem.",
        "login-userblocked": "Dis yuuza blak. No lagiin no lou.",
        "accmailtext": "A random jinariet paaswod fi [[User talk:$1|$1]] sen tu $2.\n\nDi paaswod fi dis nyuu akount kiahn chienj a di ''[[Special:ChangePassword|chienj paaswod]]'' piej afta yu lag iin.",
        "newarticle": "(Nyuu)",
        "newarticletext": "Yu fala lingk tu piej we no egzis yet.\nFi kriet di piej, taat taip ina di bax biluo (si di [$1 elp piej] fi muo infamieshan).\nEf yu de ya by mistiek, klik yu brouza '''bak''' botn.",
-       "anontalkpagetext": "----''Dis a di diskoshan piej fi ananimos yuuza uu no kriet no akount yet, ar uu no yuuzi.\nWi dierfuor afi yuuz di nyuumerikal IP ajres fi aidentifai im/ar.\nSoch a IP ajres kiahn shier bai sebral yuuza.\nEf yu a ananimos yuuza ahn fiil se irelivant kament dairek tu yu, begyu [[Special:UserLogin/signup|kriet a akount]] ar [[Special:UserLogin|lag iin]] fi avaid fyuucha kanfyuujan wid ada ananimos yuuza.''",
+       "anontalkpagetext": "----''Dis a di diskoshan piej fi ananimos yuuza uu no kriet no akount yet, ar uu no yuuzi.\nWi dierfuor afi yuuz di nyuumerikal IP ajres fi aidentifai im/ar.\nSoch a IP ajres kiahn shier bai sebral yuuza.\nEf yu a ananimos yuuza ahn fiil se irelivant kament dairek tu yu, begyu [[Special:CreateAccount|kriet a akount]] ar [[Special:UserLogin|lag iin]] fi avaid fyuucha kanfyuujan wid ada ananimos yuuza.''",
        "noarticletext": "Korentli no tex no de ina dis piej.\nYu kiahn [[Special:Search/{{PAGENAME}}|saach fi dis piej taikl]] ina ada piej,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} saach di rilietid lagdem],\nar [{{fullurl:{{FULLPAGENAME}}|action=edit}} edit dis piej]</span>.",
        "noarticletext-nopermission": "Korantli no tex no de ina dis piej.\nYu kiah [[Special:Search/{{PAGENAME}}|saach fi dis piej taikl]] ina ada piej, ar <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} saach di rilietid lagdem]</span>, bot yu not ab pomishan fi kriet dis piej.‎",
        "userpage-userdoesnotexist": "Yuuza akount \"<nowiki>$1</nowiki>\" no rejista.\nBegyu chek ef yu waahn fi kriet/edit dis piej.",
index 4473782..9c06301 100644 (file)
                        "Matma Rex"
                ]
        },
-       "tog-underline": "Garisen ngisoré pranala:",
-       "tog-hideminor": "Dhelikaké besutan cilik ing owah-owahan pungkasan",
-       "tog-hidepatrolled": "Dhelikaké besutan awasan ing owah-owahan pungkasan",
-       "tog-newpageshidepatrolled": "Dhelikaké kaca kapanto saka daptar kaca anyar",
-       "tog-hidecategorization": "Dhelikaké kategorisasi kaca",
-       "tog-extendwatchlist": "Jembaraké daptar pangawasan kanggo nuduhaké kabèh owahan, ora mung sing paling anyar",
-       "tog-usenewrc": "Owah-owahané paguyuban miturut kaca nèng owah-owahan anyar lan daptar panto",
-       "tog-numberheadings": "Wènèhana nomer judul secara otomatis",
+       "tog-underline": "Nggaris ngisori pranala:",
+       "tog-hideminor": "Dhelikaké besutan cilik saka owah-owahan pungkasan",
+       "tog-hidepatrolled": "Dhelikaké besutan ingawasan saka owah-owahan pungkasan",
+       "tog-newpageshidepatrolled": "Dhelikaké kaca ingawasan saka pratélaning kaca anyar",
+       "tog-hidecategorization": "Dhelikaké gegebengan kaca",
+       "tog-extendwatchlist": "Ambakaké pawawangan nedya nuduhaké kabèh owahan, ora mung sing paling anyar",
+       "tog-usenewrc": "Golongaké owah-owahan miturut kaca ing owah-owahan anyar lan pawawangan",
+       "tog-numberheadings": "Wènèhi angkaning sesirah kanthi otomatis",
        "tog-showtoolbar": "Tuduhaké wilah piranti sarana besut",
        "tog-editondblclick": "Besut kaca sarana ngeklik pindho",
-       "tog-editsectiononrightclick": "Fungsèkna panyuntingan sub-bagian mawa klik-tengen ing judul bagian (mbutuhaké JavaScript)",
-       "tog-watchcreations": "Tambahaké kaca sing tak gawé lan berkas sing tak unggah nèng daptar pangawasan",
-       "tog-watchdefault": "Tambahaké kaca lan barkas sing tak sunting nyang pawawanganku",
-       "tog-watchmoves": "Tambahaké kaca lan berkas sing tak pindhahaké nèng daptar pangawasan",
-       "tog-watchdeletion": "Tambahaké kaca lan berkas sing tak busak nèng daptar pangawasan",
-       "tog-watchuploads": "Tambahaké barkas anyar sing tak unggah nyang pawawanganku",
-       "tog-watchrollback": "Tambahaké kaca sing tak wurungaké nyang pawawanganku",
-       "tog-minordefault": "Tandhanana kabèh suntingan dadi suntingan cilik secara baku",
-       "tog-previewontop": "Deleng prawuryan sadurungé besut kothak",
-       "tog-previewonfirst": "Tuduhna pratayang ing suntingan kapisan",
-       "tog-enotifwatchlistpages": "Kirimi kula layang èlèktronik yèn ana kaca utawa berkas nèng daptar pangawasanku sing diowah",
-       "tog-enotifusertalkpages": "Kirimana aku layang e-mail yèn kaca dhiskusiku owah",
-       "tog-enotifminoredits": "Kirimi kula layang èlèktronik uga yèn ana suntingan cilik saka kaca lan berkas",
-       "tog-enotifrevealaddr": "Kirimana aku layang e-mail ing layang notifikasi",
-       "tog-shownumberswatching": "Tuduhna cacahé pangawas",
-       "tog-oldsig": "Tapak asma sing ana:",
-       "tog-fancysig": "Anggepen tapak asta minangka teks wiki (tanpa pranala otomatis)",
-       "tog-uselivepreview": "Trapaké prawuryan langsung",
+       "tog-editsectiononrightclick": "Idinaké mbesut pérangan sarana klik tengen ing sesirahing pérangan",
+       "tog-watchcreations": "Wuwuh kaca gawéanku lan barkas unggahanku nyang pawawanganku",
+       "tog-watchdefault": "Wuwuh kaca lan barkas besutanku nyang pawawanganku",
+       "tog-watchmoves": "Wuwuh kaca lan barkas lih-lihanku nyang pawawanganku",
+       "tog-watchdeletion": "Wuwuh kaca lan barkas busakanku nyang pawawanganku",
+       "tog-watchuploads": "Wuwuh barkas anyar unggahanku nyang pawawanganku",
+       "tog-watchrollback": "Wuwuh kaca sing tak wurungaké nyang pawawanganku",
+       "tog-minordefault": "Tengeri kabèh besutan minangka besutan cilik sacara baku",
+       "tog-previewontop": "Deleng pratuduh sadurungé mbesut kothak",
+       "tog-previewonfirst": "Delelng pratuduh nalika mbesut pisanan",
+       "tog-enotifwatchlistpages": "Kirimi aku layangtronik yèn ana kaca utawa barkas ing pawawanganku sing diowah",
+       "tog-enotifusertalkpages": "Kirimi aku layangtronik yèn kaca gegunemanku diowah",
+       "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-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",
-       "tog-watchlisthideown": "Delikna suntinganku ing daftar pangawasan",
+       "tog-watchlisthideown": "Dhelikaké besutanku saka pawawangan",
        "tog-watchlisthidebots": "Dhelikaké besutan bot saka pangawasan",
-       "tog-watchlisthideminor": "Delikna suntingan kecil di daftar pangawasan",
-       "tog-watchlisthideliu": "Ngumpetaké suntingan panganggo sing mlebu log seka daftar pangawasan",
+       "tog-watchlisthideminor": "Dhelikaké besutan cilik saka pawawangan",
+       "tog-watchlisthideliu": "Dhelikaké saka pawawangan besutaning wong sing mlebu",
        "tog-watchlistreloadautomatically": "Mot manèh pawawangan kanthi otomanis samangsa panyaring diowah (butuh JavaScript)",
-       "tog-watchlisthideanons": "Ngumpetaké suntingan panganggo anonim seka daftar pangawasan",
-       "tog-watchlisthidepatrolled": "Delikna suntingan sing wis dipatroli saka daftar pangawasan",
+       "tog-watchlisthideanons": "Dhelikaké saka pawawangan besutaning para anonim",
+       "tog-watchlisthidepatrolled": "Dhelikaké besutan ingawasan saka pawawangan",
        "tog-watchlisthidecategorization": "Dhelikaké kategorisasi kaca",
-       "tog-ccmeonemails": "Kirimana aku salinan layang e-mail sing tak-kirimaké menyang wong liya",
-       "tog-diffonly": "Aja dituduhaké isi kaca ing ngisor bédané suntingan",
-       "tog-showhiddencats": "Tuduhna kategori sing didelikaké",
-       "tog-norollbackdiff": "Lirwaaké prabédan sawusé nglakokaké sawijining pambalikan.",
-       "tog-useeditwarning": "Ã\88lingaké kula yèn kula ninggalaké suntingan sing durung kasimpen",
-       "tog-prefershttps": "Panggah sarana sambungan aman nalika mlebu",
-       "underline-always": "Mesthi",
+       "tog-ccmeonemails": "Kirimi aku salinan layangtronik sing tak kirim nyang wong liya",
+       "tog-diffonly": "Aja dituduhaké isining kaca ing ngisor bédané suntingan",
+       "tog-showhiddencats": "Tuduhaké kategori sing didhelikaké",
+       "tog-norollbackdiff": "Aja tuduhaké prabédan sawisé mbalèkaké.",
+       "tog-useeditwarning": "Ã\89lingaké kula yèn kula ninggalaké suntingan sing durung kasimpen",
+       "tog-prefershttps": "Tansah nganggo sambungan aman nalika mlebu",
+       "underline-always": "Tansah",
        "underline-never": "Ora tau",
-       "underline-default": "Kulit atau penjelajah bawaan",
-       "editfont-style": "Modhèl aksara (font) ing kotak suntingan:",
-       "editfont-default": "Standar panjelajah wèb",
+       "underline-default": "Baku kulit utawa pangluron",
+       "editfont-style": "Gagrag fon ing pambesutan:",
+       "editfont-default": "Baku pangluron",
        "editfont-monospace": "Fon monospasi",
        "editfont-sansserif": "Fon tansèrif",
        "editfont-serif": "Fon sèrif",
@@ -95,9 +95,9 @@
        "september": "Sèptèmber",
        "october": "Oktober",
        "november": "Nopèmber",
-       "december": "Désèmber",
+       "december": "Dhésèmber",
        "january-gen": "Januari",
-       "february-gen": "bruari",
+       "february-gen": "bruari",
        "march-gen": "Maret",
        "april-gen": "April",
        "may-gen": "Mèi",
        "september-gen": "Sèptèmber",
        "october-gen": "Oktober",
        "november-gen": "Nopèmber",
-       "december-gen": "Désèmber",
+       "december-gen": "Dhésèmber",
        "jan": "Jan",
        "feb": "Pèb",
        "mar": "Mar",
        "sep": "Sèp",
        "oct": "Okt",
        "nov": "Nop",
-       "dec": "Dès",
+       "dec": "D",
        "january-date": "Januari $1",
        "february-date": "Pèbruari $1",
        "march-date": "Maret $1",
        "august-date": "Agustus $1",
        "september-date": "$1 Sèptèmber",
        "october-date": "Oktober $1",
-       "november-date": "$1 Novèmber",
-       "december-date": "$1 Dèsèmber",
+       "november-date": "$1 Nopèmber",
+       "december-date": "$1 Dsèmber",
        "period-am": "Isuk-Awan",
        "period-pm": "Soré-Wengi",
        "pagecategories": "{{PLURAL:$1|Kategori|Kategori}}",
        "category_header": "Kaca sajeroning kategori \"$1\"",
-       "subcategories": "Subkategori",
+       "subcategories": "Anak kategori",
        "category-media-header": "Médhia sajeroning kategori \"$1\"",
-       "category-empty": "''Kategori iki saiki ora ngandhut artikel utawa média.''",
-       "hidden-categories": "{{PLURAL:$1|Kategori kadhelikaké|Kategori kadhelikaké}}",
+       "category-empty": "<em>Kategori iki lagi ora ngandhut artikel utawa médhia.</em>",
+       "hidden-categories": "{{PLURAL:$1|Kategori kadhelikan}}",
        "hidden-category-category": "Kategori kadhelikan",
-       "category-subcat-count": "{{PLURAL:$2|Kategori iki mung ngandhut subkategori ngisor iki.|Kategori iki ngandhut {{PLURAL:$1|subkategori|$1 subkategori}} ngisor iki saka gunggung $2 subkategori.}}",
-       "category-subcat-count-limited": "Kategori iki ora duwé {{PLURAL:$1|subkategori|$1 subkategori}} ''berikut''.",
+       "category-subcat-count": "{{PLURAL:$2|Kategori iki mung ngandhut saanak kategori ngisor iki.|Kategori iki ngandhut {{PLURAL:$1|anak kategori|$1 anak kategori}} ngisor iki saka gunggung $2 anak kategori.}}",
+       "category-subcat-count-limited": "Kategori iki duwé {{PLURAL:$1|anak kategori|$1 anak kategori}} kaya ngisor iki.",
        "category-article-count": "{{PLURAL:$2|Kategori iki mung ngandhut kaca ngisor iki.|{{PLURAL:$1|Kaca|$1 kaca}} ngisor iki ana ing kategori iki saka gunggung $2 kaca.}}",
        "category-article-count-limited": "Kategori iki ngandhut {{PLURAL:$1|kaca|$1 kaca-kaca}} sing kapacak ing ngisor iki.",
        "category-file-count": "{{PLURAL:$2|Kategori iki mung isi barkas iki.|{{PLURAL:$1|Barkas|$1 barkas}} iki ana sajeroning kategori iki saka $2 gunggungé.}}",
        "moredotdotdot": "Liyané...",
        "morenotlisted": "Pratélan iki ora jangkep.",
        "mypage": "Kaca",
-       "mytalk": "Wicara",
-       "anontalk": "Rembug",
+       "mytalk": "Geguneman",
+       "anontalk": "Geguneman",
        "navigation": "Napigasi",
        "and": "&#32;lan",
        "qbfind": "Golèk",
        "searchbutton": "Golèk",
        "go": "Menyang",
        "searcharticle": "Menyang",
-       "history": "Sajarah kaca",
+       "history": "Babading kaca",
        "history_short": "Babad",
        "updatedmarker": "wis inganyaran kawit tekaku sing pungkasan",
        "printableversion": "Cara cithakan",
        "deletethispage": "Busak kaca iki",
        "undeletethispage": "Wurungaké pambusaking kaca iki",
        "undelete_short": "Batal busak {{PLURAL:$1|sabesutan|$1 besutan}}",
-       "viewdeleted_short": "Pirsani {{PLURAL:$1|suntingan|suntingan}} ingkang sampun kabusak",
+       "viewdeleted_short": "Deleng {{PLURAL:$1|sabesutan sing kabusak|$1 besutan sing kabusak}}",
        "protect": "Reksa",
        "protect_change": "owah",
        "protectthispage": "Reksa kaca iki",
        "talkpage": "Rembug kaca iki",
        "talkpagelinktext": "gunem",
        "specialpage": "Kaca mirunggan",
-       "personaltools": "Piranti pribadi",
+       "personaltools": "Piranti priangga",
        "articlepage": "Deleng kaca isi",
-       "talk": "Rembug",
+       "talk": "Rerembugan",
        "views": "Praèn",
        "toolbox": "Piranti",
        "userpage": "Deleng kaca panganggo",
        "otherlanguages": "Ing basa liya",
        "redirectedfrom": "(Dilih saka $1)",
        "redirectpagesub": "Alih kaca",
-       "redirectto": "Malih nyang:",
+       "redirectto": "Ngalih menyang:",
        "lastmodifiedat": "Kaca iki pungkasan diowah kala $1, tabuh $2.",
-       "viewcount": "Kaca iki wis tau diaksès cacahé ping {{PLURAL:$1|siji|$1}}.",
+       "viewcount": "Kaca iki wis diaksès ping {{PLURAL:$1|siji|$1}}.",
        "protectedpage": "Kaca kareksa",
        "jumpto": "Jujug:",
        "jumptonavigation": "napigasi",
        "aboutpage": "Project:Bab",
        "copyright": "Kabèh isi kasedyakaké miturut $1.",
        "copyrightpage": "{{ns:project}}:Hak cipta",
-       "currentevents": "Kadadéan saiki",
-       "currentevents-url": "Project:Kadadéan saiki",
+       "currentevents": "Kadadian saiki",
+       "currentevents-url": "Project:Kadadian saiki",
        "disclaimers": "Sélakan",
        "disclaimerpage": "Project:Sélakan umum",
        "edithelp": "Pitulung besut",
        "helppage-top-gethelp": "Pitulung",
-       "mainpage": "Kaca Pokok",
-       "mainpage-description": "Kaca pokok",
+       "mainpage": "Tepas",
+       "mainpage-description": "Tepas",
        "policy-url": "Project:Kabijakan",
        "portal": "Gapura paguyuban",
        "portal-url": "Project:Garupa paguyuban",
-       "privacy": "Niti pripasi",
+       "privacy": "Niti priangga",
        "privacypage": "Project:Niti pripasi",
        "badaccess": "Aksès ora olèh",
        "badaccess-group0": "Panjenengan ora pareng nglakokaké tindhakan sing panjenengan gayuh.",
        "youhavenewmessagesmanyusers": "Sampéyang nduwé $1 saka akèh panganggo ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|layang anyar|999=layang anyar}}",
        "newmessagesdifflinkplural": "{{PLURAL:$1|owahan|999=owahan}} pungkasan",
-       "youhavenewmessagesmulti": "Panjenengan olèh pesen-pesen anyar $1",
+       "youhavenewmessagesmulti": "Sampéyan éntuk nawala anyar ing $1",
        "editsection": "besut",
        "editold": "besut",
        "viewsourceold": "deleng sumber",
        "nstab-template": "Cithakan",
        "nstab-help": "Kaca pitulung",
        "nstab-category": "Kategori",
-       "mainpage-nstab": "Kaca pokok",
+       "mainpage-nstab": "Tepas",
        "nosuchaction": "Ora ana lelakon mangkono",
        "nosuchactiontext": "Pratingkah sing dirinci déning URL ora sah.\nPanjenengan manawa salah ketik nalika ngisi URL, utawa salah ngisi pranala.\nIki manawa uga nuduhaké anané kesalahan ing piranti alus sing dipigunakaké déning {{SITENAME}}.",
        "nosuchspecialpage": "Ora ana kaca mirunggan mangkono",
        "laggedslavemode": "Pènget: Kaca iki mbokmenawa isiné dudu pangowahan pungkasan.",
        "readonly": "Umpak data kagembok",
        "enterlockreason": "Lebokna alesan panguncèn, kalebu uga prakiran kapan kunci bakal dibuka",
-       "readonlytext": "Database lagi dikunci marang panampan anyar. Pangurus sing ngunci mènèhi katrangan kaya mangkéné: <p>$1",
+       "readonlytext": "Juru administrasi sistem sing ngunci iku medhar mangkéné: $1",
        "missing-article": "Basis data ora bisa nemokaké tèks kaca sing kuduné ana, yaiku \"$1\" $2.\nBab iki bisasané disebabaké déning pranala daluwarsa menyang revisi sadurungé kaca sing wis dibusak.\nYèn dudu iki panyebabé, panjenengan manawa bisa nemokaké kasalahan (''bug'') jroning piranti alus (''software''). Mangga dilapuraké bab iki menyang [[Special:ListUsers/sysop|administrator]], kanthi nyebutaké alamat URL sing dituju",
        "missingarticle-rev": "(owahan#: $1)",
        "missingarticle-diff": "(Béda: $1, $2)",
        "cannotdelete-title": "Ora bisa mbusak kaca \"$1\"",
        "delete-hook-aborted": "Pambusakan dibatalaké déning ''hook''.\nOra ana alesané.",
        "no-null-revision": "Ora isa nggawe revisi 'null' anyar kanggo kaca \"$1\"",
-       "badtitle": "Sésirah ala",
-       "badtitletext": "Judhul kaca sing panjenengan ora bisa dituduhaké, kosong, utawa dadi judhul antar-basa utawa judhul antar-wiki. Iku bisa uga ana  sawijining utawa luwih aksara sing ora bisa didadèkaké judhul.",
+       "badtitle": "Sesirah ala",
+       "badtitletext": "Sesirahing kaca sing dikarepaké ora sah, suwung, utawa salah nggayut nyang sesirah antarabasa utawa antarawiki.\nIku mungkin ngandhut pralambang siji utawa luwih sing ora kena dianggo tumrap sesirah iki.",
        "perfcached": "Data iki mung dijupuk saka papan singgahan lan mungkin ora kaanyaran. Maksimum {{PLURAL:$1|sak asil|$1 asil}} sumadhiya nèng papan singgahan.",
        "perfcachedts": "Data iki mung dijupuk saka papan singgahan lan mungkin dianyari pungkasan $1. Maksimum {{PLURAL:$4|sak asil|$4 asil}} sumadhiya nèng papan singgahan.",
        "querypage-no-updates": "Update saka kaca iki lagi dipatèni. Data sing ana ing kéné saiki ora bisa bakal dibalèni unggah manèh.",
        "actionthrottled": "Tindakan diwatesi",
        "actionthrottledtext": "Minangka sawijining pepesthèn anti-spam, panjenengan diwatesi nglakoni tindhakan iki sing cacahé kakèhan ing wektu cendhak.\nMangga dicoba manèh ing sawetara menit.",
        "protectedpagetext": "Kaca iki wis digembok supaya ora bisa disunting lan diapa-apakaké.",
-       "viewsourcetext": "Panjenengan bisa mirsani utawa nulad sumber kaca iki:",
-       "viewyourtext": "Sampéyan bisa ndelok lan nyalin sumber '''suntingan Sampéyan''' nèng kaca iki:",
+       "viewsourcetext": "Sampéyan bisa ndeleng lan nyalin sumbering kaca iki.",
+       "viewyourtext": "Sampéyan bisa ndeleng lan nyalin sumbering <strong>besutaning sampéyan</strong> ing kaca iki.",
        "protectedinterface": "Kaca iki isiné tèks antarmuka sing dienggo software lan wis dikunci kanggo menghindari kasalahan.",
        "editinginterface": "'''Pènget:''' Panjenengan nyunting kaca sing dianggo nyedyakaké tèks antarmuka kanggo piranti alus.\nPangowahan kaca iki bakal awèh pangaruh marang tampilan antarmuka panganggo kanggoné panganggo liya.\nKanggo terjemahan, mangga nganggo [//translatewiki.net/wiki/Main_Page?setlang=en translatewiki.net], proyèk lokalisasi 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",
        "welcomecreation-msg": "Akun panjenengan wis kacipta. Aja lali nata konfigurasi [[Special:Preferences|preferensi {{SITENAME}}]] panjenengan.",
        "yourname": "Jeneng panganggo:",
        "userlogin-yourname": "Jeneng panganggo",
-       "userlogin-yourname-ph": "Isi jeneng panganggo Sampéyan",
+       "userlogin-yourname-ph": "Isi jeneng panganggoning sampéyan",
        "createacct-another-username-ph": "Isi jeneng panganggo",
        "yourpassword": "Tembung wadi:",
        "userlogin-yourpassword": "Tembung wadi",
        "createacct-submit": "Gawé akun sampéyan",
        "createacct-another-submit": "Gawé akun",
        "createacct-benefit-heading": "{{SITENAME}} digawé déning wong-wong kaya déné sampéyan.",
-       "createacct-benefit-body1": "{{PLURAL:$1|besutan|besutan}}",
-       "createacct-benefit-body2": "{{PLURAL:$1|kaca|kaca}}",
-       "createacct-benefit-body3": "{{PLURAL:$1|panyumbang|panyumbang}} pungkasan",
+       "createacct-benefit-body1": "{{PLURAL:$1|besutan}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|kaca}}",
+       "createacct-benefit-body3": "{{PLURAL:$1|sing nyumbang}} pungkasan",
        "badretype": "Sandhi panjenengan ora gathuk",
        "usernameinprogress": "Panggawéning akun tumrap jeneng panganggo iki tembé lumaku.\nEntèni sadhéla.",
        "userexists": "Jeneng panganggo sing dilebokaké lagi dianggo.\nMangga pilih jeneng liya.",
        "noname": "Asma panganggo sing panjenengan pilih ora sah.",
        "loginsuccesstitle": "Kasil mlebu",
        "loginsuccess": "'''Panjenengan saiki mlebu ing {{SITENAME}} kanthi asma \"$1\".'''",
-       "nosuchuser": "Ora ana panganggo mawa asma \"$1\".\nJeneng panganggo iku mbédakaké kapitalisasi.\nCoba dipriksa manèh pasang aksarané, utawa [[Special:UserLogin/signup|gawé akun anyar]].",
+       "nosuchuser": "Ora ana panganggo mawa asma \"$1\".\nJeneng panganggo iku mbédakaké kapitalisasi.\nCoba dipriksa manèh pasang aksarané, utawa [[Special:CreateAccount|gawé akun anyar]].",
        "nosuchusershort": "Ora ana panganggo mawa asma \"$1\". Coba dipriksa manèh pasang aksarané (éjaané).",
        "nouserspecified": "Panjenengan kudu milih asma panganggo.",
        "login-userblocked": "Panganggo iki pinalangan. Ora kena mbelu.",
        "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.",
-       "login-abort-generic": "Sampéyan ora suksès mlebu log - Dibatalaké",
+       "login-abort-generic": "Sampéyan ora bisa mlebu - Kawurungan",
        "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.",
        "user-mail-no-addy": "Njajal ngirim layang èlèktronik tanpa alamat layang èlèktronik.",
        "user-mail-no-body": "Nyoba ngirim layang e-mail, tapi isine kosong.",
        "changepassword": "Ganti tembung wadi",
-       "resetpass_announce": "Panjenengan wis mlebu log mawa kodhe sementara sing dikirim mawa e-mail. Menawa kersa nglanjutaké, panjenengan kudu milih tembung sandhi anyar ing kéné:",
+       "resetpass_announce": "Kanggo ngrampungaké lelakoning lumebu, sampéyan kudu masang tembung wadi anyar.",
        "resetpass_text": "<!-- Tambahaké teks ing kéné -->",
        "resetpass_header": "Ganti tembung wadining akun",
        "oldpassword": "Tembung wadi lawas:",
        "newpassword": "Tembung wadi anyar:",
        "retypenew": "Tik manèh tembung wadi anyaré:",
        "resetpass_submit": "Nata tembung sandhi lan mlebu log",
-       "changepassword-success": "Tembung sandhi panjenengan wis suksès diowahi!",
+       "changepassword-success": "Tembung wadining sampéyan kasil diowah!",
        "botpasswords": "Tembung wadi bot",
        "botpasswords-label-appid": "Jeneng bot:",
        "botpasswords-label-create": "Gawé",
        "passwordreset-emailtext-ip": "Ana uwong (mbok menawa Sampéyan, mawa angka IP $1) njaluk ganti tembung sandhiné Sampéyan ana ing {{SITENAME}} ($4). {{PLURAL:$3|Rèkèning|Rèkèning-rèkèning}} ngisor iki magepokan karo padunungané layang èlèktronik iki:\n\n$2\n\n{{PLURAL:$3|Tembung sandhi sawetara iki}} bakal kedaluwarsa ing {{PLURAL:$5|sak dina|$5 dina}}.\nSampéyan kudu mlebu log lan milih siji tembung sandhi anyar saiki. Yèn wong liya sing njaluk iki, utawa yèn Sampéyan jebul wis kèlingan tembung sandhiné sing lawas saéngga ora ana niyat kanggo ngganti, Sampéyan bisa ngejaraké wara-wara iki lan bacutaké nganggo tembung sandhiné lawas Sampéyan.",
        "passwordreset-emailtext-user": "Panganggo $1 seka {{SITENAME}} njaluk ganti tembung sandhiné Sampéyan ana ing {{SITENAME}} ($4). {{PLURAL:$3|Rèkèning|Rèkèning-rèkèning}} ngisor iki magepokan karo padunungané layang èlèktronik iki:\n\n$2\n\n{{PLURAL:$3|Tembung sandhi sawetara iki}} bakal kedaluwarsa ing {{PLURAL:$5|sak dina|$5 dina}}.\nSampéyan kudu mlebu log lan milih siji tembung sandhi anyar saiki. Yèn wong liya sing njaluk iki, utawa yèn Sampéyan jebul wis kèlingan tembung sandhiné sing lawas saéngga ora ana niyat kanggo ngganti, Sampéyan bisa ngejaraké wara-wara iki lan bacutaké nganggo tembung sandhiné lawas Sampéyan.",
        "passwordreset-emailelement": "Jeneng panganggo: \n$1\n\nTembung wadi sauntara: \n$2",
-       "passwordreset-emailsentemail": "Layang èlèktronik kanggo mbalèkaké tembung sandhi wis dikirim.",
+       "passwordreset-emailsentemail": "Yèn layang èlèktronik iki nggayut akuning sampéyan, layang kanggo salin tembung wadi bakal dikirim.",
        "passwordreset-emailsent-capture": "Layang èlèktronik kanggo mbalèkaké tembung sandhi wis dikirim, bisa didelok ngisor iki.",
        "passwordreset-emailerror-capture": "Layang èlèktronik pangèling tembung sandhi wis digawe, yaiku sing ditampilaké nèng ngisor iki, nanging ora kasil dikirim ing {{GENDER:$2|panganggo}}: $1",
-       "changeemail": "Ganti alamat layang èlèktronik",
+       "changeemail": "Owah utawa busak alamat layang èlèktronik",
        "changeemail-header": "Ganti alamat layang èlèktronik akun",
        "changeemail-no-info": "Sampéyan kudu mlebu log kanggo ngaksès kaca iki langsung.",
        "changeemail-oldemail": "Alamat layang èlèktronik saiki:",
        "resettokens-done": "Reset token.",
        "resettokens-resetbutton": "Reset token sing dipilih",
        "bold_sample": "Tulisan kandel",
-       "bold_tip": "Tulisann kandel",
-       "italic_sample": "Tulisan miring",
+       "bold_tip": "Tulisan kandel",
+       "italic_sample": "Tulisan dhoyong",
        "italic_tip": "Tulisan dhoyong",
        "link_sample": "Sesirah pranala",
        "link_tip": "Pranala njero",
        "extlink_tip": "Pranala jaba (élinga ater-ater http://)",
        "headline_sample": "Tulisan sesirah",
        "headline_tip": "Sesirah tataran 2",
-       "nowiki_sample": "Tèks iki ora bakal diformat",
+       "nowiki_sample": "Isi nganggo tulisan tanpa format ing kéné",
        "nowiki_tip": "Aja nganggo format wiki",
        "image_sample": "Conto.jpg",
-       "image_tip": "Mènèhi gambar/berkas",
+       "image_tip": "Barkas sisipan",
        "media_sample": "Conto.ogg",
        "media_tip": "Pranala barkas",
        "sig_tip": "Tandha tangan sampéyan mawa tandha wayah",
        "hr_tip": "Garis horisontal",
        "summary": "Tingkesan:",
-       "subject": "Subyek/judhul:",
+       "subject": "Jejer:",
        "minoredit": "Iki besutan cilik",
        "watchthis": "Awasi kaca iki",
        "savearticle": "Simpen kaca",
-       "preview": "Prawuryan",
-       "showpreview": "Tuduhaké prawuryan",
+       "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.''",
        "missingsummary": "'''Pènget:''' Panjenengan ora nglebokaké ringkesan panyuntingan. Menawa panjenengan mencèt tombol Simpen manèh, suntingan panjenengan bakal kasimpen tanpa ringkesan panyuntingan.",
        "missingcommenttext": "Tulung lebokna komentar ing ngisor iki.",
        "missingcommentheader": "'''Pangéling:''' Sampéyan durung nyadhiyakaké judhul/jejer kanggo tanggepan iki.\nYèn Sampéyan klik \"{{int:savearticle}}\" manèh, suntingan Sampéyan bakal kasimpen tanpa kuwi.",
-       "summary-preview": "Prawuryan tingkesan:",
-       "subject-preview": "Pratayang subyèk/judhul:",
+       "summary-preview": "Pratuduh tingkesan:",
+       "subject-preview": "Prawuryaning jejer:",
        "blockedtitle": "Panganggo kapalangan",
-       "blockedtext": "'''Asma panganggo utawa alamat IP panjenengan diblokir.'''\n\nBlokir iki sing nglakoni $1.\nAlesané ''$2''.\n\n* Diblokir wiwit: $8\n* Kadaluwarsa pemblokiran ing: $6\n* Sing arep diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké prakara iki.\n\nPanjenengan ora bisa nggunakaké fitur 'Kirim layang e-mail panganggo iki' kejaba panjenengan wis nglebokaké alamat e-mail sing sah ing [[Special:Preferences|préferènsi]] panjenengan.\n\nAlamat IP panjenengan iku $3, lan ID pamblokiran iku #$5.\nTulung kabèh informasi ing ndhuwur iki disertakaké ing saben pitakon panjenengan.",
-       "autoblockedtext": "Alamat IP panjenangan wis diblokir minangka otomatis amerga dienggo déning panganggo liyané. Pamblokiran dilakoni déning $1 mawa alesan:\n\n:''$2''\n\n* Diblokir wiwit: $8\n* Blokir kadaluwarsa ing: $6\n* Sing dikarepaké diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké perkara iki.\n\nPanjenengan ora bisa nganggo fitur \"kirim e-mail panganggo iki\" kejaba panjenengan wis nglebokaké alamat e-mail sing sah ing [[Special:Preferences|préferènsi]] panjenengan lan panjenengan wis diblokir kanggo nggunakaké.\n\nID pamblokiran panjenengan iku #$5 lan alamat IP panjenengan iku $3. Tulung sertakna informasi ing dhuwur kabèh iki saben ngajokaké pitakonan panjenengan. Matur nuwun.",
+       "blockedtext": "<b>Asma panganggo utawa alamat IP panjenengan diblokir.</b>\n\nBlokir iki sing nglakoni $1.\nAlesané <i>$2</i>.\n\n* Diblokir wiwit: $8\n* Kadaluwarsa pemblokiran ing: $6\n* Sing arep diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké prakara iki.\n\nPanjenengan ora bisa nggunakaké fitur 'Kirim layang é-mail panganggo iki' kejaba panjenengan wis nglebokaké alamat é-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan.\n\nAlamat IP panjenengan iku $3, lan ID pamblokiran iku #$5.\nTulung kabèh informasi ing ndhuwur iki disertakaké ing saben pitakon panjenengan.",
+       "autoblockedtext": "Alamat IP panjenangan wis diblokir minangka otomatis amerga dienggo déning panganggo liyané. Pamblokiran dilakoni déning $1 mawa alesan:\n\n:''$2''\n\n* Diblokir wiwit: $8\n* Blokir kadaluwarsa ing: $6\n* Sing dikarepaké diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké perkara iki.\n\nPanjenengan ora bisa nganggo fitur \"kirim e-mail panganggo iki\" kejaba panjenengan wis nglebokaké alamat e-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan lan panjenengan wis diblokir kanggo nggunakaké.\n\nID pamblokiran panjenengan iku #$5 lan alamat IP panjenengan iku $3. Tulung sertakna informasi ing dhuwur kabèh iki saben ngajokaké pitakonan panjenengan. Matur nuwun.",
        "blockednoreason": "ora ana alesan sing diwènèhaké",
-       "whitelistedittext": "Panjenengan kudu $1 supaya bisa nyunting artikel.",
+       "whitelistedittext": "Sampéyan kudu $1 murih bisa mbesut kaca.",
        "confirmedittext": "Panjenengan kudu ndhedhes alamat e-mail dhisik sadurungé pareng nyunting sawijining kaca. Mangga nglebokaké lan validasi alamat e-mail panjenengan sadurungé nglakoni panyuntingan. Alamat e-mail sawisé bisa diowahi liwat [[Special:Preferences|kaca préférènsi]]",
        "nosuchsectiontitle": "Pérangan ora katemu",
        "nosuchsectiontext": "Panjenengan nyoba nyunting sawijining bagéan sing ora ana.\nBagéan iki manawa wis dipindhah utawa dibusak nalika panjenengan buka.",
        "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.",
        "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:UserLogin/signup|nggawé akun]] utawa [[Special:UserLogin|log mlebu]] supaya ora rancu karo panganggo anonim liyané ing mangsa ngarep.''",
+       "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.''",
        "noarticletext": "Kala saiki kaca iki durung ana tulisané.\nSampéyan bisa [[Special:Search/{{PAGENAME}}|nggolèki sesirahing kaca iki]] sajeroning kaca liya,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} nggolèki log sing magepokan],\nutawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} nggawé kaca iki]</span>.",
        "noarticletext-nopermission": "Saiki ora ana tèks ing kaca iki. \nSampéyan bisa [[Special:Search/{{PAGENAME}}|nggolèki judhul kaca iki]] nèng kaca liya, \nutawa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|kaca={{urlencode:{{FULLPAGENAME}}}}}} nggolèki log sing kaitan]</span>, nanging Sampéyan ora nduwèni idin nggawé kaca iki.",
        "missing-revision": "Benahan #$1 saka kaca ajeneng \"{{FULLPAGENAME}}\" ora ana.\n\nIki biasané kasebabaké pranala riwayat sing kedaluwarsa saka kaca kuwi wis dibusak.\nRinciané bisa ditemokaké nèng [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log pambusakan].",
        "templatesusedsection": "{{PLURAL:$1|Cithakan}} sing dienggo ding bagian iki:",
        "template-protected": "(kareksa)",
        "template-semiprotected": "(semu kareksa)",
-       "hiddencategories": "Kaca iki sawijining anggota saka {{PLURAL:$1|1 kategori ndelik|$1 kategori-kategori ndelik}}:",
+       "hiddencategories": "Kaca iki anggotaning {{PLURAL:$1|1 kategori wadi|$1 kategori wadi}}:",
        "edittools": "<!-- Tèks ing ngisor iki bakal ditudhuhaké ing ngisoring isènan suntingan lan pangemotan.-->",
        "nocreatetext": "Situs iki ngwatesi kemampuan kanggo nggawé kaca anyar. Panjenengan bisa bali lan nyunting kaca sing wis ana, utawa mangga [[Special:UserLogin|mlebua log utawa ndaftar]]",
        "nocreate-loggedin": "Panjenengan ora kagungan idin kanggo nggawé kaca anyar.",
        "undo-success": "Suntingan iki bisa dibatalaké. Tulung priksa prabandhingan ing ngisor iki kanggo mesthèkaké yèn prakara iki pancèn sing bener panjenengan pèngin lakoni, banjur simpenen pangowahan iku kanggo ngrampungaké pambatalan suntingan.",
        "undo-failure": "Suntingan iki ora bisa dibatalakén amerga ana konflik panyuntingan antara.",
        "undo-norev": "Suntingan iki ora bisa dibatalaké amerga ora ana utawa wis dibusak.",
-       "undo-summary": "←Mbatalaké revisi $1 déning [[Special:Contributions/$2|$2]] ([[User talk:$2|Dhiskusi]])",
+       "undo-summary": "Balèkaké owahan $1 déning [[Special:Contributions/$2|$2]] ([[User talk:$2|gunem]])",
        "undo-summary-username-hidden": "Batalna revisi $1 saking panganggo kang didhelikake",
        "cantcreateaccounttitle": "Akun ora bisa digawé",
        "cantcreateaccount-text": "Saka alamat IP iki ('''$1''') ora diparengaké nggawé akun utawa rékening. Sing mblokir utawa ora marengaké iku [[User:$3|$3]].\n\nAlesané miturut $3 yaiku ''$2''",
        "logdelete-failure": "'''Aturan pandhelikan ora bisa disèt:'''\n$1",
        "revdel-restore": "Ngowahi visiblitas (pangatonan)",
        "pagehist": "Babading kaca",
-       "deletedhist": "Babad kabusakan",
+       "deletedhist": "Babad kabusak",
        "revdelete-hide-current": "Gagal ndhelikaké révisi tanggal $2, $1: iki arupa révisi paling anyar.\nRévisi iki ora bisa didhelikaké.",
        "revdelete-show-no-access": "Gagal nampilaké révisi tanggal $1, jam $2: révisi iki wis ditandhani \"kawates\".\nPanjenengan ora nduwèni aksès menyang révisi iki.",
        "revdelete-modify-no-access": "Gagal ngowahi révisi tanggal $1, jam $2: révisi iki wis ditandhani \"kawates\".\nPanjenengan ora nduwèni aksès menyang révisi iki.",
        "mergelog": "Gabung log",
        "revertmerge": "Batalna panggabungan",
        "mergelogpagetext": "Ing ngisor iki kapacak daftar panggabungan sajarah kaca ing kaca liyané.",
-       "history-title": "Riwayat rèvisi saka \"$1\"",
+       "history-title": "Babad owahaning \"$1\"",
        "difference-title": "Prabéda antara owahan \"$1\"",
        "difference-title-multipage": "Prabédhan antara kaca \"$1\" lan \"$2\"",
        "difference-multipage": "(Prabédhan antar kaca)",
        "searchmenu-exists": "'''Ana kaca kanthi jeneng \"[[$1]]\" ing wiki iki'''",
        "searchmenu-new": "<strong>Gawéa kaca \"[[:$1]]\" nyang wiki iki!</strong> {{PLURAL:$2|0=|Uga delenga kaca sing katemu sarana panggolèking sampéyan.|Uga delenga kasiling panggolèk.}}",
        "searchprofile-articles": "Kaca isi",
-       "searchprofile-images": "Sarwasarana",
+       "searchprofile-images": "Sarwamadya",
        "searchprofile-everything": "Samubarang",
        "searchprofile-advanced": "Lungidan",
        "searchprofile-articles-tooltip": "Golèkan ing $1",
        "search-external": "Panggolèkan èkstèrnal",
        "searchdisabled": "Sawetara wektu iki panjenengan ora bisa nggolèk mawa fungsi golèk {{SITENAME}}. Kanggo saiki mangga panjenengan bisa golèk nganggo Google. Nanging isi indèks Google kanggo {{SITENAME}} bisa waé lawas lan durung dianyari.",
        "search-error": "Ana kasalahan wektu nggoleki: $1",
-       "preferences": "Preferensi (pilihan)",
-       "mypreferences": "Préferènsi",
+       "preferences": "Pilihan",
+       "mypreferences": "Pilihan",
        "prefs-edits": "Gunggung besutan:",
        "prefsnologintext2": "Tulung $1 kanggo ngganti preferensi sampeyan.",
        "prefs-skin": "Kulit",
-       "skin-preview": "Pratilik",
-       "datedefault": "Ora ana préferènsi",
+       "skin-preview": "Pratuduh",
+       "datedefault": "Ora ana pilihan",
        "prefs-labs": "Piranti lab",
-       "prefs-user-pages": "Kaca panganggo",
-       "prefs-personal": "Profil panganggo",
+       "prefs-user-pages": "Kacaning sing nganggo",
+       "prefs-personal": "Panjèrènging sing nganggo",
        "prefs-rc": "Owah-owahan pungkasan",
-       "prefs-watchlist": "Dhaftar pangawasan",
+       "prefs-watchlist": "Pawawangan",
+       "prefs-editwatchlist": "Besut pawawangan",
+       "prefs-editwatchlist-label": "Besut isining pawawanganing sampéyan",
+       "prefs-editwatchlist-edit": "Deleng lan busak sesirah ing pawawanganing sampéyan",
+       "prefs-editwatchlist-raw": "Besut pawawangan lakaran",
+       "prefs-editwatchlist-clear": "Resiki pawawanganing sampéyan",
        "prefs-watchlist-days": "Cacahé dina sing dituduhaké ing dhaftar pangawasan:",
        "prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|dina|dina}}",
        "prefs-watchlist-edits": "Cacahé suntingan maksimum sing dituduhaké ing dhaftar pangawasan sing luwih jangkep:",
        "prefs-watchlist-edits-max": "Gunggung maksimum: 1000",
-       "prefs-watchlist-token": "Token pantauan:",
+       "prefs-watchlist-token": "Tokening pawawangan:",
        "prefs-misc": "Liya-liya",
        "prefs-resetpass": "Ganti tembung sandi",
-       "prefs-changeemail": "Ganti alamat layang èlèktronik",
+       "prefs-changeemail": "Owah utawa busak alamat layangtronik",
        "prefs-setemail": "Setèl alamat layang èlèktronik",
        "prefs-email": "Opsi layang-e",
        "prefs-rendering": "Tampilan",
        "columns": "Kolom:",
        "searchresultshead": "Panggolèkan",
        "stub-threshold": "Ambang wates kanggo format <a href=\"#\" class=\"stub\">pranala rintisan</a>:",
+       "stub-threshold-sample-link": "pralampita",
        "stub-threshold-disabled": "Dipatèni",
        "recentchangesdays": "Cacahé dina sing dituduhaké ing owah-owahan pungkasan:",
        "recentchangesdays-max": "(maksimum $1 {{PLURAL:$1|dina|dina}})",
        "recentchangescount": "Cacahing besutan sing dituduhaké kanthi baku:",
        "prefs-help-recentchangescount": "Iki klebu owah-owahan pungkasan, kaca sajarah, lan log.",
        "prefs-help-watchlist-token2": "Ini adalah kunci rahasia (token) ke web feed dari daftar pantauan Anda.\nSiapa saja yang tahu akan dapat melihat daftar pantauan Anda, jadi jangan dibagikan.\n[[Special:ResetTokens|Klik di sini jika Anda perlu menyetel ulang]].",
-       "savedprefs": "Préferènsi Panjenengan wis disimpen",
+       "savedprefs": "Prèferènsi Panjenengan wis disimpen",
+       "savedrights": "Haking panganggo {{GENDER:$1|$1}} wis kasimpen.",
        "timezonelegend": "Zona wektu:",
        "localtime": "Wektu saenggon:",
        "timezoneuseserverdefault": "Anggo gawan wiki ($1)",
        "prefs-namespaces": "Ruang jeneng / Bilik jeneng",
        "default": "baku",
        "prefs-files": "Berkas",
-       "prefs-custom-css": "CSS pribadi",
-       "prefs-custom-js": "JS pribadi",
+       "prefs-custom-css": "CSS priangga",
+       "prefs-custom-js": "JavaScript priangga",
        "prefs-common-css-js": "CSS/JS didumaké kanggo kabèh kulit:",
        "prefs-reset-intro": "Panjenengan bisa migunakaké kaca iki kanggo mbalèkaké préferensi panjenengan marang setèlan baku situs.\nPembalikan ora bisa dibatalaké.",
        "prefs-emailconfirm-label": "Konfirmasi layang-e:",
        "prefs-help-signature": "Komentar ing kaca wicara kudu ditapak astani nganggo \"<nowiki>~~~~</nowiki>\" sing bakal dikonvèrsi dadi tapak asta panjenengan lan tanggal wektu.",
        "badsig": "Tapak astanipun klèntu; cèk rambu HTML.",
        "badsiglength": "Tapak asta panjenengan kedawan.\nAja luwih saka {{PLURAL:$1|karakter|karakter}}.",
-       "yourgender": "Jinis kelamin:",
-       "gender-unknown": "Ora dinyatakaké",
-       "gender-male": "Lanang",
-       "gender-female": "Wadon",
+       "yourgender": "Kepiyé sampéyan medhar priangganing sampéyan?",
+       "gender-unknown": "Nalika nyebut sampéyan, piranti alus iku bakal nganggo tembung sing nétral jèndher sabisané",
+       "gender-male": "Dhèwèké mbesut kaca wiki",
+       "gender-female": "Dhèwèké mbesut kaca wiki",
        "prefs-help-gender": "Opsional: Dipigunakaké kanggo panyebutan jinis kelamin sing bener déning piranti alus.\nInformasi iki bakal kabuka kanggo publik.",
-       "email": "Layang élèktronik (E-mail)",
-       "prefs-help-realname": "* <strong>Asma asli</strong> (ora wajib): menawa panjenengan maringi, asma asli panjenengan bakal digunakaké kanggo mènèhi akrédhitasi kanggo kasil karya tulis panjenengan.",
+       "email": "Layangtronik",
+       "prefs-help-realname": "Jeneng asli manasuka.\nMenawa diisi, iku bakal kanggo ngatribusi sampéyan awit karyaning sampéyan.",
        "prefs-help-email": "Alamat layang èlèktronik sipaté mung pilihan, nanging dibutuhaké kanggo nyetèl ulang tembung sandhi yèn Sampéyan lali.",
        "prefs-help-email-others": "Sampéyan uga bisa milih kanggo ngidinaké wong liya ngubungi Sampéyan liwat layang èlèktronik sing ana ing kaca panganggo utawa kaca guneman.\nAlamat layang èlèktronik Sampéyan ora dituduhaké nalika wong liya ngubungi Sampéyan.",
        "prefs-help-email-required": "Alamat layang-e dibutuhaké.",
-       "prefs-info": "Informasi dhasar",
+       "prefs-info": "Katerangan pokok",
        "prefs-i18n": "Internasionalisasi",
        "prefs-signature": "Tapak asma",
        "prefs-dateformat": "Format tanggal",
        "grouppage-suppress": "{{ns:project}}:Oversight",
        "right-read": "Maca kaca-kaca",
        "right-edit": "Besut kaca",
-       "right-createpage": "Nggawé kaca (sing dudu kaca dhiskusi)",
-       "right-createtalk": "Nggawé kaca dhiskusi",
+       "right-createpage": "Gawé kaca (sing dudu kaca rerembugan)",
+       "right-createtalk": "Gawé kaca rerembugan",
        "right-createaccount": "Nggawé rékening (akun) panganggo anyar",
        "right-minoredit": "Tandhani minangka besutan cilik",
-       "right-move": "Pindhahna kaca",
+       "right-move": "Ngalih kaca",
        "right-move-subpages": "Pindhahaké kaca lan kabèh anak-kacané",
        "right-move-rootuserpages": "Ngalih kaca panganggo oyod",
        "right-movefile": "Mindhah berkas",
        "right-bot": "Anggepen minangka prosès otomatis",
        "right-nominornewtalk": "Suntingan sithik (''minor'') ora ngwetokaké prompt pesen anyar",
        "right-apihighlimits": "Nganggo wates sing luwih dhuwur ing kwéri API",
-       "right-writeapi": "Migunakaké API panulisan",
+       "right-writeapi": "Nganggo API tulis",
        "right-delete": "Busak kaca-kaca",
        "right-bigdelete": "Busak kaca-kaca mawa sajarah panyuntingan sing gedhé",
        "right-deletelogentry": "Busak lan batalaké mbusak isi log spésipik",
        "right-edituserjs": "Besut barkas-barkas JavaScript panganggo liya",
        "right-editmyusercss": "Owahi berkas CSS panganggo sampeyan",
        "right-editmyuserjs": "Owahi berkas JavaScript panganggo sampeyan",
-       "right-viewmywatchlist": "Dheleng daftar pangawasan sampeyan",
+       "right-viewmywatchlist": "Deleng pawawanganing sampéyan",
        "right-editmywatchlist": "Owahi daftar pangawasan sampeyan. Cathetan: ana cara liyane kanggo nambahi kaca menyang daftar, sanadyan ora duwe hak iki.",
        "right-viewmyprivateinfo": "Dheleng data pribadi sampeyan (kayata alamat layang elektronik, jeneng asli)",
        "right-editmyprivateinfo": "Owahi data pribadi sampeyan (kayata alamat layang elektronik, jeneng asli)",
        "right-editmyoptions": "Owahi preferensi sampeyan",
-       "right-rollback": "Sacara gelis mbalèkaké panganggo pungkasan sing nyunting kaca tartamtu",
+       "right-rollback": "Balèkaké kanthi gelis besutaning panganggo pungkasan sing mbesut kaca tinamtu",
        "right-markbotedits": "Tandhani besutan kawurungan minangka besutan bot",
        "right-noratelimit": "Ora dipengaruhi déning wates cacahing suntingan.",
        "right-import": "Impor kaca-kaca saka wiki liya",
        "action-move": "alihna kaca iki",
        "action-move-subpages": "mindahaké kaca iki, lan kabèh anak-kacané",
        "action-move-rootuserpages": "ngalih kaca panganggo oyod",
-       "action-movefile": "pindhahna berkas iki",
+       "action-movefile": "lih barkas iki",
        "action-upload": "ngunggahaké berkas iki",
        "action-reupload": "nindhih berkas sing wis ana",
        "action-reupload-shared": "nindhih berkas sing wis ana ing papan panyimpanan berkas sing dianggo bebarengan",
        "recentchanges-label-minor": "Iki besutan cilik",
        "recentchanges-label-bot": "Besutan iki diayahi bot",
        "recentchanges-label-unpatrolled": "Besutan iki durung kapatroli",
-       "recentchanges-label-plusminus": "Ukuraning kaca diowah kanthi cacahing bèt samangkéné",
+       "recentchanges-label-plusminus": "Ukuran owahing kaca ing bèt",
        "recentchanges-legend-heading": "<strong>Legendha:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (uga deleng [[Special:NewPages|pratélaning kaca-kaca anyar]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "filehist-deleteone": "busaken iki",
        "filehist-revert": "balèkna",
        "filehist-current": "saiki",
-       "filehist-datetime": "Surya/Tabuh",
+       "filehist-datetime": "Tanggal/Tabuh",
        "filehist-thumb": "Gambar cilik",
        "filehist-thumbtext": "Gambar cilik kanggo owahan $1",
        "filehist-nothumb": "Ora ana miniatur",
        "filehist-dimensions": "Alang ujur",
        "filehist-filesize": "Gedhené berkas",
        "filehist-comment": "Tanggapan",
-       "imagelinks": "Panganggoan berkas",
+       "imagelinks": "Panganggoning barkas",
        "linkstoimage": "{{PLURAL:$1|Kaca|$1 kaca}} ngisor iki nggayut barkas iki:",
        "linkstoimage-more": "Luwih saka $1 {{PLURAL:$1|kaca|kaca-kaca}} nduwèni pranala menyang berkas iki.\nDhaftar ing ngisor nuduhaké {{PLURAL:$1|kaca pisanan kanthi pranala langsung|$1 kaca kanthi pranala langsung}} menyang berkas iki.\n[[Special:WhatLinksHere/$2|dhaftar pepak]] uga ana.",
-       "nolinkstoimage": "Ora ana kaca sing nyambung menyang berkas iki.",
+       "nolinkstoimage": "Ora ana kaca sing nggayut menyang barkas iki.",
        "morelinkstoimage": "Ndeleng [[Special:WhatLinksHere/$1|luwih akèh pranala]] menyang berkas iki.",
        "linkstoimage-redirect": "$1 (alihan berkas) $2",
        "duplicatesoffile": "{{PLURAL:$1|berkas ing ngisor arupa duplikat|$1 berkas ing ngisor arupa duplikat}} saka berkas iki ([[Special:FileDuplicateSearch/$2|luwih rinci]]):",
        "doubleredirects": "Pangalihan dobel",
        "doubleredirectstext": "Kaca iki ngandhut daftar kaca sing ngalih ing kaca pangalihan liyané.\nSaben baris ngandhut pranala menyang pangalihan kapisan lan kapindho, sarta tujuan saka pangalihan kapindho, sing biasané kaca tujuan sing \"sajatiné\", yakuwi pangalihan kapisan kuduné dialihaké menyang kaca tujuan iku.\nJeneng sing wis <del>dicorèk</del> tegesé wis rampung didandani.",
        "double-redirect-fixed-move": "[[$1]] wis kapindhahaké, saiki dadi kaca peralihan menyang [[$2]]",
-       "double-redirect-fixed-maintenance": "Mbenakaké rong pangalihan saka [[$1]] nèng [[$2]].",
+       "double-redirect-fixed-maintenance": "Otomatis ndandani lih-lihan dhobel saka [[$1]] nyang [[$2]] nalika ana opèn-opènan.",
        "double-redirect-fixer": "Révisi pangalihan",
        "brokenredirects": "Pangalihan rusak",
        "brokenredirectstext": "Pengalihan ing ngisor iki tumuju menyang kaca sing ora ana:",
        "wantedtemplates": "Cithakan sing diperlokaké",
        "mostlinked": "Kaca sing kerep dhéwé dituju",
        "mostlinkedcategories": "Kategori sing kerep dhéwé dienggo",
-       "mostlinkedtemplates": "Cithakan sing kerep dhéwé dienggo",
+       "mostlinkedtemplates": "Kaca paling akèh transklusi",
        "mostcategories": "Kaca sing kategoriné akèh dhéwé",
        "mostimages": "Berkas sing kerep dhéwé dienggo",
        "mostinterwikis": "Halaman dengan interwiki terbanyak",
        "newpages-username": "Asma panganggo:",
        "ancientpages": "Kaca-kaca langkung sepuh",
        "move": "Pindhahen",
-       "movethispage": "Pindhahna kaca iki",
+       "movethispage": "Lih kaca iki",
        "unusedimagestext": "Berkas-berkas sing kapacak iki ana nanging ora dienggo ing kaca apa waé.\nTulung digatèkaké yèn situs wèb liyané mbok-menawa bisa nyambung ing sawijining berkas sacara langsung mawa URL langsung, lan berkas-berkas kaya mengkéné iku mbok-menawa ana ing daftar iki senadyan ora dienggo aktif manèh.",
        "unusedcategoriestext": "Kategori iki ana senadyan ora ana artikel utawa kategori liyané sing nganggo.",
        "notargettitle": "Ora ana sasaran",
        "nopagetext": "Kaca sing panjenengan tuju ora ditemokaké.",
        "pager-newer-n": "{{PLURAL:$1|1 luwih anyar|$1 luwih anyar}}",
        "pager-older-n": "{{PLURAL:$1|1 sing luwih lawas|$1 sing luwih lawas}}",
-       "suppress": "Pangawas (''oversight'')",
+       "suppress": "Dhelikaké",
        "querypage-disabled": "Kaca kusus iki dipatèni kanggo alesan kinerja.",
        "apisandbox": "Kothak wedhi API",
        "apisandbox-api-disabled": "API dipatèni nèng situs iki.",
        "usermessage-summary": "Tinggalaké layang sistem.",
        "usermessage-editor": "Pawartaning layang sistem",
        "watchlist": "Daptar pangawasan",
-       "mywatchlist": "Daftar pangawasan",
+       "mywatchlist": "Pawawangan",
        "watchlistfor2": "Kanggo $1 $2",
-       "nowatchlist": "Daftar pangawasan panjenengan kosong.",
+       "nowatchlist": "Ora ana apa-apa ing pawawanganing sampéyan.",
        "watchlistanontext": "Mangga $1 kanggo mirsani utawa nyunting daftar pangawasan panjenengan.",
        "watchnologin": "Durung mlebu log",
        "addwatch": "Tambah nèng daptar pangawasan",
        "addedwatchtext": "Kaca \"[[:$1]]\" wis ditambahaké menyang [[Special:Watchlist|daftar pangawasan]].\nOwah-owahan sing dumadi ing tembé ing kaca iku lan kaca dhiskusi sing kagandhèng, bakal dipacak ing kéné.",
        "removewatch": "Singkiraké saka daptar pangawasan",
        "removedwatchtext": "Kaca \"[[:$1]]\" wis dibusak saka [[Special:Watchlist|daftar pangawasan]].",
-       "watch": "Awati",
+       "watch": "Awasi",
        "watchthispage": "Periksa kaca iki",
        "unwatch": "Ora usah ngawasaké manèh",
        "unwatchthispage": "Batalna olèhé ngawasi kaca iki",
        "wlheader-showupdated": "Kaca-kaca sing wis owah wiwit ditiliki panjenengan kaping pungkasan, dituduhaké mawa '''aksara kandel'''",
        "wlnote": "Ngisor iki {{PLURAL:$1|owahan pungkasan|'''$1''' owahan pungkasan}} {{PLURAL:$2|jam|'''$2''' jam}} kapungkur, per $3, $4.",
        "wlshowlast": "Tuduhna $1 jam $2 dina  pungkasan",
-       "watchlist-options": "Opsi daftar pangawasan",
+       "watchlist-options": "Pilihaning pawawangan",
        "watching": "Ngawasi...",
        "unwatching": "Ngilangi pangawasan...",
        "watcherrortext": "Ana kasalahan nalika ngganti pangaturan daptar pangawasan Sampéyan kanggo \"$1\".",
        "cantrollback": "Ora bisa mbalèkaké suntingan; panganggo pungkasan iku siji-sijiné penulis artikel iki.",
        "alreadyrolled": "Ora bisa mbalèkaké suntingan pungkasan [[:$1]] déning [[User:$2|$2]] ([[User talk:$2|Wicara]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); wong liya wis nyunting utawa mbalèkaké kaca artikel iku.\n\nSuntingan pungkasan dilakoni déning [[User:$3|$3]] ([[User talk:$3|Wicara]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Ringkesan suntingan yaiku: <em>$1</em>.",
-       "revertpage": "Suntingan [[Special:Contributions/$2|$2]] ([[User talk:$2|dhiskusi]]) dipunwangsulaken dhateng ing vèrsi pungkasan déning [[User:$1|$1]]",
+       "revertpage": "Besutan sing dibalèkaké [[Special:Contributions/$2|$2]] ([[User talk:$2|gunem]]) bab owahan pungkasan déning [[User:$1|$1]]",
        "revertpage-nouser": "Suntingan déning panganggo sing didhelikake, dibalèkaké nèng benahan pungkasan déning [[User:$1|$1]]",
        "rollback-success": "Suntingan dibalèkaké déning $1;\ndiowahi bali menyang vèrsi pungkasan déning $2.",
        "sessionfailure-title": "Sèsi gagal",
        "namespace": "Lowah aran:",
        "invert": "Balèkaké pilihan",
        "tooltip-invert": "Centhang kothak iki kanggo ndhelikaké owahan saka kaca-kaca nèng njero bilik jeneng kapilih (lan bilik jeneng kakait yèn dicenthang)",
-       "namespace_association": "Bilik jeneng kakait",
+       "namespace_association": "Lowah aran magepokan",
        "tooltip-namespace_association": "Centhang kothak iki kanggo nglebokaké uga bilik jeneng gumenan utawa subyèk sing kakait karo bilik jeneng kapilih",
        "blanknamespace": "(Pokok)",
        "contributions": "Sumbangan {{GENDER:$1|panganggo}}",
        "nolinkshere-ns": " Ora ana kaca sing nduwé pranala menyang '''[[:$1]]''' ing bilik jeneng sing kapilih.",
        "isredirect": "kaca lih-lihan",
        "istemplate": "karo cithakan",
-       "isimage": "pranala berkas",
+       "isimage": "pranala barkas",
        "whatlinkshere-prev": "{{PLURAL:$1|sadurungé|$1 sadurungé}}",
        "whatlinkshere-next": "{{PLURAL:$1|sabanjuré|$1 sabanjuré}}",
        "whatlinkshere-links": "← pranala",
-       "whatlinkshere-hideredirs": "$1 pangalihan-pangalihan",
-       "whatlinkshere-hidetrans": "$1 transklusi",
-       "whatlinkshere-hidelinks": "pranala-pranala $1",
-       "whatlinkshere-hideimages": "$1 pranala berkas",
+       "whatlinkshere-hideredirs": "Dhelikaké lih-lihan",
+       "whatlinkshere-hidetrans": "Dhelikaké transklusi",
+       "whatlinkshere-hidelinks": "Dhelikaké pranala",
+       "whatlinkshere-hideimages": "Dhelikaké pranala barkas",
        "whatlinkshere-filters": "Panyaringan",
        "autoblockid": "Blokir otomatis #$1",
        "block": "Blokir panganggo",
        "unblock": "Uculaké blokirané panganggo",
-       "blockip": "Blokir panganggo",
+       "blockip": "Palang {{GENDER:$1|panganggo}}",
        "blockip-legend": "Blokir panganggo",
        "blockiptext": "Enggonen formulir ing ngisor iki kanggo mblokir sawijining alamat IP utawa panganggo supaya ora bisa nyunting kaca.\nPrekara iki perlu dilakoni kanggo menggak vandalisme, lan miturut [[{{MediaWiki:Policy-url}}|kawicaksanan {{SITENAME}}]].\nLebokna alesan panjenengan ing ngisor iki (contoné njupuk conto kaca sing wis tau dirusak).",
        "ipaddressorusername": "Alamat IP utawa jeneng panganggo",
        "ipbother": "Wektu liya",
        "ipboptions": "2 jam:2 hours,1 dina:1 day,3 dina:3 days,1 minggu:1 week,2 minggu:2 weeks,1 sasi:1 month,3 sasi:3 months,6 sasi:6 months,1 taun:1 year,tanpa wates:infinite",
        "ipbhidename": "Delikna jeneng panganggo saka suntingan lan pratélan",
-       "ipbwatchuser": "Ngawasi kaca panganggo lan kaca-kaca dhiskusi panganggo iki",
+       "ipbwatchuser": "Wasi kaca panganggoning lan kaca gegunemaning panganggo iki",
        "ipb-disableusertalk": "Alangi panganggo iki nyunting kaca gunemané nalika diblokir",
        "ipb-change-block": "Blokir manèh panganggo kanthi sèting iki",
        "ipb-confirm": "Pesthèkaké blokir",
        "noautoblockblock": "pamblokiran otomatis dipatèni",
        "createaccountblock": "ndamelipun akun dipunblokir",
        "emailblock": "layang e-mail diblokir",
-       "blocklist-nousertalk": "ora éntuk nyunting kaca gunemané dhéwé",
+       "blocklist-nousertalk": "ora kena mbesut kaca guneman dhéwé",
        "ipblocklist-empty": "Daftar pamblokiran kosong.",
        "ipblocklist-no-results": "alamat IP utawa panganggo sing disuwun ora diblokir.",
        "blocklink": "palang",
        "block-log-flags-nocreate": "opsi nggawé akun utawa rékening dipatèni",
        "block-log-flags-noautoblock": "blokir otomatis dipatèni",
        "block-log-flags-noemail": "e-mail diblokir",
-       "block-log-flags-nousertalk": "ora éntuk nyunting kaca gunemané dhéwé",
+       "block-log-flags-nousertalk": "ora kena mbesut kaca guneman dhéwé",
        "block-log-flags-angry-autoblock": "paningkatan sistem pamblokiran otomatis wis diaktifaké",
        "block-log-flags-hiddenname": "jeneng panganggo didhelikaké",
        "range_block_disabled": "Fungsi pamblokir blok IP kanggo para opsis dipatèni.",
        "ipbnounblockself": "Sampéyan ora dililakaké mbukak blokirané Sampéyan",
        "lockdb": "Kunci basis data",
        "unlockdb": "Buka kunci basis data",
-       "lockdbtext": "Ngunci basis data bakal menggak kabèh panganggo kanggo nyunting kaca, ngowahi préferènsi panganggo, nyunting daftar pangawasan, lan prekara-prekara liyané sing merlokaké owah-owahan basis data. Pastèkna yèn iki pancèn panjenengan gayuh, lan yèn panjenengan ora lali mbuka kunci basis data sawisé pangopènan rampung.",
-       "unlockdbtext": "Mbuka kunci basis data bakal mbalèkaké kabèh panganggo bisa nyunting kaca manèh, ngowahi préferènsi panganggo, nyunting daftar pangawasan, lan prekara-prekara liyané sing merlokaké pangowahan marang basis data.\nTulung pastèkna yèn iki pancèn sing panjenengan gayuh.",
+       "lockdbtext": "Ngunci basis data bakal menggak kabèh panganggo kanggo nyunting kaca, ngowahi prèferènsi panganggo, nyunting daftar pangawasan, lan prekara-prekara liyané sing merlokaké owah-owahan basis data. Pastèkna yèn iki pancèn panjenengan gayuh, lan yèn panjenengan ora lali mbuka kunci basis data sawisé pangopènan rampung.",
+       "unlockdbtext": "Mbuka kunci basis data bakal mbalèkaké kabèh panganggo bisa nyunting kaca manèh, ngowahi prèferènsi panganggo, nyunting daftar pangawasan, lan prekara-prekara liyané sing merlokaké pangowahan marang basis data.\nTulung pastèkna yèn iki pancèn sing panjenengan gayuh.",
        "lockconfirm": "Iya, aku pancèn péngin ngunci basis data.",
        "unlockconfirm": "Iya, aku pancèn péngin tmbuka kunci basis data.",
        "lockbtn": "Kunci basis data",
        "lockfilenotwritable": "Berkas kunci basis data ora bisa ditulis. Kanggo ngunci utawa mbuka basis data, berkas iki kudu ditulis déning server wèb.",
        "databasenotlocked": "Basis data ora dikunci.",
        "lockedbyandtime": "(déning {{GENDER:$1|$1}} tanggal $2 wanci $3)",
-       "move-page": "Pindhahna $1",
+       "move-page": "Ngalih $1",
        "move-page-legend": "Mindhah kaca",
        "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": "Kaca dhiskusi sing kagandhèng uga bakal dipindhahaké sacara otomatis '''kejaba yèn:'''\n\n*Sawijining kaca dhiskusi sing ora kosong wis ana sangisoring irah-irahan (judhul) anyar, utawa\n*Panjenengan ora maringi tandha cèk ing kothak ing ngisor iki.\n\nIng kasus-kasus iku, yèn panjenengan gayuh, panjenengan bisa mindhahaké utawa nggabung kaca iku sacara manual.",
+       "movepagetalktext": "Menawa sampéyan nyénthang kothak iki, kaca geguneman sing magepokan bakal otomatis dilih nyang sesirah anyar, kajaba kaca gegunemané wis ana isiné sadurungé.\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é.",
        "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.",
        "cant-move-user-page": "Panjenengan ora nduwèni hak aksès kanggo mindhahaké kaca panganggo (kapisah saka anak-kaca).",
        "cant-move-to-user-page": "Panjenengan ora nduwèni hak aksès kanggo mindhahaké kaca menyang sawijining kaca panganggoa (kajaba menyang anak-kaca panganggo).",
-       "newtitle": "Menyang irah-irahan utawa judhul anyar:",
+       "newtitle": "Sesirah anyar:",
        "move-watch": "Awasna kaca iki",
-       "movepagebtn": "Pindhahna kaca",
+       "movepagebtn": "Ngalih kaca",
        "pagemovedsub": "Bisa kasil dipindhahaké",
        "movepage-moved": "'''\"$1\" dipindhahaké menyang \"$2\".'''",
        "movepage-moved-redirect": "Kaca pengalihan wis kacipta.",
        "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.",
        "cantmove-titleprotected": "Panjenengan ora bisa mindhahaké kaca iki menyang lokasi iki, amerga irah-irahan tujuan lagi direksa; ora olèh digawé",
-       "movetalk": "Pindahna kaca dhiskusi sing ana gandhèngané.",
-       "move-subpages": "Pindhahna anak-kaca (nganti $1)",
-       "move-talk-subpages": "Pindhahna anak-kaca saka kaca wicara (nganti $1)",
+       "movetalk": "Lih kaca geguneman sing magepokan",
+       "move-subpages": "Lih anak kaca (tekan $1)",
+       "move-talk-subpages": "Lih anak kaca saka kaca geguneman (tekan $1)",
        "movepage-page-exists": "Kaca $1 wis ana lan ora bisa ditindhes sacara otomatis.",
        "movepage-page-moved": "Kaca $1 wis dipindhah menyang $2.",
        "movepage-page-unmoved": "Kaca $1 ora bisa dialihaké menyang $2.",
        "movenosubpage": "Kaca iki ora duwé anak-kaca.",
        "movereason": "Alesan:",
        "revertmove": "balèkaké",
-       "delete_and_move_text": "== Perlu mbusak ==\n\nArtikel sing dituju, \"[[:$1]]\", wis ana isiné.\nApa panjenengan kersa mbusak iku supaya kacané bisa dialihaké?",
+       "delete_and_move_text": "Kaca jujugan \"[[:$1]]\" wis ana.\nApa sampéyan kersa mbusak iku supaya kacané bisa dilih?",
        "delete_and_move_confirm": "Ya, busak kaca iku.",
        "delete_and_move_reason": "Dibusak kanggo jaga-jaga ananing pamindhahan saka \"[[$1]]\"",
        "selfmove": "Pangalihan kaca ora bisa dilakoni amerga irah-irahan utawa judhul sumber lan tujuané padha.",
        "move-leave-redirect": "Gawé pangalihan menyang irah-irahan anyar",
        "protectedpagemovewarning": "'''Pènget:''' Kaca iki wis dikunci dadi mung panganggo sing nduwé hak aksès pangurus baé sing bisa mindhahaké.\nCathetan entri pungkasan disadiakaké ing ngisor kanggo referensi:",
        "semiprotectedpagemovewarning": "'''Cathetan:''' Kaca iki wis direksa saéngga mung panganggo kadhaptar sing bisa mindhahaké.\nEntri cathetan pungkasan disadiakake ing ngisor kanggo referensi:",
-       "move-over-sharedrepo": "== Berkas wis ana ==\n[[:$1]] ana ing panyimpenan bebarengan. Mindhahaké berkas mawa judul iki bakal nibani berkas bebarengan.",
+       "move-over-sharedrepo": "[[:$1]] ana ing panyimpenan barengan. Ngalih barkas mawa sesirah iki bakal ngamblegi barkas barengan iku.",
        "file-exists-sharedrepo": "Jeneng berkas kapilih wis ana kanggo nèng panyimpenan bebarengan.\nMangga pilih jeneng liya.",
        "export": "Ekspor kaca",
        "exporttext": "Panjenengan bisa ngèkspor tèks lan sajarah panyuntingan sawijining kaca tartamtu utawa sawijining sèt kaca awujud XML tartamtu. Banjur iki bisa diimpor ing wiki liyané nganggo MediaWiki nganggo fasilitas [[Special:Import|impor kaca]].\n\nKanggo ngèkspor kaca-kaca artikel, lebokna irah-irahan utawa judhul sajroning kothak tèks ing ngisor iki, irah-irahan utawa judhul siji per baris, lan pilihen apa panjenengan péngin ngèkspor jangkep karo vèrsi sadurungé, utawa namung vèrsi saiki mawa cathetan panyuntingan pungkasan.\n\nYèn panjenengan namun péngin ngimpor vèrsi pungkasan, panjenengan uga bisa nganggo pranala kusus, contoné [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] kanggo ngèkspor artikel \"[[{{MediaWiki:Mainpage}}]]\".",
        "thumbnail_gd-library": "Konfigurasi pustaka GD ora pepak: fungsi $1 ilang",
        "thumbnail_image-missing": "Berkas katonané ilang: $1",
        "import": "Impor kaca",
-       "importinterwiki": "Impor transwiki",
+       "importinterwiki": "Impor saka wiki liya",
        "import-interwiki-text": "Pilih sawijining wiki lan irah-irahan kaca sing arep diimpor.\nTanggal révisi lan jeneng panyunting bakal dilestarèkaké.\nKabèh aktivitas impor transwiki bakal dilog ing [[Special:Log/import|log impor]].",
        "import-interwiki-history": "Tuladen kabèh vèrsi lawas saka kaca iki",
        "import-interwiki-templates": "Katutna kabèh cithakan",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|révisi}} saka $2",
        "javascripttest": "Panjajalan JavaScript",
        "javascripttest-qunit-intro": "Delok [dhokumèntasi panjajalan $1] nèng mediawiki.org.",
-       "tooltip-pt-userpage": "Kaca {{GENDER:|panganggo sampéyan}}",
+       "tooltip-pt-userpage": "Kaca {{GENDER:|panganggoning sampéyan}}",
        "tooltip-pt-anonuserpage": "Kaca panganggo IP panjenengan",
-       "tooltip-pt-mytalk": "Kaca paguneman {{GENDER:|sampéyan}}",
-       "tooltip-pt-anontalk": "Dhiskusi perkara suntingan saka alamat IP iki",
-       "tooltip-pt-preferences": "Pamiji {{GENDER:|sampéyan}}",
-       "tooltip-pt-watchlist": "Daftar kaca sing tak-awasi.",
+       "tooltip-pt-mytalk": "Kaca gegunemaning {{GENDER:|sampéyan}}",
+       "tooltip-pt-anontalk": "Rerembugan bab besutan-besutan saka alamat IP iki",
+       "tooltip-pt-preferences": "Pilih-pilihaning {{GENDER:|sampéyan}}",
+       "tooltip-pt-watchlist": "Pratélaning kaca sing sampéyan awasi owah-owahané",
        "tooltip-pt-mycontris": "Pratélaning sumbanganing {{GENDER:|sampéyan}}",
        "tooltip-pt-login": "Sampéyan prayogané mlebu masiya ora kudu",
        "tooltip-pt-logout": "Metu",
        "tooltip-pt-createaccount": "Sampéyan prayogané gawé akun lan mlebu masiya ora kudu",
-       "tooltip-ca-talk": "Parembuganing kaca isi",
+       "tooltip-ca-talk": "Rerembuganing kaca isi",
        "tooltip-ca-edit": "Besut kaca iki",
        "tooltip-ca-addsection": "Miwiti pérangan anyar",
        "tooltip-ca-viewsource": "Kaca iki direksa. \nSampéyan bisa ndeleng sumberé",
        "tooltip-ca-unprotect": "Ganti panjagan kaca iki",
        "tooltip-ca-delete": "Busak kaca iki",
        "tooltip-ca-undelete": "Balèkna suntingan ing kaca iki sadurungé kaca iki dibusak",
-       "tooltip-ca-move": "Aliha kaca iki",
+       "tooltip-ca-move": "Lih kaca iki",
        "tooltip-ca-watch": "Tambahaké kaca iki nyang pawawangan sapéyan",
-       "tooltip-ca-unwatch": "Busak kaca iki saka daftar pangawasan panjenengan",
+       "tooltip-ca-unwatch": "Busak kaca iki saka pawawanganing sampéyan",
        "tooltip-search": "Golèk nyang {{SITENAME}}",
        "tooltip-search-go": "Jujug kaca asesirah persis mangkéné yèn ana",
        "tooltip-search-fulltext": "Golèk kaca isi tulisan kaya mangkéné",
-       "tooltip-p-logo": "Menyang kaca pokok",
-       "tooltip-n-mainpage": "Menyang kaca pokok",
-       "tooltip-n-mainpage-description": "Menyang kaca pokok",
+       "tooltip-p-logo": "Menyang tepas",
+       "tooltip-n-mainpage": "Menyang tepas",
+       "tooltip-n-mainpage-description": "Menyang tepas",
        "tooltip-n-portal": "Bab proyèk, apa sing bisa sampéyan garap, ana ing endi saprelu golèk apa-apa",
        "tooltip-n-currentevents": "Temokaké katerangan latar wuri saka kadadéan saiki",
        "tooltip-n-recentchanges": "Pratélaning owah-owahan pungkasan sajeroning wiki.",
        "tooltip-ca-nstab-category": "Deleng kaca kategori",
        "tooltip-minoredit": "Tandhani iki minangka besutan cilik",
        "tooltip-save": "Simpen owah-owahaning sampéyan",
-       "tooltip-preview": "Prawuryan owah-owahaning sampéyan. Anggoa cara iki sadurungé nyimpen.",
+       "tooltip-preview": "Pratuduhing owah-owahaning sampéyan. Anggoa cara iki sadurungé nyimpen.",
        "tooltip-diff": "Tuduhaké owah-owahan endi sing sampéyan gawé tumrap tulisan iki",
        "tooltip-compareselectedversions": "Delengen prabédan antara rong vèrsi kaca iki sing dipilih.",
-       "tooltip-watch": "Tambahna kaca iki ing daftar pangawasan panjenengan",
+       "tooltip-watch": "Wuwuh kaca iki nyang pawawanganing sampéyan",
        "tooltip-watchlistedit-normal-submit": "Singkiraké judhul",
        "tooltip-watchlistedit-raw-submit": "Anyari daptar pangawasan",
        "tooltip-recreate": "Gawéa kaca iki manèh senadyan tau dibusak",
        "file-info-size": "$1 × $2 piksel, ukuran barkas: $3, jinis MIME: $4",
        "file-info-size-pages": "$1 × $2 piksel, gedhéné berkas: $3, jinisé MIME: $4, $5 {{PLURAL:$5|kaca|kaca}}",
        "file-nohires": "Ora ana résolusi sing luwih dhuwur.",
-       "svg-long-desc": "Berkas SVG, nominal $1 × $2 piksel, gedhené berkas: $3",
+       "svg-long-desc": "Barkas SVG, nominal $1 × $2 piksel, gedhéning barkas: $3",
        "svg-long-desc-animated": "Berkas SVG, nominal $1 × $2 piksel, gedhené berkas: $3",
        "svg-long-error": "Berkas SVG ora sah: $1",
        "show-big-image": "Barkas asli",
-       "show-big-image-preview": "Gedhéné pratayang iki: $1",
+       "show-big-image-preview": "Gedhéning pratuduh iki: $1",
        "show-big-image-other": "{{PLURAL:$2|Résolusi|Résolusi}} liya: $1.",
        "show-big-image-size": "$1 × $2 piksel",
        "file-info-gif-looped": "mubeng",
        "exif-primarychromaticities": "Kromatisitas werna primer",
        "exif-ycbcrcoefficients": "Koèfisièn matriks transformasi papan werna",
        "exif-referenceblackwhite": "Wiji réferènsi pasangan ireng putih",
-       "exif-datetime": "Tanggal lan wektu pangowahan berkas",
+       "exif-datetime": "Tanggal lan tabuh owahing barkas",
        "exif-imagedescription": "Judhul gambar",
        "exif-make": "Produsèn kamera",
        "exif-model": "Modhèl kaméra",
        "exif-sublocationdest": "Dhaèrahé kutha katampilaké",
        "exif-objectname": "Judhul cendhèk",
        "exif-specialinstructions": "Prèntah kusus",
-       "exif-headline": "Warta utama",
+       "exif-headline": "Tajuk",
        "exif-credit": "Krédit/Panyadhiya",
        "exif-source": "Sumber",
        "exif-editstatus": "Status kapanyuntingan gambar",
        "namespacesall": "kabèh",
        "monthsall": "kabèh",
        "confirmemail": "Konfirmasi alamat e-mail",
-       "confirmemail_noemail": "Panjenengan ora maringi alamat e-mail sing absah ing [[Special:Preferences|préferènsi]] panjenengan.",
+       "confirmemail_noemail": "Panjenengan ora maringi alamat é-mail sing absah ing [[Special:Preferences|prèferènsi]] panjenengan.",
        "confirmemail_text": "{{SITENAME}} ngwajibaké panjenengan ndhedhes utawa konfirmasi alamat e-mail panjenengan sadurungé bisa nganggo fitur-fitur e-mail.\nPencèten tombol ing ngisor iki kanggo ngirim sawijining kode konfirmasi arupa sawijining pranala;\nTuladen pranala iki ing panjlajah wèb panjenengan kanggo ndhedhes yèn alamat e-mail panjenengan pancèn bener.",
        "confirmemail_pending": "Sawijining kode konfirmasi wis dikirim menyang alamat e-mail panjenengan;\nyèn panjenengan lagi waé nggawé akun utawa rékening panjenengan, mangga nunggu sawetara menit nganti layang iku tekan sadurungé nyuwun kode anyar manèh.",
        "confirmemail_send": "Kirim kode konfirmasi",
        "lag-warn-normal": "Owah-owahan pungkasan sing luwih anyar tinimbang $1 {{PLURAL:$1|detik|detik}} mbokmanawa ora metu ing pratélan iki.",
        "lag-warn-high": "Amarga gedhéné ''lag'' basis data server, owah-owahan pungkasan sing luwih anyar saka $1 {{PLURAL:$1|detik|detik}} mbokmanawa ora metu ing daftar iki.",
        "watchlistedit-normal-title": "Besut pawawangan",
-       "watchlistedit-normal-legend": "Busak irah-irahan saka daftar pangawasan",
+       "watchlistedit-normal-legend": "Busak sesirah saka pawawangan",
        "watchlistedit-normal-explain": "Irah-irahan utawa judhul ing daftar pangawasan panjenengan kapacak ing ngisor iki.\nKanggo mbusak sawijining irah-irahan, kliken kothak ing pinggiré, lan banjur kliken \"Busak judhul\".\nPanjenengan uga bisa [[Special:EditWatchlist/raw|nyunting daftar mentah]].",
        "watchlistedit-normal-submit": "Busak irah-irahan",
        "watchlistedit-normal-done": "Irah-irahan {{PLURAL:$1|siji|$1}} wis dibusak saka daftar pangawasan panjenengan:",
        "watchlistedit-raw-legend": "Besut pawawangan wantahan",
        "watchlistedit-raw-explain": "Irah-irahan ing daftar pangawasan panjenengan kapacak ing ngisor iki, lan bisa diowahi mawa nambahaké utawa mbusak daftar; sairah-irahan saban barisé.\nYèn wis rampung, anyarana kaca daftar pangawasan iki.\nPanjenengan uga bisa [[Special:EditWatchlist|nganggo éditor standar panjenengan]].",
        "watchlistedit-raw-titles": "Irah-irahan:",
-       "watchlistedit-raw-submit": "Anyarana daftar pangawasan",
-       "watchlistedit-raw-done": "Daftar pangawasan panjenengan wis dianyari.",
+       "watchlistedit-raw-submit": "Anyari pawawangan",
+       "watchlistedit-raw-done": "Pawawanganing sampéyan wis dianyari.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 irah-irahan wis|$1 irah-irahan wis}} ditambahaké:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 irah-irahan wis|$1 irah-irahan wis}} diwetokaké:",
        "watchlisttools-view": "Tuduhna owah-owahan sing ana gandhèngané",
        "watchlisttools-edit": "Deleng lan besut pawawangan",
        "watchlisttools-raw": "Besut pawawangan wantahan",
-       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|wicara]])",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|geguneman]])",
        "duplicate-defaultsort": "Pènget: Kunci pilih asal (''Default sort key'') \"$2\" nggantèkaké kunci pilih asal sadurungé \"$1\".",
        "version": "Versi",
        "version-extensions": "Èkstènsi sing wis diinstalasi",
-       "version-skins": "Kulit",
+       "version-skins": "Kulit sing disetèl",
        "version-specialpages": "Kaca astaméwa (kaca kusus)",
        "version-parserhooks": "Canthèlan parser",
        "version-variables": "Variabel",
        "version-entrypoints": "URL tithik lebon",
        "version-entrypoints-header-entrypoint": "Tithik lebon",
        "version-entrypoints-header-url": "URL",
-       "redirect": "Dialihake dening gambar, panganggo, kaca, utawa ID revisi",
+       "redirect": "Lih-lihan miturut barkas, panganggo, kaca, owahan, utawa cathetan",
        "redirect-summary": "Kaca astamiwa iki dialihake menyang gambar (jeneng gambar diwenehi), kaca (ID revisi utama ID kaca diwenehi), utawa kaca panganggo (ID panganggo diwenehi). Cara nganggo: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Nuju",
        "redirect-lookup": "Golek:",
        "intentionallyblankpage": "Kaca iki disengajakaké kosong",
        "external_image_whitelist": " #Umbarna larikan iki apa anané<pre>\n#Pigunakaké fragmèn èksprèsi regular (mung bagéyan ing antara //) ing ngisor\n#Fragmèn ini bakal dicocogaké karo URL saka gambar-gambar èksternal\n#Fragmèn sing cocog bakal ditampilaké minangka gambar, yèn ora mung pranala menyang gambar waé sing ditampilaké\n#Larikan sing diwiwiti nganggo # dianggep minangka komentar\n#Iki ora mbédakaké aksara gedhé/cilik\n#Dèlèhna kabèh fragmèn èksprèsi regular sadhuwuré larikan iki. Umbarna larikan iki apa anané</pre>",
        "tags": "Tag pangowahan sing absah",
-       "tag-filter": "Panyaringan [[Special:Tags|tandha]]:",
+       "tag-filter": "Panyaringan [[Special:Tags|tenger]]:",
        "tag-filter-submit": "Penyaring",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tenger|Tenger}}]]: $2)",
        "tags-title": "Tag",
        "revdelete-uname-unhid": "jeneng panganggo dituduhaké",
        "revdelete-restricted": "rèstriksi ditrapaké marang para opsis",
        "revdelete-unrestricted": "rèstriksi marang para opsis dijabel",
-       "logentry-move-move": "$1 {{GENDER:$2|mindhahaké}} kaca $3 nèng $4",
+       "logentry-move-move": "$1 {{GENDER:$2|ngalih}} kaca $3 nyang $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|mindhahaké}} kaca $3 nèng $4 tanpa ninggalaké pangalihan",
        "logentry-move-move_redir": "$1 {{GENDER:$2|mindhahaké}} kaca $3 nèng $4 nindesi pangalihan liyane",
        "logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|mindhahaké}} kaca $3 nèng $4 nindesi pangalihan liyane tanpa nginggalaké pangalihan",
        "api-error-badaccess-groups": "Sampéyan ora dililakaké ngunggah berkas nèng wiki iki.",
        "api-error-badtoken": "Kasalahan njero: Token èlèk.",
        "api-error-copyuploaddisabled": "Ngunggah saka URL dipatèni nèng sasana iki.",
-       "api-error-duplicate": "Ana {{PLURAL:$1|berkas liya|pirang-pirang berkas liya}} sing wis ana nèng situsé saha isiné padha.",
+       "api-error-duplicate": "Wis ana {{PLURAL:$1|barkas liya|barkas-barkas liya}} mawa isi sing padha sajeroning sana jaringan iki.",
        "api-error-duplicate-archive": "Ana {{PLURAL:$1|berkas liya|pirang-pirang berkas liya}} sing wis ana nèng situsé saha isiné padha, nanging {{PLURAL:$1|kuwi|kuwi kabèh}} wis dibusak.",
        "api-error-empty-file": "Berkas sing Sampéyan kirim kosong.",
        "api-error-emptypage": "Nggawé kaca kosong anyar ora dilikaké.",
        "special-characters-group-lao": "Lao",
        "special-characters-group-khmer": "Khmer",
        "api-error-blacklisted": "Mangga pilih judhul liya sing njelasaké",
-       "randomrootpage": "Kaca root waton"
+       "randomrootpage": "Kaca dhasaran waton"
 }
index 6685cc6..b9885e0 100644 (file)
        "noname": "თქვენს მიერ მითითებული მომხმარებლის სახელი ქმედითი არ არის.",
        "loginsuccesstitle": "სისტემაში შესვლა განხორციელდა",
        "loginsuccess": "'''ამჟამად შესული ხართ {{SITENAME}}-ში როგორც „$1“.'''",
-       "nosuchuser": "მომხმარებელი სახელად $1 არ არსებობს.\nმომხმარებელთა სახელები გრძნობადია ასოების რეგისტრამდე..\nშეამოწმეთ სახელის დაწერა ან[[Special:UserLogin/signup|შექმენით ახალი ანგარიში]].",
+       "nosuchuser": "მომხმარებელი სახელად $1 არ არსებობს.\nმომხმარებელთა სახელები გრძნობადია ასოების რეგისტრამდე..\nშეამოწმეთ სახელის დაწერა ან[[Special:CreateAccount|შექმენით ახალი ანგარიში]].",
        "nosuchusershort": "მომხმარებელი სახელით „$1“ არ არსებობს. შეამოწმეთ მართლწერა.",
        "nouserspecified": "საჭიროა მომხმარებლის სახელის მითითება.",
        "login-userblocked": "ეს მომხმარებელი დაბლოკილია. სისტემაში შესვლა არაა ნებადართული.",
        "accmailtext": "შემთხვევითი მეთოდით შექმნილი პაროლი მომხმარებლისათვის [[User talk:$1|$1]] გაგზავნილია მისამართზე $2.\n\nავტორიზაციის გავლის შემდეგ შესაძლებელი იქნება ამ ანგარიშის  ''[[Special:ChangePassword|პაროლის შეცვლა]]'' ანგარიშში შესვლის გვერდზე.",
        "newarticle": "(ახალი)",
        "newarticletext": "ბმულის მეშვეობით თქვენ მოხვდით გვერდზე, რომელიც ჯერ არ არსებობს.\nგვერდის შესაქმნელად შეიყვანეთ ინფორმაცია ქვემო ფანჯარაში\n(იხ. [$1 დახმარების გვერდი] დამატებითი ინფორმაციისთვის).\nთუ ამ გვერდზე შეცდომით მოხვდით, დაბრუნდით უკან თქვენი ბრაუზერის მეშვეობით.",
-       "anontalkpagetext": "----''ეს არის ანონიმური მომხმარებლის განხილვის გვერდი, რომელსაც ანგარიში ჯერ არ შეუქმნია ან არ იყენებს მას.\n\nშესაბამისად, ჩვენ მისი ციფრული IP მისამართი უნდა გამოვიყენოთ მისი იდენტიფიცირებისთვის.\n\nამგვარი მისამართი შეიძლება რამდენიმე მომხმარებელმა გამოიყენოს.\n\nთუ თქვენ ანონიმური მომხმარებელი ხართ და თვლით, რომ სხვისთვის გამიზნული მითითება მიიღეთ, გთხოვთ [[Special:UserLogin/signup|შექმენით ანგარიში ან დარეგისტრირდით]] მომავალში გაუგებრობის თავიდან ასაცილებლად.''",
+       "anontalkpagetext": "----''ეს არის ანონიმური მომხმარებლის განხილვის გვერდი, რომელსაც ანგარიში ჯერ არ შეუქმნია ან არ იყენებს მას.\n\nშესაბამისად, ჩვენ მისი ციფრული IP მისამართი უნდა გამოვიყენოთ მისი იდენტიფიცირებისთვის.\n\nამგვარი მისამართი შეიძლება რამდენიმე მომხმარებელმა გამოიყენოს.\n\nთუ თქვენ ანონიმური მომხმარებელი ხართ და თვლით, რომ სხვისთვის გამიზნული მითითება მიიღეთ, გთხოვთ [[Special:CreateAccount|შექმენით ანგარიში ან დარეგისტრირდით]] მომავალში გაუგებრობის თავიდან ასაცილებლად.''",
        "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}}}} მოძებნოთ ჟურნალების შესაბამისი ჩანაწერები].</span> თქვენ არ გაქვთ ამ გვერდის შექმნის ნებართვა.",
        "missing-revision": "ვერსია $1 გვერდისათვის „{{FULLPAGENAME}}“ არ არსებობს.\n\nეს ჩვეულებრივ ხდება მაშინ, თუ მოძველებული ბმულით გადადიხართ გვერდზე, რომელიც წაიშალა.\nდეტალური ინფორმაცია შესაძლებელია იყოს [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} წაშლების ჟურნალში].",
        "upload-form-label-infoform-description-tooltip": "მოკლედ აღწერეთ ამ ნამუშევრის შესახებ ყველაფერი მნიშვნელოვანი.\nფოტოსათვის, მიუთითეთ რა არის გამოსახული, სად არის გადაღებული, რა ვითარებაში.",
        "upload-form-label-usage-title": "გამოყენება",
        "upload-form-label-usage-filename": "ფაილის სახელი",
-       "foreign-structured-upload-form-label-own-work": "ეს ჩემი პირადი ნამუშევარია",
-       "foreign-structured-upload-form-label-infoform-categories": "კატეგორიები",
-       "foreign-structured-upload-form-label-infoform-date": "თარიღი",
-       "foreign-structured-upload-form-label-own-work-message-local": "ვადასტურებ, რომ ვტვირთავვ ამ ფაილს მომსახურების პირობებისა და ლიცენსიის პოლიტიკის შესაბამისად {{SITENAME}}-ზე.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "თუ ვერ ტვირთავთ ამ ფაილს {{SITENAME}}-ის წესების დაცვით, გთხოვთ დახურეთ ეს ფანჯარა და სცადეთ სხვა მეთოდი.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "შეგიძლიათ სცადოთ [[Special:Upload|მთავარი ატვირთვის გვერდი]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "ვიცი, რომ ამ ფაილს ვტვირთავ საზიარო ბაზაში. ვადასტურებ, რომ ამას ვაკეთებ მომსახურების პირობებისა და ლიცენზიის პოლიტიკის შესაბამისად.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "თუ ვერ ტვირთავთ ამ ფაილს {{SITENAME}}-ის წესების დაცვით, გთხოვთ დახურეთ ეს ფანჯარა და სცადეთ სხვა მეთოდი.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "შეგიძლიათ ასევე სცადოთ [[Special:Upload|ატვირთვის გვერდი {{SITENAME}}-ზე]], თუ ამ ფაილის ატვირთვა დაშვებულია მათი პოლიტიკის მიხედვით.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "მე ვადასტურებ, რომ ამ ფაილზე საავტორო უფლებების მფლობელი ვარ და ვთანხმდები ამ ფაილის შეუქცევადად განთავსებაზე ვიკისაწყობში [https://creativecommons.org/licenses/by-sa/4.0/deed.ka Creative Commons Attribution-ShareAlike 4.0] ლიცენზიით, აგრეთვე ვეთანხმები [https://wikimediafoundation.org/wiki/Terms_of_Use გამოყენების წესებს].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "თუ თქვენ არ ხართ საავტორო უფლებებს ამ ფაილზე, ან გსურთ გაუშვათ განსხვავებული ლიცენზიით, გთხოვთ გამოიყენეთ [https://commons.wikimedia.org/wiki/Special:UploadWizard ვიკისაწყობის ატვირთვის ფუნქცია].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "შეგიძლიათ ასევე სცადოთ [[Special:Upload|ატვირთვის გვერდი {{SITENAME}}-ზე]], თუ ამ ფაილის ატვირთვა დაშვებულია მათი პოლიტიკის შესაბამისად.",
+       "upload-form-label-own-work": "ეს ჩემი პირადი ნამუშევარია",
+       "upload-form-label-infoform-categories": "კატეგორიები",
+       "upload-form-label-infoform-date": "თარიღი",
+       "upload-form-label-own-work-message-generic-local": "ვადასტურებ, რომ ვტვირთავვ ამ ფაილს მომსახურების პირობებისა და ლიცენსიის პოლიტიკის შესაბამისად {{SITENAME}}-ზე.",
+       "upload-form-label-not-own-work-message-generic-local": "თუ ვერ ტვირთავთ ამ ფაილს {{SITENAME}}-ის წესების დაცვით, გთხოვთ დახურეთ ეს ფანჯარა და სცადეთ სხვა მეთოდი.",
+       "upload-form-label-not-own-work-local-generic-local": "შეგიძლიათ სცადოთ [[Special:Upload|მთავარი ატვირთვის გვერდი]].",
+       "upload-form-label-own-work-message-generic-foreign": "ვიცი, რომ ამ ფაილს ვტვირთავ საზიარო ბაზაში. ვადასტურებ, რომ ამას ვაკეთებ მომსახურების პირობებისა და ლიცენზიის პოლიტიკის შესაბამისად.",
+       "upload-form-label-not-own-work-message-generic-foreign": "თუ ვერ ტვირთავთ ამ ფაილს {{SITENAME}}-ის წესების დაცვით, გთხოვთ დახურეთ ეს ფანჯარა და სცადეთ სხვა მეთოდი.",
+       "upload-form-label-not-own-work-local-generic-foreign": "შეგიძლიათ ასევე სცადოთ [[Special:Upload|ატვირთვის გვერდი {{SITENAME}}-ზე]], თუ ამ ფაილის ატვირთვა დაშვებულია მათი პოლიტიკის მიხედვით.",
        "backend-fail-stream": "ფაილი $1 ტრანსლირება ვერ მოხერხდა.",
        "backend-fail-backup": "ფაილი $1 სარეზერვო ასლის გაკეთება ვერ მოხერხდა.",
        "backend-fail-notexists": "ფაილი $1 არ არსებობს.",
index e37711b..226f578 100644 (file)
        "noname": "Siz kiritken paydalanıwshı atı qate.",
        "loginsuccesstitle": "Kiriw tabıslı a'melge asırıldı",
        "loginsuccess": "'''{{SITENAME}} saytına \"$1\" paydalanıwshı atı menen kirdin'iz.'''",
-       "nosuchuser": "\"$1\" atlı paydalanıwshı joq.\nTuwrı jazılg'anlıg'ın tekserin' yamasa [[Special:UserLogin/signup|taza akkaunt jaratın']].",
+       "nosuchuser": "\"$1\" atlı paydalanıwshı joq.\nTuwrı jazılg'anlıg'ın tekserin' yamasa [[Special:CreateAccount|taza akkaunt jaratın']].",
        "nosuchusershort": "\"$1\" atlı paydalanıwshı joq. Tuwrı jazılg'anlıg'ın tekserin'.",
        "nouserspecified": "Siz paydalanıwshı atın ko'rsetpedin'iz.",
        "wrongpassword": "Qate parol kiritlgen. Qaytadan kiritin'.",
        "categories": "Kategoriyalar",
        "categoriespagetext": "To'mendegi {{PLURAL:$1|kategoriya|kategoriyalar}} o'z ishine betler yamasa medialardı alg'an.\nBul jerde [[Special:UnusedCategories|paydalanılmag'an kategoriyalar]] ko'rsetilmegen.\nJa'nede [[Special:WantedCategories|kerekli kategoriyalardı]] qarap ko'rin'.",
        "categoriesfrom": "Kategoriyalardı to'mendegilerden baslap ko'rset:",
-       "special-categories-sort-count": "sanı boyınsha ta'rtiplew",
-       "special-categories-sort-abc": "a'lipbe boyınsha ta'rtiplew",
        "deletedcontributions": "Paydalanıwshının' o'shiriw u'lesi",
        "linksearch": "Sırtqı siltewler",
        "linksearch-pat": "İzlew shablonı:",
index 1be5057..3daac22 100644 (file)
        "noname": "Ur tefkiḍ ara isem n wemseqdac ṣaḥiḥ.",
        "loginsuccesstitle": "Tkecmeḍ !",
        "loginsuccess": "'''Tkecmeḍ ar {{SITENAME}} s yisem n wemseqdac \"$1\".'''",
-       "nosuchuser": "Aseqdac « $1 » ulac-it d-agi.\nSsenqed tira n yisem-nni, naɣ [[Special:UserLogin/signup|snulfu-d amiḍan amaynut]].",
+       "nosuchuser": "Aseqdac « $1 » ulac-it d-agi.\nSsenqed tira n yisem-nni, naɣ [[Special:CreateAccount|snulfu-d amiḍan amaynut]].",
        "nosuchusershort": "Ulac isem n wemseqdac s yisem \"$1\". Ssenqed tira n yisem-nni.",
        "nouserspecified": "Yessefk ad tefkeḍ isem n wemseqdac.",
        "login-userblocked": "Aseqdac agi i sewḥel. Tuqqna t-ugwi.",
        "accmailtext": "Awal n uɛaddi id yuran s ugacur i [[User talk:$1|$1]] yetweceggaɛ i $2.\nYezmer ad yetbeddel ɣef usebtar [[Special:ChangePassword|Abeddel n awal uɛddi]] sakin tuqqna.",
        "newarticle": "(Amaynut)",
        "newarticletext": "Tḍefreḍ azday ɣer usebter mazal ur yettwaxleq ara.\nAkken ad txelqeḍ asebter-nni, aru deg tenkult i tella deg ukessar\n(ẓer [$1 asebter n tallalt] akken ad tessneḍ kter).\nMa tɣelṭeḍ, wekki kan ɣef tqeffalt \"Back/Précédent\" n browser/explorateur inek.",
-       "anontalkpagetext": "---- ''Wagi d asebter n umyennan n useqdac adrig, mazal ur d-yesnufa ara amiḍan. I taɣẓint agi, ilaq an seqdec tansa IP ines iwakken at-id n sulu. Yiwet tansa IP tezmer at tettuseqdac sɣur aṭṭas n iseqdacen. Lukan ula d kečč aqla-k amseqdac adrig dɣa ur tebɣiḍ ara ad tettwabcreḍ izen am wigini, ihi [[Special:UserLogin/signup|snulfud amiḍan]] naɣ [[Special:UserLogin|qqened]] iwakken sya d asawen ur t-illint ara uguren n usulu.''",
+       "anontalkpagetext": "---- ''Wagi d asebter n umyennan n useqdac adrig, mazal ur d-yesnufa ara amiḍan. I taɣẓint agi, ilaq an seqdec tansa IP ines iwakken at-id n sulu. Yiwet tansa IP tezmer at tettuseqdac sɣur aṭṭas n iseqdacen. Lukan ula d kečč aqla-k amseqdac adrig dɣa ur tebɣiḍ ara ad tettwabcreḍ izen am wigini, ihi [[Special:CreateAccount|snulfud amiḍan]] naɣ [[Special:UserLogin|qqened]] iwakken sya d asawen ur t-illint ara uguren n usulu.''",
        "noarticletext": "Ulac aḍris deg usebter-agi, tzemreḍ ad [[Special:Search/{{PAGENAME}}|tnadiḍ ɣef wezwel n usebter-agi]] deg isebtar wiyaḍ neɣ [{{fullurl:{{FULLPAGENAME}}|action=edit}} tettbeddileḍ asebter-agi].",
        "noarticletext-nopermission": "Imira ulac aḍris deg usebter agi.\nTzemreḍ [[Special:Search/{{PAGENAME}}|ad nadiḍ ɣef azwel agi]] deg isebtaren nniḍen,\nnaɣ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|asebter={{FULLPAGENAMEE}}}} ad nadiḍ deg iɣmisen iqqenen]</span>.",
        "missing-revision": "Tacaggart #$1 n usebter s isem « {{FULLPAGENAME}} » ulac-itt.\n\nAcku azday n umezruy, ɣef wayen tsennedeḍ, d-aqbur. Asebter yemḥa.\nTzemreḍ ad affeḍ tilɣa deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n isebtar yemḥan].",
index 8fce9dc..2a9aad5 100644 (file)
        "noname": "ЦӀыхухэтыцӀэ хъунур иптхакъым.",
        "loginsuccesstitle": "ЗыкъегъэцӀыхуныр фӀыуэ зэфӀэкӀа.",
        "loginsuccess": "'''Иджы цӀэ \"$1\" зепхьу улэжьэфыну.'''",
-       "nosuchuser": "\"$1\" цӀэ зезыхьэ цӀыхухэт щыӀэкъым.\nЦӀыхухэтыцӀэхэр гурышхъу щытхэ хьэрыф регистырымкӀэ.\nЦӀэр тэрэзу птхамэ еплъ иэ [[Special:UserLogin/signup|аккаунт щӀэуэ щӀы]].",
+       "nosuchuser": "\"$1\" цӀэ зезыхьэ цӀыхухэт щыӀэкъым.\nЦӀыхухэтыцӀэхэр гурышхъу щытхэ хьэрыф регистырымкӀэ.\nЦӀэр тэрэзу птхамэ еплъ иэ [[Special:CreateAccount|аккаунт щӀэуэ щӀы]].",
        "nosuchusershort": "ЦӀыхухэт \"$1\" щыӀэкъым.\nЦӀэр тэрэзу итхамэ еплъ.",
        "nouserspecified": "ЦӀыхухэтыцӀэр иптхэн хуэй.",
        "login-userblocked": "Мы цӀыхухэтыр теубыдауэ щытщ. Ихьэныр хуадэкъым.",
        "accmailtext": "ЦӀыхухэт [[User talk:$1|$1-м]] щхьэкӀэ дэмыгъэ зэхэдзыгъэншу парол зэхэгъувар $2 адресым егъэхьа.\nСистемэм регистрациэ быщӀа яуж ''[[Special:ChangePassword|паролыр зэпхъуэкӀыфыну]].''",
        "newarticle": "(ЩIэуэ)",
        "newarticletext": "НапэкӀуэцӀ иджыри щымыӀэм утехуащ техьэпӀэмкэ.\nАр быщӀын щхьэкӀэ, и щӀагъым щӀэт игъувапӀэм тхылъ итхэ (еплъ [$1 щӀэупщӀэгъуэхэм я напэкӀуэцӀ]).\nГъуэщэгъуэкӀэ мыбым утехуамэ, уи браузерым гъэзэжыгъуэ иӀэм текъузи зэфэкащ.",
-       "anontalkpagetext": "----''Мы напэкӀуэцӀ тепсэлъыхьыгъуэр аноним цӀыхухэтым ей, аккаунт зымыщӀам иэ къэзмыгъэсэбэпым.\nАбым щхьэкӀэ IP-адресымкӀэ идентификациэ иратыр.\nАноним цӀыхухэту ущытмэ, тхыгъэ къыпхуэкӀуауэ уигугъэмэ, [[Special:UserLogin/signup|аккаунт щӀы]] иэ [[Special:UserLogin|системэм зыкъегъэцӀыху]], япэкӀэ хэукъуэгъуэ пэмыкӀ цӀыхухэт анэнимхэм ям ухэмыхуэн щхьэкӀэ.''",
+       "anontalkpagetext": "----''Мы напэкӀуэцӀ тепсэлъыхьыгъуэр аноним цӀыхухэтым ей, аккаунт зымыщӀам иэ къэзмыгъэсэбэпым.\nАбым щхьэкӀэ IP-адресымкӀэ идентификациэ иратыр.\nАноним цӀыхухэту ущытмэ, тхыгъэ къыпхуэкӀуауэ уигугъэмэ, [[Special:CreateAccount|аккаунт щӀы]] иэ [[Special:UserLogin|системэм зыкъегъэцӀыху]], япэкӀэ хэукъуэгъуэ пэмыкӀ цӀыхухэт анэнимхэм ям ухэмыхуэн щхьэкӀэ.''",
        "noarticletext": "Иджыпсту мы напэкӀуэцӀыр нэщӀ.\nУзхуэныкъуэм [[Special:Search/{{PAGENAME}}|игугъ бгъуэтыфыну]] нэгъуэщӀ напэкӀуэцӀхэм, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тхылъхэм абым теухуа тхыгъэхэм], иэ '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} апхуэдэцӀэ зиӀэ напэкӀуцӀ быщӀыфынущ]'''</span>.",
        "noarticletext-nopermission": "Джыпсту мы напэкӀуэцӀыр нэщӀу щытщ.\nУзхуэныкъуэм [[Special:Search/{{PAGENAME}}|и гугъ бгъуэтыфыну]] нэгъуэщӀ напэкӀуэцӀхэм, \nиэ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} абым теухуа тхъыгъэхэр].</span>",
        "userpage-userdoesnotexist": "Аккаунт «<nowiki>$1</nowiki>» щыӀэкъым. У арэзыуэ ущыт мы напэкӀуэцӀыр быщӀыным иэ зэпхъуэкӀыным.",
index 8d9a6ad..db9d962 100644 (file)
        "noname": "تو تان صحیح اسم صارفو داخل نو آرو",
        "loginsuccesstitle": "داخلہ کامیاب",
        "loginsuccess": "'''ھانیسے تو {{SITENAME}} بنام \"$1\" داخل بیتی آسوس'''",
-       "nosuchuser": "\"$1\" نامو سورا کیہ صارف موجود نیکی.\nبرائے مہربانی! ہجان درست اندراجان تصدیقو کورے.\nاگر تو کہ مناسب جوشیتاو تھے [[Special:UserLogin/signup|نوغ کھاتہ دی ساوزیکو بوس]].",
+       "nosuchuser": "\"$1\" نامو سورا کیہ صارف موجود نیکی.\nبرائے مہربانی! ہجان درست اندراجان تصدیقو کورے.\nاگر تو کہ مناسب جوشیتاو تھے [[Special:CreateAccount|نوغ کھاتہ دی ساوزیکو بوس]].",
        "nouserspecified": "تہ ای اسمِ صارفو مخصوص کوریلک",
        "login-userblocked": "ھیہ صارفو سورا پاوبندی شیر. داخلِ نوشتہ بیکو اجازت نیکی",
        "wrongpassword": "تو غلط کلمۂ شناخت درج کوری آسوس دوبارہ کو شش کورے",
index 2fe53e9..5c72e48 100644 (file)
        "noname": "Ebe namê do vêrdoği ra cınêkota.",
        "loginsuccesstitle": "Cıkotene biye ra",
        "loginsuccess": "'''Sıta {{SITENAME}} de ebe namê karberi \"$1\" kota cı.'''",
-       "nosuchuser": "Ebe namê \"$1\"i jü karber çino.\nNustena namunê karberu de herfa pil u qıze rê diqet kerê.\nNustena ho qonrol kerê, ya ki [[Special:UserLogin/signup|jü hesabo newe rakerê]].",
+       "nosuchuser": "Ebe namê \"$1\"i jü karber çino.\nNustena namunê karberu de herfa pil u qıze rê diqet kerê.\nNustena ho qonrol kerê, ya ki [[Special:CreateAccount|jü hesabo newe rakerê]].",
        "nosuchusershort": "Karberê do ebe namê \"$1\" çino.\nNustena cı qontrol ke.",
        "nouserspecified": "Gunê namê jü karberi bıdekernê.",
        "login-userblocked": "No karber engel biyo. Cıkotene rê mısade cı nêdino.",
        "yournick": "Leqeme:",
        "badsig": "İmza kala nêvêrdiye.\nEtiketê ''HTML''i qontrol ke.",
        "badsiglength": "İmza to zaf derga.\nA gunê ebe $1 {{PLURAL:$1|herfe|herfu}} ra jêde mebo.",
-       "yourgender": "Cınsiyet:",
+       "yourgender": "Şeklê xitabi?",
        "gender-male": "Cüamêrd",
        "gender-female": "Cüanıke",
        "email": "E-poste",
        "rclistfrom": "$3 $2 ra hata nıka vurnaisunê newu bıasne",
        "rcshowhideminor": "$1 vurnaisê qızkeki",
        "rcshowhidebots": "Botu $1",
-       "rcshowhideliu": "Karberunê qeydbiyayu $1",
+       "rcshowhideliu": "karberê qeydbiyayeyi $1",
        "rcshowhideanons": "$1 karberê anonimi",
        "rcshowhidepatr": "Vurnayışê cıyê vênıtey $1",
        "rcshowhidemine": "Vurnayisanê mı $1",
index f839f94..dd471d5 100644 (file)
@@ -14,7 +14,8 @@
                        "Macofe",
                        "Batyrbek.kz",
                        "Matma Rex",
-                       "Nemo bis"
+                       "Nemo bis",
+                       "Mormegil"
                ]
        },
        "tog-underline": "Сілтеменің астын сызу:",
        "noname": "Сізде жарамды қатысушы аты анықталмаған.",
        "loginsuccesstitle": "Кірдіңіз.",
        "loginsuccess": "<strong>Сіз енді {{SITENAME}} жобасына «$1» ретінде кірдіңіз.</strong>",
-       "nosuchuser": "Мұнда «$1» деп аталған қатысушы тіркелмеген.\nҚатысушы аттары кіші әріптерден тұру керек.\nЕмлеңізді тексеріңіз немесе [[Special:UserLogin/signup|жаңа тіркелгі жасаңыз]].",
+       "nosuchuser": "Мұнда «$1» деп аталған қатысушы тіркелмеген.\nҚатысушы аттары кіші әріптерден тұру керек.\nЕмлеңізді тексеріңіз немесе [[Special:CreateAccount|жаңа тіркелгі жасаңыз]].",
        "nosuchusershort": "Мұнда «$1» деп аталған қатысушы тіркелмеген.\nЕмлеңізді тексеріңіз.",
        "nouserspecified": "Қатысушы атын көрсетуіңіз керек.",
        "login-userblocked": "Бұл қатысушы бұғатталған. Жүйеге кiру рұқсат етiлмеген.",
        "accmailtext": "$2 дегенге [[User talk:$1|$1]] үшін құпия сөзі жөнелтілді. Оны <em>[[Special:ChangePassword|құпия сөзді өзгерту]]</em> бетінде жүйеге кірген кезде өзгеруге болады.",
        "newarticle": "(Жаңа)",
        "newarticletext": "Сілтеме бойынша әлі басталмаған бетке келіпсіз.\nБетті бастау үшін төменгі терезеде мәтінді теріңіз (көбірек ақпарат үшін [$1 анықтама бетін] қараңыз).\nЕгер жаңылғаннан осында келген болсаңыз браузеріңіздің <strong>артқа</strong> деген батырмасын басыңыз.",
-       "anontalkpagetext": "----\n<em>Бұл тіркелгісіз анонимді (немесе тіркелгісін қолданбаған) қатысушының талқылау беті.</em> \nСондықтан біз оны сандық IP мекенжайымен қолдануға тиістіміз.\nОсындай IP мекенжайды бірнеше пайдаланушы ортақтаса алады.\nЕгер сіз анонимді қатысушы болсаңыз және сізге қатыссыз хабарлама жіберілгенін сезсеңіз басқа анонимді қатысушылармен алдағы уақыттарда шатастырмау үшін [[Special:UserLogin/signup|тіркеліңіз]] не [[Special:UserLogin|кіріңіз]].",
+       "anontalkpagetext": "----\n<em>Бұл тіркелгісіз анонимді (немесе тіркелгісін қолданбаған) қатысушының талқылау беті.</em> \nСондықтан біз оны сандық IP мекенжайымен қолдануға тиістіміз.\nОсындай IP мекенжайды бірнеше пайдаланушы ортақтаса алады.\nЕгер сіз анонимді қатысушы болсаңыз және сізге қатыссыз хабарлама жіберілгенін сезсеңіз басқа анонимді қатысушылармен алдағы уақыттарда шатастырмау үшін [[Special:CreateAccount|тіркеліңіз]] не [[Special:UserLogin|кіріңіз]].",
        "noarticletext": "Қазіргі уақытта бұл бетте еш мәтін жоқ.\n* Басқа беттерден [[Special:Search/{{PAGENAME}}|бұл бет атауын іздеу]],\n* <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Журналдардан бұл бетке қатысты сәйкес жазбаларды табу]</span>,\n* <span class=\"plainlinks\">'''[{{fullurl:{{FULLPAGENAME}}|action=edit}} Бұл бетті жаңадан бастау]'''</span>.",
        "noarticletext-nopermission": "Қазіргі уақытта бұл бетте мәтін жоқ.\nСіз бұл бет атауын басқа беттерден [[Special:Search/{{PAGENAME}}|іздей аласыз]], немесе <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} қатысты журналдардан іздей аласыз]</span>. Ал бұл бетті жаңадан бастауға сізде рұқсат жоқ.",
        "missing-revision": "#$1 нұсқалы «{{FULLPAGENAME}}» деп аталатын бет жоқ",
        "upload-form-label-infoform-description": "Сипаттамасы",
        "upload-form-label-usage-title": "Қолданылуы",
        "upload-form-label-usage-filename": "Файл атауы",
-       "foreign-structured-upload-form-label-own-work": "Бұл менің өз туындым",
-       "foreign-structured-upload-form-label-infoform-categories": "Санаттар",
-       "foreign-structured-upload-form-label-infoform-date": "Ай-күні",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Мен осы файлдың авторы екенінімді растаймын және бұл файлды Wikimedia Commons ортаққорына [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] лицензиясы аясында қайтымсыз жариялауға келісемін, сонымен бірге [https://wikimediafoundation.org/wiki/Terms_of_Use Қолдану шарттарына] да келісемін.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Егер сіз осы файлдың авторы өзіңіз болмасаңыз немесе оны сіз басқа лицензия аясында жариялағыңыз келсе [https://commons.wikimedia.org/wiki/Special:UploadWizard Ортаққор Жүктеу шеберін] қолданыңыз.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Егер сайт бұл файлды жүктеуге өзінің ережелері аясында рұқсат беретін болса, сіз сондай-ақ [[Special:Upload|{{SITENAME}} жобасындағы жүктеу бетін]] қолданып көргіңіз келетін шығар.",
+       "upload-form-label-own-work": "Бұл менің өз туындым",
+       "upload-form-label-infoform-categories": "Санаттар",
+       "upload-form-label-infoform-date": "Ай-күні",
        "backend-fail-stream": "«$1» файлы ақпады.",
        "backend-fail-backup": "«$1» файлының сақтық есесі жасалмады.",
        "backend-fail-notexists": "$1 файлы бар емес.",
        "delete-toobig": "Бұл бетте үлкен түзету тарихы бар, $1 {{PLURAL:$1|түзетуден|түзетуден}} астам.\nБұндай беттердің жоюы {{SITENAME}} торабын әлдеқалай үзіп тастауына бөгет салу үшін тиымдалған.",
        "delete-warning-toobig": "Бұл бетте үлкен өңдеу тарихы бар, $1 {{PLURAL:$1|түзетуден|түзетуден}} астам.\nБұның жоюы {{SITENAME}} торабындағы дерекқор әрекеттерді үзіп тастауын мүмкін;\nбұны абайлап өткізіңіз.",
        "deleteprotected": "Сіз бұл бетті жоя алмайсыз, себебі ол қорғалған.",
-       "deleting-backlinks-warning": "</strong>Ескерту:</strong>  Сіз жоймақшы болған бетке [[Special:WhatLinksHere/{{FULLPAGENAME}}|басқа беттерден]] сілтенген немесе [[Special:WhatLinksHere/{{FULLPAGENAME}}|басқа беттерге]] кірістірілген.",
+       "deleting-backlinks-warning": "<strong>Ескерту:</strong>  Сіз жоймақшы болған бетке [[Special:WhatLinksHere/{{FULLPAGENAME}}|басқа беттерден]] сілтенген немесе [[Special:WhatLinksHere/{{FULLPAGENAME}}|басқа беттерге]] кірістірілген.",
        "rollback": "Өңдемелерді шегіндіру",
        "rollbacklink": "шегіндіру",
        "rollbacklinkcount": "$1 {{PLURAL:$1|өңдемені|өңдемені}} шегіндіру",
index 95a8fee..f9a100f 100644 (file)
        "noname": "អ្នកមិនបានផ្ដល់អត្តនាមត្រឹមត្រូវទេ។",
        "loginsuccesstitle": "កត់ឈ្មោះចូលបានសម្រេច",
        "loginsuccess": "'''ពេលនេះអ្នកបានកត់ឈ្មោះចូល{{SITENAME}}ដោយប្រើឈ្មោះ \"$1\"ហើយ។'''",
-       "nosuchuser": "មិនមានអ្នកប្រើដែលមានឈ្មោះ \"$1\" ទេ។\n\nសូម​ពិនិត្យ​ក្រែង​លោ​មានកំហុស​អក្ខរាវិរុទ្ធឬ [[Special:UserLogin/signup|បង្កើត​គណនី​ថ្មី]]។",
+       "nosuchuser": "មិនមានអ្នកប្រើដែលមានឈ្មោះ \"$1\" ទេ។\n\nសូម​ពិនិត្យ​ក្រែង​លោ​មានកំហុស​អក្ខរាវិរុទ្ធឬ [[Special:CreateAccount|បង្កើត​គណនី​ថ្មី]]។",
        "nosuchusershort": "គ្មានអ្នកប្រើដែលមានឈ្មោះ $1\" ទេ។\n\nសូម​ពិនិត្យ​​អក្ខរាវិរុទ្ធ​របស់អ្នក ។",
        "nouserspecified": "អ្នកត្រូវតែ​ផ្ដល់អត្តនាម។",
        "login-userblocked": "អ្នកប្រើប្រាស់នេះស្ថិតក្រោមការហាមឃាត់។ មិនអនុញ្ញាតអោយកត់ឈ្មោះចូលទេ។",
        "accmailtext": "ពាក្យសម្ងាត់​ដែល​បាន​បង្កើត​ដោយ​ចៃដន្យ​សម្រាប់ [[User talk:$1|$1]] ត្រូវបានផ្ញើទៅ $2 ហើយ​។ អ្នកអាច​​ប្ដូរ​វាបាននៅ​​ទំព័រ ''[[Special:ChangePassword|ប្ដូរ​ពាក្យសម្ងាត់]]'' បន្ទាប់ពីកត់ឈ្មោះចូលហើយ​។",
        "newarticle": "(ថ្មី)",
        "newarticletext": "អ្នកបានតាម​តំណភ្ជាប់​ទៅ​ទំព័រដែលមិនទាន់មាននៅឡើយ។\nដើម្បីបង្កើតទំព័រនេះ សូមចាប់ផ្ដើមវាយ​ក្នុងប្រអប់ខាងក្រោម (សូមមើល [$1 ទំព័រ​ជំនួយ] សម្រាប់​ព័ត៌មានបន្ថែម)។\nបើ​អ្នក​មក​ទីនេះដោយអចេតនា សូមចុចប៊ូតុង '''ត្រឡប់ក្រោយ''' របស់ឧបករណ៍រាវរករបស់អ្នក។",
-       "anontalkpagetext": "----''ទំព័រពិភាក្សានេះគឺសម្រាប់តែអ្នកប្រើប្រាស់អនាមិកដែលមិនទាន់បានបង្កើតគណនីតែប៉ុណ្ណោះ។ ដូច្នេះអាសយដ្ឋានលេខIPរបស់កុំព្យូទ័ររបស់លោកអ្នក​នឹងត្រូវបានបង្ហាញ ដើមី្បសម្គាល់លោកអ្នក។\n\nអាសយដ្ឋានIPទាំងនោះអាចនឹងត្រូវប្រើដោយមនុស្សច្រើននាក់។\n\nប្រសិនបើអ្នកជាអ្នកប្រើប្រាស់អនាមិក​ហើយ​ប្រសិនបើអ្នកឃើញមានការបញ្ចេញយោបល់មកអ្នកពីអ្វី​ដែល​មិន​ទាក់ទងទៅនឹងអ្វីដែល​អ្នកបាន​ធ្វើ​ សូម[[Special:UserLogin/signup|បង្កើតគណនី]] ឬ [[Special:UserLogin|កត់ឈ្មោះចូល]] ដើម្បីចៀសវាង​ការភ័ន្តច្រឡំ​ណាមួយជាយថាហេតុជាមួយនិងអ្នកប្រើប្រាស់អនាមិកដទៃទៀត។''",
+       "anontalkpagetext": "----''ទំព័រពិភាក្សានេះគឺសម្រាប់តែអ្នកប្រើប្រាស់អនាមិកដែលមិនទាន់បានបង្កើតគណនីតែប៉ុណ្ណោះ។ ដូច្នេះអាសយដ្ឋានលេខIPរបស់កុំព្យូទ័ររបស់លោកអ្នក​នឹងត្រូវបានបង្ហាញ ដើមី្បសម្គាល់លោកអ្នក។\n\nអាសយដ្ឋានIPទាំងនោះអាចនឹងត្រូវប្រើដោយមនុស្សច្រើននាក់។\n\nប្រសិនបើអ្នកជាអ្នកប្រើប្រាស់អនាមិក​ហើយ​ប្រសិនបើអ្នកឃើញមានការបញ្ចេញយោបល់មកអ្នកពីអ្វី​ដែល​មិន​ទាក់ទងទៅនឹងអ្វីដែល​អ្នកបាន​ធ្វើ​ សូម[[Special:CreateAccount|បង្កើតគណនី]] ឬ [[Special:UserLogin|កត់ឈ្មោះចូល]] ដើម្បីចៀសវាង​ការភ័ន្តច្រឡំ​ណាមួយជាយថាហេតុជាមួយនិងអ្នកប្រើប្រាស់អនាមិកដទៃទៀត។''",
        "noarticletext": "បច្ចុប្បន្នគ្មានអត្ថបទក្នុងទំព័រនេះទេ។\n\nអ្នកអាច [[Special:Search/{{PAGENAME}}|ស្វែងរក​ចំណងជើង​នៃទំព័រនេះ]]ក្នុងទំព័រដទៃទៀត​​ ឬ [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ស្វែង​រក​កំណត់​ហេតុ​ដែល​ពាក់ព័ន្ធ] ឬ [{{fullurl:{{FULLPAGENAME}}|action=edit}} កែប្រែ​ទំព័រនេះ]។",
        "noarticletext-nopermission": "បច្ចុប្បន្ន គ្មានអត្ថបទណាមួយក្នុងទំព័រនេះទេ។\n\nអ្នកអាច [[Special:Search/{{PAGENAME}}|ស្វែងរក​ចំណងជើង​នៃទំព័រនេះ]] ក្នុងទំព័រ​ផ្សេងៗ ឬ<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ស្វែង​រក​កំណត់​ហេតុ​ដែល​ពាក់ព័ន្ធ]</span>។ ប៉ុន្តែអ្នកគ្មានសិទ្ធិក្នុងការបង្កើតទំព័រនេះទេ។",
        "userpage-userdoesnotexist": "គណនីអ្នកប្រើឈ្មោះ\"<nowiki>$1</nowiki>\" មិនទាន់បានចុះបញ្ជី។\n\nចូរគិតម្ដងទៀតថាអ្នកចង់ បង្កើត / កែប្រែ ទំព័រនេះឬទេ។",
        "upload-form-label-infoform-description": "ការ​ពិពណ៌នា",
        "upload-form-label-usage-title": "បម្រើបម្រាស់",
        "upload-form-label-usage-filename": "ឈ្មោះឯកសារ",
-       "foreign-structured-upload-form-label-own-work": "នេះជាការងារផ្ទាល់ខ្លួនរបស់ខ្ញុំ",
-       "foreign-structured-upload-form-label-infoform-categories": "ចំណាត់ថ្នាក់ក្រុម",
-       "foreign-structured-upload-form-label-infoform-date": "កាលបរិច្ឆេទ",
+       "upload-form-label-own-work": "នេះជាការងារផ្ទាល់ខ្លួនរបស់ខ្ញុំ",
+       "upload-form-label-infoform-categories": "ចំណាត់ថ្នាក់ក្រុម",
+       "upload-form-label-infoform-date": "កាលបរិច្ឆេទ",
        "backend-fail-notexists": "គ្មានឯកសារ \"$1\" ទេ។",
        "backend-fail-notsame": "ឯកសារដែលមិនដូចគ្នាបេះបិទមួយមានរួចហើយនៅ \"$1\"។",
        "backend-fail-delete": "មិនអាចលុបឯកសារ \"$1\" បានទេ។",
index 50f3aa5..e8993dd 100644 (file)
        "noname": "ನೀವು ಸರಿಯಾದ ಬಳಕೆದಾರ ಹೆಸರನ್ನು ಸೂಚಿಸಿಲ್ಲ.",
        "loginsuccesstitle": "ಲಾಗಿನ್ ಯಶಸ್ವಿ",
        "loginsuccess": "ನೀವು ಈಗ \"$1\" ಆಗಿ ವಿಕಿಪೀಡಿಯಕ್ಕೆ ಲಾಗಿನ್ ಆಗಿದ್ದೀರಿ.",
-       "nosuchuser": "\"$1\" ಹೆಸರಿನ ಯಾವ ಸದಸ್ಯರೂ ಇಲ್ಲ.\nಸದಸ್ಯನಾಮದಲ್ಲಿ ಲಘು ಮತ್ತು ದೀರ್ಘ ಅಕ್ಷರಗಳಲ್ಲಿ ವ್ಯತ್ಯಾಸವಿದೆ.\nಕಾಗುಣಿತವನ್ನು ಪರೀಕ್ಷಿಸಿ, ಅಥವಾ [[Special:UserLogin/signup|ಹೊಸ ಸದಸ್ಯತ್ವ ಖಾತೆಯನ್ನು ಸೃಷ್ಟಿಸಿ]].",
+       "nosuchuser": "\"$1\" ಹೆಸರಿನ ಯಾವ ಸದಸ್ಯರೂ ಇಲ್ಲ.\nಸದಸ್ಯನಾಮದಲ್ಲಿ ಲಘು ಮತ್ತು ದೀರ್ಘ ಅಕ್ಷರಗಳಲ್ಲಿ ವ್ಯತ್ಯಾಸವಿದೆ.\nಕಾಗುಣಿತವನ್ನು ಪರೀಕ್ಷಿಸಿ, ಅಥವಾ [[Special:CreateAccount|ಹೊಸ ಸದಸ್ಯತ್ವ ಖಾತೆಯನ್ನು ಸೃಷ್ಟಿಸಿ]].",
        "nosuchusershort": "\"$1\" ಹೆಸರಿನ ಸದಸ್ಯರು ಯಾರೂ ಇಲ್ಲ.\nಹೆಸರಲ್ಲಿ ಕಾಗುಣಿತ ತಪ್ಪಿದೆಯೆ ಎಂದು ಪರೀಕ್ಷಿಸಿ.",
        "nouserspecified": "ನೀವು ಒಂದು ಸದಸ್ಯತ್ವದ ಹೆಸರನ್ನು ಸೂಚಿಸಬೇಕು.",
        "login-userblocked": "ಈ ಬಳಕೆದಾರರನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. ಲಾಗಿನ್ ಆಗಲು ಸಾದ್ಯವಿಲ್ಲ.",
        "accmailtext": "[[User talk:$1|$1]] ಅವರ ಹೊಸ ಪ್ರವೇಶಪದ $2 ಗೆ ಕಳುಹಿಸಲಾಗಿದೆ.\n\nಈ ಖಾತೆಯ ಪ್ರವೇಶಪದವನ್ನು ಲಾಗಿನ್ ಆದ ನಂತರ ''[[Special:ChangePassword|ಪ್ರವೇಶಪದ ಬದಲಾವಣೆ]]'' ಪುಟದಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು.",
        "newarticle": "(ಹೊಸತು)",
        "newarticletext": "ಇನ್ನೂ ಅಸ್ಥಿತ್ವದಲ್ಲಿ ಇರದ ಪುಟದ ಲಿಂಕ್ ಅನ್ನು ನೀವು ಒತ್ತಿರುವಿರಿ.\nಈ ಪುಟವನ್ನು ಸೃಷ್ಟಿಸಲು ಕೆಳಗಿನ ಚೌಕದಲ್ಲಿ ಬರೆಯಲು ಆರಂಭಿಸಿರಿ.\n(ಹೆಚ್ಚು ಮಾಹಿತಿಗೆ [$1 ಸಹಾಯ ಪುಟ] ನೋಡಿ).\nಈ ಪುಟಕ್ಕೆ ನೀವು ತಪ್ಪಾಗಿ ಬಂದಿದ್ದಲ್ಲಿ ನಿಮ್ಮ ಬ್ರೌಸರ್‍ನ '''back''' ಬಟನ್ ಅನ್ನು ಒತ್ತಿ.",
-       "anontalkpagetext": "----''ಇದು ಖಾತೆಯೊಂದನ್ನು ಹೊಂದಿರದ ಅನಾಮಧೇಯ ಬಳಕೆದಾರರೊಬ್ಬರ ಚರ್ಚೆ ಪುಟ.\nಖಾತೆಯಿಲ್ಲದಿರುವುದರಿಂದ ಅವರನ್ನು ಗುರುತಿಸಲು ಅವರ IP ವಿಳಾಸವನ್ನು ಉಪಯೋಗಿಸುತ್ತಿದ್ದೇವೆ.\nಈ ರೀತಿಯ IP ವಿಳಾಸವು ಅನೇಕ ಬಳಕೆದಾರರಿಂದ ಉಪಯೋಗದಲ್ಲಿರಬಹುದು.\nನೀವು ಅನಾಮಧೇಯ ಬಳಕೆದಾರರಾಗಿದ್ದಲ್ಲಿ, ಹಾಗು ನಿಮಗೆ ಸಂಬಂಧವಿಲ್ಲದಂತ ಸಂದೇಶಗಳು ಬರುತ್ತಿವೆ ಎಂದು ಅನಿಸಿದರೆ, ಮುಂದೆ ಬೇರೆ ಅನಾಮಧೇಯ ಬಳಕೆದಾರರೊಂದಿಗೆ ತಪ್ಪಾಗಿ ಗುರುತಿಸಬಾರದೆಂದಿದ್ದರೆ ದಯವಿಟ್ಟು [[Special:UserLogin/signup|ಸದಸ್ಯರಾಗಿ]] ಅಥವ [[Special:UserLogin|ಲಾಗ್ ಇನ್ ಆಗಿ]].''",
+       "anontalkpagetext": "----''ಇದು ಖಾತೆಯೊಂದನ್ನು ಹೊಂದಿರದ ಅನಾಮಧೇಯ ಬಳಕೆದಾರರೊಬ್ಬರ ಚರ್ಚೆ ಪುಟ.\nಖಾತೆಯಿಲ್ಲದಿರುವುದರಿಂದ ಅವರನ್ನು ಗುರುತಿಸಲು ಅವರ IP ವಿಳಾಸವನ್ನು ಉಪಯೋಗಿಸುತ್ತಿದ್ದೇವೆ.\nಈ ರೀತಿಯ IP ವಿಳಾಸವು ಅನೇಕ ಬಳಕೆದಾರರಿಂದ ಉಪಯೋಗದಲ್ಲಿರಬಹುದು.\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ನೀವು ಇತರ ಪುಟಗಳಲ್ಲಿ [[ವಿಶೇಷ:Search/{{PAGENAME}}|ಈ ಶೀರ್ಷಿಕೆಗಾಗಿ ಹುಡುಕಬಹುದು]],\nಅಥವಾ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ಸಂಬಂಧಿಸಿದ ದಾಖಲಾತಿ ಹುಡುಕಬಹುದು]</span>, ಆದರೆ ನಿಮಗೆ ಈ ಪುಟವನ್ನು ಸಂಪಾದಿಸಲು ಅನುಮತಿಯಿಲ್ಲ.",
        "userpage-userdoesnotexist": "ಬಳಕೆದಾರ ಖಾತೆ \"<nowiki>$1</nowiki>\" ದಾಖಲಾಗಿಲ್ಲ. ನೀವು ಇದೇ ಪುಟವನ್ನು ಸೃಷ್ಟಿ/ಸಂಪಾದನೆ ಮಾಡಬೇಕೆಂದಿರುವಿರಿ ಎಂದು ಖಾತ್ರಿ ಮಾಡಿಕೊಳ್ಳಿ.",
index 63d53ef..aad4c59 100644 (file)
@@ -60,7 +60,8 @@
                        "Joolee0104",
                        "Mooozi",
                        "Ellif",
-                       "HDNua"
+                       "HDNua",
+                       "Ykhwong"
                ]
        },
        "tog-underline": "링크에 밑줄:",
@@ -78,6 +79,7 @@
        "tog-watchdefault": "내가 편집하는 문서와 파일을 주시문서 목록에 추가",
        "tog-watchmoves": "내가 이동하는 문서와 파일을 주시문서 목록에 추가",
        "tog-watchdeletion": "내가 삭제하는 문서와 파일을 주시문서 목록에 추가",
+       "tog-watchuploads": "내가 올린 파일을 주시문서 목록에 추가",
        "tog-watchrollback": "내가 되돌리기 기능을 사용한 문서를 주시문서 목록에 추가",
        "tog-minordefault": "모든 편집에 기본적으로 사소한 편집을 표시",
        "tog-previewontop": "편집 상자 앞에 미리 보기 보이기",
        "tog-watchlistreloadautomatically": "필터가 수정될 때마다 주시문서 목록 자동으로 새로 고치기 (자바스크립트 필요)",
        "tog-watchlisthideanons": "주시문서 목록에서 익명 사용자의 편집을 숨기기",
        "tog-watchlisthidepatrolled": "주시문서 목록에서 점검한 편집을 숨기기",
-       "tog-watchlisthidecategorization": "페이지 류 숨기기",
+       "tog-watchlisthidecategorization": "페이지 류 숨기기",
        "tog-ccmeonemails": "이메일을 보낼 때 내 이메일로 복사본을 보내기",
        "tog-diffonly": "편집 차이를 비교할 때 문서 내용을 보지 않기",
        "tog-showhiddencats": "숨은 분류 보이기",
        "december-date": "12월 $1일",
        "period-am": "오전",
        "period-pm": "오후",
-       "pagecategories": "{{PLURAL:$1|분류}}",
+       "pagecategories": "{{PLURAL:$1|분류|분류}}",
        "category_header": "\"$1\" 분류에 속하는 문서",
        "subcategories": "하위 분류",
        "category-media-header": "\"$1\" 분류에 속하는 미디어",
        "category-empty": "이 분류에 속하는 문서나 자료가 없습니다.",
-       "hidden-categories": "{{PLURAL:$1|숨은 분류}}",
+       "hidden-categories": "{{PLURAL:$1|숨은 분류|숨은 분류}}",
        "hidden-category-category": "숨은 분류",
        "category-subcat-count": "{{PLURAL:$2|이 분류에는 하위 분류 1개만이 속해 있습니다.|다음은 이 분류에 속하는 {{PLURAL:$1|하위 분류}} $2개 가운데 $1개입니다.}}",
        "category-subcat-count-limited": "이 분류에 {{PLURAL:$1|하위 분류가|하위 분류 $1개가}} 있습니다.",
        "badaccess-group0": "요청한 명령을 실행할 권한이 없습니다.",
        "badaccess-groups": "요청한 명령은 {{PLURAL:$2|다음|다음 중 하나의}} 권한을 가진 사용자에게 제한됩니다: $1.",
        "versionrequired": "미디어위키 $1 버전 필요",
-       "versionrequiredtext": "이 문서를 사용하려면 $1 버전 미디어위키가 필요합니다.\n[[Special:Version|설치된 미디어위키 버전]]을 참조하세요.",
+       "versionrequiredtext": "이 문서를 사용하려면 $1 버전 미디어위키가 필요합니다.\n[[Special:Version|설치된 미디어위키 버전]]을 참조하세요.",
        "ok": "확인",
        "retrievedfrom": "원본 주소 \"$1\"",
        "youhavenewmessages": "다른 사용자로부터의 $1가 {{PLURAL:$3|있습니다}}. ($2)",
        "youhavenewmessagesfromusers": "{{PLURAL:$3|다른 사용자로|사용자 $3명으로}}부터의 $1가 {{PLURAL:$4|있습니다}}. ($2)",
        "youhavenewmessagesmanyusers": "여러 사용자로부터의 $1가 있습니다. ($2)",
-       "newmessageslinkplural": "{{PLURAL:$1|새 메시지}}",
-       "newmessagesdifflinkplural": "마지막으로 {{PLURAL:$1|바뀐 내용}}",
+       "newmessageslinkplural": "{{PLURAL:$1|새 메시지|999=새 메시지}}",
+       "newmessagesdifflinkplural": "마지막으로 {{PLURAL:$1|바뀐 내용|999=바뀐 내용}}",
        "youhavenewmessagesmulti": "다른 사용자가 $1에 남긴 새 메시지가 있습니다",
        "editsection": "편집",
        "editold": "편집",
        "nstab-category": "분류",
        "mainpage-nstab": "대문",
        "nosuchaction": "이러한 명령이 없습니다",
-       "nosuchactiontext": "URL에 지정한 명령이 올바르지 않습니다.\nURL을 잘못 입력했거나, 올바르지 않은 링크를 따라갔을 수 있습니다.\n{{SITENAME}}에 사용하는 소프트웨어의 버그가 있을 수 있습니다.",
+       "nosuchactiontext": "URL에 지정한 명령이 올바르지 않습니다.\nURL을 잘못 입력했거나, 올바르지 않은 링크를 따라갔을 수 있습니다.\n{{SITENAME}}에 사용하는 소프트웨어의 버그일 수도 있습니다.",
        "nosuchspecialpage": "해당하는 특수 문서가 없습니다",
        "nospecialpagetext": "<strong>요청한 특수 문서가 존재하지 않습니다.</strong>\n\n특수 문서의 목록은 [[Special:SpecialPages|여기]]에서 볼 수 있습니다.",
        "error": "오류",
        "databaseerror": "데이터베이스 오류",
-       "databaseerror-text": "데이터베이스 쿼리에 오류가 발생했습니다.\n소프트웨어에 버그가 있을 수 있습니다.",
-       "databaseerror-textcl": "데이터베이스 쿼리 오류가 발생했습니다.",
+       "databaseerror-text": "데이터베이스 쿼리 오류가 발생했습니다.\n소프트웨어의 버그일 수 있습니다.",
+       "databaseerror-textcl": "데이터베이스 쿼리 오류가 발생했습니다.",
        "databaseerror-query": "쿼리: $1",
        "databaseerror-function": "함수: $1",
        "databaseerror-error": "오류: $1",
+       "transaction-duration-limit-exceeded": "쓰기 시간($1)이 $2 초 제한을 초과하였으므로 이 트랜잭션은 중단되었습니다. 이는 높은 수준의 반복 지연을 피하기 위해서입니다.\n한번에 수많은 항목을 변경하려면, 작업을 여러 작은 단위로 나누어 시도하십시오.",
        "laggedslavemode": "<strong>경고:</strong> 문서가 최근에 바뀐 내용을 포함하지 않을 수도 있습니다.",
        "readonly": "데이터베이스 잠김",
        "enterlockreason": "데이터베이스를 잠그는 이유와 예상되는 기간을 적어 주세요.",
-       "readonlytext": "데이터베이스가 잠겨 있어서 문서를 편집할 수 없습니다. 데이터베이스 관리가 끝난 후에는 정상으로 돌아올 것입니다.\n\n관리자가 데이터베이스를 잠글 때 남긴 메시지는 다음과 같습니다: $1",
+       "readonlytext": "데이터베이스가 잠겨 있어서 문서를 편집할 수 없습니다. 데이터베이스 관리가 끝난 후에는 정상으로 돌아올 것입니다.\n\n시스템 관리자가 데이터베이스를 잠글 때 남긴 메시지는 다음과 같습니다: $1",
        "missing-article": "데이터베이스에서 \"$1\" 문서의 $2 텍스트를 찾지 못했습니다.\n\n삭제된 문서의 오래된 차이나 역사 링크를 보려고 시도할 때 이러한 문제가 발생할 수 있습니다.\n\n그렇지 않다면, 소프트웨어에 버그가 발생했을 수도 있습니다.\n[[Special:ListUsers/sysop|관리자]]에게 URL을 참조하여 알려주세요.",
-       "missingarticle-rev": "(판번호: $1)",
+       "missingarticle-rev": "(판 번호: $1)",
        "missingarticle-diff": "(차이: $1, $2)",
-       "readonly_lag": "슬레이브 데이터베이스가 마스터 서버의 자료를 새로 고치는 중입니다. 데이터베이스가 자동으로 잠겨져 있습니다",
+       "readonly_lag": "슬레이브 데이터베이스 서버들이 마스터 서버와 동기화되고 있습니다. 그 동안 데이터베이스가 자동으로 잠겨져 있습니다.",
        "nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' HTTP 헤더가 붙어있지만 API 쓰기 모듈에 대한 요청을 했습니다.",
        "internalerror": "내부 오류",
        "internalerror_info": "내부 오류: $1",
        "cannotdelete": "\"$1\" 문서나 파일을 삭제할 수 없습니다.\n이미 삭제되었을 수도 있습니다.",
        "cannotdelete-title": "\"$1\" 문서를 삭제할 수 없습니다.",
        "delete-hook-aborted": "훅이 삭제를 중단했습니다.\n아무런 설명도 주어지지 않았습니다.",
-       "no-null-revision": "\"$1\" 문서에 대한 새 빈 판을 만들 수 없습니다",
+       "no-null-revision": "\"$1\" 문서에 대한 빈 판을 새로 만들 수 없습니다.",
        "badtitle": "잘못된 제목",
        "badtitletext": "요청한 문서 제목이 잘못되었거나, 비어있거나, 잘못된 인터위키 제목으로 링크했습니다.\n문서 제목에 사용할 수 없는 문자를 사용했을 수 있습니다.",
-       "title-invalid-empty": "요청한 페이지의 제목이 비거나 이름공간밖에 안 들어있습니다.",
+       "title-invalid-empty": "요청한 페이지의 제목이 비어있거나 이름공간의 이름만 포함하고 있습니다.",
        "title-invalid-utf8": "요청한 페이지 제목에 잘못된 UTF-8 글자가 있습니다.",
        "title-invalid-interwiki": "요청한 페이지 제목에 제목에는 사용될 수 없는 위키간 링크가 있습니다.",
        "title-invalid-talk-namespace": "요청한 페이지 제목이 존재하지 않는 토론 문서를 가리킵니다.",
        "title-invalid-characters": "요청된 문서 제목이 잘못된 문자를 포함하고 있습니다: \"$1\".",
        "title-invalid-relative": "제목에 상대 경로가 있습니다. 제목에 상대 경로를 쓰는 것은 적절하지 않습니다. 제목에 붙은 상대 경로는 사용자 브라우저에서 제대로 인식하지 못할 수도 있습니다.",
        "title-invalid-magic-tilde": "요청한 문서의 제목에 적절하지 못한 특수 물결 문자열(magic tilde sequence; <nowiki>~~~</nowiki>)이 들어있습니다.",
-       "title-invalid-too-long": "페이지 제목이 너무 깁니다. 페이지 제목 길이는 최대 $1 까지 설정할 수 있습니다.",
-       "title-invalid-leading-colon": "페이지 제목에 잘못된 문자가 포함되어 있습니다.",
+       "title-invalid-too-long": "요청된 페이지 제목이 너무 깁니다. 길이는 UTF-8 인코딩 기준 최대 $1 바이트까지 설정할 수 있습니다.",
+       "title-invalid-leading-colon": "요청된 페이지 제목 처음에 잘못된 콜론 문자가 포함되어 있습니다.",
        "perfcached": "다음 자료는 캐시된 것이며 최신이 아닐 수 있습니다. 캐시에 최대 {{PLURAL:$1|결과 한 개|결과 $1개}}가 있습니다.",
        "perfcachedts": "다음 자료는 캐시된 것으로, $1에 마지막으로 업데이트되었습니다. 캐시에 최대 {{PLURAL:$4|결과 한 개|결과 $4개}}가 있습니다.",
-       "querypage-no-updates": "ì\9d´ ë¬¸ì\84\9cì\9d\98 ê°±ì\8b ì\9d´ í\98\84ì\9e¬ ì¤\91ì§\80ë\90\98ì\96´ ì\9e\88ì\8aµë\8b\88ë\8b¤.\nì\9e\90ë£\8cê°\80 ì\9e ì\8b\9c 갱신되지 않을 것입니다.",
+       "querypage-no-updates": "ì\9d´ ë¬¸ì\84\9cì\9d\98 ê°±ì\8b ì\9d´ í\98\84ì\9e¬ ì¤\91ì§\80ë\90\98ì\96´ ì\9e\88ì\8aµë\8b\88ë\8b¤.\nì§\80ê¸\88ì\9d\80 ì\9e\90ë£\8cê°\80 갱신되지 않을 것입니다.",
        "viewsource": "원본 보기",
        "viewsource-title": "$1 문서 원본 보기",
        "actionthrottled": "동작 중지",
-       "actionthrottledtext": "악용을 막기 위해 짧은 시간 동안 이 작업을 너무 많이 수행하는 것을 ë§\89ê³  ì\9e\88ì\8aµë\8b\88ë\8b¤.\nì \9cí\95\9cì\9d\84 ë\84\98ì\97\88ì\9c¼ë\8b\88 ëª\87 ë¶\84 ë\92¤ì\97\90 ì\83\88ë¡\9c ì\8b\9cë\8f\84í\95\98ì\84¸ì\9a\94.",
+       "actionthrottledtext": "악용을 막기 위해 짧은 시간 동안 이 작업을 너무 많이 수행하는 것을 ì \9cí\95\9cí\95\98ê³  ì\9e\88ì\8aµë\8b\88ë\8b¤.\nì \9cí\95\9cì\9d\84 ë\84\98ì\97\88ì\9c¼ë\8b\88 ëª\87 ë¶\84 ë\92¤ì\97\90 ë\8b¤ì\8b\9c ì\8b\9cë\8f\84í\95\98ì\84¸ì\9a\94.",
        "protectedpagetext": "이 문서는 편집하거나 다른 명령을 할 수 없도록 보호되어 있습니다.",
        "viewsourcetext": "문서의 원본을 보거나 복사할 수 있습니다.",
-       "viewyourtext": "이 문서로의 <strong>당신의 편집</strong>의 원본을 보고 복사할 수 있습니다.",
+       "viewyourtext": "이 문서에 속한 <strong>당신의 편집</strong>의 원본을 보고 복사할 수 있습니다.",
        "protectedinterface": "이 문서는 이 위키의 소프트웨어 인터페이스에 쓰이는 문서로, 부정 행위를 막기 위해 보호되어 있습니다.\n모든 위키에 대한 번역을 추가하거나 바꾸려면 미디어위키 지역화 프로젝트인 [//translatewiki.net/wiki/Main_Page?setlang=ko translatewiki.net]에 참여하시기 바랍니다.",
        "editinginterface": "<strong>경고:</strong> 소프트웨어 인터페이스에 쓰이는 문서를 고치고 있습니다.\n이 문서에 있는 내용을 바꾸면 이 위키에 있는 모든 사용자에게 영향을 끼칩니다.",
        "translateinterface": "모든 위키를 위해 번역을 추가하거나 바꾸려면, 미디어위키 지역화 프로젝트인 [//translatewiki.net/ translatewiki.net]을 사용해 주시기 바랍니다.",
        "cascadeprotected": "이 문서는 다음 \"연쇄적\" 보호가 걸린 {{PLURAL:$1|문서|문서들}}에 포함되어 있어 함께 보호됩니다:\n$2",
-       "namespaceprotected": "<strong>$1</strong> 이름공간의 문서를 편집할 수 있는 권한이 없습니다.",
+       "namespaceprotected": "<strong>$1</strong> 이름공간의 문서를 편집할 권한이 없습니다.",
        "customcssprotected": "여기에는 다른 사용자의 개인 설정이 포함되어 있기 때문에 이 CSS 문서를 편집할 수 없습니다.",
        "customjsprotected": "여기에는 다른 사용자의 개인 설정이 포함되어 있기 때문에 이 자바스크립트 문서를 편집할 수 없습니다.",
        "mycustomcssprotected": "이 CSS 문서를 편집할 권한이 없습니다.",
        "mypreferencesprotected": "내 환경 설정을 편집할 권한이 없습니다.",
        "ns-specialprotected": "특수 문서는 편집할 수 없습니다.",
        "titleprotected": "[[User:$1|$1]] 사용자가 문서 만들기를 금지했습니다.\n이유는 다음과 같습니다. <em>$2</em>",
-       "filereadonlyerror": "\"$2\" 파일 저장소가 읽기 전용이기 때문에 \"$1\" 파일을 바꿀 수 없습니다.\n\n저장소 관리자가 파일 저장소를 잠근 이유에 대한 설명을 남겼습니다: \"$3\".",
+       "filereadonlyerror": "\"$2\" 파일 저장소가 읽기 전용이기 때문에 \"$1\" 파일을 바꿀 수 없습니다.\n\n파일 저장소를 잠근 시스템 관리자가 다음과 같은 설명을 남겼습니다: \"$3\".",
        "invalidtitle-knownnamespace": "제목 오류: \"$2\" 이름공간과 \"$3\" 텍스트",
-       "invalidtitle-unknownnamespace": "제목 오류: 알 수 없는 $1 이름공간 번호와, \"$2\" 텍스트",
+       "invalidtitle-unknownnamespace": "제목 오류: 알 수 없는 $1 이름공간 번호와 \"$2\" 텍스트",
        "exception-nologin": "로그인하지 않음",
        "exception-nologin-text": "이 문서에 접근하거나 이 동작을 수행하려면 로그인하세요.",
        "exception-nologin-text-manual": "이 문서에 접근하거나 이 명령을 수행하려면 $1하세요.",
        "virus-scanfailed": "검사 실패 (코드 $1)",
        "virus-unknownscanner": "알 수 없는 안티 바이러스:",
        "logouttext": "<strong>이제 로그아웃했습니다.</strong>\n\n브라우저 캐시를 지울 때까지 일부 문서에서 아직 로그인이 되어 있는 것처럼 보일 수 있음에 유의하세요.",
-       "cannotlogoutnow-title": "지금 로그아웃 할 수 없습니다",
+       "cannotlogoutnow-title": "지금 로그아웃할 수 없습니다",
        "cannotlogoutnow-text": "$1 사용 중에는 로그아웃이 불가능합니다.",
        "welcomeuser": "$1님, 환영합니다!",
-       "welcomecreation-msg": "계정이 만들어졌습니다.\n[[Special:Preferences|{{SITENAME}} 사용자 환경 설정]]을 바꿀 수 있습니다.",
+       "welcomecreation-msg": "계정이 만들어졌습니다.\n{{SITENAME}}의 사용자 [[Special:Preferences|환경 설정]]을 바꿀 수 있습니다.",
        "yourname": "사용자 이름:",
-       "userlogin-yourname": "사용자 계정 이름",
-       "userlogin-yourname-ph": "사용자 계정 이름을 입력하세요",
-       "createacct-another-username-ph": "사용자 계정 이름을 입력하세요",
+       "userlogin-yourname": "사용자 이름",
+       "userlogin-yourname-ph": "사용자 이름을 입력하세요",
+       "createacct-another-username-ph": "사용자 이름을 입력하세요",
        "yourpassword": "비밀번호:",
        "userlogin-yourpassword": "비밀번호",
        "userlogin-yourpassword-ph": "비밀번호를 입력하세요",
        "yourpasswordagain": "비밀번호 다시 입력:",
        "createacct-yourpasswordagain": "비밀번호 확인",
        "createacct-yourpasswordagain-ph": "비밀번호를 다시 입력하세요",
-       "remembermypassword": "이 브라우저에서 로그인 상태를 저장하기 (최대 $1{{PLURAL:$1|일}})",
+       "remembermypassword": "이 브라우저에 로그인 상태 저장하기 (최대 $1일)",
        "userlogin-remembermypassword": "로그인 상태를 유지하기",
        "userlogin-signwithsecure": "보안 연결 사용",
-       "cannotloginnow-title": "지금 로그인 할 수 없습니다.",
+       "cannotloginnow-title": "지금 로그인할 수 없습니다.",
        "cannotloginnow-text": "$1 사용 중에는 로그인이 불가능합니다.",
        "yourdomainname": "도메인 이름:",
        "password-change-forbidden": "이 위키에서 비밀번호를 바꿀 수 없습니다.",
-       "externaldberror": "바깥 인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
+       "externaldberror": "인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
        "login": "로그인",
+       "login-security": "사용자 정보 확인",
        "nav-login-createaccount": "로그인 / 계정 만들기",
        "userlogin": "로그인 / 계정 만들기",
        "userloginnocreate": "로그인",
        "userlogin-resetpassword-link": "비밀번호를 잊으셨나요?",
        "userlogin-helplink2": "로그인에 대한 도움말",
        "userlogin-loggedin": "이미 {{GENDER:$1|$1}} 사용자로 로그인되어 있습니다.\n다른 사용자로 로그인하려면 아래의 양식을 사용하세요.",
+       "userlogin-reauth": "사용자가 $1임을 확인하려면 다시 로그인해야 합니다.",
        "userlogin-createanother": "다른 계정 만들기",
        "createacct-emailrequired": "이메일 주소",
        "createacct-emailoptional": "이메일 주소 (선택 사항)",
        "createacct-email-ph": "이메일 주소를 입력하세요",
        "createacct-another-email-ph": "이메일 주소를 입력하세요",
-       "createaccountmail": "ì\9e\84ì\8b\9c ì\9e\84ì\9d\98 비밀번호를 이메일로 보내기",
+       "createaccountmail": "ì\9e\84ì\9d\98ì\9d\98 ì\9e\84ì\8b\9c 비밀번호를 이메일로 보내기",
        "createacct-realname": "실명 (선택 사항)",
        "createaccountreason": "이유:",
        "createacct-reason": "이유",
        "userexists": "입력한 사용자 계정 이름이 이미 사용되고 있습니다.\n다른 이름을 선택하세요.",
        "loginerror": "로그인 오류",
        "createacct-error": "계정 만들기 오류",
-       "createaccounterror": "계정을 만들수 없습니다: $1",
-       "nocookiesnew": "사용자 계정을 만들었지만, 아직 로그인하고 있지 않습니다.\n{{SITENAME}}에서는 로그인 정보를 저장하기 위해 쿠키를 사용합니다.\n지금 사용하는 웹 브라우저는 쿠키를 사용하지 않도록 설정되어 있습니다.\n로그인하기 전에 웹 브라우저에서 쿠키를 사용하도록 설정해주세요.",
+       "createaccounterror": "계정을 만들 수 없습니다: $1",
+       "nocookiesnew": "사용자 계정을 만들었지만, 로그인되어 있지 않습니다.\n{{SITENAME}}에서는 로그인을 위해 쿠키를 사용합니다.\n사용자는 쿠키를 사용하지 않도록 설정되어 있습니다.\n쿠키를 사용하도록 설정한 다음 새로운 사용자 이름과 비밀번호로 로그인하세요.",
        "nocookieslogin": "{{SITENAME}}에서는 로그인을 위해 쿠키를 사용합니다.\n쿠키가 비활성되어 있습니다.\n쿠키 사용을 활성화한 다음 다시 시도하세요.",
        "nocookiesfornew": "요청의 출처를 확인할 수 없기 때문에 사용자 계정이 만들어지지 않았습니다.\n쿠키를 활성화한 것을 확인하고, 이 문서를 새로 고치고 나서 다시 시도하세요.",
        "noname": "사용자 계정 이름이 올바르지 않습니다.",
        "loginsuccesstitle": "로그인함",
        "loginsuccess": "<strong>{{SITENAME}}에 \"$1\" 계정으로 로그인했습니다.</strong>",
-       "nosuchuser": "이름이 \"$1\"인 사용자는 없습니다.\n사용자 계정 이름은 대소문자를 구별합니다.\n철자가 맞는지 확인해주세요. [[Special:UserLogin/signup|새 계정을 만들 수도 있습니다]].",
+       "nosuchuser": "이름이 \"$1\"인 사용자는 없습니다.\n사용자 이름은 대소문자를 구별합니다.\n철자가 맞는지 확인해주세요. [[Special:CreateAccount|새 계정을 만들 수도 있습니다]].",
        "nosuchusershort": "이름이 \"$1\"인 사용자는 없습니다.\n철자가 맞는지 확인하세요.",
        "nouserspecified": "사용자 계정 이름을 입력하지 않았습니다.",
        "login-userblocked": "이 사용자는 차단되었습니다. 로그인할 수 없습니다.",
        "noemail": "\"$1\" 사용자는 이메일 주소를 등록하지 않았습니다.",
        "noemailcreate": "올바른 이메일 주소를 제공해야 합니다.",
        "passwordsent": "\"$1\" 계정의 새로운 비밀번호를 이메일로 보냈습니다.\n비밀번호를 받고 다시 로그인해 주세요.",
-       "blocked-mailpassword": "당신의 IP 주소는 편집을 할 수 없게 차단되어 있어서 악용하지 못하도록 비밀번호 되살리기 기능 사용이 금지됩니다.",
+       "blocked-mailpassword": "사용 중인 IP 주소는 편집을 할 수 없도록 차단되어 있습니다. 악용 방지를 위해 비밀번호 되살리기 기능의 사용은 금지됩니다.",
        "eauthentsent": "입력한 이메일로 확인 이메일을 보냈습니다.\n다른 모든 형태의 이메일을 당신의 계정으로 보내기 전에, 계정이 정말 당신의 것인지 확인하기 위해 이메일 내용의 지시대로 계정 확인 절차를 실행해 주셔야 합니다.",
        "throttled-mailpassword": "비밀번호 재설정 이메일을 이미 최근 {{PLURAL:$1|$1시간}} 안에 보냈습니다.\n악용을 방지하기 위해 비밀번호 재설정 메일은 {{PLURAL:$1|$1시간}}마다 오직 하나씩만 보낼 수 있습니다.",
        "mailerror": "메일을 보내는 중 오류: $1",
        "accountcreatedtext": "[[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|토론]]) 사용자 계정이 만들어졌습니다.",
        "createaccount-title": "{{SITENAME}} 계정 만들기",
        "createaccount-text": "누군가가 {{SITENAME}} ($4)에서 사용자 이름 \"$2\", 비밀번호 \"$3\"로 당신의 이메일 주소가 등록된 계정을 만들었습니다. \n지금 로그인하여 비밀번호를 바꾸십시오.\n\n실수로 계정을 잘못 만들었다면 이 메시지는 무시해도 됩니다.",
-       "login-throttled": "로그인에 연속으로 실패하였습니다.\n$1 기다렸다가 다시 시도하세요.",
+       "login-throttled": "최근 너무 많이 로그인을 시도했습니다.\n$1 뒤에 다시 시도하세요.",
        "login-abort-generic": "로그인에 실패했습니다 - 중지됨",
        "login-migrated-generic": "당신의 계정이 마이그레이션되었으며, 당신의 사용자 이름이 더 이상 이 위키에 존재하지 않습니다.",
        "loginlanguagelabel": "언어: $1",
        "createacct-another-realname-tip": "실명은 선택 사항입니다.\n실명을 입력하면 문서 기여에 사용자의 이름이 들어가게 됩니다.",
        "pt-login": "로그인",
        "pt-login-button": "로그인",
+       "pt-login-continue-button": "로그인 계속",
        "pt-createaccount": "계정 만들기",
        "pt-userlogout": "로그아웃",
        "php-mail-error-unknown": "PHP의 mail() 함수에서 알 수 없는 오류가 발생했습니다.",
        "changepassword-success": "비밀번호가 바뀌었습니다!",
        "changepassword-throttled": "최근 너무 많이 로그인을 시도했습니다.\n$1 뒤에 다시 시도하세요.",
        "botpasswords": "봇 비밀번호",
+       "botpasswords-summary": "'''봇 비밀번호'''는 사용자의 기본 로그인 정보를 이용하지 않고 API를 통한 사용자 계정으로의 접근을 허용합니다. 봇 비밀번호를 이용하여 로그인할 때 이용 가능한 사용자 권한은 제한될 수 있습니다.\n\n이 기능을 이용할 이유가 없다면 굳이 이용하지 않으셔도 됩니다. 누구도 이 비밀번호의 생성을 사용자에게 요청할 수 없으며, 이를 수락하여 전달하지 말아 주십시오.",
        "botpasswords-disabled": "봇 비밀번호가 비활성화되었습니다.",
+       "botpasswords-no-central-id": "봇 비밀번호를 사용하려면 통합 계정으로 로그인해야 합니다.",
        "botpasswords-existing": "기존의 봇 비밀번호",
        "botpasswords-createnew": "새로운 봇 비밀번호 만들기",
        "botpasswords-editexisting": "기존의 봇 비밀번호 편집하기",
        "botpasswords-label-delete": "삭제",
        "botpasswords-label-resetpassword": "비밀번호 재설정",
        "botpasswords-label-grants": "적용할 수 있는 부여:",
+       "botpasswords-help-grants": "개개의 부여 기능은 사용자 계정이 이미 소유하고 있는 사용자 권한에 대한 접근을 제공합니다. 자세한 사항은 [[Special:ListGrants|부여 목록]]을 확인해 주십시오.",
        "botpasswords-label-restrictions": "사용 제한:",
        "botpasswords-label-grants-column": "승인됨",
        "botpasswords-bad-appid": "\"$1\"이라는 봇 이름은 유효하지 않습니다.",
        "botpasswords-newpassword": "<strong>$1</strong> 계정의 비밀번호가 <strong>$2</strong>로 변경되었습니다. <em>잊어버리지 않도록 기록해두시기 바랍니다.</em>",
        "botpasswords-no-provider": "'BotPasswordsSessionProvider'는 이용할 수 없습니다.",
        "botpasswords-restriction-failed": "봇 비밀번호 제한으로 인해 로그인할 수 없습니다.",
+       "botpasswords-invalid-name": "지정된 사용자 이름은 봇 비밀번호 구분자(\"$1\")를 포함하고 있지 않습니다.",
        "botpasswords-not-exist": "\"$1\" 사용자가 이름이 \"$2\"인 봇의 비밀번호를 가지고 있지 않습니다.",
        "resetpass_forbidden": "비밀번호를 바꿀 수 없습니다",
-       "resetpass-no-info": "이 특수 문서에 직접 접근하려면 반드시 로그인해야 합니다.",
+       "resetpass_forbidden-reason": "암호를 변경할 수 없습니다: $1",
+       "resetpass-no-info": "이 페이지에 직접 접근하려면 로그인해야 합니다.",
        "resetpass-submit-loggedin": "비밀번호 바꾸기",
        "resetpass-submit-cancel": "취소",
        "resetpass-wrong-oldpass": "비밀번호가 잘못되었거나 현재의 비밀번호와 같습니다.\n이미 비밀번호를 바꾸었거나 새 임시 비밀번호를 요청했을 수 있습니다.",
        "passwordreset-text-many": "{{PLURAL:$1|이메일을 통해 임시 비밀번호를 받으려면 필드 중 하나를 채우세요.}}",
        "passwordreset-disabled": "이 위키에서는 비밀번호를 재설정할 수 없습니다.",
        "passwordreset-emaildisabled": "이 위키에서 이메일 기능이 비활성화되어 있습니다.",
-       "passwordreset-username": "사용자 계정 이름:",
+       "passwordreset-username": "사용자 이름:",
        "passwordreset-domain": "도메인:",
        "passwordreset-capture": "발송 결과 이메일을 보시겠습니까?",
        "passwordreset-capture-help": "이 상자에 체크하면 이메일이 발송된 즉시 임시 비밀번호가 담긴 이메일을 볼 수 있습니다.",
        "passwordreset-emailsentemail": "당신의 계정과 연결된 이메일 주소가 있다면, 비밀번호 재설정 메일이 전해질 것입니다.",
        "passwordreset-emailsentusername": "이 사용자 이름과 연결된 이메일 주소가 있다면 비밀번호 초기화 이메일이 전송됩니다.",
        "passwordreset-emailsent-capture": "비밀번호 재설정 이메일이 발송되었으며, 아래에 나타나 있습니다.",
-       "passwordreset-emailerror-capture": "비밀번호 재설정 이메일이 생성되어 아래에 보여져 있지만, {{GENDER:$2|사용자}}에게 발송하는 데에는 실패했습니다: $1",
+       "passwordreset-emailerror-capture": "비밀번호 재설정 이메일이 생성되어 아래에 나타나 있지만, {{GENDER:$2|사용자}}에게 발송하는 데에는 실패했습니다: $1",
+       "passwordreset-invalideamil": "잘못된 이메일 주소",
+       "passwordreset-nodata": "사용자 이름이나 이메일 주소가 지정되지 않았습니다",
        "changeemail": "이메일 주소를 바꾸거나 제거하기",
        "changeemail-header": "이메일 주소를 바꾸려면 이 양식을 채우세요. 계정에서 이메일 연동을 취소하고 싶다면 양식을 제출할 때 새 이메일 주소를 공란으로 두세요.",
        "changeemail-passwordrequired": "변경을 적용하려면 비밀번호를 입력해야 합니다.",
-       "changeemail-no-info": "ì\9d´ í\8a¹ì\88\98 ë¬¸ì\84\9cì\97\90 ì§\81ì \91 ì \91ê·¼í\95\98려면 ë°\98ë\93\9cì\8b\9c 로그인해야 합니다.",
+       "changeemail-no-info": "ì\9d´ í\8e\98ì\9d´ì§\80ì\97\90 ì§\81ì \91 ì \91ê·¼í\95\98려면 로그인해야 합니다.",
        "changeemail-oldemail": "현재 이메일 주소:",
        "changeemail-newemail": "새 이메일 주소:",
        "changeemail-newemail-help": "이메일 주소를 삭제하고자 한다면 이 칸을 빈칸으로 두세요. 비밀번호 재설정이 불가능해지며, 이메일 주소가 없다면 이메일을 받을 수 없습니다.",
        "changeemail-none": "(없음)",
        "changeemail-password": "{{SITENAME}} 비밀번호:",
        "changeemail-submit": "이메일 주소 바꾸기",
-       "changeemail-throttled": "로그인에 연속으로 너무 많이 실패하였습니다.\n$1 기다렸다가 다시 시도하세요.",
+       "changeemail-throttled": "최근 너무 많이 로그인을 시도했습니다.\n$1 뒤에 다시 시도하세요.",
        "changeemail-nochange": "다른 이메일 주소를 입력해 주세요.",
        "resettokens": "토큰 재설정",
        "resettokens-text": "여기에 당신의 계정과 관련된 특정 개인 데이터에 접근을 허용하는 토큰을 재설정합니다.\n\n토큰이 다른 사람에게 알려졌거나 계정이 침해되었을 때는 재설정해야 합니다.",
        "minoredit": "사소한 편집입니다",
        "watchthis": "이 문서 주시하기",
        "savearticle": "문서 저장",
+       "publishpage": "문서 게시",
        "preview": "미리 보기",
        "showpreview": "미리 보기",
        "showdiff": "차이 보기",
-       "blankarticle": "<strong>경고:</strong> 만들려는 문서가 비어 있습니다.\n\"{{int:savearticle}}\"을 다시 클릭하면, 문서에 내용이 없이 만들어집니다.",
-       "anoneditwarning": "<strong>경고:</strong> 로그인을 하고 있지 않습니다. 편집을 하게 되면 IP 주소가 공개적으로 보여집니다. <strong>[$1 로그인]</strong>하거나 <strong>[$2 계정을 생성하면]</strong>, 편집 시에 다른 이점과 함께 사용자 이름이 표시됩니다.",
+       "blankarticle": "<strong>경고:</strong> 만들려는 문서가 비어 있습니다.\n\"{{int:savearticle}}\"을 다시 클릭하면, 아무 내용 없이 문서가 만들어집니다.",
+       "anoneditwarning": "<strong>경고:</strong> 로그인을 하고 있지 않습니다. 편집을 하면 IP 주소가 공개적으로 보이게 됩니다. <strong>[$1 로그인]</strong>하거나 <strong>[$2 계정을 생성하면]</strong>, 편집 시에 사용자 이름이 표시되며 더불어 다른 혜택들도 누릴 수 있습니다.",
        "anonpreviewwarning": "<em>로그인하고 있지 않습니다. 문서를 저장하면 당신의 IP 주소가 문서의 편집 역사에 남게 됩니다.</em>",
        "missingsummary": "'''알림:''' 편집 요약을 적지 않았습니다.\n이대로 \"{{int:savearticle}}\"을 클릭하면 편집 요약 없이 저장됩니다.",
        "selfredirect": "<strong>경고:</strong> 자기 자신으로 문서를 넘겨주고 있습니다.\n넘겨줄 대상을 잘못 입력했거나, 잘못된 문서를 편집하고 있을 수 있습니다.\n\"{{int:savearticle}}\"을 입력하면, 넘겨주기 문서가 생성될 것입니다.",
        "accmailtext": "[[User talk:$1|$1]] 사용자의 비밀번호를 임의로 만들어 $2(으)로 보냈습니다. 로그인하고 나서 <em>[[Special:ChangePassword|비밀번호를 바꿀]]</em> 수 있습니다.",
        "newarticle": "(새 문서)",
        "newarticletext": "아직 없는 문서의 링크를 따라왔습니다.\n새 문서를 만들려면 아래 상자에 내용을 입력하면 됩니다. (자세한 내용은 [$1 도움말 문서]를 참조하세요)\n만약 잘못 찾아왔다면, 브라우저의 '''뒤로''' 버튼을 눌러 주세요.",
-       "anontalkpagetext": "----\n여기는 계정을 만들지 않았거나 사용하고 있지 않은 익명 사용자를 위한 토론 문서입니다.\n익명 사용자를 구별하기 위해서는 숫자로 된 IP 주소를 사용해야만 합니다.\nIP 주소는 여러 사용자가 공유할 수 있습니다.\n자신과 관계없는 의견이 자신에게 남겨져 있어 불쾌하다고 생각하는 익명 사용자는 [[Special:UserLogin/signup|계정을 만들고]] [[Special:UserLogin|로그인해서]] 나중에 다른 익명 사용자에게 줄 혼란을 줄일 수 있습니다.",
+       "anontalkpagetext": "----\n여기는 계정을 만들지 않았거나 사용하고 있지 않은 익명 사용자를 위한 토론 문서입니다.\n익명 사용자를 구별하기 위해서는 숫자로 된 IP 주소를 사용해야만 합니다.\nIP 주소는 여러 사용자가 공유할 수 있습니다.\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}}|검색하거나]], 이 문서에 관련된 <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 기록]을 확인할 수 있습니다.</span> 그러나 이 문서를 만들 수 있는 권한은 없습니다.",
        "missing-revision": "\"{{FULLPAGENAME}}\"이라는 문서의 #$1판이 존재하지 않습니다.\n\n이 문제는 주로 삭제된 문서를 가리키는 오래된 문서 역사 링크로 인해 발생합니다.\n자세한 내용은 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 삭제 기록]에서 확인할 수 있습니다.",
        "userpage-userdoesnotexist": "\"$1\" 사용자 계정은 등록되어 있지 않습니다.\n이 문서를 만들거나 편집하기 전에 계정이 존재하는지 확인해주세요.",
        "userpage-userdoesnotexist-view": "\"$1\" 사용자 계정은 등록되어 있지 않습니다.",
        "blocked-notice-logextract": "이 사용자는 현재 차단되어 있습니다.\n해당 사용자의 최근 차단 기록을 참조하십시오:",
-       "clearyourcache": "<strong>참고:</strong> 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.\n* <strong>Firefox / Safari</strong>: <em>Shift</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em> 또는 <em>Ctrl-R</em> 을 입력 (Mac에서는 <em>⌘-R</em>)\n* <strong>Google Chrome</strong>: <em>Ctrl-Shift-R</em>키를 입력 (Mac에서는 <em>⌘-Shift-R</em>)\n* <strong>Internet Explorer</strong>: <em>Ctrl</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em>를 입력.\n* <strong>Opera</strong>: <em>도구→설정</em>에서 캐시를 비움",
+       "clearyourcache": "<strong>참고:</strong> 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.\n* <strong>파이어폭스 / 사파리</strong>: <em>Shift</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em> 또는 <em>Ctrl-R</em> 을 입력 (Mac에서는 <em>⌘-R</em>)\n* <strong>구글 크롬</strong>: <em>Ctrl-Shift-R</em>키를 입력 (Mac에서는 <em>⌘-Shift-R</em>)\n* <strong>인터넷 익스플로러</strong>: <em>Ctrl</em> 키를 누르면서 새로 고침을 클릭하거나, <em>Ctrl-F5</em>를 입력.\n* <strong>오페라:</strong> <em>메뉴 → 설정</em>(맥의 경우 <em>오페라 → 환경 설정</em>)으로 이동한 다음 <em>개인 정보 보호 및 보안 → 검색 데이터 지우기 → 캐시한 이미지 및 파일</em>을 누름.",
        "usercssyoucanpreview": "'''안내''': CSS 문서를 저장하기 전에 \"{{int:showpreview}}\" 기능을 통해 작동을 확인해주세요.",
        "userjsyoucanpreview": "'''안내''': 자바스크립트 문서를 저장하기 전에 \"{{int:showpreview}}\" 기능을 통해 작동을 확인해주세요.",
        "usercsspreview": "'''사용자 CSS의 미리 보기입니다.'''\n'''아직 저장하지 않았습니다!'''",
        "note": "<strong>참고:</strong>",
        "previewnote": "'''이 화면은 미리 보기입니다.'''\n편집한 내용은 아직 저장하지 않았습니다!",
        "continue-editing": "편집 영역으로 가기",
-       "previewconflict": "이 미리 보기는 저장할 때의 모습으로 위쪽 편집창의 문서를 반영합니다.",
-       "session_fail_preview": "세션 데이터가 없어져 편집을 저장하지 못했습니다.\n\n로그아웃 되었는지도 모릅니다. <strong>아직 로그인 상태인지 확인하고 다시 시도해주세요</strong>.\n다시 시도해도 되지 않으면 [[Special:UserLogout|로그아웃]]한 다음 다시 로그인하세요. 그리고 브라우저 설정에서 쿠키 사용을 허용하는지 확인하세요.",
+       "previewconflict": "이 미리 보기는 저장할 때의 모습으로, 위쪽 편집 영역의 텍스트를 반영합니다.",
+       "session_fail_preview": "세션 데이터가 없어져 편집을 저장하지 못했습니다.\n\n로그아웃되었는지도 모릅니다. <strong>아직 로그인 상태인지 확인하고 다시 시도해주세요</strong>.\n다시 시도해도 되지 않으면 [[Special:UserLogout|로그아웃]]한 다음 다시 로그인하세요. 그리고 브라우저 설정에서 쿠키 사용을 허용하는지 확인하세요.",
        "session_fail_preview_html": "세션 데이터가 없어져 편집을 저장하지 못했습니다.\n\n<em>{{SITENAME}}에서 HTML 입력을 허용하기 때문에, 자바스크립트 공격을 막기 위해 미리 보기는 숨겨져 있습니다.</em>\n\n<strong>적합하게 편집을 시도했다면 다시 시도해주세요.</strong>\n다시 시도해도 되지 않으면 [[Special:UserLogout|로그아웃]]한 다음 다시 로그인하고, 브라우저가 이 사이트에서 쿠키를 허용하는지 확인하세요.",
        "token_suffix_mismatch": "'''저장하려는 내용의 문장 부호가 망가져 있습니다.'''\n문서 보호를 위해 해당 내용을 저장하지 않습니다.\n버그가 있는 익명 프록시 서비스 등을 사용할 때 이런 문제가 발생할 수 있습니다.",
        "edit_form_incomplete": "'''편집의 일부 내용이 서버에 전달되지 않았습니다. 편집이 손상되지 않았는지 확인하고 다시 시도해 주십시오.'''",
        "explainconflict": "문서를 편집하는 도중에 누군가 이 문서를 고쳤습니다.\n위쪽의 문서가 지금 바뀐 문서이고, 아래쪽의 문서가 당신이 편집한 문서입니다.\n아래쪽의 내용을 위쪽에 적절히 합쳐 주시기 바랍니다.\n\"{{int:savearticle}}\"을 누르면 '''위쪽의 편집 내역만''' 저장됩니다.",
        "yourtext": "당신의 편집",
        "storedversion": "현재 문서",
-       "nonunicodebrowser": "<strong>경고: 웹 브라우저가 유니코드를 완벽하게 지원하지 않습니다.</strong>\n아스키가 아닌 문자가 16진수 코드로 나타날 수 있습니다.",
+       "nonunicodebrowser": "<strong>경고: 브라우저가 유니코드를 지원하지 않습니다.</strong>\n문서를 안전하게 편집할 수 있도록 다음의 우회 방안이 제공됩니다: 편집 상자에서 아스키가 아닌 문자가 16진수 코드로 나타납니다.",
        "editingold": "<strong>경고: 이 문서의 오래된 판을 편집하고 있습니다.</strong>\n이것을 저장하면, 이 판 이후로 바뀐 모든 편집이 사라집니다.",
        "yourdiff": "차이",
        "copyrightwarning": "{{SITENAME}}에서의 모든 기여는 $2 라이선스로 배포된다는 점을 유의해 주세요 (자세한 내용에 대해서는 $1 문서를 읽어주세요).\n만약 여기에 동의하지 않는다면 문서를 저장하지 말아 주세요.<br />\n또한, 직접 작성했거나 퍼블릭 도메인과 같은 자유 문서에서 가져왔다는 것을 보증해야 합니다.\n<strong>저작권이 있는 내용을 허가 없이 저장하지 마세요!</strong>",
        "readonlywarning": "<strong>경고: 데이터베이스가 관리를 위해 잠겨 있습니다. 따라서 문서를 편집한 내용을 지금 저장할 수 없습니다.</strong>\n편집 내용을 복사하여 붙여넣기 등을 사용하여 일단 다른 곳에 저장한 후, 나중에 다시 시도해 주세요.\n\n데이터베이스를 잠근 시스템 관리자가 남긴 설명은 다음과 같습니다: $1",
        "protectedpagewarning": "<strong>경고: 이 문서는 관리자 권한이 있는 사용자만 편집할 수 있도록 보호되어 있습니다.</strong>\n이 문서의 최근 기록을 참조하십시오:",
        "semiprotectedpagewarning": "<strong>참고:</strong> 이 문서는 계정을 등록한 사용자만이 편집할 수 있도록 보호되어 있습니다.\n이 문서의 최근 기록을 참조하십시오:",
-       "cascadeprotectedwarning": "<strong>경고:</strong> 이 문서는 보호되어 있어 관리자만 편집할 수 있습니다. 연쇄적 보호가 걸린 다음 {{PLURAL:$1|문서}}에서 이 문서를 사용하고 있습니다:",
+       "cascadeprotectedwarning": "<strong>경고:</strong> 이 문서는 보호되어 있어 관리자 권한이 있는 사용자만 편집할 수 있습니다. 연쇄적 보호가 걸린 다음 {{PLURAL:$1|문서}}에서 이 문서를 사용하고 있습니다:",
        "titleprotectedwarning": "<strong>경고: 이 문서는 보호되어 있어, 문서를 만드려면 [[Special:ListGroupRights|특정한 권한]]이 필요합니다.</strong>\n아래 문서의 최근 기록을 참조하십시오:",
        "templatesused": "이 문서에서 사용한 {{PLURAL:$1|틀}}:",
        "templatesusedpreview": "이 미리 보기에서 사용하고 있는 {{PLURAL:$1|틀}}:",
        "permissionserrors": "권한 오류",
        "permissionserrorstext": "해당 명령을 수행할 권한이 없습니다. 다음 {{PLURAL:$1|이유}}를 확인해보세요:",
        "permissionserrorstext-withaction": "$2 권한이 없습니다. 다음 {{PLURAL:$1|이유}}를 확인해주세요:",
+       "contentmodelediterror": "사용자는 이 판을 편집할 수 없습니다. 콘텐츠 모델은 <code>$1</code>이며, 이 문서의 현 콘텐츠 모델은 <code>$2</code>이므로 차이가 있습니다.",
        "recreate-moveddeleted-warn": "<strong>경고: 삭제된 문서를 다시 만들고 있습니다.</strong>\n\n이 문서를 계속 편집하는 것이 적합한 것인지 확인해주세요.\n편의를 위해 삭제와 이동 기록을 다음과 같이 제공합니다:",
        "moveddeleted-notice": "이 문서는 삭제되었습니다.\n이 문서의 삭제 및 이동 기록은 다음과 같습니다.",
        "moveddeleted-notice-recent": "죄송합니다, 이 문서는 최근 (24시간 내)에 삭제된 적이 있습니다.\n삭제와 이동 기록이 참고를 위해 남겨져 있습니다.",
        "duplicate-args-category": "중복된 인수를 사용한 틀의 호출을 포함한 문서",
        "duplicate-args-category-desc": "문서에 <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code>나 <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>와 같은, 인수를 중복하여 사용한 틀 호출을 포함합니다.",
        "expensive-parserfunction-warning": "<strong>경고:</strong> 이 문서는 너무 많은 파서 함수를 포함하고 있습니다.\n\n$2개 보다 적게 {{PLURAL:$2|써야}} 하지만 {{PLURAL:$1|지금은 $1개를 쓰고 있습니다}}.",
-       "expensive-parserfunction-category": "느린 파서 함수 호출을 너무 많이 하는 문서",
+       "expensive-parserfunction-category": "파서 함수 호출을 너무 많이 사용하는 문서",
        "post-expand-template-inclusion-warning": "<strong>경고:</strong> 틀 포함 크기가 너무 큽니다.\n일부 틀은 포함되지 않을 수 있습니다.",
        "post-expand-template-inclusion-category": "사용한 틀의 크기가 지나치게 큰 문서의 목록",
        "post-expand-template-argument-warning": "<strong>경고:</strong> 이 문서는 전개하면 크기가 너무 큰 틀 인수가 하나 이상 포함되어 있습니다.\n이 인수는 생략했습니다.",
        "parser-unstrip-loop-warning": "Unstrip의 반복을 감지했습니다",
        "parser-unstrip-recursion-limit": "Unstrip의 재귀 한도를 초과했습니다 ($1)",
        "converter-manual-rule-error": "언어 변환 규칙을 수동으로 지정하는 도중 오류",
-       "undo-success": "편집을 되돌릴 수 있습니다.\n편집 되돌리기를 완료하려면 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 저장해주세요.",
+       "undo-success": "편집을 되돌릴 수 있습니다.\n이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 저장해주세요.",
        "undo-failure": "중간의 다른 편집과 충돌하여 이 편집을 되돌릴 수 없습니다.",
        "undo-norev": "문서가 없거나 삭제되었기 때문에 편집을 되돌릴 수 없습니다.",
        "undo-nochange": "편집이 이미 되돌려진 것으로 나타납니다.",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|토론]])의 $1판 편집을 되돌림",
        "undo-summary-username-hidden": "숨겨진 사용자가 $1 판을 되돌림",
        "cantcreateaccounttitle": "계정을 만들 수 없습니다",
-       "cantcreateaccount-text": "현재 아이피 주소('''$1''')는 [[User:$3|$3]] 사용자에 의해 계정 만들기가 차단되었습니다.\n\n차단 이유는 다음과 같습니다: $2",
+       "cantcreateaccount-text": "현재 IP 주소('''$1''')는 [[User:$3|$3]] 사용자에 의해 계정 만들기가 차단되었습니다.\n\n차단 이유는 다음과 같습니다: $2",
        "cantcreateaccount-range-text": "당신의 IP 주소(<strong>$4</strong>)가 속해 있는 <strong>$1</strong> 대역에서의 계정 생성을 [[User:$3|$3]] 사용자가 차단하였습니다.\n\n$3 사용자가 제시한 이유는 \"$2\"입니다.",
        "viewpagelogs": "이 문서의 기록 보기",
        "nohistory": "이 문서는 편집 역사가 없습니다.",
        "logdelete-selected": "{{PLURAL:$1|선택한 기록}}:",
        "revdelete-text-text": "삭제된 판은 여전히 문서 역사에 남게 되지만, 그 내용의 일부는 다른 사람들이 접근할 수 없게 됩니다.",
        "revdelete-text-file": "삭제된 파일 버전은 계속 파일 역사에 남게 되지만, 내용의 일부는 다른 사람들이 접근할 수 없게 됩니다.",
-       "logdelete-text": "ì\82­ì \9cë\90\9c ë¡\9cê·¸ ë\82´ì\9a©ì\9d\80 ë¡\9cê·¸ì\97\90 ë³´ì\97¬ì§\80ê² ì§\80ë§\8c, ë\82´ì\9a©ì\9d\98 ì\9d¼ë¶\80ë\8a\94 ë\8b¤ë¥¸ ì\82¬ë\9e\8cë\93¤이 접근할 수 없게 됩니다.",
+       "logdelete-text": "ì\82­ì \9cë\90\9c ë¡\9cê·¸ ë\82´ì\9a©ì\9d\80 ë¡\9cê·¸ì\97\90 ë\82\98í\83\80ë\82\98ì§\80ë§\8c, ë\82´ì\9a© ì¤\91 ì\9d¼ë¶\80ë\8a\94 ì\9d¼ë°\98ì\9d¸이 접근할 수 없게 됩니다.",
        "revdelete-text-others": "다른 관리자는 여전히 숨겨진 내용에 접근할 수 있고 추가 제한이 설정되어 있지 않으면, 다시 되살릴 수 있습니다.",
-       "revdelete-confirm": "ì\9d´ ì\9e\91ì\97\85ì\9d\84 ì\88\98í\96\89í\95\98ë\8a\94 ê²\83ì\9d\98 ê²°ê³¼ë¥¼ ì\95\8cê³  ì\9e\88ì\9c¼ë©°, [[{{MediaWiki:Policy-url}}|ì \95ì±\85]]ì\97\90 ë§\9eë\8a\94 í\96\89ë\8f\99ì\9d¸지 확인해주세요.",
+       "revdelete-confirm": "ì\9d´ ì\9e\91ì\97\85ì\9d\98 ì\88\98í\96\89 ê²°ê³¼ë¥¼ ì\95\8cê³  ì\9e\88ì\9c¼ë©°, ì\9d´ ì\9e\91ì\97\85ì\9d´ [[{{MediaWiki:Policy-url}}|ì \95ì±\85]]ì\97\90 ì\9d\98ê±°í\95\98ë\8a\94지 확인해주세요.",
        "revdelete-suppress-text": "숨기기는 <strong>다음 경우에만</strong> 사용되어야 합니다:\n* 잠재적인 비방 정보\n* 부적절한 개인 정보\n*: 집 주소, 전화번호, 주민등록번호 등",
        "revdelete-legend": "보이기 제한을 설정",
        "revdelete-hide-text": "판 문자열",
        "mergehistory-submit": "판 합치기",
        "mergehistory-empty": "합칠 수 있는 판이 없습니다.",
        "mergehistory-done": "$1 문서의 {{PLURAL:$3|판}} $3개{{PLURAL:$3|가}} [[:$2]]에 성공적으로 합쳐졌습니다.",
-       "mergehistory-fail": "역사 합치기를 수행할 수 없습니다, 문서와 시간 변수를 다시 확인하세요.",
-       "mergehistory-fail-bad-timestamp": "타임스탬프(timestamp)가 적절하지 않습니다.",
+       "mergehistory-fail": "역사 합치기를 수행할 수 없습니다. 문서와 시간 변수를 다시 확인하세요.",
+       "mergehistory-fail-bad-timestamp": "타임스탬프가 적절하지 않습니다.",
        "mergehistory-fail-invalid-source": "원본 문서가 적절하지 않습니다.",
        "mergehistory-fail-invalid-dest": "대상 문서가 적절하지 않습니다.",
+       "mergehistory-fail-no-change": "역사 병합은 모든 판을 병합하지 못했습니다. 문서와 시간 변수를 다시 확인하여 주십시오.",
+       "mergehistory-fail-permission": "역사를 병합할 권한이 부족합니다.",
        "mergehistory-fail-self-merge": "원본과 대상 문서가 같습니다.",
+       "mergehistory-fail-timestamps-overlap": "원본의 판들을 겹치거나 대상이 되는 판들 이후에 오게 할 수 없습니다.",
        "mergehistory-fail-toobig": "이동하려는 {{PLURAL:$1|판}} $1개 제한보다 많이 역사 병합을 수행할 수 없습니다.",
        "mergehistory-no-source": "원본인 $1 문서가 존재하지 않습니다.",
        "mergehistory-no-destination": "대상인 $1 문서가 존재하지 않습니다.",
        "notextmatches": "해당하는 문서 없음",
        "prevn": "이전 {{PLURAL:$1|$1개}}",
        "nextn": "다음 {{PLURAL:$1|$1개}}",
-       "prev-page": "전 페이지",
+       "prev-page": "ì\9d´ì \84 í\8e\98ì\9d´ì§\80",
        "next-page": "다음 페이지",
        "prevn-title": "이전 {{PLURAL:$1|결과}} $1개",
        "nextn-title": "다음 {{PLURAL:$1|결과}} $1개",
        "right-createpage": "문서 만들기 (토론 문서 제외)",
        "right-createtalk": "토론 문서 만들기",
        "right-createaccount": "새 사용자 계정 만들기",
+       "right-autocreateaccount": "외부 사용자 계정으로 자동 로그인",
        "right-minoredit": "사소한 편집으로 표시",
        "right-move": "문서 이동",
        "right-move-subpages": "문서와 하위 문서 이동하기",
        "right-managechangetags": "데이터베이스에서 [[Special:Tags|태그]]를 만들거나 지우기",
        "right-applychangetags": "자신이 편집할 때 [[Special:Tags|태그]]를 적용하기",
        "right-changetags": "문서의 특정 판과 특정 기록 항목에 임의의 [[Special:Tags|태그]]를 추가하거나 제거하기",
+       "right-deletechangetags": "데이터베이스에서 [[Special:Tags|태그]]를 지우기",
        "grant-generic": "\"$1\" 권한 번들",
        "grant-group-page-interaction": "문서로 상호 작용",
        "grant-group-file-interaction": "미디어로 상호 작용",
        "action-viewmyprivateinfo": "자신의 개인정보 보기",
        "action-editmyprivateinfo": "자신의 개인정보 편집",
        "action-editcontentmodel": "문서의 콘텐츠 모델을 편집",
-       "action-managechangetags": "ë\8d°ì\9d´í\84°ë² ì\9d´ì\8a¤ì\97\90ì\84\9c í\83\9c그를 ë§\8cë\93¤ê±°ë\82\98 ì§\80ì\9a¸",
+       "action-managechangetags": "ë\8d°ì\9d´í\84°ë² ì\9d´ì\8a¤ì\97\90ì\84\9c í\83\9c그를 ë§\8cë\93¤ê±°ë\82\98 ì§\80ì\9a°ê¸°",
        "action-applychangetags": "당신이 편집할 때 태그를 적용하기",
        "action-changetags": "문서의 특정 판과 특정 기록 항목에 임의의 태그를 추가하거나 제거하기",
+       "action-deletechangetags": "데이터베이스에서 태그를 지우기",
        "nchanges": "$1개 {{PLURAL:$1|바뀜}}",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|마지막 방문 이후}} $1개",
        "enhancedrc-history": "역사",
        "recentchangeslinked-page": "문서 이름:",
        "recentchangeslinked-to": "해당 문서를 가리키는 문서의 바뀜 보기",
        "recentchanges-page-added-to-category": "[[:$1]]이(가) 분류에 추가되었습니다",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] 문서가 분류에 추가되었습니다. [[Special:WhatLinksHere/$1|이 문서는 다른 문서들에 포함되어 있습니다]]",
        "recentchanges-page-removed-from-category": "[[:$1]]이(가) 분류에서 제거되었습니다",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] 문서가 분류에서 제거되었습니다. [[Special:WhatLinksHere/$1|이 문서는 다른 문서들에 포함되어 있습니다]]",
        "autochange-username": "미디어위키 자동 변경",
        "upload": "파일 올리기",
        "uploadbtn": "파일 올리기",
        "upload-scripted-pi-callback": "XML 스타일시트 프로세싱 명령을 포함하는 파일은 업로드 할 수 없습니다.",
        "uploaded-script-svg": "업로드 된 SVG 파일에서 스크립트로 만들 수 있는 \"$1\" 요소를 발견했습니다.",
        "uploaded-hostile-svg": "업로드 된 SVG 파일의 스타일 요소에 안전하지 못한 CSS가 있습니다.",
+       "uploaded-event-handler-on-svg": "이벤트 핸들러 속성 <code>$1=\"$2\"</code> 설정은 SVG 파일에서 사용할 수 없습니다.",
+       "uploaded-href-attribute-svg": "SVG 파일의 href 속성은 http:// 또는 https:// 대상의 링크만 허용되지만 <code>&lt;$1 $2=\"$3\"&gt;</code>를 발견했습니다.",
+       "uploaded-href-unsafe-target-svg": "안전하지 않은 데이터를 가리키는 href를 발견했습니다: 업로드된 SVG 파일의 URI 대상 <code>&lt;$1 $2=\"$3\"&gt;</code>",
+       "uploaded-animate-svg": "업로드된 SVG 파일에서 \"from\" 속성 <code>&lt;$1 $2=\"$3\"&gt;</code>을 이용 중 \"animate\" 태그가 href를 변경할 수 있음을 발견했습니다.",
+       "uploaded-setting-event-handler-svg": "이벤트 핸들러 속성을 차단으로 설정한 상태에서 <code>&lt;$1 $2=\"$3\"&gt;</code>가 업로드된 SVG 파일에서 발견되었습니다.",
+       "uploaded-setting-href-svg": "부모 요소에 \"href\" 속성을 추가할 목적으로 \"set\" 태그를 사용하는 것은 금지됩니다.",
+       "uploaded-wrong-setting-svg": "원격/데이터/스크립트 대상을 임의의 속성에 추가할 목적으로 \"set\" 태그를 사용하는 것은 금지됩니다. 업로드된 SVG 파일에 <code>&lt;set to=\"$1\"&gt;</code>를 발견했습니다.",
+       "uploaded-setting-handler-svg": "원격/데이터/스크립트와 함께 \"handler\" 속성을 설정한 SVG는 이용이 금지됩니다. 업로드된 SVG 파일에서 <code>$1=\"$2\"</code>를 발견하였습니다.",
        "uploaded-remote-url-svg": "원격 URL로 style 속성이 설정된 SVG파일은 금지됩니다. 업로드된 SVG 파일에서 <code>$1=\"$2\"</code>를 발견하였습니다.",
+       "uploaded-image-filter-svg": "URL에 이미지 필터를 발견했습니다: 업로드된 SVG 파일의 <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploadscriptednamespace": "이 SVG 파일은 사용할 수 없는 이름공간 '$1'을 포함하고 있습니다.",
        "uploadinvalidxml": "업로드된 파일의 XML의 구문을 분석할 수 없습니다.",
        "uploadvirus": "파일이 바이러스를 포함하고 있습니다!\n자세한 설명: $1",
        "upload-options": "올리기 설정",
        "watchthisupload": "이 파일 주시하기",
        "filewasdeleted": "같은 이름을 가진 파일이 올라온 적이 있었고 그 후에 삭제되었습니다.\n올리기 전에 $1을 확인해 주시기 바랍니다.",
+       "filename-thumb-name": "섬네일 제목으로 추정됩니다. 같은 위키에 섬네일 그림을 다시 업로드하지 마십시오. 섬네일이 아니라면 파일 이름을 보다 유의미한 이름으로 수정해 주시고 이름에 섬네일 접두어는 포함하지 마십시오.",
        "filename-bad-prefix": "올리려고 하는 파일 이름이 '''\"$1\"''' 이름으로 시작합니다. \"$1\" 이름은 디지털 사진기가 자동으로 붙이는 의미없는 이름입니다.\n파일에 대해 알기 쉬운 이름을 골라주세요.",
        "filename-prefix-blacklist": " #<!-- 이 줄은 그대로 두십시오 --> <pre>\n# 문법은 다음과 같습니다:\n#   * \"#\" 문자에서 줄의 끝까지는 주석입니다\n#   * 비어 있지 않은 줄은 디지털 카메라에서 자동적으로 부여하는 파일 접두어입니다\nCIMG # 카시오\nDSC_ # 니콘\nDSCF # 후지\nDSCN # 니콘\nDUW # 일부 휴대폰\nIMG # 일반\nJD # 제놉틱\nMGP # 펜탁스\nPICT # 기타\n #</pre> <!-- 이 줄은 그대로 두십시오 -->",
        "upload-proto-error": "잘못된 프로토콜",
        "upload-too-many-redirects": "URL이 너무 많은 넘겨주기를 포함하고 있습니다.",
        "upload-http-error": "HTTP 오류 발생: $1",
        "upload-copy-upload-invalid-domain": "이 도메인에 속하지 않는 웹사이트의 파일을 올릴 수 없습니다.",
+       "upload-foreign-cant-upload": "이 위키는 요청된 외부 파일 저장소에 파일을 업로드할 수 있도록 구성되어 있지 않습니다.",
        "upload-dialog-title": "파일 올리기",
        "upload-dialog-button-cancel": "취소",
        "upload-dialog-button-done": "완료",
        "upload-dialog-button-upload": "올리기",
        "upload-form-label-infoform-title": "자세한 사항",
        "upload-form-label-infoform-name": "이름",
+       "upload-form-label-infoform-name-tooltip": "이 파일을 설명할 수 있는 제목이며, 파일 이름으로 쓰일 것입니다. 띄어쓰기를 포함한 자연어를 쓸 수 있습니다. 파일 확장자는 포함하지 말아 주세요.",
        "upload-form-label-infoform-description": "설명",
+       "upload-form-label-infoform-description-tooltip": "이 작품에 대해 주목할만한 모든 내용을 간략하게 기술합니다.\n사진의 경우 주로 무엇이 찍혀 있는지와 언제 어디서 촬영했는지를 언급합니다.",
        "upload-form-label-usage-title": "사용",
        "upload-form-label-usage-filename": "파일 이름",
-       "foreign-structured-upload-form-label-own-work": "자작입니다",
-       "foreign-structured-upload-form-label-infoform-categories": "분류",
-       "foreign-structured-upload-form-label-infoform-date": "날짜",
-       "foreign-structured-upload-form-label-own-work-message-shared": "나는 이 파일에 대한 저작권을 소유하고 있음을 입증하고, 영구히 위키미디어 공용에 이 파일을 [https://creativecommons.org/licenses/by-sa/4.0/ 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0]에 따라 배포하는 데 동의하며, [https://wikimediafoundation.org/wiki/Terms_of_Use 이용 약관]에 동의합니다.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "이 파일의 저작권을 소유하지 않거나 다른 라이선스로 배포하고 싶다면 [https://commons.wikimedia.org/wiki/Special:UploadWizard 공용 파일 올리기 마법사]를 사용하는 것을 고려해보세요.",
+       "upload-form-label-own-work": "자작입니다",
+       "upload-form-label-infoform-categories": "분류",
+       "upload-form-label-infoform-date": "날짜",
+       "upload-form-label-own-work-message-generic-local": "사용자는 이 파일을 업로드할 때 {{SITENAME}}의 서비스 조항과 라이선스 정책에 동의하는 것으로 간주합니다.",
+       "upload-form-label-not-own-work-message-generic-local": "이 파일을 {{SITENAME}}의 정책에 따라 업로드할 수 없으면 이 대화 상자를 닫고 다른 방식을 사용하십시오.",
+       "upload-form-label-not-own-work-local-generic-local": "[[Special:Upload|기본 파일 올리기 문서]]를 이용할 수도 있습니다.",
+       "upload-form-label-own-work-message-generic-foreign": "이 파일을 공유 저장소에 업로드하면 사용자는 서비스 조항과 라이선스 정책에 동의하는 것으로 간주합니다.",
+       "upload-form-label-not-own-work-message-generic-foreign": "이 파일을 공유 저장소의 정책에 따라 업로드할 수 없으면 이 대화 상자를 닫고 다른 방식을 사용하십시오.",
+       "upload-form-label-not-own-work-local-generic-foreign": "이 파일이 정책에 따라 업로드가 가능할 경우 [[Special:Upload|{{SITENAME}}의 기본 파일 올리기 문서]]를 이용할 수도 있습니다.",
        "backend-fail-stream": "\"$1\" 파일을 스트림할 수 없습니다.",
        "backend-fail-backup": "\"$1\" 파일을 백업할 수 없습니다.",
        "backend-fail-notexists": "$1 파일이 존재하지 않습니다.",
        "apihelp": "API 도움말",
        "apihelp-no-such-module": "\"$1\" 모듈을 찾을 수 없습니다.",
        "apisandbox": "API 실험실",
+       "apisandbox-jsonly": "API 연습장을 이용하려면 자바스크립트가 필요합니다.",
        "apisandbox-api-disabled": "이 사이트에서는 API가 꺼져 있습니다.",
        "apisandbox-intro": "<strong>미디어위키 웹 서비스 API</strong>를 시험해보려면 이 페이지를 이용해보세요. API 용법에 대해서는 [[mw:API:Main page|API 문서]]을 참고하십시오. 예: [//www.mediawiki.org/wiki/API#A_simple_example 대문의 내용 요청하기]. 더 많은 예를 보려면 액션을 선택하세요.\n\n여기가 연습장이라도 이 페이지에서 실행하는 동작때문에 위키를 변경할 수도 있다는 점에 유의하십시오.",
        "apisandbox-fullscreen": "패널 늘리기",
        "apisandbox-fullscreen-tooltip": "브라우저 창에 맞도록 샌드박스 패널 늘리기",
        "apisandbox-unfullscreen": "페이지 보기",
+       "apisandbox-unfullscreen-tooltip": "연습장 틀의 크기를 줄이면 미디어위키 둘러보기 링크들을 이용할 수 있습니다.",
        "apisandbox-submit": "요청하기",
        "apisandbox-reset": "지우기",
        "apisandbox-retry": "재시도",
        "apisandbox-loading": "API 모듈 \"$1\"의 정보 불러오는 중...",
+       "apisandbox-load-error": "API 모듈 \"$1\"의 정보를 가져오는 도중 오류가 발생했습니다: $2",
        "apisandbox-no-parameters": "이 API 모듈은 변수가 없습니다.",
        "apisandbox-helpurls": "도움 링크들",
        "apisandbox-examples": "예시",
        "apisandbox-dynamic-parameters-add-placeholder": "변수 이름",
        "apisandbox-dynamic-error-exists": "\"$1\"이라는 변수 이름은 이미 존재합니다.",
        "apisandbox-deprecated-parameters": "앞으로 제거될 변수",
+       "apisandbox-fetch-token": "토큰 자동 채우기",
        "apisandbox-submit-invalid-fields-title": "부적절한 필드가 있음",
+       "apisandbox-submit-invalid-fields-message": "강조된 필드를 수정한 다음 다시 시도해 주십시오.",
        "apisandbox-results": "결과",
        "apisandbox-sending-request": "API 요청을 보내는 중...",
        "apisandbox-loading-results": "API 결과를 받는 중...",
-       "apisandbox-results-error": "API ì§\88ì\9d\98 ì\9a\94ì²­ì\9d\84 ë¡\9cë\94©í\95\98ë\8a\94 ë\8f\84ì¤\91 ì\97\90ë\9f¬ 발생: $1.",
+       "apisandbox-results-error": "API ì§\88ì\9d\98 ì\9d\91ë\8bµì\9d\84 ë¶\88ë\9f¬ì\98¤ë\8a\94 ë\8f\84ì¤\91 ì\98¤ë¥\98 발생: $1.",
        "apisandbox-request-url-label": "요청 URL:",
        "apisandbox-request-time": "요청 처리 시간: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "토큰 수정 후 다시 제출",
        "apisandbox-results-fixtoken-fail": "\"$1\" 토크을 가져오는데 실패했습니다.",
        "apisandbox-alert-page": "이 문서에 있는 필드가 유효하지 않습니다.",
        "apisandbox-alert-field": "이 필드의 값이 유효하지 않습니다.",
        "listgrouprights-namespaceprotection-namespace": "이름공간",
        "listgrouprights-namespaceprotection-restrictedto": "사용자가 편집할 수 있는 권한",
        "listgrants": "부여",
-       "listgrants-summary": "다음은 사용자 권한에 관련된 접근 권한을 통해 부여된 부여 목록입니다. 사용자는 자신의 계정에 대해 권한을 부여 할 수 있지만, 사용자가 애플리케이션에 부여한 권한 설정에 따라 제한이 있습니다. 사용자를 대신하여 동작하는 애플리케이션은 사용자가 갖고 있지 않은 권한은 사용할 수 없습니다. \n각각의 권한에 대한 [[{{MediaWiki:Listgrouprights-helppage}}|추가 정보]]가 있습니다.",
+       "listgrants-summary": "다음은 사용자 권한에 관련된 접근 권한을 통해 부여된 부여 목록입니다. 사용자는 자신의 계정에 대해 권한을 부여할 수 있지만, 사용자가 애플리케이션에 부여한 권한 설정에 따라 제한이 있습니다. 사용자를 대신하여 동작하는 애플리케이션은 사용자가 갖고 있지 않은 권한을 사용할 수는 없습니다. \n각각의 권한에 대한 [[{{MediaWiki:Listgrouprights-helppage}}|추가 정보]]가 있습니다.",
        "listgrants-grant": "부여",
        "listgrants-rights": "권한",
        "trackingcategories": "추적용 분류",
        "unwatchthispage": "주시 해제하기",
        "notanarticle": "문서가 아님",
        "notvisiblerev": "이 판은 삭제되었습니다.",
-       "watchlist-details": "별도의 토론 문서를 세지 않고, 주시문서 목록에 {{PLURAL:$1|문서 $1개}}가 있습니다.",
+       "watchlist-details": "토론 문서의 수는 제외하고, 주시문서 목록에 {{PLURAL:$1|문서 $1개}}가 있습니다.",
        "wlheader-enotif": "이메일 알림 기능이 활성화되었습니다.",
        "wlheader-showupdated": "마지막으로 방문한 이후에 바뀐 문서는 '''굵은 글씨'''로 보입니다.",
        "wlnote": "$3 $4 기준으로, 아래에 최근 {{PLURAL:$2|한 시간|<strong>$2</strong>시간}} 동안 {{PLURAL:$1|마지막 바뀜이|마지막 바뀜 <strong>$1</strong>개가}} 있습니다.",
        "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님이 비 기본값 \"$5\" 콘텐츠 모델을 사용해  $3 문서를 {{GENDER:$2|만들었습니다}}",
        "whatlinkshere-prev": "{{PLURAL:$1|이전|이전 $1개}}",
        "whatlinkshere-next": "{{PLURAL:$1|다음|다음 $1개}}",
        "whatlinkshere-links": "← 가리키는 문서 목록",
-       "whatlinkshere-hideredirs": "넘겨주기를 $1",
-       "whatlinkshere-hidetrans": "끼워넣기를 $1",
-       "whatlinkshere-hidelinks": "링크를 $1",
-       "whatlinkshere-hideimages": "파일 링크를 $1",
+       "whatlinkshere-hideredirs": "넘겨주기를 숨기기",
+       "whatlinkshere-hidetrans": "끼워넣기를 숨기기",
+       "whatlinkshere-hidelinks": "링크를 숨기기",
+       "whatlinkshere-hideimages": "파일 링크를 숨기기",
        "whatlinkshere-filters": "필터",
        "whatlinkshere-submit": "계속",
        "autoblockid": "자동 차단 #$1",
        "ipb-unblock": "사용자 또는 IP 주소 차단 해제하기",
        "ipb-blocklist": "현재 차단 기록 보기",
        "ipb-blocklist-contribs": "{{GENDER:$1|$1}}의 기여",
+       "ipb-blocklist-duration-left": "남은 기간: $1",
        "unblockip": "사용자 차단 해제",
        "unblockiptext": "아래의 양식에 차단 해제하려는 IP 주소나 사용자 이름을 입력하세요.",
        "ipusubmit": "차단 해제",
        "lockdbsuccesstext": "데이터베이스가 잠겼습니다.<br />\n관리가 끝나면 잊지 말고 [[Special:UnlockDB|잠금을 풀어]] 주세요.",
        "unlockdbsuccesstext": "데이터베이스 잠금 상태가 해제되었습니다.",
        "lockfilenotwritable": "데이터베이스 잠금 파일에 쓰기 권한이 없습니다.\n데이터베이스를 잠그거나 잠금 해제하려면, 웹 서버에서 이 파일의 쓰기 권한을 설정해야 합니다.",
+       "databaselocked": "데이터베이스가 이미 잠겨 있습니다.",
        "databasenotlocked": "데이터베이스가 잠겨 있지 않습니다.",
        "lockedbyandtime": "({{GENDER:$1|$1}} 사용자가 $2 $3에 잠금)",
        "move-page": "$1 이동",
        "import-nonewrevisions": "가져온 판 없음(모든 판이 이미 존재하거나 오류로 인해 건너뛰었을 수도 있습니다.)",
        "xml-error-string": "$3단 $2줄 (바이트 $4)에서 $1: $5",
        "import-upload": "XML 데이터 올리기",
-       "import-token-mismatch": "세션 데이터가 손실되었습니다.\n\n로그아웃 되었는지도 모릅니다. <strong>아직 로그인 상태인지 확인하고 다시 시도해주세요</strong>.\n다시 시도해도 되지 않으면 [[Special:UserLogout|로그아웃]]한 다음 다시 로그인하세요. 그리고 브라우저 설정에서 쿠키 사용을 허용하는지 확인하세요.",
+       "import-token-mismatch": "세션 데이터가 손실되었습니다.\n\n로그아웃되었는지도 모릅니다. <strong>아직 로그인 상태인지 확인하고 다시 시도해주세요</strong>.\n다시 시도해도 되지 않으면 [[Special:UserLogout|로그아웃]]한 다음 다시 로그인하세요. 그리고 브라우저 설정에서 쿠키 사용을 허용하는지 확인하세요.",
        "import-invalid-interwiki": "해당 위키에서 문서를 가져올 수 없습니다.",
        "import-error-edit": "문서를 편집할 수 없기 때문에 \"$1\" 문서를 가져올 수 없었습니다.",
        "import-error-create": "문서를 만들 수 없기 때문에 \"$1\" 문서를 가져올 수 없었습니다.",
        "tooltip-ca-nstab-category": "분류 문서 보기",
        "tooltip-minoredit": "이 편집을 사소한 편집으로 표시하기",
        "tooltip-save": "바뀐 내용 저장하기",
+       "tooltip-publish": "변경사항 게시",
        "tooltip-preview": "바뀜을 미리 봅니다. 저장하기 전에 미리 보기를 해주세요!",
        "tooltip-diff": "자신이 바꾼 내용 보기",
        "tooltip-compareselectedversions": "이 문서에서 선택한 두 판 간의 차이를 비교",
        "pageinfo-watchers": "문서를 주시하는 사용자 수",
        "pageinfo-visiting-watchers": "이 문서를 최근에 방문한 주시하는 사용자 수",
        "pageinfo-few-watchers": "{{PLURAL:$1|주시하는 사용자}} $1명보다 적음",
+       "pageinfo-few-visiting-watchers": "최근의 편집을 주시하는 사용자가 있을 수도 없을 수도 있습니다",
        "pageinfo-redirects-name": "이 문서의 넘겨주기 수",
        "pageinfo-redirects-value": "$1",
        "pageinfo-subpages-name": "이 문서의 하위 문서 수",
        "saturday-at": "토요일 $1",
        "sunday-at": "일요일 $1",
        "yesterday-at": "어제 $1",
-       "bad_image_list": "형식은 아래와 같습니다.\n\n\"*\"로 시작하는 목록의 내용만 적용됩니다.\n매 줄의 첫번째 링크는 부적절한 파일을 가리켜야 합니다.\n같은 줄에 따라오는 모든 링크는 예외로 봅니다. (예: 파일이 사용되어야 하는 문서)",
+       "bad_image_list": "형식은 아래와 같습니다.\n\n\"*\"로 시작하는 목록의 내용만 적용됩니다.\n매 줄의 첫 번째 링크는 부적절한 파일을 가리켜야 합니다.\n같은 줄에 따라오는 모든 링크는 예외로 봅니다. (예: 파일이 사용되어야 하는 문서)",
        "variantname-zh-hans": "간체",
        "variantname-zh-hant": "번체",
        "metadata": "메타데이터",
        "confirmemail_body_set": "$1 IP 주소를 사용하는 사용자가\n{{SITENAME}}의 \"$2\" 계정의 이메일 주소를 지정하였습니다.\n\n이 계정이 당신의 계정이고 {{SITENAME}}에서 이메일 기능을\n활성화하려면 아래 주소를 열어서 이메일 인증을 해 주세요:\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": "IP 주소 $1에 속하는 누군가가 {{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 \"$3\"으로 변경하였습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.",
+       "notificationemail_body_removed": "IP 주소 $1에 속하는 누군가가 {{SITENAME}}의 사용자 \"$2\" 계정의 이메일 주소를 제거하였습니다.\n\n지금 이 글을 보고 계신 사용자로 추정되지만 만약 본인이 아닌 경우, 지금 바로 사이트 관리자에게 문의하십시오.",
        "scarytranscludedisabled": "[인터위키가 비활성되어 있습니다]",
        "scarytranscludefailed": "[$1 틀을 불러오는 데에 실패했습니다]",
        "scarytranscludefailed-httpstatus": "[$1 틀을 가져오는 데 실패했습니다: HTTP $2]",
        "timezone-local": "로컬",
        "duplicate-defaultsort": "<strong>경고:</strong> 기본 정렬 키 \"$2\"가 이전의 기본 정렬 키 \"$1\"를 덮어쓰고 있습니다.",
        "duplicate-displaytitle": "<strong>경고:</strong> \"$2\" 제목 표시는 기존의 표시되는 제목 \"$1\"을 덮어씁니다.",
+       "restricted-displaytitle": "<strong>경고:</strong> 표시하려는 제목 \"$1\"은(는) 문서의 실제 제목과 동일하지 않으므로 무시되었습니다.",
        "invalid-indicator-name": "<strong>오류:</strong> 문서 상태 표시기의 <code>name</code> 특성은 비어 있지 않아야 합니다.",
        "version": "버전",
        "version-extensions": "설치된 확장 기능",
        "tags-deactivate": "비활성화",
        "tags-hitcount": "$1개 {{PLURAL:$1|바뀜}}",
        "tags-manage-no-permission": "태그를 변경할 권한이 없습니다.",
+       "tags-manage-blocked": "차단된 상태에서는 변경 태그를 관리할 수 없습니다.",
        "tags-create-heading": "태그 생성",
        "tags-create-explanation": "기본적으로 새로 생성된 태그는 사용자와 봇이 사용할 수 있습니다.",
        "tags-create-tag-name": "태그 이름:",
        "tags-delete-submit": "이 태그를 영구히 삭제",
        "tags-delete-not-allowed": "확장 기능에서 정의된 태그는 확장 기능 설정에서 허용되지 않은 이상 삭제할 수 없습니다.",
        "tags-delete-not-found": "\"$1\" 태그가 존재하지 않습니다.",
+       "tags-delete-too-many-uses": "\"$1\" 태그가 $2개 이상의 판에 적용되어 있으므로 삭제할 수 없습니다.",
+       "tags-delete-warnings-after-delete": "\"$1\" 태그가 삭제되었으나 다음과 같은 $2개의 경고 태그가 발생하였습니다:",
+       "tags-delete-no-permission": "변경 태그를 삭제할 권한이 없습니다.",
        "tags-activate-title": "태그 활성화",
        "tags-activate-question": "\"$1\" 태그를 활성화하려고 합니다.",
        "tags-activate-reason": "이유:",
        "tags-deactivate-reason": "이유:",
        "tags-deactivate-not-allowed": "\"$1\" 태그를 비활성화할 수 없습니다.",
        "tags-deactivate-submit": "비활성화",
+       "tags-apply-no-permission": "사용자의 변경 사항과 변경 태그를 적용할 권한이 없습니다.",
+       "tags-apply-blocked": "차단된 상태에서는 사용자의 변경 사항과 변경 태그를 적용할 수 없습니다.",
        "tags-apply-not-allowed-one": "\"$1\" 태그를 수동으로 추가하는 것은 허용되지 않습니다.",
        "tags-apply-not-allowed-multi": "다음 {{PLURAL:$2|태그를}} 수동으로 추가하는 것은 허용되지 않습니다: $1",
        "tags-update-no-permission": "태그를 문서 판이나 로그 기록에서 추가하거나 삭제할 권한이 없습니다.",
+       "tags-update-blocked": "차단된 상태에서는 변경 태그를 추가하거나 제거할 수 없습니다.",
        "tags-update-add-not-allowed-one": "\"$1\" 태그를 수동으로 추가하는 것은 허용되지 않습니다.",
        "tags-update-add-not-allowed-multi": "다음 {{PLURAL:$2|태그는}} 수동으로 추가하는 것이 허용되지 않습니다: $1",
        "tags-update-remove-not-allowed-one": "\"$1\" 태그를 제거하는 것은 허용되지 않습니다.",
        "logentry-import-upload": "$1님이 $3 문서를 파일 올리기로 {{GENDER:$2|가져왔습니다}}",
        "logentry-import-upload-details": "$1님이 $3 문서 ({{PLURAL:$4|판}} $4개)를 파일 올리기로 {{GENDER:$2|가져왔습니다}}",
        "logentry-import-interwiki": "$1님이 $3 문서를 다른 위키에서 {{GENDER:$2|가져왔습니다}}",
-       "logentry-import-interwiki-details": "$1님이 $3 문서 ({{PLURAL:$4|판}} $4개)를 다른 위키에서 {{GENDER:$2|가져왔습니다}}",
+       "logentry-import-interwiki-details": "$1님이 $3 문서($4개의 판)를 $5에서 {{GENDER:$2|가져왔습니다}}",
        "logentry-merge-merge": "$1님이 $3 문서를 $4 안에 {{GENDER:$2|병합했습니다}} (판은 $5까지)",
        "logentry-move-move": "$1님이 $3 문서를 $4 문서로 {{GENDER:$2|이동했습니다}}",
        "logentry-move-move-noredirect": "$1님이 $3 문서를 넘겨주기를 만들지 않고 $4 문서로 {{GENDER:$2|이동했습니다}}",
        "feedback-useragent": "사용자 에이전트:",
        "searchsuggest-search": "검색",
        "searchsuggest-containing": "다음 문자열 포함...",
+       "api-error-autoblocked": "사용자의 IP 주소는 차단된 사용자에 의해 사용되었으므로 자동으로 차단된 상태입니다.",
        "api-error-badaccess-groups": "이 위키에 파일을 올릴 권한이 없습니다.",
        "api-error-badtoken": "내부 오류: 토큰이 잘못되었습니다.",
+       "api-error-blocked": "편집에서 차단되어 있습니다.",
        "api-error-copyuploaddisabled": "이 서버에서 URL을 통해 파일 올리기가 비활성화되어 있습니다.",
        "api-error-duplicate": "이 위키에 내용이 똑같은 {{PLURAL:$1|다른 파일}}이 있습니다.",
        "api-error-duplicate-archive": "같은 내용을 담고 있던 {{PLURAL:$1|다른 파일}}이 있었지만 이 {{PLURAL:$1|파일}}은 삭제되었습니다.",
        "api-error-nomodule": "내부 오류: 올리기 모듈이 설정되지 않았습니다.",
        "api-error-ok-but-empty": "내부 오류: 서버에서 응답이 없습니다.",
        "api-error-overwrite": "이미 있는 파일을 덮어쓸 수 없습니다.",
+       "api-error-ratelimited": "짧은 시간 안에 위키가 허용하는 것 보다 더 많은 파일을 업로드하려고 합니다.\n몇 분 뒤에 다시 시도해 주십시오.",
        "api-error-stashfailed": "내부 오류: 서버가 임시 파일을 저장하지 못했습니다.",
        "api-error-publishfailed": "내부 오류: 서버가 임시 파일을 게시하지 못했습니다.",
        "api-error-stasherror": "파일을 안전한 곳으로 업로드 하는 동안 오류가 발생했습니다.",
        "api-error-stashzerolength": "서버는 파일을 저장하지 못했는데, 파일의 용량이 0이기 때문입니다.",
        "api-error-stashnotloggedin": "파일을 업로드하기 위해 로그인이 필요합니다.",
        "api-error-stashwrongowner": "저장된 임시 저장소에 존재하는 파일에 접근할 권한이 없습니다.",
+       "api-error-stashnosuchfilekey": "미공개 위치에 접근을 시도한 파일 키는 존재하지 않습니다.",
        "api-error-timeout": "서버가 제 시간 내에 응답하지 않았습니다.",
        "api-error-unclassified": "알 수 없는 오류가 발생했습니다.",
        "api-error-unknown-code": "알 수 없는 오류: \"$1\"",
        "api-error-unknownerror": "알 수 없는 오류: \"$1\"",
        "api-error-uploaddisabled": "이 위키에서 파일 올리기가 비활성화되어 있습니다.",
        "api-error-verification-error": "파일이 손상되었거나 잘못된 확장자를 사용하고 있습니다.",
+       "api-error-was-deleted": "이 이름으로 된 파일은 과거에 업로드된 이후 삭제된 적이 있습니다.",
        "duration-seconds": "$1{{PLURAL:$1|초}}",
        "duration-minutes": "$1{{PLURAL:$1|분}}",
        "duration-hours": "$1{{PLURAL:$1|시간}}",
        "expand_templates_generate_xml": "XML 구문 트리 보기",
        "expand_templates_generate_rawhtml": "원본 HTML 보이기",
        "expand_templates_preview": "미리 보기",
+       "expand_templates_preview_fail_html": "<em>{{SITENAME}}에서 순수 HTML 입력을 허용한 상태에서 세션 데이터가 분실되었습니다. 그러므로 자바스크립트 공격을 예방하기 위해 미리 보기는 숨겨져 있습니다.</em>\n\n<strong>적합하게 미리 보기를 시도했다면 다시 시도해 주십시오.</strong>\n그래도 되지 않으면 [[Special:UserLogout|로그아웃]]한 다음 다시 로그인하여 사용자의 브라우저가 이 사이트에서 쿠키를 허용하는지 확인해 주십시오.",
+       "expand_templates_preview_fail_html_anon": "<em>{{SITENAME}}에서 순수 HTML 입력을 허용한 상태에서 사용자는 로그인되어 있지 않습니다. 그러므로 자바스크립트 공격을 예방하기 위해 미리 보기는 숨겨져 있습니다.</em>\n\n<strong>적합하게 미리 보기를 시도했다면 [[Special:UserLogin|로그인]]한 다음 다시 시도해 주십시오.",
        "expand_templates_input_missing": "전개할 내용을 입력해야 합니다.",
        "pagelanguage": "문서 언어 바꾸기",
        "pagelang-name": "문서",
        "special-characters-group-ipa": "IPA 문자",
        "special-characters-group-symbols": "기호",
        "special-characters-group-greek": "그리스 문자",
+       "special-characters-group-greekextended": "그리스어 확장",
        "special-characters-group-cyrillic": "키릴 문자",
        "special-characters-group-arabic": "아랍 문자",
        "special-characters-group-arabicextended": "아랍어 확장",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "쿠키 기반 세션",
        "sessionprovider-nocookies": "브라우저의 쿠키 기능이 꺼져 있는지 확인하십시오. 쿠키 기능을 켠 다음 다시 시작해야 합니다.",
        "randomrootpage": "임의 루트 페이지",
-       "log-action-filter-block": "차단의 유형:"
+       "log-action-filter-block": "차단의 유형:",
+       "log-action-filter-contentmodel": "콘텐츠 모델 수정 분류:",
+       "log-action-filter-delete": "삭제 종류:",
+       "log-action-filter-import": "가져오기 종류:",
+       "log-action-filter-managetags": "태그 관리 동작 종류:",
+       "log-action-filter-move": "이동 종류:",
+       "log-action-filter-newusers": "계정 생성 종류:",
+       "log-action-filter-patrol": "점검 종류:",
+       "log-action-filter-protect": "보호 종류:",
+       "log-action-filter-rights": "권한 변경 종류",
+       "log-action-filter-suppress": "숨기기 종류",
+       "log-action-filter-upload": "업로드 종류:",
+       "log-action-filter-all": "모두",
+       "log-action-filter-block-block": "차단",
+       "log-action-filter-block-reblock": "차단 변경",
+       "log-action-filter-block-unblock": "차단 해제",
+       "log-action-filter-contentmodel-change": "콘텐츠 모델 변경",
+       "log-action-filter-contentmodel-new": "비표준 콘텐츠 모델 문서 생성",
+       "log-action-filter-delete-delete": "문서 삭제",
+       "log-action-filter-delete-restore": "문서 복구",
+       "log-action-filter-delete-event": "로그 삭제",
+       "log-action-filter-delete-revision": "판 삭제",
+       "log-action-filter-import-interwiki": "트랜스위키 가져오기",
+       "log-action-filter-import-upload": "XML 업로드를 통한 가져오기",
+       "log-action-filter-managetags-create": "태그 생성",
+       "log-action-filter-managetags-delete": "태그 삭제",
+       "log-action-filter-managetags-activate": "태그 활성화",
+       "log-action-filter-managetags-deactivate": "태그 비활성화",
+       "log-action-filter-move-move": "넘겨주기를 덮어쓰지 않고 이동",
+       "log-action-filter-move-move_redir": "넘겨주기를 덮어쓰며 이동",
+       "log-action-filter-newusers-create": "익명의 사용자에 의한 생성",
+       "log-action-filter-newusers-create2": "등록된 사용자에 의한 생성",
+       "log-action-filter-newusers-autocreate": "자동 생성",
+       "log-action-filter-newusers-byemail": "이메일로 보낸 비밀번호로 생성",
+       "log-action-filter-patrol-patrol": "수동 점검",
+       "log-action-filter-patrol-autopatrol": "자동 점검",
+       "log-action-filter-protect-protect": "보호",
+       "log-action-filter-protect-modify": "보호 변경",
+       "log-action-filter-protect-unprotect": "보호 해제",
+       "log-action-filter-protect-move_prot": "이동 보호",
+       "log-action-filter-rights-rights": "수동 변경",
+       "log-action-filter-rights-autopromote": "자동 변경",
+       "log-action-filter-suppress-event": "로그 숨기기",
+       "log-action-filter-suppress-revision": "판 숨기기",
+       "log-action-filter-suppress-delete": "문서 숨기기",
+       "log-action-filter-suppress-block": "차단을 통한 사용자 숨기기",
+       "log-action-filter-suppress-reblock": "재차단을 통한 사용자 숨기기",
+       "log-action-filter-upload-upload": "새로 업로드",
+       "log-action-filter-upload-overwrite": "다시 업로드",
+       "authmanager-authn-not-in-progress": "인증이 진행 중이 아니거나 세션 데이터를 분실했습니다. 처음부터 다시 시작해 주십시오.",
+       "authmanager-authplugin-setpass-failed-title": "비밀번호 변경 실패",
+       "authmanager-authplugin-setpass-failed-message": "인증 플러그인이 비밀번호 변경을 거부했습니다.",
+       "authmanager-authplugin-create-fail": "인증 플러그인이 계정 만들기를 거부했습니다.",
+       "authmanager-authplugin-setpass-denied": "인증 플러그인이 비밀번호 변경을 허용하지 않습니다.",
+       "authmanager-authplugin-setpass-bad-domain": "잘못된 도메인.",
+       "authmanager-autocreate-noperm": "자동 계정 만들기는 허용되지 않습니다.",
+       "authmanager-password-help": "인증을 위한 비밀번호",
+       "authmanager-domain-help": "외부 인증의 도메인",
+       "authmanager-email-label": "이메일",
+       "authmanager-email-help": "이메일 주소",
+       "authmanager-realname-label": "실명",
+       "authmanager-realname-help": "사용자의 실명",
+       "authmanager-provider-password": "비밀번호 기반 인증",
+       "authmanager-provider-temporarypassword": "임시 비밀번호",
+       "authform-wrongtoken": "잘못된 토큰",
+       "authpage-cannot-login": "로그인을 시작할 수 없습니다.",
+       "authpage-cannot-login-continue": "로그인을 계속할 수 없습니다. 사용자 세션의 시간이 초과되었을 가능성이 높습니다.",
+       "authpage-cannot-create": "계정 만들기를 시작할 수 없습니다.",
+       "authpage-cannot-create-continue": "계정 만들기를 계속할 수 없습니다. 사용자 세션의 시간이 초과되었을 가능성이 높습니다.",
+       "authpage-cannot-link": "계정 연결을 시작할 수 없습니다.",
+       "authpage-cannot-link-continue": "계정 연결을 계속할 수 없습니다. 사용자 세션의 시간이 초과되었을 가능성이 높습니다.",
+       "changecredentials-submit-cancel": "취소",
+       "removecredentials-submit": "제거",
+       "removecredentials-submit-cancel": "취소"
 }
index 432a3ab..3a4caa7 100644 (file)
        "noname": "Терс атны джазгъансыз.",
        "loginsuccesstitle": "Авторизация тыйыншлы ётдю",
        "loginsuccess": "'''Энди {{SITENAME}} сайтха «$1» ат бла кирдигиз.'''",
-       "nosuchuser": "$1 аты бла къошулуучу джокъду.\nКъошулуучу атла харифни регистрин (уллу-гитчеликлерин) айырады.\nАтны тюз джазылгъанына къарагъыз неда [[Special:UserLogin/signup|джангы тергеу джазыу (аккаунт) къурагъаз]].",
+       "nosuchuser": "$1 аты бла къошулуучу джокъду.\nКъошулуучу атла харифни регистрин (уллу-гитчеликлерин) айырады.\nАтны тюз джазылгъанына къарагъыз неда [[Special:CreateAccount|джангы тергеу джазыу (аккаунт) къурагъаз]].",
        "nosuchusershort": "$1 аты бла къшулуучу джокъду. Атны тюз джазылгъанына къарагъыз.",
        "nouserspecified": "Сиз къошулуучу атыгъызны джазаргъа керексиз.",
        "login-userblocked": "Бу къошулуучу блокга салыннганды. Кирирге мадары джокъду.",
        "accmailtext": "[[User talk:$1|$1]] къошулуучугъа къуралгъан пароль $2 адресине джиберилгенди.\n\nРегистрацияны тындыргъындан сора,  ''[[Special:ChangePassword|паролну тюрлендирирге]]'' боллукъсуз.",
        "newarticle": "(Джангы)",
        "newarticletext": "Сиз джибериу бла алкъын къуралмагъан бетге кёчгенсиз.\nАны къурар ючюн тюбюндеги терезеде статьяны текстин басмалагъыз (толуракъ [$1 ангылатыу бетде] къарагъыз).\nДжангылыб кирген эсегиз а уа бери, браузеригизни '''артха''' тиегин басыгъызда къоюгъуз.",
-       "anontalkpagetext": "----''Бу сюзюу бет, тергеу джазыу (аккаунт) къурамагъан неда аны бла хайырланмагъан аноним къошулуучунукъуду.\nАны ючюн идентификация этерге IP-адрес хайырланады.\nТалай башха къошулуучуланы да болургъа боллукъ быллай адреслери.\nСиз аноним къошулуучу эсегиз эмда сизге джиберилмеген билдириуле алама деб суна эсегиз, ангылашылмаула болмаз ючюн [[Special:UserLogin|тергеу джазыу (аккаунт) къурагъыз]] неда [[Special:UserLogin/signup|системагъа киригиз]].''",
+       "anontalkpagetext": "----''Бу сюзюу бет, тергеу джазыу (аккаунт) къурамагъан неда аны бла хайырланмагъан аноним къошулуучунукъуду.\nАны ючюн идентификация этерге IP-адрес хайырланады.\nТалай башха къошулуучуланы да болургъа боллукъ быллай адреслери.\nСиз аноним къошулуучу эсегиз эмда сизге джиберилмеген билдириуле алама деб суна эсегиз, ангылашылмаула болмаз ючюн [[Special:UserLogin|тергеу джазыу (аккаунт) къурагъыз]] неда [[Special:CreateAccount|системагъа киригиз]].''",
        "noarticletext": "Бусагъатда бу бетде текст джокъду.\nСиз [[Special:Search/{{PAGENAME}}|бу атны башха статьялада]] излерге , <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} журналлагъа къараргъа], неда '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} быллай атлы джангы бет къураргъа боллукъсуз]'''</span>.",
        "noarticletext-nopermission": "Бусагъатда бу бетде текст джокъду.\nСиз [[Special:Search/{{PAGENAME}}|бу атны таныгъан]] башха статьяланы, неда <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} журналлада джазылгъанланы]</span> табаргъа боллукъсуз, алай а бу бетни къураргъа эркинлигигиз джокъду.",
        "userpage-userdoesnotexist": "«<nowiki>$1</nowiki>» тергеу джазыу (аккаунт) джокъду. Къураргъа/тюрлендирирге излеймисиз бу бетни?",
index 4f98138..0a3f3a3 100644 (file)
@@ -52,7 +52,7 @@
        "tog-ccmeonemails": "Scheck mer en Kopie, wann ich en <i lang=\"en\">e-mail</i> an ene andere Metmaacher scheck",
        "tog-diffonly": "Zeich beim Versione Verjliche nur de Ungerscheide aan (ävver pack nit noch de janze Sigg dodronger)",
        "tog-showhiddencats": "Donn de verschtoche Saachjroppe aanzeije",
-       "tog-norollbackdiff": "Donn noh „{{int:Rollback}}“ de Ungerscheide nit aanzeije",
+       "tog-norollbackdiff": "Donn noh „{{int:Rollback}}“ de Ongerscheide nit aanzeije",
        "tog-useeditwarning": "Donn mesch warne, wann esch vun en Sigg fott jonn, ih dat esch all ming Änderunge avjeschpeischert hann.",
        "tog-prefershttps": "Jangk emmer övver en verschlößelte Verbendong bei Enlogge",
        "underline-always": "jo, ongershtriishe",
        "noname": "Dat jeiht nit als ene Metmaacher Name. Jetz muss De et noch ens versöke.",
        "loginsuccesstitle": "Enjelogg",
        "loginsuccess": "'''Do bes jetz enjelogg {{GRAMMAR:en|{{SITENAME}}}}, un Dinge Name als ene Metmaacher es „$1“.'''",
-       "nosuchuser": "Dä Metmaacher Name „$1“ wor verkihrt.\nJroß- un Kleinboochshtabe maache ene Ungerscheid!\n<br />\nJetz muss De et noch ens versöke.\nUdder donn_[[Special:UserLogin/signup|ene neue Metmaacher aanmelde]].",
+       "nosuchuser": "Dä Metmaacher Name „$1“ wor verkihrt.\nJroß- un Kleinboochshtabe maache ene Ungerscheid!\n<br />\nJetz muss De et noch ens versöke.\nUdder donn_[[Special:CreateAccount|ene neue Metmaacher aanmelde]].",
        "nosuchusershort": "Dä Metmaacher Name „$1“ wor verkihrt. Jetz muss De et noch ens versöke.",
        "nouserspecified": "Dat jeiht nit als ene Metmaacher Name",
        "login-userblocked": "Heh dä Kääl es jesperrt. Enlogge verbodde.",
        "accmailtext": "En automattesch un zofällesch neu ußjewörfelt Passwood för dä\nMetmaacher „[[User talk:$1|$1]]“ es noh „$2“ jescheck woode.\n\nDat Passwoot för dä neue Zojang kanns De op dä {{int:Specialpage}} zom\n„[[Special:ChangePassword|{{int:resetpass}}]]“ ändere,\nwann De wider enjelogg bes.",
        "newarticle": "(Neu)",
        "newarticletext": "Ene Link op en Sigg, wo noch nix drop steiht, weil et se noch jar nit jitt, hät Dich noh heh jebraht.\nÖm di Sigg aanzelähje, schriev heh unge en dat Feld eren, un dun dat dann avspeichere.\nLuur op de [$1 Sigge met Hölp] noh, wann De mih doh drövver weßße wells.\nWann De jar nit heh hen kumme wollts, dann jangk zeröck op di Sigg, wo De herjekumme bes, Dinge Brauser hät ene Knopp doför.",
-       "anontalkpagetext": "----\n<i>Dat heh es de Klaaf Sigg för ene namenlose Metmaacher. Dä hät sich noch keine Metmaacher Name jejovve un\nenjerich, ov deit keine bruche. Dröm bruche mer sing IP Adress öm It oder In en uns Lisste fasszehalde.\nSu en IP Adress kann vun janz vill Metmaacher jebruch wääde, un eine Metmaacher kann janz flöck\nzwesche de ungerscheidlichste IP Adresse wähßele, womöchlich ohne dat hä et merk. Wann Do jetz ene namenlose\nMetmaacher bes, un fings, dat heh Saache an Dich jeschrevve wääde, wo Do jar nix met am Hot häs, dann bes Do\nwahrscheinlich och nit jemeint. Denk villeich ens drüvver noh, datte Dich [[Special:UserLogin/signup|anmelde]] deis,\ndomet De dann donoh nit mieh met esu en Ömständ ze dun häs, wie de andere namenlose Metmaacher heh. Wann de aanjemelldt bes un deis [[Special:UserLogin|enlogge]], dann kam_mer Desch och fun alle andere Metmaacher ongerschejde.</i>",
+       "anontalkpagetext": "----\n<i>Dat heh es de Klaaf Sigg för ene namenlose Metmaacher. Dä hät sich noch keine Metmaacher Name jejovve un\nenjerich, ov deit keine bruche. Dröm bruche mer sing IP Adress öm It oder In en uns Lisste fasszehalde.\nSu en IP Adress kann vun janz vill Metmaacher jebruch wääde, un eine Metmaacher kann janz flöck\nzwesche de ungerscheidlichste IP Adresse wähßele, womöchlich ohne dat hä et merk. Wann Do jetz ene namenlose\nMetmaacher bes, un fings, dat heh Saache an Dich jeschrevve wääde, wo Do jar nix met am Hot häs, dann bes Do\nwahrscheinlich och nit jemeint. Denk villeich ens drüvver noh, datte Dich [[Special:CreateAccount|anmelde]] deis,\ndomet De dann donoh nit mieh met esu en Ömständ ze dun häs, wie de andere namenlose Metmaacher heh. Wann de aanjemelldt bes un deis [[Special:UserLogin|enlogge]], dann kam_mer Desch och fun alle andere Metmaacher ongerschejde.</i>",
        "noarticletext": "<span class=\"plainlinks\">Em Momang es keine Täx op heh dä Sigg. Jangk en de Täxte vun ander Sigge [[Special:Search/{{PAGENAME}}|noh däm Titel söhke]], udder [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} donn en de Logböscher donoh loore], udder [{{FULLURL:{{FULLPAGENAME}}|action=edit}} fang di Sigg aan] ze schrieve, udder jangk zeröck woh De heer kohms. Do hät Dinge Brauser ene Knopp för.</span>",
        "noarticletext-nopermission": "Op dä Sigg es em Momang nix drop.\nDo kanns noh däm Tittel vun heh dä Sigg [[Special:Search/{{PAGENAME}}|em Tex op ander Sigge söhke]],\nudder en dä zopaß <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Logbööscher nohloore]</span>.",
        "missing-revision": "En Version $1 vun dä Sigg „{{FULLPAGENAME}}“ jidd_et nit.\n\nEsu jät kütt för jewöhnlesch, wam_mer enem övverhollte Lengk ob en Sigg follesch, di zweschedren fottjeschmeße woode es.\nMih doh drövver fengk mer em [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Logbooch vum Sigge Fottschmiiße].",
        "upload-form-label-infoform-description": "Äkliehrong",
        "upload-form-label-usage-title": "Der Jebruch",
        "upload-form-label-usage-filename": "Dä Dattei iehre Nahme",
-       "foreign-structured-upload-form-label-own-work": "dat es ming eije Wärk",
-       "foreign-structured-upload-form-label-infoform-categories": "Saachjroppe",
-       "foreign-structured-upload-form-label-infoform-date": "Dattum",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Wann De di Dattei nit en de jemeinsamme Sammlong vun Datteule huh lahde kanns un derbei de Rähjelle {{ucfirst:{{GRAMMAR:vun|{{ucfirst:{{SITENAME}}}}}}}} ennhalde, dann maach heh nit wigger, un probehr ene anndere Wähsch.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Do künnts edd och ens met dä [[Special:Upload|Schtandatt-Sigg zom Huhlahde]] versöhke welle.",
-       "foreign-structured-upload-form-label-own-work-message-default": "Esch verschtonn, dadd esch en en jemeinsamme Sammlong huh aam lahde ben un dadd sesch dat met dä Bedengonge un de Lezänzbedengonge heh verdräht.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Wann De di Dattei nit en de jemeinsamme Sammlong vun Datteule huh lahde kanns un derbei de Rähjelle ennhalde, dann maach heh nit wigger, un probehr ene anndere Wähsch.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Wann dat noh dä Rähjelle doh jeiht, kanns De och probehre, [[Special:Upload|di Dattei {{GRAMMAR:em|{{ucfirst:{{SITENAME}}}}}} huhzelahde]]",
+       "upload-form-label-own-work": "Dat es ming eije Wärk",
+       "upload-form-label-infoform-categories": "Saachjroppe",
+       "upload-form-label-infoform-date": "Dattum",
+       "upload-form-label-not-own-work-message-generic-local": "Wann De di Dattei nit en de jemeinsamme Sammlong vun Datteule huh lahde kanns un derbei och de Rähjelle {{GRAMMAR:vun|{{ucfirst:{{SITENAME}}}}}} ennhalde, dann maach heh nit wigger, un probehr ene anndere Wähsch.",
+       "upload-form-label-not-own-work-local-generic-local": "Do künnts edd och ens met dä [[Special:Upload|Schtandatt-Sigg zom Huhlahde]] versöhke welle.",
+       "upload-form-label-own-work-message-generic-foreign": "Esch verschtonn, dadd esch en en jemeinsamme Sammlong huh aam lahde ben un dadd sesch dat met dä Bedengonge un de Lezänzbedengonge heh verdräht.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Wann De di Dattei nit en de jemeinsamme Sammlong vun Datteije huh lahde kanns un derbei de Rähjelle ennhalde, dann maach heh nit wigger, un probehr ene anndere Wähsch.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Wann dat noh dä Rähjelle doh jeiht, kanns De och probehre, [[Special:Upload|di Dattei {{GRAMMAR:em|{{ucfirst:{{SITENAME}}}}}} huhzelahde]]",
        "backend-fail-stream": "Mer kunnte di Dattei $1 nit övverdraare.",
        "backend-fail-backup": "Mer kunnte kein Sescherongskopih vun dä Dattei $1 maache.",
        "backend-fail-notexists": "En Dattei $1 jidd et nit.",
        "whatlinkshere-prev": "de vörijje {{PLURAL:$1||$1|noll}} zeije",
        "whatlinkshere-next": "de nächste {{PLURAL:$1||$1|noll}} zeije",
        "whatlinkshere-links": "← Links",
-       "whatlinkshere-hideredirs": "de Ömleijdonge $1",
-       "whatlinkshere-hidetrans": "de Oproofe $1",
-       "whatlinkshere-hidelinks": "de nommahle Lengks $1",
-       "whatlinkshere-hideimages": "$1 de Lengks op Datteihje",
+       "whatlinkshere-hideredirs": "De Ömleijdonge verschteijsche",
+       "whatlinkshere-hidetrans": "De Oproofe verschteijsche",
+       "whatlinkshere-hidelinks": "De nommahle Lengks verschteijsche",
+       "whatlinkshere-hideimages": "De Lengks op Datteihje verscheijsche",
        "whatlinkshere-filters": "Ußsööke",
        "whatlinkshere-submit": "Lohß jonn!",
        "autoblockid": "Automattesche Sperr Nommer $1",
        "searchsuggest-containing": "dren änthallde…",
        "api-error-badaccess-groups": "Do häs nit et Rääsch, Datteije en heh dat Wiki huhzelaade.",
        "api-error-badtoken": "Fähler: et Kännzeijsche (<i lang=\"en\">token</i>) es kappott.",
+       "api-error-blocked": "Do bes föh et Änndere jeschpächt.",
        "api-error-copyuploaddisabled": "Et Huhlaade vun enem <i lang=\"en\">URL</i> es op däm ẞööver heh nit zohjelohße.",
        "api-error-duplicate": "Mer han em Wiki ald {{PLURAL:$1|[en Dattei]|[$1 andere Datteije]|[kein Dattei]}} mem akeraat sellve Enhalldt.",
        "api-error-duplicate-archive": "Mer hatte {{PLURAL:$1|[en ander Dattei]|[ander Datteije]|[kein ander Dattei]}} heh em Wiki mem sellve Enhalt, ävver se {{PLURAL:$1|es|sen|es}} ald fottjeschmeße woode.",
index 60e5995..f03072b 100644 (file)
@@ -24,6 +24,7 @@
        "tog-hideminor": "Guherandinên biçûk ji listêya guherandinên dawî veşêre",
        "tog-hidepatrolled": "Guherandinên hatine kontrolkirin ji nav guherandinên dawî veşêre",
        "tog-newpageshidepatrolled": "Rûpelên hatine kontrolkirin ji lîsteya rûpelên nû veşêre",
+       "tog-hidecategorization": "Kategorîzekirina rûpelan veşêre",
        "tog-extendwatchlist": "Lîsteya şopandinê berfireh bike ji bo dîtina hemû guherandinan, ne tenê yên nû",
        "tog-usenewrc": "Weşandina zêdetir (JavaScript pêwîst e)",
        "tog-numberheadings": "Sernavan otomatîk bihejmêre",
@@ -51,6 +52,7 @@
        "tog-watchlisthideminor": "Guhertinên biçûk ji lîsteya şopandinê veşêre",
        "tog-watchlisthideliu": "Guherandinên bikarhênerên qeydkirî ji lîsteya şopandinê veşêre",
        "tog-watchlisthideanons": "Guherandinên bikarhênerên neqeydkirî ji lîsteya şopandinê veşêre",
+       "tog-watchlisthidecategorization": "Kategorîzekirina rûpelan veşêre",
        "tog-ccmeonemails": "Kopiyên e-nameyên min ji bikarhênerên din re şandî, ji min re jî bişîne.",
        "tog-diffonly": "Li cem guhertinan, naveroka rûpelê nîşan nede",
        "tog-showhiddencats": "Kategoriyên veşartî bibîne",
        "actionthrottled": "Hejmara guherandinên hatine hesibandin",
        "actionthrottledtext": "Te ev tişt di demeke gelekî kin de kir. Ji kerema xwe çend xulekan bisekine û carekî din biceribîne.",
        "protectedpagetext": "Ev rûpel ji bo guhertin û karên din ne kirin hatiye parastin.",
-       "viewsourcetext": "Tu dikarî li çavkaniya vê rûpelê binêrî û wê kopî bikî:",
+       "viewsourcetext": "Tu dikarî li çavkaniya vê rûpelê binêrî û wê kopî bikî.",
        "viewyourtext": "Hûn çavkaniyê <strong>guhertinê xwe</strong> yê di vê rûpelê de dikarin bibînin û kopî bikin:",
        "protectedinterface": "Di vê rûpelê de nivîsandin ji bo navrû(interface)yî zimanan yê vê nivîsbariyê ye û ew tê parastin ku vandalîzm li vê derê çênebe.\nBo lêzêdekirin an jî guherandina wergerên bo hemû wîkiyan ji kerema xwe re mehelîkirina Mediawîkiyê [//translatewiki.net/ translatewiki.net]'ê bi kar bîne.",
        "editinginterface": "'''Hişyarî:''' Tu rûpelekê a ku di Wîkîpediya de ji bo sîstemê girîng e,  diguherînî. Guherandinên di vê rûpelê de wê ji aliyê hemû bikarhêneran ve werin dîtin. Ji bo wergerê ji kerema xwe di [//translatewiki.net/wiki/Main_Page?setlang=ku-latn translatewiki.net] de bixebite, projeya MediaWiki.",
        "newpassword": "Şîfreya nû",
        "retypenew": "Şîfreya nû careke din binîvîse",
        "resetpass_submit": "Şîfreyê pêkbîne û têkeve",
-       "changepassword-success": "Guhertine şîfreya te serkeftî bû!",
+       "changepassword-success": "Şîfreya te hate guhertandin!",
+       "botpasswords-label-appid": "Navê bot:",
+       "botpasswords-label-create": "Çêke",
        "botpasswords-label-update": "Rojane bike",
        "botpasswords-label-cancel": "Betal bike",
+       "botpasswords-label-delete": "Jê bibe",
+       "botpasswords-bad-appid": "Navê bot \"$1\" ne derbasdar e.",
        "resetpass_forbidden": "Şîfre nikarin werin guhertin",
        "resetpass-submit-loggedin": "Şîfreyê biguherîne",
        "resetpass-submit-cancel": "Betal bike",
        "accmailtext": "Şîfreyekê ketober ê ji bo [[User talk:$1|$1]] hatiye çêkirin ji navnîşana $2 re hat şandin. Şîfreya ji bo vê hesabê nû, piştî ko te têket ji beşa <em>[[Special:ChangePassword|şîfreyê biguherîne]]</em> dikare were guhertin.",
        "newarticle": "(Nû)",
        "newarticletext": "Ev rûpel hîn tune. Eger tu bixwazî vê rûpelê çêkî, dest bi nivîsandinê bike û piştre qeyd bike. '''Wêrek be''', biceribîne!<br />\nJi bo alîkariyê binêre: [$1 Alîkarî].<br />\nHeke tu bi şaşîtî hatî, bizîvire rûpela berê.",
-       "anontalkpagetext": "----''Ev rûpela gotûbêjê ye ji bo bikarhênerên nediyarkirî ku hîn hesabekî xwe çênekirine an jî bikarnaînin. Ji ber vê yekê divê em wan bi navnîşana IP ya hejmarî nîşan bikin. Navnîşaneke IP dikare ji aliyê gelek kesan ve were bikaranîn. Heger tu bikarhênerekî nediyarkirî bî û bawerdikî ku nirxandinên bê peywend di der barê te de hatine kirin ji kerema xwe re [[Special:UserLogin/signup|hesabekî xwe veke an jî têkeve]] da ku tu xwe ji tevlîheviyên bi bikarhênerên din re biparêzî.''",
+       "anontalkpagetext": "----''Ev rûpela gotûbêjê ye ji bo bikarhênerên nediyarkirî ku hîn hesabekî xwe çênekirine an jî bikarnaînin. Ji ber vê yekê divê em wan bi navnîşana IP ya hejmarî nîşan bikin. Navnîşaneke IP dikare ji aliyê gelek kesan ve were bikaranîn. Heger tu bikarhênerekî nediyarkirî bî û bawerdikî ku nirxandinên bê peywend di der barê te de hatine kirin ji kerema xwe re [[Special:CreateAccount|hesabekî xwe veke an jî têkeve]] da ku tu xwe ji tevlîheviyên bi bikarhênerên din re biparêzî.''",
        "noarticletext": "Ev rûpel niha vala ye, tu dikarî [[Special:Search/{{PAGENAME}}|Di nav gotarên din de li \"{{PAGENAME}}\" bigerî]] an jî [{{fullurl:{{FULLPAGENAME}}|action=edit}} vê rûpelê biguherînî].",
        "noarticletext-nopermission": "Ev rûpel niha vala ye. \nTu dikarî [[Special:Search/{{PAGENAME}}|Di nav gotarên din de li \"{{PAGENAME}}\" bigere]] \nan <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} vê rûpelê biguherînî]</span>, lê ji bo çêkirina vê rûpelê desthilatdariya te tine ye.",
        "userpage-userdoesnotexist": "Hesabê bikarhêneran \"<nowiki>$1</nowiki>\" nehatiye qeydkirin. Heke tu bixwazî vê rûpelê çêkî/biguherînî ji kerema xwe lê binêre.",
        "editingold": "'''Hişyarî: Tu li ser guhertoyeke kevn a vê rûpelê dixebitî.\nHeke tu qeyd bikî, hemû guhertinên piştî vê revîzyonê winda dibin.\n'''",
        "yourdiff": "Cudahî",
        "copyrightwarning": "Hemû tevkariyên {{SITENAME}} di bin $2 de tên belav kirin (ji bo hûragahiyan li $1 binêre).\nEger tu nexwazî ku nivîsên te bê dilrehmî bên guherandin û li gora keyfa herkesî bên belavkirin, li vir neweşîne.<br />\nTu soz didî ku te ev bi xwe nivîsand an jî ji çavkaniyekê azad an geliyane (''public domain'') girt.\n'''Berhemên mafên wan parastî bê destûr neweşîne!'''",
-       "protectedpagewarning": "'''Hişyarî:  Ev rûpel tê parastin. Bi tenê bikarhênerên ku xwediyên mafên \"koordînatoriyê\" ne, dikarin vê rûpelê biguherînin.'''",
+       "protectedpagewarning": "<strong>Hişyarî: Ev rûpel tê parastin. Tenê bikarhênerên ku xwediyê mafên rêveberiyê ne, dikarin vê rûpelê biguherînin.</strong>",
        "semiprotectedpagewarning": "'''Hişyarî:''' Ev rûpel tê parastin, lewma tenê bikarhênerên tomarkirî dikarin vê biguherînin.\nGuhertina herî dawî bi referansa li jêr hatiye piştrastkirin:",
        "templatesused": "{{PLURAL:$1|Şablona|Şablonên}} ku li ser vê rûpelê {{PLURAL:$1|tê|tên}} bikaranîn:",
        "templatesusedpreview": "{{PLURAL:$1|Şablona|Şablonên}} di vê pêşdîtinê de {{PLURAL:$1|tê|tên}} bikaranîn:",
        "postedit-confirmation-restored": "Ev rûpel hate restorekirin.",
        "postedit-confirmation-saved": "Guhertina te hate tomarkirin.",
        "edit-already-exists": "Nikarî rûpeleka nuh çêke.\nEw berê heye.",
+       "invalid-content-data": "Daneyên naverokê yên nederbasdar",
        "content-model-wikitext": "wîkînivîs",
        "content-model-text": "nivîsê sade",
        "content-model-javascript": "JavaScript",
        "right-userrights": "Hemû mafên bikarhêner biguherîne",
        "right-userrights-interwiki": "Mafên bikarhênerên li ser wîkiyên din biguherîne",
        "right-sendemail": "Ji bikarhênerên di re ename bişîne",
+       "grant-editpage": "Rûpelên ku hene biguherîne",
+       "grant-editprotected": "Rûpelên parastî bigûherîne",
+       "grant-basic": "Mafên bingehîn",
        "newuserlogpage": "Çêkirina hesabê nû",
        "newuserlogpagetext": "Ev têketina hesabên bikarhêneriyê ye ên ku nû hatine afirandin.",
        "rightslog": "Guhertina mafê bikarhêneriyê",
        "rcshowhidemine": "Guherandinên min $1",
        "rcshowhidemine-show": "nîşan bide",
        "rcshowhidemine-hide": "veşêre",
-       "rcshowhidecategorization-show": "Nîşan bide",
-       "rcshowhidecategorization-hide": "Veşêre",
+       "rcshowhidecategorization": "Kategorîzekirina rûpelan $1",
+       "rcshowhidecategorization-show": "nîşan bide",
+       "rcshowhidecategorization-hide": "veşêre",
        "rclinks": "$1 guherandinên di $2 rojên dawî de nîşan bide<br />$3",
        "diff": "cudahî",
        "hist": "dîrok",
        "filewasdeleted": "Data'yek bi vê navê hatibû barkirin û jêbirin. Xêra xwe li $1 seke ku barkirina te hêja ye ya na.",
        "filename-bad-prefix": "Nava wê data'yê, yê tu niha bardikê, bi '''\"$1\"''' destpêdike. Kamêrayên dîjîtal wan navan didin wêneyên xwe. Ji kerema xwe navekî baştir binivisîne ji bo mirov zûtir zanibin ku şayeşê vê wêneyê çî ye.",
        "upload-file-error": "Çewtiya navxweyî",
+       "upload-misc-error": "Çewtiya barkirinê ya nenas",
+       "upload-dialog-title": "Dosyeyê bar bike",
        "upload-dialog-button-cancel": "Betal bike",
        "upload-dialog-button-done": "Çêbû",
        "upload-dialog-button-save": "Tomar bike",
        "upload-form-label-infoform-description": "Danasîn",
        "upload-form-label-usage-title": "Bikaranîn",
        "upload-form-label-usage-filename": "Navê dosyeyê",
-       "foreign-structured-upload-form-label-own-work": "Min ev xebat bi xwe çêkiriye",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorî",
-       "foreign-structured-upload-form-label-infoform-date": "Dîrok",
+       "upload-form-label-own-work": "Min ev xebat bi xwe çêkiriye",
+       "upload-form-label-infoform-categories": "Kategorî",
+       "upload-form-label-infoform-date": "Dîrok",
        "backend-fail-notexists": "Dosye $1 tune ye.",
        "backend-fail-delete": "Dosyeya \"$1\" nikaribû bê jêbirin.",
        "backend-fail-store": "Dosyeya \"$1\" di bin \"$2\" nikaribû bê tomarkirin.",
        "mostcategories": "Rûpelên bi pir kategorî",
        "mostinterwikis": "Rûpelên bi bêhtirîn girêdanên înterwîkiyê",
        "prefixindex": "Hemû rûpelên bi pêşbendik",
+       "prefixindex-submit": "Nîşan bide",
        "shortpages": "Rûpelên kurt",
        "longpages": "Rûpelên dirêj",
        "deadendpages": "Rûpelên bê dergeh",
        "listusers-editsonly": "Tenê bikarhênerên bi guherrandinan nîşan bide",
        "usercreated": "di $1 de, li $2 hate çêkirin",
        "newpages": "Rûpelên nû",
+       "newpages-submit": "Nîşan bide",
        "newpages-username": "Navê bikarhêner:",
        "ancientpages": "Gotarên herî kevin",
        "move": "Hilgire",
        "specialloguserlabel": "Bikarhêner:",
        "speciallogtitlelabel": "Armanc (sernav an bikarhêner)",
        "log": "Têketin",
+       "logeventslist-submit": "Nîşan bide",
        "all-logs-page": "Hemû têketin",
        "alllogstext": "Hemû têketinên {{SITENAME}} li jêr tên nîşandan.\nTu dikarî ji xwe re têketinekê hilbijêrî, navê bikarhêneriyê an navê rûpelekê binivîse û agahiyan li ser wê bibîne.",
        "logempty": "Tiştek di vir de nîne.",
        "log-title-wildcard": "Li sernavên bi vê dest pê dikin bigere",
+       "checkbox-select": "Hilbijêre:$1",
+       "checkbox-all": "Hemû",
        "allpages": "Hemû rûpel",
        "nextpage": "Rûpela pêşî ($1)",
        "prevpage": "Rûpelê berî vê ($1)",
        "allpages-bad-ns": "Namespace'a \"$1\" di {{SITENAME}} da tune ye.",
        "allpages-hide-redirects": "Beralîkirinan veşêre",
        "categories": "Kategorî",
+       "categories-submit": "Nîşan bide",
        "categoriespagetext": "Di van kategoriyan de rûpel an jî medya hene.\n[[Special:UnusedCategories|Kategoriyên nayên bikaranîn]] li vir nayên nîşandan.\nLi [[Special:WantedCategories|kategoriyên xwestî]] binêre.",
        "deletedcontributions": "Beşdariyên bikarhênerekî yê jêbirî",
        "deletedcontributions-title": "Guherandinên bikarhêner yê jêbirî",
        "listusers-noresult": "Bikarhêner nehate dîtin.",
        "listusers-blocked": "(hate astengkirin)",
        "activeusers": "Lîsteya bikarhênerên çalak",
+       "activeusers-from": "Li bikarhênerên bi vê dest pê dikin bigere:",
        "activeusers-hidebots": "Bot'an veşêre",
        "activeusers-hidesysops": "Rêveberan veşêre",
        "activeusers-noresult": "Tu bikarhêner nehate dîtin.",
        "listgrouprights-members": "(lîsteya endaman)",
        "listgrouprights-addgroup-all": "Hemû koman tevlî bike",
        "listgrouprights-removegroup-all": "Hemû koman jê bibe",
+       "listgrants-rights": "Maf",
        "trackingcategories-name": "Navê peyamê",
        "trackingcategories-nodesc": "Ti danasîn tune ye.",
        "mailnologin": "Navnîşanê neşîne",
        "wlshowlast": "Guhertinên berî $1 saetan, $2 rojan, ya  nîşan bide",
        "watchlist-hide": "Veşêre",
        "watchlist-submit": "Nîşan bide",
+       "wlshowhidebots": "bot",
        "wlshowhideliu": "bikarhênerên tomarkirî",
+       "wlshowhideanons": "bikarhênerên bênav",
        "wlshowhidecategorization": "kategorîzekirina rûpelan",
        "watchlist-options": "Vebijarkên lîsteya şopandinê",
        "watching": "Tê şopandin...",
        "whatlinkshere-prev": "{{PLURAL:$1|yê|$1 yên}} berê",
        "whatlinkshere-next": "{{PLURAL:$1|yê|$1 yên}} din",
        "whatlinkshere-links": "← girêdan",
-       "whatlinkshere-hideredirs": "Beralîkirinan $1",
+       "whatlinkshere-hideredirs": "Beralîkirinan veşêre",
        "whatlinkshere-hidetrans": "Naverokan $1",
-       "whatlinkshere-hidelinks": "Girêdanan $1",
-       "whatlinkshere-hideimages": "Girêdanên wêneyan $1",
+       "whatlinkshere-hidelinks": "Girêdanan veşêre",
+       "whatlinkshere-hideimages": "Girêdanên wêneyan veşêre",
        "whatlinkshere-filters": "Parzûn",
        "block": "Bikarhêner asteng bike",
        "unblock": "Astengkirinê rake",
        "tooltip-feed-rss": "RSS feed'ên ji bo rûpelê",
        "tooltip-feed-atom": "Atom feed'ên ji bo vê rûpelê",
        "tooltip-t-contributions": "Lîsteyekî beşdariyên {{GENDER:$1|vê bikarhênerê}} bibîne",
-       "tooltip-t-emailuser": "Jê re name bişîne",
+       "tooltip-t-emailuser": "Jê {{GENDER:$1|vî bikarhênerî}}re peyamê bişîne",
        "tooltip-t-info": "Bêhtir agahî di derbarê vê rûpelê de",
        "tooltip-t-upload": "Dosyeyan bar bike",
        "tooltip-t-specialpages": "Lîsteya hemû rûpelên taybetî",
        "spamprotectiontitle": "Parastina spam",
        "spamprotectiontext": "Rûpela ku tu dixwazî tomar bikî ji ber parastina spamê hate astengkirin.\nJi ber ku girêdaneke derve di wê rûpelê de heye ev pirsgirêk pêk hat.",
        "spamprotectionmatch": "Ev nivîsa parastinê spam vêxist: $1",
+       "pageinfo-title": "Agahiyên bo \"$1\"",
        "pageinfo-header-basic": "Agahiyên sereke",
        "pageinfo-header-edits": "Dîrokê biguherîne",
        "pageinfo-header-restrictions": "Parastina rûpelê",
        "pageinfo-firsttime": "Dema çêkirina rûpelê",
        "pageinfo-lasttime": "Dema guherandina dawî",
        "pageinfo-edits": "Hejmara guherandinan",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|Kategoriya|Kategoriyên}} veşartî ($1)",
        "pageinfo-toolboxlink": "Agahiyên rûpelê",
        "pageinfo-redirectsto-info": "agahî",
        "pageinfo-contentpage-yes": "Erê",
        "exif-usercomment": "Şîroveyên bikarhêner",
        "exif-datetimedigitized": "Dema pencekîkirinê",
        "exif-exposuretime-format": "$1 sanî ($2)",
-       "exif-brightnessvalue": "Zelalî",
+       "exif-brightnessvalue": "Zelaliya APEX",
        "exif-flash": "Flaş",
        "exif-filesource": "Çavkaniya pelê",
        "exif-contrast": "Kontrast",
        "exif-disclaimer": "Ferexetname",
        "exif-unknowndate": "Dîroka nayê zanîn",
        "exif-orientation-1": "Normal",
+       "exif-componentsconfiguration-0": "tune ye",
        "exif-exposureprogram-1": "Manûel",
        "exif-exposureprogram-2": "Programa normal",
        "exif-subjectdistance-value": "$1 metre",
        "confirm-purge-top": "Bîra vê rûpelê jêbîbe ?",
        "confirm-watch-button": "Temam",
        "confirm-unwatch-button": "Baş e",
+       "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← rûpela berî vê",
        "imgmultipagenext": "rûpela din →",
        "imgmultigo": "Here!",
        "imgmultigoto": "Here rûpela $1",
+       "img-lang-default": "(zimanê standart)",
        "table_pager_next": "Rûpela pêş",
        "table_pager_prev": "Rûpela berî",
        "table_pager_first": "Rûpela pêşîn",
        "watchlistedit-clear-titles": "Sernav:",
        "watchlisttools-edit": "Lîsteya şopandinê bibîne û biguherîne",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|gotûbêj]])",
+       "timezone-local": "Herêmî",
        "version": "Versiyon",
        "version-specialpages": "Rûpelên taybet",
        "version-other": "Yên din",
        "version-software-product": "Berhem",
        "version-software-version": "Guherto",
        "version-entrypoints-header-url": "URL",
+       "version-libraries-version": "Guherto",
+       "version-libraries-license": "Destûr",
        "version-libraries-description": "Danasîn",
+       "version-libraries-authors": "Xwedî",
        "fileduplicatesearch-filename": "Navê dosyeyê:",
        "fileduplicatesearch-submit": "Lê bigere",
        "specialpages": "Rûpelên taybet",
        "tags-active-yes": "Erê",
        "tags-active-no": "Na",
        "tags-edit": "biguherîne",
+       "tags-delete": "jê bibe",
        "tags-create-reason": "Sedem:",
        "tags-delete-reason": "Sedem:",
        "tags-activate-reason": "Sedem:",
        "tags-deactivate-reason": "Sedem:",
+       "tags-edit-reason": "Sedem:",
        "comparepages": "Rûpelan bide ber hev",
        "compare-page1": "Rûpel 1",
        "compare-page2": "Rûpel 2",
        "htmlform-selectorother-other": "Yên din",
        "htmlform-no": "Na",
        "htmlform-yes": "Erê",
+       "htmlform-chosen-placeholder": "Vebijarkekê hilbijêre",
+       "htmlform-title-not-exists": "$1 tune ye.",
+       "htmlform-user-not-exists": "<strong>$1</strong> tune ye.",
+       "htmlform-user-not-valid": "<strong>$1</strong> ne navekî derbasdar e.",
        "logentry-delete-delete": "$1 rûpela $3 {{GENDER:$2|jê bir}}",
        "revdelete-content-hid": "naverok veşartî ye",
        "revdelete-uname-hid": "navê bikarhêneriyê yê veşartî",
        "feedback-error-title": "Çewtî",
        "feedback-message": "Peyam:",
        "feedback-subject": "Mijar:",
+       "feedback-submit": "Tomar bike",
        "feedback-thanks-title": "Spas!",
        "searchsuggest-search": "Lêgerîn",
        "searchsuggest-containing": "dihundirîne...",
        "api-error-filename-tooshort": "Navê dosyeyê pir kurt e.",
+       "api-error-unknown-code": "Çewtiya nenas: \"$1\".",
+       "api-error-unknownerror": "Çewtiya nenas: \"$1\".",
+       "duration-years": "$1 {{PLURAL:$1|sal}}",
        "expand_templates_output": "Encam",
        "expand_templates_ok": "Baş e",
        "expand_templates_preview": "Pêşdîtin",
        "pagelanguage": "Zimanê rûpelê biguherîne",
+       "pagelang-name": "Rûpel",
        "pagelang-language": "Ziman",
+       "pagelang-use-default": "Zimanê standart bi kar bîne",
        "pagelang-select-lang": "Zimanekî hilbijêre",
        "pagelang-submit": "Tomar bike",
        "right-pagelang": "Zimanê rûpelê biguherîne",
        "action-pagelang": "zimanê rûpelê biguherîne",
        "log-name-pagelang": "Têketina guherandina ziman",
+       "logentry-pagelang-pagelang": "$1 zimanê $3 ji $4 {{GENDER:$2|guherand}} $5",
        "mediastatistics-header-total": "Hemû dosye",
        "special-characters-group-latin": "Latînî",
        "special-characters-group-latinextended": "Latînî berfirehkirî",
        "special-characters-group-lao": "Lao",
        "special-characters-group-khmer": "Khmer",
        "mw-widgets-titleinput-description-new-page": "rûpel hê tune ye",
-       "mw-widgets-titleinput-description-redirect": "beralî bike ber bi $1 ve"
+       "mw-widgets-titleinput-description-redirect": "beralî bike ber bi $1 ve",
+       "log-action-filter-all": "Hemû",
+       "log-action-filter-block-block": "Asteng bike"
 }
index d67a8b7..7896792 100644 (file)
        "noname": "Сиз колдонуучунун анык атын көрсөткөн жоксуз.",
        "loginsuccesstitle": "Сиз ийгиликтүү кирдиңиз",
        "loginsuccess": "'''Сиз азыр {{SITENAME}} сайтына \"$1\" болуп кирдиңиз.'''",
-       "nosuchuser": "\"$1\" аттуу колдонуучу катталган эмес.\nКолдонуучун аты регистирди айырмалайт.\nКатасын текшериңиз же [[Special:UserLogin/signup|жаңы эсеп түзүү]]",
+       "nosuchuser": "\"$1\" аттуу колдонуучу катталган эмес.\nКолдонуучун аты регистирди айырмалайт.\nКатасын текшериңиз же [[Special:CreateAccount|жаңы эсеп түзүү]]",
        "nosuchusershort": "\"$1\" аттуу колдонуучу жок.\nЖазылышын текшериңиз.",
        "nouserspecified": "Сиз колдонуучу атын көрсөтүшүңүз керек.",
        "login-userblocked": "Бул колдонуучу бөгөттөлгөн. Системага кирүүгө уруксат жок.",
index f02128e..db42da9 100644 (file)
        "noname": "Nomen usoris ratum non designavisti.",
        "loginsuccesstitle": "Nomen feliciter datum est",
        "loginsuccess": "'''Apud {{grammar:accusative|{{SITENAME}}}} agnosceris nomine \"$1\".'''",
-       "nosuchuser": "Nomen \"$1\" ignoratur.\nIn nominibus interest litterarum aut maiuscularum aut minuscularum!\nQuarum cura rationem habeas aut [[Special:UserLogin/signup|novum nomen tibi da]].",
+       "nosuchuser": "Nomen \"$1\" ignoratur.\nIn nominibus interest litterarum aut maiuscularum aut minuscularum!\nQuarum cura rationem habeas aut [[Special:CreateAccount|novum nomen tibi da]].",
        "nosuchusershort": "Usor \"$1\" non est.\nConfirma orthographiam.",
        "nouserspecified": "Nomen usoris indicare debes.",
        "wrongpassword": "Tessera quam scripsisti non constat. Conare denuo.",
        "accmailtext": "Tessera nova usori [[User talk:$1|$1]] per inscriptionem $2 missa est. Nomine dato suademus ipsam tesseram ''[[Special:ChangePassword|mutare]]''.",
        "newarticle": "(Nova)",
        "newarticletext": "Per nexum progressus es ad paginam quae nondum exsistit.\nNovam paginam si vis creare, in capsam infra praebitam scribe.\n(Vide [$1 paginam auxilii] si plura cognoscere vis.)\nSi hic es propter errorem, solum '''Retrorsum''' in navigatro tuo preme.",
-       "anontalkpagetext": "----\n<em>Haec est pagina disputationis usoris anonymi vel potius loci IP cuiusdam.</em>\nMemento locos IP interdum mutari et ab usoribus vel pluribus adhiberi.\nSi ipse sis usor ignotus et ex improviso invenias querulas aliquas, nomen tibi [[Special:UserLogin/signup|impone]] vel [[Special:UserLogin|nomen tuum da]], ut decetero confusionem effugias!",
+       "anontalkpagetext": "----\n<em>Haec est pagina disputationis usoris anonymi vel potius loci IP cuiusdam.</em>\nMemento locos IP interdum mutari et ab usoribus vel pluribus adhiberi.\nSi ipse sis usor ignotus et ex improviso invenias querulas aliquas, nomen tibi [[Special:CreateAccount|impone]] vel [[Special:UserLogin|nomen tuum da]], ut decetero confusionem effugias!",
        "noarticletext": "Hac in pagina non sunt litterae.\nLicet [[Special:Search/{{PAGENAME}}|hanc rem in aliis paginis quaerere]] vel\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} acta huius paginae inspicere]\nvel [{{fullurl:{{FULLPAGENAME}}|action=edit}} hanc paginam creare]</span>.",
        "noarticletext-nopermission": "Hac in pagina non sunt litterae.\nPotes [[Special:Search/{{PAGENAME}}|hanc rem in aliis paginis quaerere]] aut <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} acta huius paginae videre], sed tibi non licet hanc paginam creare.",
        "userpage-userdoesnotexist": "Usor \"<nowiki>$1</nowiki>\" non est. Visne re vera hanc paginam creare vel recensere?",
index 9e1c92a..f6c6e64 100644 (file)
@@ -34,6 +34,7 @@
        "tog-watchdefault": "Säiten a Fichieren déi ech änneren op meng Iwwerwaachungslëscht derbäisetzen",
        "tog-watchmoves": "Säiten a Fichieren déi ech réckelen automatesch op meng Iwwerwaachungslëscht derbäisetzen",
        "tog-watchdeletion": "Säiten a Fichieren déi ech läschen op meng Iwwerwaachungslëscht derbäisetzen",
+       "tog-watchuploads": "Nei Fichieren déi ech eroplueden op meng Iwwerwaachungslëscht setzen",
        "tog-watchrollback": "Säiten déi ech zréckgesat hunn op meng Iwwerwaachungslëscht derbäisetzen",
        "tog-minordefault": "All Ännerungen automatesch als 'Kleng Ännerungen' markéieren.",
        "tog-previewontop": "Déi ''nach-net gespäichert Versioun'' iwwer der Ännerungsfënster weisen",
        "noname": "Dir hutt kee gëltege Benotzernumm uginn.",
        "loginsuccesstitle": "Ageloggt",
        "loginsuccess": "'''Dir sidd elo als \"$1\" op {{SITENAME}} ugemellt.'''",
-       "nosuchuser": "Et gëtt kee Benotzernumm mam Numm \"$1\".\nBeim Benotzernumm gëtt tëscht groussen a klenge Buschtawen ënnerscheet (casesensitive).\nKuckt w.e.g. op d'Schreifweis richteg ass, oder [[Special:UserLogin/signup|maacht en neie Benotzerkont op]].",
+       "nosuchuser": "Et gëtt kee Benotzernumm mam Numm \"$1\".\nBeim Benotzernumm gëtt tëscht groussen a klenge Buschtawen ënnerscheet (casesensitive).\nKuckt w.e.g. op d'Schreifweis richteg ass, oder [[Special:CreateAccount|maacht en neie Benotzerkont op]].",
        "nosuchusershort": "De Benotzernumm \"$1\" gëtt et net.\nKuckt w.e.g. op d'Schreifweis richteg ass.",
        "nouserspecified": "Gitt w.e.g. e Benotzernumm un.",
        "login-userblocked": "Dëse Benotzer ass gespaart. Aloggen ass net erlaabt.",
        "minoredit": "Dëst ass eng kleng Ännerung",
        "watchthis": "Dës Säit iwwerwaachen",
        "savearticle": "Säit späicheren",
+       "publishpage": "Säit publizéieren",
        "preview": "Kucken ouni ofzespäicheren",
        "showpreview": "Kucken ouni ofzespäicheren",
        "showdiff": "Ännerunge weisen",
        "accmailtext": "En zoufälleg generéiert Passwuert fir [[User talk:$1|$1]] gouf op $2 geschéckt.\n\nEt kann op der ''[[Special:ChangePassword|Passwuert ännere]]'' Säit beim Alogge geännert ginn.",
        "newarticle": "(Nei)",
        "newarticletext": "Dir hutt op e Link vun enger Säit geklickt, déi et nach net gëtt. Fir déi Säit unzeleeën, gitt w.e.g. Ären Text an déi Këscht hei drënner an (kuckt d'[$1 Hëllef Säit] fir méi Informatiounen). Wann Dir duerch een Iertum heihi komm sidd, da klickt einfach op de Knäppchen '''Zréck''' vun Ärem Browser.",
-       "anontalkpagetext": "---- ''Dëst ass d'Diskussiounssäit fir en anonyme Benotzer deen nach kee Kont opgemaach huet oder en net benotzt. Dowéinst musse mir d'IP Adress benotzen, fir de Benotzer z'identifizéieren.\nSou eng IP Adress ka vun e puer Benotzer gedeelt ginn.\nWann Dir en anonyme Benotzer sidd an Dir irrelevant Bemierkunge krut, [[Special:UserLogin/signup|maacht w.e.g. e Kont op]] oder [[Special:UserLogin|loggt Iech an]], fir weider Verwiesselunge mat aneren anonyme Benotzer ze verhënneren.''",
+       "anontalkpagetext": "---- ''Dëst ass d'Diskussiounssäit fir en anonyme Benotzer deen nach kee Kont opgemaach huet oder en net benotzt. Dowéinst musse mir d'IP Adress benotzen, fir de Benotzer z'identifizéieren.\nSou eng IP Adress ka vun e puer Benotzer gedeelt ginn.\nWann Dir en anonyme Benotzer sidd an Dir irrelevant Bemierkunge krut, [[Special:CreateAccount|maacht w.e.g. e Kont op]] oder [[Special:UserLogin|loggt Iech an]], fir weider Verwiesselunge mat aneren anonyme Benotzer ze verhënneren.''",
        "noarticletext": "Dës Säit huet elo keen Text.\nDir kënnt op anere Säiten no [[Special:Search/{{PAGENAME}}|dësem Säitentitel sichen]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} an den entspriechende Logbicher nokucken] oder [{{fullurl:{{FULLPAGENAME}}|action=edit}} dës Säit uleeën]</span>.",
        "noarticletext-nopermission": "Elo ass keen Text op dëser Säit.\nDir kënnt op anere Säiten [[Special:Search/{{PAGENAME}}|no dësem Säitentitel sichen]], oder <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} an de Logbicher sichen]</span>, mä Dir hutt net déi néideg Rechter fir dës Säit unzeleeën.",
        "missing-revision": "D'Versioun #$1 vun der Säit mam Numm \"{{FULLPAGENAME}}\" gëtt et net.\n\nDat geschitt normalerweis wann Dir op e vereelste Link vun enger Versioun vun enger Säit klickt déi geläscht ginn ass.\nDetailer fannt Dir am [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Logbuch vum Läschen].",
        "continue-editing": "Gitt weider an de Beräich fir z'änneren",
        "previewconflict": "Dir gesitt an dem ieweschten Textfeld wéi den Text ausgesi wäert, wann Dir späichert.",
        "session_fail_preview": "<strong>Är Ännerung konnt net gespäichert gi well d'Date vun Ärer Sessioun verluergaange sinn.</strong>\nVersicht et w.e.g. nach eng Kéier.\nWann de Problem dann ëmmer nach bestoe sollt, da versicht Iech [[Special:UserLogout|auszeloggen]] an dann erëm anzeloggen.",
-       "session_fail_preview_html": "<strong>Är Ännerung konnt net gespäichert gi well d'Date vun Ärer Sessioun verluergaange sinn.</strong>\n\n<em>Well op {{SITENAME}} 'raw HTML' aktivéiert ass, gouf d'Uweise vun der nach net gespäicherter Versioun ausgeblennt fir JavaScript-Attacken ze vermeiden.</em>\n\n<strong>Wann Dir eng berechtegt Ännerung maache wëllt, da versicht et w.e.g. nach eng Kéier.\nWann de Problem dann ëmmer nach bestoe sollt, versicht Iech [[Special:UserLogout|auszeloggen]] an dann erëm anzeloggen.</strong>",
+       "session_fail_preview_html": "Är Ännerung konnt net gespäichert gi well d'Date vun Ärer Sessioun verluergaange sinn.\n\n<em>Well op {{SITENAME}} 'raw HTML' aktivéiert ass, gouf d'Uweise vun der nach net gespäicherter Versioun ausgeblennt fir JavaScript-Attacken ze vermeiden.</em>\n\n<strong>Wann Dir eng berechtegt Ännerung maache wëllt, da versicht et w.e.g. nach eng Kéier.</strong>\n\nWann de Problem dann ëmmer nach bestoe sollt, versicht Iech [[Special:UserLogout|auszeloggen]] an dann erëm anzeloggen a vergewëssert Iech datt Äre Browser d^Späichere vu Cookieë vun dësem Site zouléisst.",
        "token_suffix_mismatch": "'''Är Ännerung gouf refuséiert, well Äre Browser Zeechen am Ännerungs-Identifiant verännert huet.'''\nD'Ännerung gouf refuséiert, fir ze verhënneren datt den Text op der Säit onliesbar gëtt.\nDëst geschitt heiansdo wann Dir en anonyme Proxy-Service um Internet benotzt.",
        "edit_form_incomplete": "'''En Deel vum Ännerungsformulaire koum net um Server un; iwwerpréift w.e.g ob Är Ännerunge komplett sinn a probéiert nach emol.'''",
        "editing": "Ännere vu(n) $1",
        "upload-form-label-infoform-description": "Beschreiwung",
        "upload-form-label-usage-title": "Benotzung",
        "upload-form-label-usage-filename": "Numm vum Fichier",
-       "foreign-structured-upload-form-label-own-work": "Dëst ass mäin eegent Wierk",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorien",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work": "Dëst ass mäin eegent Wierk",
+       "upload-form-label-infoform-categories": "Kategorien",
+       "upload-form-label-infoform-date": "Datum",
        "backend-fail-stream": "De Fichier $1 konnt net iwwerdroe ginn.",
        "backend-fail-backup": "De Fichier $1 konnt net geséchert ginn.",
        "backend-fail-notexists": "De Fichier $1 gëtt et net.",
        "whatlinkshere-prev": "{{PLURAL:$1|vireg|vireg $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|nächsten|nächst $1}}",
        "whatlinkshere-links": "← Linken",
-       "whatlinkshere-hideredirs": "Viruleedunge $1",
-       "whatlinkshere-hidetrans": "Agebonne Schabloune $1",
-       "whatlinkshere-hidelinks": "Linken $1",
-       "whatlinkshere-hideimages": "Linken op Fichiere $1",
+       "whatlinkshere-hideredirs": "Viruleedunge verstoppen",
+       "whatlinkshere-hidetrans": "Agebonne Schabloune verstoppen",
+       "whatlinkshere-hidelinks": "Linke verstoppen",
+       "whatlinkshere-hideimages": "Linken op Fichiere verstoppen",
        "whatlinkshere-filters": "Filteren",
        "whatlinkshere-submit": "Lass",
        "autoblockid": "Automatesch Spär #$1",
        "ipbexpiry": "Gültegkeet:",
        "ipbreason": "Grond:",
        "ipbreason-dropdown": "*Heefeg Ursaache fir Benotzer ze spären:\n**Bewosst falsch Informatiounen an eng oder méi Säite gesat\n**Ouni Grond Inhalt vu Säite geläscht\n**Spam-Verknëppunge mat externe Säiten\n**Topereien an d'Säite gesat\n**Beleidegt oder bedréit aner Mataarbechter\n**Mëssbrauch vu verschiddene Benotzernimm\n**Net akzeptabele Benotzernumm",
-       "ipb-hardblock": "Verhënneren datt ageloggte Benotzer vun dëser IP-Adress aus Ännerunge maache kënnen",
+       "ipb-hardblock": "Verhënneren datt ageloggt Benotzer vun dëser IP-Adress aus Ännerunge maache kënnen",
        "ipbcreateaccount": "Opmaache vun engem Benotzerkont verhënneren",
        "ipbemailban": "Verhënneren datt de Benotzer E-Maile verschéckt",
        "ipbenableautoblock": "Automatesch déi lescht IP-Adress spären déi vun dësem Benotzer benotzt gouf, an all IP-Adresse vun denen dëse Benotzer versicht Ännerunge virzehuelen",
        "tooltip-ca-nstab-category": "Kategoriesäit weisen",
        "tooltip-minoredit": "Dës Ännerung als kleng markéieren.",
        "tooltip-save": "Ännerunge späicheren",
+       "tooltip-publish": "Är Ännerunge publizéieren",
        "tooltip-preview": "Kuckt är Ännerungen ouni ofzespäicheren, Benotzt dëst w.e.g. virum späicheren!",
        "tooltip-diff": "Weist wéi eng Ännerungen Dir beim Text gemaach hutt.",
        "tooltip-compareselectedversions": "D'Ënnerscheeder op dëser Säit tëscht den zwou gewielte Versioune weisen.",
        "feedback-useragent": "User Agent:",
        "searchsuggest-search": "Sichen",
        "searchsuggest-containing": "mat ...",
+       "api-error-autoblocked": "Är IP-Adress gouf automatesch gespaart wëll se vun engem gespaarte Benotzer  benotzt gouf",
        "api-error-badaccess-groups": "Et ass Iech net erlaabt fir Fichieren op dës Wiki eropzelueden.",
        "api-error-badtoken": "Interne Feeler: falschen Token.",
+       "api-error-blocked": "Dir gouft gespaart a  kënnt dofir keng Ännerunge maachen.",
        "api-error-copyuploaddisabled": "D'Eroplueden iwwer eng URL ass op dësem Server desaktivéiert.",
        "api-error-duplicate": "Et gëtt schonn {{PLURAL:$1|en anere Fichier|e puer aner Fichiere}} mat dem selwechten Inhalt op dem Site",
        "api-error-duplicate-archive": "Et gouf schonn {{PLURAL:$1| een anere Fichier|e puer aner Fichieren}} op dem Site mat deemselwechten Inhalt, {{PLURAL:$1|e gouf|se goufen}} awer geläscht.",
index 11c7397..2dc0222 100644 (file)
        "noname": "Towandise linnya ly'obwa memba eriyinz'okukkirizibwa.",
        "loginsuccesstitle": "Oyingidde",
        "loginsuccess": "'''Kati oyingidde mu {{SITENAME}} nga okozesa erinnya \"$1\".'''",
-       "nosuchuser": "Tewali memba akozesa lya \"$1\".<br />\nEry'obwa memba bw'okyusa obunene bw'ennukuta z'oliwandisa obeera owandise eddala.<br />\nKebera bw'oliwandise, oba [[Special:UserLogin/signup|kolawo akawunti empya]].",
+       "nosuchuser": "Tewali memba akozesa lya \"$1\".<br />\nEry'obwa memba bw'okyusa obunene bw'ennukuta z'oliwandisa obeera owandise eddala.<br />\nKebera bw'oliwandise, oba [[Special:CreateAccount|kolawo akawunti empya]].",
        "nosuchusershort": "Tewali memba akozesa lya \"$1\".<br />\nKebera bw'oliwandise.",
        "nouserspecified": "Kyetaagisa owandikewo erinnya ly'obwa memba.",
        "login-userblocked": "Memba ono agaanidwa. Takkirizibwa kuyingira.",
        "accmailtext": "Ekigambo ekikuumi ekya akawunti empya [[User talk:$1|$1]] kisindikiddwa ku $2.\n\nOkukyusa ekigambo kino memba, ng'ayingidde mu wiki, alage ku lupapula ''[[Special:ChangePassword|change password]]''.",
        "newarticle": "(Lupapula lupya)",
        "newarticletext": "Enyunzi gy'ogenzeko egguka ku lupapula olutannakolebwawo.<br />\nOba gwe oyagala okulukolawo, wandika mu kabokisi akaddako wano.\n(okuyiga ebisingawo, genda ku [$1 lupapula olw'obuyambi]).<br />\nBw'obanga tewagenderedde kutuuka wano, nyigabunyizi eppeesa ery'omu kalambula-neti yo\nerya '''ddayo'''.",
-       "anontalkpagetext": "----''Olupapula olw'emboozi luno lukoledwawo at'eyanjula atakolera mu akawunti ya ku wiki eno.<br />\nKino kitwetaagisizza okumwawulira ku ndagiriro ya IP kwe yayimidde.<br />\nAbantu bangi basobola okukozesa endagiriro eya IP y'emu.<br />\nBw'obanga naawe okola ng'at'eyanjudde nga owulira nti ebimu ku birowozo ebikwolekezedwa wano<br />\ntebiggirawo oba tebikwatagana naawe, [[Special:UserLogin/signup|funa akawunti]] oba [[Special:UserLogin|yingira]] baleme okukutabula n'abalala abakola ng'abat'eyanjudde.",
+       "anontalkpagetext": "----''Olupapula olw'emboozi luno lukoledwawo at'eyanjula atakolera mu akawunti ya ku wiki eno.<br />\nKino kitwetaagisizza okumwawulira ku ndagiriro ya IP kwe yayimidde.<br />\nAbantu bangi basobola okukozesa endagiriro eya IP y'emu.<br />\nBw'obanga naawe okola ng'at'eyanjudde nga owulira nti ebimu ku birowozo ebikwolekezedwa wano<br />\ntebiggirawo oba tebikwatagana naawe, [[Special:CreateAccount|funa akawunti]] oba [[Special:UserLogin|yingira]] baleme okukutabula n'abalala abakola ng'abat'eyanjudde.",
        "noarticletext": "Wano tewali kyawandikidwawo.<br />\nKy'obadde onoonya osobola [[Special:Search/{{PAGENAME}}| okukinoonyeza]] ku mpapula ndala oba oyinza [{{fullurl:{{FULLPAGENAME}}|action=edit}} gwe okukiwandikako] wano.",
        "noarticletext-nopermission": "Wano tewali kyawandikidwawo.<br />\nKy'obadde onoonya osobola [[Special:Search/{{PAGENAME}}| okukinoonyeza]] ku mpapula ndala oba\noyinza <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} okukinoonyeza mu nkalala z'ebifuddeyo ebyekuusana nakyo]</span>.",
        "userpage-userdoesnotexist": "Akawunti y'obwamemba \"<nowiki>$1</nowiki>\" teri mu nkalala za wiki eno.<br />\nSooka okakase oba ddala oyagala okukolawo olupapula olupya luno.",
index 62e24b2..1875dee 100644 (file)
        "noname": "De mos 'n gebroekersnaam opgaeve.",
        "loginsuccesstitle": "Aanmèlde geluk.",
        "loginsuccess": "Doe bis noe es \"$1\" aangemeld bie {{SITENAME}}.",
-       "nosuchuser": "D'r besjteit geine gebroeker mit de naam \"$1\".\nDie seen huidlettegevullig\nControleer dien spelling, of gebroek ongersjtaond formuleer om 'n [[Special:UserLogin/signup|nuuj]] gebroekersprofiel aan te make.",
+       "nosuchuser": "D'r besjteit geine gebroeker mit de naam \"$1\".\nDie seen huidlettegevullig\nControleer dien spelling, of gebroek ongersjtaond formuleer om 'n [[Special:CreateAccount|nuuj]] gebroekersprofiel aan te make.",
        "nosuchusershort": "De gebroeker \"$1\" besjteit neet. Konterleer de sjriefwieze.",
        "nouserspecified": "Doe deens 'ne gebroekersnaam op te gaeve.",
        "login-userblocked": "Deze gebroeker steit geblokkeerd. Aanmèlje is neet toegestange.",
        "accmailtext": "'n Willekäörig wachwaord veur [[User talk:$1|$1]] is nao $2 gesjtuurd.\n\n't Wachwaord veur deze nuje gebroeker kan gewiezig waere via de pagina ''[[Special:ChangePassword|Wachwaord wiezige]]'' nao 't aanmelje.",
        "newarticle": "(Nuuj)",
        "newarticletext": "De höbs 'ne link gevolg nao 'n pagina die nog neet besjteit.\nType in de box hiejónger óm de pagina te beginne (zuug de [$1 helppagina] veur mie infermasie).\nEs te hie per óngelök terech bis gekómme, klik dan op de '''trök'''-knóp van diene browser.",
-       "anontalkpagetext": "----''Dit is de euverlèkpagina veur 'ne anonieme gebroeker dae nog gein account haet aangemaak of dae 't neet gebroek.\nDaoveur gebroeke v'r 't IP-adres óm de gebroeker te identificere.\nDet adres kan waere gedeild door mierdere gebroekers.\nEs te 'ne anonieme gebroeker bis en de höbs 't geveul dat 'r ónrelevante commentare aan dich gerich zeen, kèns te 't bèste [[Special:UserLogin/signup|'n account crëere]] of [[Special:UserLogin|inlogge]] óm toekomstige verwarring mit anger anoniem gebroekers te veurkomme.''",
+       "anontalkpagetext": "----''Dit is de euverlèkpagina veur 'ne anonieme gebroeker dae nog gein account haet aangemaak of dae 't neet gebroek.\nDaoveur gebroeke v'r 't IP-adres óm de gebroeker te identificere.\nDet adres kan waere gedeild door mierdere gebroekers.\nEs te 'ne anonieme gebroeker bis en de höbs 't geveul dat 'r ónrelevante commentare aan dich gerich zeen, kèns te 't bèste [[Special:CreateAccount|'n account crëere]] of [[Special:UserLogin|inlogge]] óm toekomstige verwarring mit anger anoniem gebroekers te veurkomme.''",
        "noarticletext": "Dees pagina bevat gein teks.\nDe kèns [[Special:Search/{{PAGENAME}}|nao deze term zeuke]] in anger pagina's, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logbeuk doorzeuke] of [{{fullurl:{{FULLPAGENAME}}|action=edit}} dees pagina bewirke]</span>.",
        "noarticletext-nopermission": "Dees pagina bevat gein teks.\nDe kans [[Special:Search/{{PAGENAME}}|nao dees term zeuke]] in anger pagina's of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logbeuk doorzeuke]</span>.",
        "userpage-userdoesnotexist": "Doe bewirks 'n gebroekerspagina van 'ne gebroeker dae neet besjteit (gebroeker \"<nowiki>$1</nowiki>\"). Controlere ofs doe dees pagina waal wils aanmake/bewirke.",
index 9ede18f..cd2aa8c 100644 (file)
        "noname": "O nomme d'ûtente o l'è sballiòu.",
        "loginsuccesstitle": "Accesso effettuòu",
        "loginsuccess": "'''O collegamento a-o server de {{SITENAME}} co-o nomme d'ûtente \"$1\" o l'è attivo.'''",
-       "nosuchuser": "No gh'è nisciun utente de nomme \"$1\".\nI nommi utente son senscibbili a-e maiuscole.\nVerifica o nomme inserîo ò [[Special:UserLogin/signup|crea una neuva utensa]].",
+       "nosuchuser": "No gh'è nisciun utente de nomme \"$1\".\nI nommi utente son senscibbili a-e maiuscole.\nVerifica o nomme inserîo ò [[Special:CreateAccount|crea una neuva utensa]].",
        "nosuchusershort": "No gh'è nisciûn ûtente con quello nomme \"$1\". Verificâ o nomme inserîo.",
        "nouserspecified": "Ti g'hæ da specificâ un nomme utente.",
        "login-userblocked": "St'utente o l'è bloccou. Accesso negou.",
        "accmailtext": "Una poula segretta generâ abrettio pe [[User talk:$1|$1]] a l'è stæta mandâ a $2.\n\nSta poula segretta a peu ese cangiâ inta paggina pe ''[[Special:ChangePassword|cangiâ a poula segretta]]'' subbito doppo l'accesso.",
        "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''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. 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 anonimo e ti ritegni che i commenti inte sta pagina no se riferiscian a ti, [[Special:UserLogin/signup|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 .''",
+       "anontalkpagetext": "----\n''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. 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 anonimo 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. L'è poscibbile [[Special:Search/{{PAGENAME}}|çercâ 'sto tittolo]] inte âtre pagine do scîto opû [{{fullurl:{{FULLPAGENAME}}|action=edit}} cangiâ a pagina òua].",
        "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].",
        "allpagesbadtitle": "O tittolo dæto a-a paggina o non va ben, òpû o conten di prefissi inter-lengua o inter-wiki. O porriæ ascì contegnî un o ciù caratteri che inti tittoli no se peuan deuviâ.",
        "allpages-bad-ns": "\"$1\" o no ghe in {{SITENAME}}.",
        "categories": "Categorîe",
-       "special-categories-sort-count": "ordenâ pe nûmmero",
-       "special-categories-sort-abc": "ordenâ arfabeticamente",
        "linksearch-line": "$1 colegòu a-a pagina $2",
        "listusers-submit": "Fanni vedde",
        "listusers-noresult": "Utente non trovöo.",
index ced7c58..dbc94bc 100644 (file)
@@ -5,7 +5,8 @@
                        "Hosseinblue",
                        "Lakzon",
                        "Mjbmr",
-                       "Macofe"
+                       "Macofe",
+                       "Huji"
                ]
        },
        "tog-underline": "کڕ(خط)کیشائن ژێر پیوندەل:",
        "noname": ".هؤمة نام کاربةری معتبری دیاری نکردئة",
        "loginsuccesstitle": "إنۆم سیستم هەتن انجۆم گرت",
        "loginsuccess": "هؤمة ایسة هةتیإ نؤم سیستم {{SITENAME}} وۀ نام\"$1\".'",
-       "nosuchuser": "کاربةری وۀ نام «$1» ئة ائرة نیة.\nنام کاربةری وة کةڵنگی و گؤجةری حروف حساسة .\nاملای نام را بررسی کنید، یا [[Special:UserLogin/signup|یک حساب کاربری تازه بسازید]].",
+       "nosuchuser": "کاربةری وۀ نام «$1» ئة ائرة نیة.\nنام کاربةری وة کةڵنگی و گؤجةری حروف حساسة .\nاملای نام را بررسی کنید، یا [[Special:CreateAccount|یک حساب کاربری تازه بسازید]].",
        "nosuchusershort": "هؤیچ کاربةری وة نام ''$1'' ئة ائرة نیة.\nاملایتان را وارسی کنید.",
        "nouserspecified": ".باید یإ گِلة  نام کاربةری دیاری کئین",
        "login-userblocked": ".ئی کاربرە بەسیائە. إنؤم هەتِن سیستم ڕاووآ(مجاز)نیە",
        "accmailtext": "یک گذرواژهٔ تصادفی برای [[User talk:$1|$1]] به $2 فرستاده شد. می‌توان آن را از صفحهٔ ''[[Special:ChangePassword|تغییر گذرواژه]]'' که هنگام ثبت ورود نمایش می‌یابد تغییر داد.",
        "newarticle": "(تازه)",
        "newarticletext": "شما پیوندی را دنبال کرده‌اید و به صفحه‌ای رسیده‌اید که هنوز وجود ندارد.\nبرای ایجاد صفحه، در مستطیل زیر شروع به نوشتن کنید (برای اطلاعات بیشتر به [$1 صفحهٔ راهنما] مراجعه کنید).\nاگر به اشتباه اینجا آمده‌اید، دکمهٔ «بازگشت» مرورگرتان را بزنید.",
-       "anontalkpagetext": "----''این صفحهٔ بحث برای کاربر گمنامی است که هنوز حسابی درست نکرده است یا از آن استفاده نمی‌کند.\nبنا بر این برای شناسایی‌اش مجبوریم از نشانی آی‌پی عددی استفاده کنیم.\nچنین نشانی‌های آی‌پی ممکن است توسط چندین کاربر به شکل مشترک استفاده شود.\nاگر شما کاربر گمنامی هستید و تصور می‌کنید اظهار نظرات نامربوط به شما صورت گرفته است، لطفاً برای پیشگیری از اشتباه گرفته شدن با کاربران گمنام دیگر در آینده [[Special:UserLogin/signup|حسابی ایجاد کنید]] یا [[Special:UserLogin|به سامانه وارد شوید]].''",
+       "anontalkpagetext": "----''این صفحهٔ بحث برای کاربر گمنامی است که هنوز حسابی درست نکرده است یا از آن استفاده نمی‌کند.\nبنا بر این برای شناسایی‌اش مجبوریم از نشانی آی‌پی عددی استفاده کنیم.\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}}}} سیاهه‌های مرتبط را بگردید]</span> ولی شما اجازه ایجاد این صفحه را ندارید.",
        "missing-revision": "ویرایش #$1 از صفحهٔ «{{FULLPAGENAME}}» موجود نیست.\n\nمعمولاً در اثر پیوند به تاریخچهٔ به‌روز نشدهٔ صفحهٔ حذف شده است.\nمی‌توانید جزئیات بیشتر را در [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیاههٔ حذف] بیابید.",
        "upload-form-label-infoform-description": "توضیحةل",
        "upload-form-label-usage-title": "کاربرد",
        "upload-form-label-usage-filename": "نؤم فایل",
-       "foreign-structured-upload-form-label-own-work": "یة کار ووژمة",
-       "foreign-structured-upload-form-label-infoform-categories": "ڕزگەل",
-       "foreign-structured-upload-form-label-infoform-date": "تاریخ",
-       "foreign-structured-upload-form-label-own-work-message-local": "تائید می کنم که این پرونده را تحت مجوزها و سیاست‌های {{SITENAME}} بارگذاری می‌کنم.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "اگر امکان بارگذاری پرونده تحت سیاست‌های {{SITENAME}} را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "ممکن بخواهید از [[Special:Upload|پنجرهٔ بارگذاری پیش‌فرض]] استفاده کنید.",
-       "foreign-structured-upload-form-label-own-work-message-default": "متوجهم که این پرونده را بر روی مخزن مشترک بارگذاری می‌کنم و تائید می‌کنم که تحت سیاست‌ها و مجوزهای آنجا عمل می‌کنم.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "اگر امکان بارگذاری پرونده تحت سیاست‌ها و مجوزهای مخزن اشتراک‌گذاری را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "در صورتی که نمی‌شود پرونده را تحت سیاست‌ها بارگذاری کنید ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "تصدیق می‌کنم که مالک حق تکثیر این پرونده هستم و موافق اشتراک‌گذاری آن تحت مجوز [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] هستم و موافق [https://wikimediafoundation.org/wiki/Terms_of_Use سیاست نحوهٔ استفاده] هستم.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "اگر مالک حق تکثیر این پرونده نیستید یا قصد بارگذاری تحت مجوز دیگری دارید، از [https://commons.wikimedia.org/wiki/Special:UploadWizard جادوگر بارگذاری ویکی‌انبار] استفاده کنید.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "در صورتی که سایت امکان بارگذاری پرونده را تحت سیاست‌ها بارگذاری می‌دهد ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
+       "upload-form-label-own-work": "یة کار ووژمة",
+       "upload-form-label-infoform-categories": "ڕزگەل",
+       "upload-form-label-infoform-date": "تاریخ",
+       "upload-form-label-own-work-message-generic-local": "تائید می کنم که این پرونده را تحت مجوزها و سیاست‌های {{SITENAME}} بارگذاری می‌کنم.",
+       "upload-form-label-not-own-work-message-generic-local": "اگر امکان بارگذاری پرونده تحت سیاست‌های {{SITENAME}} را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
+       "upload-form-label-not-own-work-local-generic-local": "ممکن بخواهید از [[Special:Upload|پنجرهٔ بارگذاری پیش‌فرض]] استفاده کنید.",
+       "upload-form-label-own-work-message-generic-foreign": "متوجهم که این پرونده را بر روی مخزن مشترک بارگذاری می‌کنم و تائید می‌کنم که تحت سیاست‌ها و مجوزهای آنجا عمل می‌کنم.",
+       "upload-form-label-not-own-work-message-generic-foreign": "اگر امکان بارگذاری پرونده تحت سیاست‌ها و مجوزهای مخزن اشتراک‌گذاری را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
+       "upload-form-label-not-own-work-local-generic-foreign": "در صورتی که نمی‌شود پرونده را تحت سیاست‌ها بارگذاری کنید ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
        "backend-fail-stream": "نمی‌توان پروندهٔ $1 را ارسال کرد.",
        "backend-fail-backup": "نمی‌توان نسخهٔ پشتیبان برای پروندهٔ $1 ایجاد کرد",
        "backend-fail-notexists": "پروندهٔ $1 وجود ندارد.",
        "exbeforeblank": "محتوای صفحه قبل از خالی‌کردن این بود: «$1»",
        "delete-confirm": "حذف «$1»",
        "delete-legend": "حۀذف کردن/پاک کردن",
-       "historywarning": "<strong>هشدار:</strong> صفحه‌ای که در حال پاک‌کردن آن هستید دارای یک تاریخچه همراه $1 {{PLURAL:$1|بازبینی|بازبینی‌ها}} است:",
+       "historywarning": "<strong>هشدار:</strong> صفحه‌ای که در حال پاک‌کردن آن هستید دارای یک تاریخچه همراه $1 {{PLURAL:$1|بازبینی|بازبینی}} است:",
        "historyaction-submit": "نیشان دائن",
        "confirmdeletetext": "شما در حال حذف کردن یک صفحه یا تصویر از پایگاه داده‌ها همراه با تمام تاریخچهٔ آن هستید.\nلطفاً این عمل را تأیید کنید و اطمینان حاصل کنید که عواقب این کار را می‌دانید و این عمل را مطابق با [[{{MediaWiki:Policy-url}}|سیاست‌ها]] انجام می‌دهید.",
        "actioncomplete": "عملكرد كامل بيه",
index 554ddad..9aa2d1a 100644 (file)
        "noname": "Te g'hét mìa specificàt en nòm de ütènt bù.",
        "loginsuccesstitle": "Login efetuàt.",
        "loginsuccess": "<strong>Adès te sét cunitìt al server de {{SITENAME}} col nòm ütènt de \"$1\".</strong>",
-       "nosuchuser": "A gh'è nissün druvat cun 'l nom ''$1''. <br />\nI suranomm i henn sensibil a i leter majùscul.<br />\nCuntrola 'l nom che t'hee metüü denter o [[Special:UserLogin/signup|crea un cünt növ]].",
+       "nosuchuser": "A gh'è nissün druvat cun 'l nom ''$1''. <br />\nI suranomm i henn sensibil a i leter majùscul.<br />\nCuntrola 'l nom che t'hee metüü denter o [[Special:CreateAccount|crea un cünt növ]].",
        "nosuchusershort": "Ghe n'è mia d'ütent cun el nom de \"$1\". Ch'el cuntrola se l'ha scrivüü giüst.",
        "nouserspecified": "Te gh'heet da specificà un nom del druvatt.",
        "login-userblocked": "Chèsta ütènsa l'è blocàda. La conesiù l'è mìa cunsentìda.",
        "accmailtext": "La password per [[User talk:$1|$1]] l'è stada mandada a $2. Chèsta password la pöl véser cambiàda per [[Special:ChangePassword|cambià la password]] apéna dòpo che te g'harét fat el log-in.",
        "newarticle": "(Nöf)",
        "newarticletext": "Te seet andaa adree a un ligam a una pagina che la esista gnamò.\nPer creà la pagina, a l'è assee che te tachet a scriv in del box desota (varda la [$1 pagina de vüt] per savèn püssee).\nSe te seet chì per erur, schiscia \"indree\" in sül tò browser.",
-       "anontalkpagetext": "''Questa chí a l'é la pagina da ciciarada d'un druvadur che l'ha nonanmò registraa un cünt, o che 'l le dröva mia.\nPer 'sta reson chí, el pò vess identificaa dumà cunt el sò indirizz nümereg de IP.\n'Stu indirizz IP el pö vess druvaa da püssee d'un druvadur. Se te seet un druvadur anònim e te someja che un quaj messagg al ga nagòt à vidé con de ti, prœuva a [[Special:UserLogin/signup|creà 'n ütènsa nöa]] o [[Special:UserLogin|regìstret con chèla che te g'hét zà]] inscì de minga vess scunfundüü anmò con quaj alter ütent anomim.''",
+       "anontalkpagetext": "''Questa chí a l'é la pagina da ciciarada d'un druvadur che l'ha nonanmò registraa un cünt, o che 'l le dröva mia.\nPer 'sta reson chí, el pò vess identificaa dumà cunt el sò indirizz nümereg de IP.\n'Stu indirizz IP el pö vess druvaa da püssee d'un druvadur. Se te seet un druvadur anònim e te someja che un quaj messagg al ga nagòt à vidé con de ti, prœuva a [[Special:CreateAccount|creà 'n ütènsa nöa]] o [[Special:UserLogin|regìstret con chèla che te g'hét zà]] inscì de minga vess scunfundüü anmò con quaj alter ütent anomim.''",
        "noarticletext": "Per 'l mument quela pagina chì l'è vöja. Te pòdet [[Special:Search/{{PAGENAME}}|cercà quel articul chì]] int i alter paginn, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercà int i register imparentaa], o sedenò [{{fullurl:{{FULLPAGENAME}}|action=edit}} mudifichè 'sta pagina chì adess-adess]</span>.",
        "noarticletext-nopermission": "Per 'l mument quela pagina chì l'è vöja. Te pòdet [[Special:Search/{{PAGENAME}}|cercà quel articul chì]] int i alter paginn, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercà int i register imparentaa], ma te gh'ét nò i permiss per creà-la</span>.",
        "userpage-userdoesnotexist": "L'ütènsa \"$1\" l'è mìa registràda. Arda bé semài che te ölet creàla o mudificàa adilbù.",
        "allpagesprefix": "Varda i pagin ch'i scumenza per:",
        "allpages-hide-redirects": "Scond i bot",
        "categories": "Categurij",
-       "special-categories-sort-count": "mèt en ùrden per nömer",
-       "special-categories-sort-abc": "mèt en ùrden alfabétich",
        "deletedcontributions": "Mudìfiche del ütènt scancelàde",
        "deletedcontributions-title": "Cuntribüziun scancelaa",
        "sp-deletedcontributions-contribs": "mudìfiche",
index a835f91..eab4db5 100644 (file)
@@ -8,7 +8,8 @@
                        "Mjbmr",
                        "Matma Rex",
                        "Lakzon",
-                       "Nemo bis"
+                       "Nemo bis",
+                       "Amire80"
                ]
        },
        "tog-underline": "هوم پئیڤأند زیرخأط دار:",
        "upload-form-label-infoform-description": "توضی",
        "upload-form-label-usage-title": "ڤئ کار گئرئتئن",
        "upload-form-label-usage-filename": "نوم جانیا",
-       "foreign-structured-upload-form-label-own-work": "یە کار مئنە",
-       "foreign-structured-upload-form-label-infoform-categories": "دأسە یا",
-       "foreign-structured-upload-form-label-infoform-date": "گات",
+       "upload-form-label-own-work": "یە کار مئنە",
+       "upload-form-label-infoform-categories": "دأسە یا",
+       "upload-form-label-infoform-date": "گات",
        "backend-fail-stream": "نبوئه جانیا\"$1\" کل بوئه.",
        "backend-fail-backup": "نبوئه سی \"$1\" پشتجا گرت.",
        "backend-fail-notexists": "جانیا $1 وجود ناره.",
        "listgrouprights-rights": "حقوقیا",
        "listgrouprights-helppage": "هومیاری:حقوق گرو",
        "listgrouprights-members": "(نوم گه اندومیا)",
-       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code dir=\"ltr\">($2)</code></span>",
        "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "{{PLURAL:$2|جأرغە|جأرغە یا}} نە ئضاف بأکیت: $1",
        "listgrouprights-removegroup": "{{PLURAL:$2|جأرغە|جأرغە یا}} نە ڤئرداریت: $1",
index 6220352..915a875 100644 (file)
        "remembermypassword": "Prisiminti prisijungimo duomenis šiame kompiuteryje (daugiausiai $1 {{PLURAL:$1|dieną|dienas|dienų}})",
        "userlogin-remembermypassword": "Įsiminti mane",
        "userlogin-signwithsecure": "Naudoti saugią jungtį",
-       "cannotloginnow-title": "Negalima prisijungti dabar",
+       "cannotloginnow-title": "Dabar negalima prisijungti",
        "cannotloginnow-text": "Prisijungimas negalimas, kai naudojama $1.",
        "yourdomainname": "Jūsų domenas:",
        "password-change-forbidden": "Jus negalite keisti slaptažodžių šioje wiki.",
        "noname": "Jūs nesate nurodęs teisingo naudotojo vardo.",
        "loginsuccesstitle": "Sėkmingai prisijungėte",
        "loginsuccess": "'''Dabar jūs prisijungęs prie {{SITENAME}} kaip „$1“.'''",
-       "nosuchuser": "Nėra jokio naudotojo, turinčio vardą „$1“.\nNaudotojų varduose skiriamos didžiosios ir mažosios raidės.\nPatikrinkite rašybą, arba [[Special:UserLogin/signup|sukurkite naują paskyrą]].",
+       "nosuchuser": "Nėra jokio naudotojo, turinčio vardą „$1“.\nNaudotojų varduose skiriamos didžiosios ir mažosios raidės.\nPatikrinkite rašybą, arba [[Special:CreateAccount|sukurkite naują paskyrą]].",
        "nosuchusershort": "Nėra jokio naudotojo, pavadinto „$1“. Patikrinkite rašybą.",
        "nouserspecified": "Jums reikia nurodyti naudotojo vardą.",
        "login-userblocked": "Šis naudotojas yra užblokuotas. Prisijungti neleidžiama.",
        "minoredit": "Tai smulkus pataisymas",
        "watchthis": "Stebėti šį puslapį",
        "savearticle": "Išsaugoti puslapį",
+       "publishpage": "Skelbti puslapį",
        "preview": "Peržiūra",
        "showpreview": "Rodyti peržiūrą",
        "showdiff": "Rodyti skirtumus",
        "accmailtext": "Atsitiktinai sukurtas naudotojo [[User talk:$1|$1]] slaptažodis nusiųstas į $2.\n\nŠios naujos paskyros slaptažodis gali būti pakeistas <em>[[Special:ChangePassword|keisti slaptažodį]]</em> puslapyje beprisijungiant.",
        "newarticle": "(Naujas)",
        "newarticletext": "Jūs patekote į dar neegzistuojantį puslapį.\nNorėdami sukurti puslapį, pradėkite rašyti žemiau esančiame įvedimo lauke\n(plačiau [$1 pagalbos puslapyje]).\nJei patekote čia per klaidą, paprasčiausiai spustelkite  naršyklės mygtuką '''atgal'''.",
-       "anontalkpagetext": "----''Tai yra anoniminio naudotojo, nesusikūrusio arba nenaudojančio paskyros, aptarimų puslapis.\nDėl to naudojamas IP adresas jo identifikavimui.\nŠis IP adresas gali būti dalinamas keliems naudotojams.\nJeigu Jūs esate anoniminis naudotojas ir atrodo, kad komentarai nėra skirti Jums, [[Special:UserLogin/signup|sukurkite paskyrą]] arba [[Special:UserLogin|prisijunkite]], ir nebūsite tapatinamas su kitais anoniminiais naudotojais.''",
+       "anontalkpagetext": "----''Tai yra anoniminio naudotojo, nesusikūrusio arba nenaudojančio paskyros, aptarimų puslapis.\nDėl to naudojamas IP adresas jo identifikavimui.\nŠis IP adresas gali būti dalinamas keliems naudotojams.\nJeigu Jūs esate anoniminis naudotojas ir atrodo, kad komentarai nėra skirti Jums, [[Special:CreateAccount|sukurkite paskyrą]] arba [[Special:UserLogin|prisijunkite]], ir nebūsite tapatinamas su kitais anoniminiais naudotojais.''",
        "noarticletext": "Šiuo metu šiame puslapyje nėra jokio teksto.\nJūs galite [[Special:Search/{{PAGENAME}}|ieškoti šio puslapio pavadinimo]] kituose puslapiuose,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ieškoti susijusių įrašų] arba [{{fullurl:{{FULLPAGENAME}}|action=edit}} sukurti šį puslapį]</span>.",
        "noarticletext-nopermission": "Šiuo metu šiame puslapyje nėra jokio teksto.\nJūs galite [[Special:Search/{{PAGENAME}}|ieškoti šio puslapio pavadinimo]] kituose puslapiuose,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ieškoti susijusių įrašų]</span>, bet jūs neturite teisės sukurti šį puslapį.",
        "missing-revision": "Puslapio peržiūra #$1 pavadinto „{{FULLPAGENAME}}“ neegzistuoja.\n\nTai paprastai atsitinka kai pasenusi nuoroda veda į puslapį, kuris buvo ištrintas.\nInformaciją galima rasti [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].",
        "upload-form-label-infoform-description-tooltip": "Trumpai apibūdinkite viską, kad įsimintina apie šį darbą.\nNuotraukoms paminėkite pagrindinius dalykus, kurie yra pavaizduoti, asociacijas ar vietą.",
        "upload-form-label-usage-title": "Naudojimas",
        "upload-form-label-usage-filename": "Failo pavadinimas",
-       "foreign-structured-upload-form-label-own-work": "Tai yra mano darbas",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorijos",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Patvirtinu, kad įkeliu šį failą su šiomis naudojimosi sąlygomis ir licencijavimo politika į {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Jeigu Jūs negalite įkelti šio failo su {{SITENAME}}  politika, prašome uždaryti dialogą ir pabandyti kitą metodą.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Jūs taip pat galite norėti išbandyti [[Special:Upload|numatytąjį įkėlimo puslapį]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Aš suprantu, kad įkeliu šį failą į dalinimosi repozitoriją. Aš patvirtinu, kad tai darau laikydamasis jų paslaugų teikimo sąlygų ir licencijavimo politikos.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Jeigu negalite įkelti šio failo su dalinimosi repozitorijos politika, prašome uždaryti šį dialogą ir bandyti kitą metodą.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Jūs taip pat galite norėti išbandyti [[Special:Upload|{{SITENAME}} įkėlimo puslapį]], jeigu šis failas gali būti įkeltas su jų politika.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Aš patvirtinu, kad man priklauso šio failo autorinės teisės ir sutinku neatšaukiamai išleisti šį failą į Wikimedia Commons su [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] licencija, ir aš sutinku su [https://wikimediafoundation.org/wiki/Terms_of_Use paslaugų teikimo sąlygomis].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Jeigu Jums nepriklauso šio failo autorinės teisės arba Jūs norite išleisti jį su kitokia licencija, apsvarstykite naudojimą [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons įkėlimo vedlį].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Jūs taip pat galite norėti išbandyti [[Special:Upload|{{SITENAME}} įkėlimo puslapį]], jeigu šis puslapis leidžia failų įkėlimą pagal jų politiką.",
+       "upload-form-label-own-work": "Tai yra mano darbas",
+       "upload-form-label-infoform-categories": "Kategorijos",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Patvirtinu, kad įkeliu šį failą su šiomis naudojimosi sąlygomis ir licencijavimo politika į {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Jeigu Jūs negalite įkelti šio failo su {{SITENAME}}  politika, prašome uždaryti dialogą ir pabandyti kitą metodą.",
+       "upload-form-label-not-own-work-local-generic-local": "Jūs taip pat galite norėti išbandyti [[Special:Upload|numatytąjį įkėlimo puslapį]].",
+       "upload-form-label-own-work-message-generic-foreign": "Aš suprantu, kad įkeliu šį failą į dalinimosi repozitoriją. Aš patvirtinu, kad tai darau laikydamasis jų paslaugų teikimo sąlygų ir licencijavimo politikos.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Jeigu negalite įkelti šio failo su dalinimosi repozitorijos politika, prašome uždaryti šį dialogą ir bandyti kitą metodą.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Jūs taip pat galite norėti išbandyti [[Special:Upload|{{SITENAME}} įkėlimo puslapį]], jeigu šis failas gali būti įkeltas su jų politika.",
        "backend-fail-stream": "Negali būti apdorotas failas $1.",
        "backend-fail-backup": "Negali būti išsaugotas failas $1.",
        "backend-fail-notexists": "Failas $1 neegzistuoja.",
        "whatlinkshere-links": "← nuorodos",
        "whatlinkshere-hideredirs": "$1 nukreipimus",
        "whatlinkshere-hidetrans": "$1 įtraukimus",
-       "whatlinkshere-hidelinks": "$1 nuorodas",
-       "whatlinkshere-hideimages": "$1 failų nuorodos",
+       "whatlinkshere-hidelinks": "Paslėpti nuorodas",
+       "whatlinkshere-hideimages": "Paslėpti failo nuorodas",
        "whatlinkshere-filters": "Filtrai",
        "whatlinkshere-submit": "Eiti",
        "autoblockid": "Automatinis blokavimas # $1",
        "ipb-unblock": "Atblokuoti naudotojo vardą arba IP adresą",
        "ipb-blocklist": "Rodyti egzistuojančius blokavimus",
        "ipb-blocklist-contribs": "{{GENDER:$1|$1}} indėlis",
+       "ipb-blocklist-duration-left": "$1 kairėje",
        "unblockip": "Atblokuoti naudotoją",
        "unblockiptext": "Naudokite šią formą, kad atkurtumėte redagavimo galimybę\nankščiau užblokuotam IP adresui ar naudotojui.",
        "ipusubmit": "Atblokuoti šį adresą",
        "tooltip-ca-nstab-category": "Rodyti kategorijos puslapį",
        "tooltip-minoredit": "Pažymėti keitimą kaip smulkų",
        "tooltip-save": "Išsaugoti pakeitimus",
+       "tooltip-publish": "Skelbti jūsų pakeitimus",
        "tooltip-preview": "Pakeitimų peržiūra, prašome pažiūrėti prieš išsaugant!",
        "tooltip-diff": "Rodo, kokius pakeitimus padarėte tekste",
        "tooltip-compareselectedversions": "Žiūrėti dviejų pasirinktų puslapio versijų skirtumus.",
        "sessionprovider-generic": "$1 sesijos",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sesijos su slapukais",
        "sessionprovider-nocookies": "Slapukai gali būti neaktyvuoti. Įsitikinkite, kad slapukai yra aktyvuoti ir pradėkite vėl.",
-       "randomrootpage": "Atsitiktinis šakninis puslapis"
+       "randomrootpage": "Atsitiktinis šakninis puslapis",
+       "log-action-filter-all": "Visi",
+       "log-action-filter-newusers-autocreate": "Automatinis kūrimas",
+       "log-action-filter-protect-protect": "Apsauga"
 }
index 740b149..a1532c4 100644 (file)
        "noname": "Hmangtu hming dik a ziak lo.",
        "loginsuccesstitle": "Hlawhtling takin i lût tâ e.",
        "loginsuccess": "'''{{SITENAME}}-ah \"$1\" hming puin a i lût ta.'''",
-       "nosuchuser": "Hmingtuhming \"$1\" a awm lo.\nHmangtuhming hi hawrawppui leh të thliar hran a ngai a ni (entirna: Thara leh thara an inang lo).\nI thilziah enfiah rawh, a nih loh pawhin [[Special:UserLogin/signup|siangchan thar siam rawh]].",
+       "nosuchuser": "Hmingtuhming \"$1\" a awm lo.\nHmangtuhming hi hawrawppui leh të thliar hran a ngai a ni (entirna: Thara leh thara an inang lo).\nI thilziah enfiah rawh, a nih loh pawhin [[Special:CreateAccount|siangchan thar siam rawh]].",
        "nosuchusershort": "Hmangtu hming \"$1\" a awm lo.\nI thilziah enfiah rawh.",
        "nouserspecified": "Hmangtuhming i ziah a ngai.",
        "login-userblocked": "Hë hmangtu hi danbeh a ni. Luh phalsak a ni lo.",
index 6c0b718..3eb98b1 100644 (file)
        "site-atom-feed": "$1 Atom padeve",
        "page-rss-feed": "\"$1\" RSS barotne",
        "page-atom-feed": "\"$1\" Atom barotne",
-       "red-link-title": "$1 (lapa neeksistē)",
+       "red-link-title": "$1 (lapa nepastāv)",
        "sort-descending": "Kārtot dilstošā secībā",
        "sort-ascending": "Kārtot augošā secībā",
        "nstab-main": "Raksts",
        "noname": "Tu neesi norādījis derīgu lietotāja vārdu.",
        "loginsuccesstitle": "Ieiešana veiksmīga",
        "loginsuccess": "Tu esi ienācis {{grammar:lokatīvs|{{SITENAME}}}} kā \"$1\".",
-       "nosuchuser": "Šeit nav lietotāja ar vārdu \"$1\". Lietotājvārdi ir reģistrjutīgi (lielie un mazie burti nav viens un tas pats) Pārbaudi, vai pareizi uzrakstīts, vai arī [[Special:UserLogin/signup|izveido jaunu kontu]].",
+       "nosuchuser": "Šeit nav lietotāja ar vārdu \"$1\". Lietotājvārdi ir reģistrjutīgi (lielie un mazie burti nav viens un tas pats) Pārbaudi, vai pareizi uzrakstīts, vai arī [[Special:CreateAccount|izveido jaunu kontu]].",
        "nosuchusershort": "Šeit nav lietotāja ar vārdu \"$1\". Pārbaudi, vai nav drukas kļūda.",
        "nouserspecified": "Tev jānorāda lietotājvārds.",
        "login-userblocked": "Šis dalībnieks ir bloķēts. Pieslēgšanās nav atļauta.",
        "accmailtext": "Nejauši ģenerēta parole lietotājam [[User talk:$1|$1]] tika nosūtīta uz $2.\n\nŠī konta paroli pēc ielogošanās varēs nomainīt ''[[Special:ChangePassword|šeit]]''.",
        "newarticle": "(Jauns raksts)",
        "newarticletext": "Šajā projektā vēl nav lapas ar šādu nosaukumu.\nLai izveidotu lapu, sāc rakstīt teksta logā apakšā (par teksta formatēšanu un sīkākai informācija skatīt [$1 palīdzības lapu]).\nJa tu šeit nonāci kļūdas pēc, vienkārši uzspied <strong>back</strong> pogu pārlūkprogrammā.",
-       "anontalkpagetext": "----''Šī ir diskusiju lapa anonīmam dalībniekam, kurš vēl nav kļuvis par reģistrētu dalībnieku vai arī neizmanto savu dalībnieka vārdu. Tādēļ mums ir jāizmanto skaitliskā IP adrese, lai viņu identificētu.\nŠāda IP adrese var būt vairākiem dalībniekiem.\nJa tu esi anonīms dalībnieks un uzskati, ka tev ir adresēti neatbilstoši komentāri, lūdzu, [[Special:UserLogin/signup|kļūsti par dalībnieku]] vai arī [[Special:UserLogin|izmanto jau izveidotu dalībnieka vārdu]], lai izvairītos no turpmākām neskaidrībām un tu netiktu sajaukts ar citiem anonīmiem dalībniekiem.''",
+       "anontalkpagetext": "----''Šī ir diskusiju lapa anonīmam dalībniekam, kurš vēl nav kļuvis par reģistrētu dalībnieku vai arī neizmanto savu dalībnieka vārdu. Tādēļ mums ir jāizmanto skaitliskā IP adrese, lai viņu identificētu.\nŠāda IP adrese var būt vairākiem dalībniekiem.\nJa tu esi anonīms dalībnieks un uzskati, ka tev ir adresēti neatbilstoši komentāri, lūdzu, [[Special:CreateAccount|kļūsti par dalībnieku]] vai arī [[Special:UserLogin|izmanto jau izveidotu dalībnieka vārdu]], lai izvairītos no turpmākām neskaidrībām un tu netiktu sajaukts ar citiem anonīmiem dalībniekiem.''",
        "noarticletext": "Šajā lapā šobrīd nav nekāda teksta, tu vari [[Special:Search/{{PAGENAME}}|meklēt citās lapās pēc šīs lapas nosaukuma]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} meklēt saistītos reģistru ierakstos] vai arī [{{fullurl:{{FULLPAGENAME}}|action=edit}} sākt rediģēt šo lapu]</span>.",
        "noarticletext-nopermission": "Šajā lapā pašlaik nav nekāda teksta.\nTu vari [[Special:Search/{{PAGENAME}}|meklēt šīs lapas nosaukumu]] citās lapās,\nvai <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} meklēt saistītus reģistru ierakstus]</span>, bet jums nav atļauja izveidot si lapu.",
        "userpage-userdoesnotexist": "Lietotājs \"<nowiki>$1</nowiki>\" nav reģistrēts.\nLūdzu, pārliecinies vai vēlies izveidot/izmainīt šo lapu.",
        "special-characters-group-sinhala": "Singāļu",
        "special-characters-group-gujarati": "Gudžarati",
        "mw-widgets-dateinput-no-date": "Nav izvēlēts datums",
+       "mw-widgets-titleinput-description-new-page": "lapa vēl nepastāv",
        "api-error-blacklisted": "Lūdzu, izvēlieties citu, aprakstošu nosaukumu!"
 }
index 1272972..eee6234 100644 (file)
        "noname": "缺簿名,或不格也。",
        "loginsuccesstitle": "登簿成矣",
        "loginsuccess": "'''$1'''登{{SITENAME}}矣",
-       "nosuchuser": "查無此人。惠請更名,查大小寫或[[Special:UserLogin/signup|立此簿]]。",
+       "nosuchuser": "查無此人。惠請更名,查大小寫或[[Special:CreateAccount|立此簿]]。",
        "nosuchusershort": "查無\"$1\",惠核之。",
        "nouserspecified": "簿名必須",
        "login-userblocked": "此簿見錮矣。是之登未見許可。",
        "accmailtext": "\"$1\"之新符節至$2矣\n\n此符節可於登簿後改",
        "newarticle": "撰",
        "newarticletext": "此頁尚缺。欲補,撰於下,有惑見[$1 助]。\n誤入者,返前即可。",
-       "anontalkpagetext": "----''此匿論也,為未簿或不簿者設,IP俱錄以辨人焉。然IP不獨,恐生亂象,不喜惠[[Special:UserLogin/signup|增]][[Special:UserLogin|登簿]]遠之。",
+       "anontalkpagetext": "----''此匿論也,為未簿或不簿者設,IP俱錄以辨人焉。然IP不獨,恐生亂象,不喜惠[[Special:CreateAccount|增]][[Special:UserLogin|登簿]]遠之。",
        "noarticletext": "查無此文。[[Special:Search/{{PAGENAME}}|尋題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 尋誌],\n或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 纂頁]</span>。",
        "noarticletext-nopermission": "查無此文。[[Special:Search/{{PAGENAME}}|尋題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 尋誌],\n或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 纂頁]</span>。",
        "userpage-userdoesnotexist": "「<nowiki>$1</nowiki>」之簿未增也。請建纂本頁前查之。",
index 180f350..c968a69 100644 (file)
        "noname": "अहाँ वैध प्रयोक्तानाम नै देने छी।",
        "loginsuccesstitle": "सम्प्रवेश भएल",
        "loginsuccess": "'''अहाँ सम्प्रवेश केलहुँ {{SITENAME}} \"$1\".'''क रूपमे।",
-       "nosuchuser": "\"$1\" नामसँ कोनो प्रयोक्ता नै अछि।\nप्रयोक्तानाम ब्रह्मक्षर-लघ्वक्षर भेद युक्त अछि।\nअपन ह्रिजै जाँची, वा [[Special:UserLogin/signup|नव खाता बनाबी]] ।",
+       "nosuchuser": "\"$1\" नामसँ कोनो प्रयोक्ता नै अछि।\nप्रयोक्तानाम ब्रह्मक्षर-लघ्वक्षर भेद युक्त अछि।\nअपन ह्रिजै जाँची, वा [[Special:CreateAccount|नव खाता बनाबी]] ।",
        "nosuchusershort": "\"$1\" नाम्ना कोनो प्रयोक्ता नै अछि।\nअपन हिजए सुधारी।",
        "nouserspecified": "अहाँक एकटा प्रयोक्तानाम देबऽ पडत।",
        "login-userblocked": "ई प्रयोक्ता प्रतिबन्धित अछि। सम्प्रवेशक अधिकार नै अछि।",
        "accmailtext": "एकटा बिना क्रमबला निर्माण पद्धतिसँ कूटशब्दक निर्माण [[User talk:$1|$1]] लेल $2 केँ पठाएल गेल अछि।\n\nऐ खाताक कूटशब्द बदलल जा सकैए ''[[Special:ChangePassword|कूटशब्द बदलू]]'' पन्नापर सम्प्रवेश केलाक बाद।",
        "newarticle": "(नव)",
        "newarticletext": "अहाँ एहेन पन्नाक लिंकक अनुसरण कऽ आएल छी जे पन्ना अखन बनले नै अछि।\nपन्ना बनेबाक लेल नीचाँक बक्शामे टाइप केनाइ शुरू करू (देखू [$1  सहायता पन्ना] विषेष जानकारी लेल)।",
-       "anontalkpagetext": "----'' ई एकटा अनाम प्रयोक्ताक लेल वार्ता पन्ना छी जे अखन धरि अपन खाता नै खोलने छथि, वा जे एकर प्रयोग नै करै छथि।\nहमरा सभकेँ तइ लेल अंकीय अनिकेतक प्रयोग हुनका देखार करबा लेल करऽ पड़ि रहल अछि।\nऐ तरहक अनिकेत अनेक प्रयोक्ता द्वारा साझी कएल जा सकैत अछि।\nजँ अहाँ अनाम प्रयोक्ता छी आ बुझै छी जे बिना मतलबक टिप्पणी अहाँ दिस देल जा रहल अछि, तँ कृपा कऽ [[Special:UserLogin/signup|एकटा खाता खोलू]] वा [[Special:UserLogin|सम्प्रवेश]] जइसँ भविष्यमे आन अनाम प्रयोक्तासँ अहाँकेँ दिक्कत नै हो।''",
+       "anontalkpagetext": "----'' ई एकटा अनाम प्रयोक्ताक लेल वार्ता पन्ना छी जे अखन धरि अपन खाता नै खोलने छथि, वा जे एकर प्रयोग नै करै छथि।\nहमरा सभकेँ तइ लेल अंकीय अनिकेतक प्रयोग हुनका देखार करबा लेल करऽ पड़ि रहल अछि।\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}}|ऐ पन्ना शीर्षक लेल ताकू]]",
        "missing-revision": "\"{{FULLPAGENAME}}\" पन्ना के अवतरण #$1 मौजूद नई अछि ।\n\nओन त ई एक हट्याल गेल पन्ना के पुराना कडी पे क्लिक करबाक कारण से होएत अछि।\nअधिक जानकारी के लेल आहाँ [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटाबै के लॉग] देख सकै अछि।",
        "upload-form-label-infoform-description": "विवरण",
        "upload-form-label-usage-title": "उपयोग",
        "upload-form-label-usage-filename": "फाइलक नाम",
-       "foreign-structured-upload-form-label-own-work": "ई हमर काज छी",
-       "foreign-structured-upload-form-label-infoform-categories": "श्रेणीसभ",
-       "foreign-structured-upload-form-label-infoform-date": "दिनाङ्क",
+       "upload-form-label-own-work": "ई हमर काज छी",
+       "upload-form-label-infoform-categories": "श्रेणीसभ",
+       "upload-form-label-infoform-date": "दिनाङ्क",
        "backend-fail-stream": "\"$1\" केँ नै स्ट्रिम क सकल।",
        "backend-fail-backup": "\"$1\" केँ नै ब्याकअप क सकल।",
        "backend-fail-notexists": "फाइल $1 नै अछि।",
index f6f6ef7..b2eed98 100644 (file)
        "noname": "Jeneng panganggo sing Rika lebokna ora sah.",
        "loginsuccesstitle": "Sukses mlebu log",
        "loginsuccess": "'''Rika sekiye mlebu log nang {{SITENAME}} nganggo jeneng \"$1\".'''",
-       "nosuchuser": "Ora ana panganggo sing jenenge \"$1\".\nJeneng panganggo kuwe mbedakna kapitalisasi.\nPriksa maning ejaane Rika, utawa [[Special:UserLogin/signup|gawe akun anyar]]",
+       "nosuchuser": "Ora ana panganggo sing jenenge \"$1\".\nJeneng panganggo kuwe mbedakna kapitalisasi.\nPriksa maning ejaane Rika, utawa [[Special:CreateAccount|gawe akun anyar]]",
        "nosuchusershort": "Ora ana panganggo sing jenenge \"$1\".\nJajal dipriksa maning ejaane Rika.",
        "nouserspecified": "Rika kudu nglebokna jeneng panganggo.",
        "login-userblocked": "Panganggo kiye diblok. Ora olih mlebu log.",
index bd32f5a..0031621 100644 (file)
        "noname": "Тон изеть пута кемокстаф тиить лемоц.",
        "loginsuccesstitle": "Сувамась ётась лац",
        "loginsuccess": "'''Тон сувать {{SITENAME}}-с кода \"$1\".'''",
-       "nosuchuser": "Тиись \"$1\" лемса аш.\nВанк, улема, тон сёрмадыть лемть аф лац.\nИлякс тондейть эряви [[Special:UserLogin/signup|сёрматфтомс одукс]].",
+       "nosuchuser": "Тиись \"$1\" лемса аш.\nВанк, улема, тон сёрмадыть лемть аф лац.\nИлякс тондейть эряви [[Special:CreateAccount|сёрматфтомс одукс]].",
        "nosuchusershort": "Тиись \"$1\" лемса аш. Ванк, улема, тон сёрмадыть лемть аф лац.",
        "nouserspecified": "Тиить лемсь эряви.",
        "login-userblocked": "Тиись перяф. Сувама кардаф.",
        "accmailtext": "Апак арьсек тиф [[User talk:$1|$1]]нь сувама валоц кучфоль $2с.\n\nТя од сёрматфтомать сувама валть ули кода полафтомс ''[[Special:ChangePassword|сувама валонь полафтома]]'' лопаса сувамда меле.",
        "newarticle": "(Од)",
        "newarticletext": "Тон сать лопас кона нинге изь тие.\nСёрматк паксяв алу тя лопать тиеманкса\n(ванк [$1 лезкс лопа] лездама информациенкса).\nСаньдярять тяза эльбядезь, люпштак пунять '''меки''' тонь интернет полатксонь вальмасонза.",
-       "anontalkpagetext": "----''Тя корхтама лопать тизе лемфтома тиись кона нинге аф сёрматфтф эли кона эсь сёрматфтомас аф кунци. Тяса минь нолдамс сонь лувомтяшкснень IP адресонц тиить содафтоманкса.\nТя IP адресть вельде ули кода сашендомс иля тиихненди.\nУляндяряйхть иля тиинь мяльполатксонза конатнень аф видекста лувозь тоннекс, тондейть пароль сай пингста [[Special:UserLogin/signup|сёрматфтомс/сувамс]] иля лемфтома тиихнень марс аф шовореманкса.''",
+       "anontalkpagetext": "----''Тя корхтама лопать тизе лемфтома тиись кона нинге аф сёрматфтф эли кона эсь сёрматфтомас аф кунци. Тяса минь нолдамс сонь лувомтяшкснень IP адресонц тиить содафтоманкса.\nТя IP адресть вельде ули кода сашендомс иля тиихненди.\nУляндяряйхть иля тиинь мяльполатксонза конатнень аф видекста лувозь тоннекс, тондейть пароль сай пингста [[Special:CreateAccount|сёрматфтомс/сувамс]] иля лемфтома тиихнень марс аф шовореманкса.''",
        "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}}}} вешендемс малады лувомава]</span>, аньцек тонь аш мярьговомаце тя лопать ушедомс.",
        "userpage-userdoesnotexist": "Сёрматфтомась «<nowiki>$1</nowiki>» лемса аш. Арьсек лацкаста, афкукс тонь улендяряй мяльце тиемс эли полафтомс тя лопать.",
index b7a8909..b1d70c2 100644 (file)
        "accmailtext": "Nalefa tany amin'i $2 ny tenimiafina kisendra ho an'ny kaonty [[User talk:$1|$1]]! Azo ovaina eo amin'i ''[[Special:ChangePassword|Manova tenimiafina]]'' izany amin'ny alalan'ny fidirana.",
        "newarticle": "(Vaovao)",
        "newarticletext": "Mbola tsy misy ity takelaka ity koa azonao atao ny mamorona azy eto ambany. Jereo ny [$1 Fanoroana] raha misy fanazavana ilainao.\n\nRaha toa moa ka tsy nieritreritra ny hamorona ity takelaka ity ianao dia miverena etsy amin'ny fandraisana.",
-       "anontalkpagetext": "----<i>Ity pejy ity dia pejin-dresak'olona tsy nanokatra na tsy nampiasa ny kaontiny.\nNoho izany dia ilainay ny mampiasa ny adiresy IP-ny hanondroana azy. Mety zarazarain'olona maro ny adiresy IP iray. Raha mpikambana tsy nisoratra anarana ianao, ka raha mahita resaka ts ho anao, azonao atao ny [[Special:UserLogin/signup|manokatra kaonty]], na [[Special:UserLogin|miditra]] mba tsy ho voafangarao amin'ny mpikambana hafa tsy nisoratra anarana.</i>",
+       "anontalkpagetext": "----<i>Ity pejy ity dia pejin-dresak'olona tsy nanokatra na tsy nampiasa ny kaontiny.\nNoho izany dia ilainay ny mampiasa ny adiresy IP-ny hanondroana azy. Mety zarazarain'olona maro ny adiresy IP iray. Raha mpikambana tsy nisoratra anarana ianao, ka raha mahita resaka ts ho anao, azonao atao ny [[Special:CreateAccount|manokatra kaonty]], na [[Special:UserLogin|miditra]] mba tsy ho voafangarao amin'ny mpikambana hafa tsy nisoratra anarana.</i>",
        "noarticletext": "'''Tsy mbola nisy namorona io lahatsoratra io.\nAzonao atao ny [[Special:Search/{{PAGENAME}}|Tadiavo ny momba ny {{PAGENAME}}]].'''\n* '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} Na forony eto ny lahatsoratra momba ny {{PAGENAME}}]'''.",
        "noarticletext-nopermission": "Mbola tsy misy lahatsoratra ao amin'io pejy io.\n\nAzonao atao ny [[Special:Search/{{PAGENAME}}|mikaroka ity lohateny ity]] eny amin'ny pejy hafa na <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mitady ao amin'ny laogy misy fifandraisana]</span>, fa tsy azonao atao ny mamorona ity pejy ity.",
        "missing-revision": "Tsy misy ny santiôna #$1 ny pejy \"{{FULLPAGENAME}}\".\n\nMitranga izany rehefa manaraka rohin-tantara tola mankany amina pejy voafafa. Ahitana fampahalalana fanampiny ny  [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} laogim-pamafana].",
        "upload-form-label-infoform-description": "Famisavisana",
        "upload-form-label-usage-title": "Fampiasana",
        "upload-form-label-usage-filename": "Anaran-drakitra",
-       "foreign-structured-upload-form-label-own-work": "Asako ity",
-       "foreign-structured-upload-form-label-infoform-categories": "Sokajy",
-       "foreign-structured-upload-form-label-infoform-date": "Daty",
+       "upload-form-label-own-work": "Asako ity",
+       "upload-form-label-infoform-categories": "Sokajy",
+       "upload-form-label-infoform-date": "Daty",
        "backend-fail-stream": "Tsy afaka mamaky ilay rakitra $1.",
        "backend-fail-backup": "Tsy afaka mitahiry ilay rakitra $1.",
        "backend-fail-notexists": "Tsy misy ilay rakitra $1.",
index 5df7821..c1006b7 100644 (file)
        "userlogin-resetpassword-link": "Шолыпмутым монденат?",
        "createaccountmail": "Кӱчык жаплан чокым ыштыме шолыпмутым мылам e-mail дене колташ",
        "createacct-benefit-heading": "{{SITENAME}} тендан гаяк еҥ-влак дене ыштен шындалтын.",
-       "nosuchuser": "\"$1\" лӱман пайдаланыше уке.\nПайдаланышын лӱмыштӧ йӱкпале-влакын кугытшо тӱрыс лийшаш.\nЛӱмым чын возымым терге але [[Special:UserLogin/signup|регистрацийым эрте]].",
+       "nosuchuser": "\"$1\" лӱман пайдаланыше уке.\nПайдаланышын лӱмыштӧ йӱкпале-влакын кугытшо тӱрыс лийшаш.\nЛӱмым чын возымым терге але [[Special:CreateAccount|регистрацийым эрте]].",
        "nosuchusershort": "\"$1\" лӱман пайдаланыше уке.\nЛӱмым чын возымым терге.",
        "nouserspecified": "Тылат пайдаланышын лӱмжым пуртыман.",
        "wrongpassword": "Тый йоҥылыш шолыпмутым пуртенат.\nЭше ик гана ыштен ончо.",
index 7b6abe6..38a19d1 100644 (file)
        "noname": "Namo pangguno nan Sanak masuakan indak sah.",
        "loginsuccesstitle": "Bahasil masuak log",
        "loginsuccess": "'''Sanak kini lah masuak log di {{SITENAME}} sabagai \"$1\".'''",
-       "nosuchuser": "Indak ado pangguno jo namo \"$1\".\nNamo pangguno mambedoan kapitalisasi.\nPariso baliak ejaan Sanak, atau [[Special:UserLogin/signup|buek akun baru]].",
+       "nosuchuser": "Indak ado pangguno jo namo \"$1\".\nNamo pangguno mambedoan kapitalisasi.\nPariso baliak ejaan Sanak, atau [[Special:CreateAccount|buek akun baru]].",
        "nosuchusershort": "Indak ado pangguno jo namo \"$1\".\nCubo pariso baliak ejaan Sanak.",
        "nouserspecified": "Sanak harus mamasuakan namo pangguno.",
        "login-userblocked": "Pangguno ko kanai sakek. Indak diizinan untuak masuak log.",
        "accmailtext": "Sabuah kato sandi acak untuak [[User talk:$1|$1]] alah dibuek dan dikiriman ka $2.\n\nKato sandi untuak akun baharu iko dapek diubah di laman ''[[Special:ChangePassword|pangubahan kato sandi]]'' satalah masuak log.",
        "newarticle": "(Baru)",
        "newarticletext": "Laman nan Sanak cari alun ado.\nUntuak mambuek laman tu, mulailah jo manulih dalam kotak di bawah (caliak [$1 laman bantuan] untuak informasi labiah lanjuik).\nJikok Sanak indak sangajo sampai ka laman ko, klik tombol '''back''' pado paramban web Sanak.",
-       "anontalkpagetext": "----''Iko adolah laman rundiang saurang pangguno anonim nan alun mambuek akun atau indak manggunoannyo.\nJadi, kami tapaso mamakai alamat IP nan takaik untuak mangenalinyo.\nJikok Sanak adolah pangguno anonim dan maraso mandapek komentar nan indak lamak nan ditujuan langsung kapado Sanak, cubolah [[Special:UserLogin/signup|mambuek akun]] atau [[Special:UserLogin|masuak log]] guno manghindari karancuan jo pangguno anonim lainnyo.''",
+       "anontalkpagetext": "----''Iko adolah laman rundiang saurang pangguno anonim nan alun mambuek akun atau indak manggunoannyo.\nJadi, kami tapaso mamakai alamat IP nan takaik untuak mangenalinyo.\nJikok Sanak adolah pangguno anonim dan maraso mandapek komentar nan indak lamak nan ditujuan langsung kapado Sanak, cubolah [[Special:CreateAccount|mambuek akun]] atau [[Special:UserLogin|masuak log]] guno manghindari karancuan jo pangguno anonim lainnyo.''",
        "noarticletext": "Kini ko indak ado teks di laman ko.\nSanak dapek [[Special:Search/{{PAGENAME}}|malakuan pancarian untuak judul laman ko]] di laman-laman lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mancari log takaik], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} manyuntiang laman iko]</span>.",
        "noarticletext-nopermission": "Kini ko indak ado teks dalam laman ko.\nSanak dapek [[Special:Search/{{PAGENAME}}|malakukan pancarian untuak judul laman ko]] di laman lain, atau <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mancahari log takaik] </span>, tapi Sanak indak punyo izin untuak mambuek laman ko.",
        "missing-revision": "Revisi $1 di laman nan banamo \"{{FULLPAGENAME}}\" ko indak ado.\n\nHal iko biasonyo disababkan dek pautan sijarah nan alah kadaluarsa ka laman nan alah diapuih.\nRinciannyo dapek dicaliak di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log pangapuihan].",
        "creditspage": "Panghargaan laman",
        "spam_blanking": "Sado revisi nan ado pautan ka $1, kosong",
        "spam_deleting": "Sado revisi nan ado pautan ka $1, dihapuih",
-       "simpleantispam-label": "Pamarisoan anti-spam.\nMasukan ko '''DILARANG'''!",
+       "simpleantispam-label": "Pamarisoan anti-spam.\n<strong>Jan</strong> diisi!",
        "pageinfo-title": "Informasi untuak \"$1\"",
        "pageinfo-not-current": "Maaf, indak dapek mangagiahan informasi ko ka revisi lamo.",
        "pageinfo-header-basic": "Informasi dasar",
index 902150a..accb759 100644 (file)
        "noname": "Внесовте погрешно корисничко име.",
        "loginsuccesstitle": "Најавени сте",
        "loginsuccess": "Сега сте најавени на {{SITENAME}} како „$1“.",
-       "nosuchuser": "Нема корисник со името „$1“.\nКорисничките имиња разликуваат мали и големи букви.\nПроверете да не сте направиле грешка во пишувањето, или [[Special:UserLogin/signup|создајте нова корисничка сметка]].",
+       "nosuchuser": "Нема корисник со името „$1“.\nКорисничките имиња разликуваат мали и големи букви.\nПроверете да не сте направиле грешка во пишувањето, или [[Special:CreateAccount|создајте нова корисничка сметка]].",
        "nosuchusershort": "Нема корисник со името „$1“.\nПроверете дали правилно сте напишале.",
        "nouserspecified": "Мора да наведете корисничко име.",
        "login-userblocked": "Овој корисник е блокиран. Најавувањето не е дозволено.",
        "accmailtext": "На $2 е спратена е случајно создадена лозинка за [[User talk:$1|$1]] е испратена. Истата може да се смени на страницата ''[[Special:ChangePassword|Менување на лозинка]]'' откако ќе се најавите.",
        "newarticle": "(нова)",
        "newarticletext": "Проследивте врска до страница која не постои.\nЗа да ја создадете страницата, напишете текст во полето подолу ([$1 помош]). Ако сте овде по грешка, само систнете на копчето '''назад''' во вашиот прелистувач.",
-       "anontalkpagetext": "----''Ова е страница за разговор со анонимен корисник кој сè уште не регистрирал корисничка сметка или не ја користи.\nЗатоа мораме да ја користиме неговата бројчена IP-адреса за да го препознаеме.\nЕдна ваква IP-адреса може да ја делат повеќе корисници.\nАко сте анонимен корисник и сметате дека кон вас се упатени нерелевантни коментари, тогаш [[Special:UserLogin/signup|создајте корисничка сметка]] или [[Special:UserLogin|најавете се]] за да избегнете поистоветување со други анонимни корисници во иднина.''",
+       "anontalkpagetext": "----''Ова е страница за разговор со анонимен корисник кој сè уште не регистрирал корисничка сметка или не ја користи.\nЗатоа мораме да ја користиме неговата бројчена IP-адреса за да го препознаеме.\nЕдна ваква IP-адреса може да ја делат повеќе корисници.\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}}|дали насловот се споменува]] во други статии или пак да <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарате поврзаните дневници]</span>, но немате дозвола да ја создадете страницата.",
        "missing-revision": "Не ја пронајдов преработката бр. $1 на страницата со наслов „{{FULLPAGENAME}}“.\n\nОва обично се должи на застарена врска за разлики што води кон избришана страница.\nПовеќе подробности ќе најдете во [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневникот на бришења].",
        "upload-form-label-infoform-description-tooltip": "Накратко опишете го сето она што е значајно за делото. Ако е фотографија, споменете ги главните нешта што се прикажани на неа, настанот или местото.",
        "upload-form-label-usage-title": "Употреба",
        "upload-form-label-usage-filename": "Назив на податотеката",
-       "foreign-structured-upload-form-label-own-work": "Ова е мое дело",
-       "foreign-structured-upload-form-label-infoform-categories": "Категории",
-       "foreign-structured-upload-form-label-infoform-date": "Датум",
-       "foreign-structured-upload-form-label-own-work-message-local": "Потврдувам дека податотекава ја подигам во согласност со уловите на користење и правилата за лиценцирање на {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Ако не сте во можност да ја подигнете податотекава согласно правилата на {{SITENAME}}. Затворете го дијалогов и обидете се на друг начин.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Можете да ја пробате и [[Special:Upload|стандардната страница за подигање]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Разбирам дека ја подигам податотекава на заедничко складиште. Потврдувам дека со тоа ги почитувам тамошните услови на користење и лиценцните правила.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Ако не сте во можност да ја подигнете податотекава во склад со правилата на заедничкото складиште, би ве замолиле да го затворите дијалогов и да пробате на друг начин.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Можете да се обидете и на [[Special:Upload|страницата за подигање на {{SITENAME}}]], доколку податотеката може да се подигне под тамошните правила.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Сведочам дека јас сум имател на авторските права на оваа податотека, дека се согласувам дека неотповикливо ја објавувам на Ризницата под лиценцата [https://creativecommons.org/licenses/by-sa/4.0/deed.mk Криејтив комонс Наведи извор-Сподели под исти услови 4.0] и дека се согласувам да се придржувам до [https://wikimediafoundation.org/wiki/Terms_of_Use/mk Условите на употреба].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Доколку вие не сте имател на авторските права на податотекава, или пак сакате да ја објавите под поинаква лиценца, веројатно ќе треба да се послужите со [https://commons.wikimedia.org/wiki/Special:UploadWizard?uselang=mk Помошникот за подигање].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Можете да се обидете и на [[Special:Upload|страницата за подигање на {{SITENAME}}]], доколку податотеката може да се подигне под тамошните правила.",
+       "upload-form-label-own-work": "Ова е мое дело",
+       "upload-form-label-infoform-categories": "Категории",
+       "upload-form-label-infoform-date": "Датум",
+       "upload-form-label-own-work-message-generic-local": "Потврдувам дека податотекава ја подигам во согласност со уловите на користење и правилата за лиценцирање на {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Ако не сте во можност да ја подигнете податотекава согласно правилата на {{SITENAME}}. Затворете го дијалогов и обидете се на друг начин.",
+       "upload-form-label-not-own-work-local-generic-local": "Можете да ја пробате и [[Special:Upload|стандардната страница за подигање]].",
+       "upload-form-label-own-work-message-generic-foreign": "Разбирам дека ја подигам податотекава на заедничко складиште. Потврдувам дека со тоа ги почитувам тамошните услови на користење и лиценцните правила.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Ако не сте во можност да ја подигнете податотекава во склад со правилата на заедничкото складиште, би ве замолиле да го затворите дијалогов и да пробате на друг начин.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Можете да се обидете и на [[Special:Upload|страницата за подигање на {{SITENAME}}]], доколку податотеката може да се подигне под тамошните правила.",
        "backend-fail-stream": "Не можев да ја емитувам податотеката $1.",
        "backend-fail-backup": "Не можев да направам резерва на податотеката $1.",
        "backend-fail-notexists": "Податотеката $1 не постои.",
        "whatlinkshere-prev": "{{PLURAL:$1|претходна|претходни $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|следна|следни $1}}",
        "whatlinkshere-links": "← врски",
-       "whatlinkshere-hideredirs": "$1 пренасочувања",
-       "whatlinkshere-hidetrans": "$1 превметнувања",
-       "whatlinkshere-hidelinks": "$1 врски",
+       "whatlinkshere-hideredirs": "Скриј пренасочувања",
+       "whatlinkshere-hidetrans": "Скриј превметнувања",
+       "whatlinkshere-hidelinks": "Скриј врски",
        "whatlinkshere-hideimages": "$1 врски кон податотека",
        "whatlinkshere-filters": "Филтри",
        "whatlinkshere-submit": "Дај",
index 560ecb0..0ec3c1b 100644 (file)
        "noname": "താങ്കൾ സാധുവായ ഉപയോക്തൃനാമം സൂചിപ്പിച്ചിട്ടില്ല.",
        "loginsuccesstitle": "സ്വാഗതം! \nതാങ്കൾ വിജയകരമായി പ്രവേശിച്ചിരിക്കുന്നു.",
        "loginsuccess": "'''{{SITENAME}} സംരംഭത്തിൽ \"$1\" എന്ന പേരിൽ താങ്കൾ ലോഗിൻ ചെയ്തിരിക്കുന്നു.'''",
-       "nosuchuser": "ഇതുവരെ \"$1\" എന്ന പേരിൽ ആരും അംഗത്വമെടുത്തിട്ടില്ല.\nദയവായി അക്ഷരപ്പിശകുകൾ പരിശോധിക്കുക, അല്ലെങ്കിൽ\nപുതിയ [[Special:UserLogin/signup|അംഗത്വമെടുക്കുക]].",
+       "nosuchuser": "ഇതുവരെ \"$1\" എന്ന പേരിൽ ആരും അംഗത്വമെടുത്തിട്ടില്ല.\nദയവായി അക്ഷരപ്പിശകുകൾ പരിശോധിക്കുക, അല്ലെങ്കിൽ\nപുതിയ [[Special:CreateAccount|അംഗത്വമെടുക്കുക]].",
        "nosuchusershort": "\"$1\" എന്ന പേരിൽ ഒരു ഉപയോക്താവ് ഇല്ല. അക്ഷരങ്ങൾ ഒന്നു കൂടി പരിശോധിക്കുക.",
        "nouserspecified": "ഉപയോക്തൃനാമം നിർബന്ധമായും ചേർക്കണം.",
        "login-userblocked": "ഈ ഉപയോക്താവ് തടയപ്പെട്ടിരിക്കുന്നു. പ്രവേശനം അനുവദിക്കുന്നില്ല.",
        "accmailtext": "[[User talk:$1|$1]] എന്ന ഉപയോക്താവിനുള്ള ക്രമരഹിതമായി നിർമ്മിച്ച രഹസ്യവാക്ക് $2 എന്ന വിലാസത്തിലേക്ക് അയച്ചിട്ടുണ്ട്. പ്രവേശിച്ചതിനു ശേഷം ഇത് ''[[Special:ChangePassword|രഹസ്യവാക്ക് മാറ്റുക]]'' എന്ന താളിൽ മാറ്റാവുന്നതാണ്.",
        "newarticle": "(പുതിയത്)",
        "newarticletext": "ഇതുവരെ നിലവിലില്ലാത്ത ഒരു താൾ സൃഷ്ടിക്കാനുള്ള ശ്രമത്തിലാണ് താങ്കൾ. അതിനായി താഴെ ആവശ്യമുള്ള വിവരങ്ങൾ എഴുതിച്ചേർത്ത് സേവ് ചെയ്യുക (കൂടുതൽ വിവരങ്ങൾക്ക് [$1 സഹായം താൾ] കാണുക). താങ്കളിവിടെ അബദ്ധത്തിൽ വന്നതാണെങ്കിൽ ബ്രൗസറിന്റെ ബാക്ക് ബട്ടൺ ഞെക്കിയാൽ തിരിച്ചുപോകാം.",
-       "anontalkpagetext": "----\n{| class=\"messagebox standard-talk\" style=\"border: 1px solid #B3B300; background-color:#FFFFBF; text-align: left;\"\n|\n''ഇതുവരെ അംഗത്വം എടുക്കാതിരിക്കുകയോ, നിലവിലുള്ള അംഗത്വം ഉപയോഗിക്കാതിരിക്കുകയോ ചെയ്യുന്ന '''ഒരു അജ്ഞാത ഉപയോക്താവിന്റെ സം‌വാദം താളാണിത്'''.\nഅതിനാൽ അദ്ദേഹത്തെ തിരിച്ചറിയുവാൻ അക്കരൂപത്തിലുള്ള ഐ.പി. വിലാസം ഉപയോഗിക്കേണ്ടതുണ്ട്. ഇത്തരം ഒരു ഐ.പി. വിലാസം പല ഉപയോക്താക്കൾ പങ്കുവെക്കുന്നുണ്ടാവാം.\nതാങ്കൾ ഈ സന്ദേശം ലഭിച്ച ഒരു അജ്ഞാത ഉപയോക്താവാണെങ്കിൽ, ഭാവിയിൽ ഇതര ഉപയോക്താക്കളുമായി ഉണ്ടായേക്കാവുന്ന ആശയക്കുഴപ്പം ഒഴിവാക്കാൻ ദയവായി [[Special:UserLogin/signup|ഒരു അംഗത്വമെടുക്കുക]] അല്ലെങ്കിൽ  [[Special:UserLogin|പ്രവേശിക്കുക]].\n|}",
+       "anontalkpagetext": "----\n{| class=\"messagebox standard-talk\" style=\"border: 1px solid #B3B300; background-color:#FFFFBF; text-align: left;\"\n|\n''ഇതുവരെ അംഗത്വം എടുക്കാതിരിക്കുകയോ, നിലവിലുള്ള അംഗത്വം ഉപയോഗിക്കാതിരിക്കുകയോ ചെയ്യുന്ന '''ഒരു അജ്ഞാത ഉപയോക്താവിന്റെ സം‌വാദം താളാണിത്'''.\nഅതിനാൽ അദ്ദേഹത്തെ തിരിച്ചറിയുവാൻ അക്കരൂപത്തിലുള്ള ഐ.പി. വിലാസം ഉപയോഗിക്കേണ്ടതുണ്ട്. ഇത്തരം ഒരു ഐ.പി. വിലാസം പല ഉപയോക്താക്കൾ പങ്കുവെക്കുന്നുണ്ടാവാം.\nതാങ്കൾ ഈ സന്ദേശം ലഭിച്ച ഒരു അജ്ഞാത ഉപയോക്താവാണെങ്കിൽ, ഭാവിയിൽ ഇതര ഉപയോക്താക്കളുമായി ഉണ്ടായേക്കാവുന്ന ആശയക്കുഴപ്പം ഒഴിവാക്കാൻ ദയവായി [[Special:CreateAccount|ഒരു അംഗത്വമെടുക്കുക]] അല്ലെങ്കിൽ  [[Special:UserLogin|പ്രവേശിക്കുക]].\n|}",
        "noarticletext": "ഈ താളിൽ ഇതുവരെ ഉള്ളടക്കം ആയിട്ടില്ല.\nതാങ്കൾക്ക് മറ്റുതാളുകളിൽ [[Special:Search/{{PAGENAME}}|ഇതേക്കുറിച്ച് അന്വേഷിക്കുകയോ]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ബന്ധപ്പെട്ട രേഖകൾ പരിശോധിക്കുകയോ], [{{fullurl:{{FULLPAGENAME}}|action=edit}} ഈ താൾ തിരുത്തുകയോ ചെയ്യാവുന്നതാണ്]</span>.",
        "noarticletext-nopermission": "ഇപ്പോൾ ഈ താളിൽ എഴുത്തുകളൊന്നും ഇല്ല.\nതാങ്കൾക്ക് മറ്റു താളുകളിൽ [[Special:Search/{{PAGENAME}}|ഈ താളിന്റെ തലക്കെട്ടിനായി തിരയാവുന്നതാണ്‌]], അല്ലെങ്കിൽ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ബന്ധപ്പെട്ട രേഖകൾ പരിശോധിക്കാവുന്നതാണ്‌]</span>. പക്ഷേ ഈ താൾ സൃഷ്ടിക്കാൻ താങ്കൾക്ക് അനുവാദമില്ല.",
        "missing-revision": "\"{{FULLPAGENAME}}\" എന്ന താളിന്റെ #$1 എന്ന നാൾപ്പതിപ്പ് നിലവിലില്ല.\n\nമായ്ക്കപ്പെട്ട താളിന്റെ കാലഹരണപ്പെട്ട നാൾവഴി കണ്ണി ഉപയോഗിച്ചാലാണ് സാധാരണ ഇങ്ങനെ സംഭവിക്കുക.\nകൂടുതൽ വിവരങ്ങൾ [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} മായ്ക്കൽ രേഖയിൽ] കാണാവുന്നതാണ്.",
        "upload-form-label-infoform-description-tooltip": "ഈ കൃതിയെക്കുറിച്ചുള്ള ശ്രദ്ധേയമായ എല്ലാം ചുരുക്കി ചേർക്കുക.\nഒരു ഫോട്ടോയിൽ, പതിഞ്ഞിരിക്കുന്ന പ്രധാന കാര്യം, വേള, സ്ഥലം തുടങ്ങിയ വിവരങ്ങൾ ഉൾപ്പെടുത്താം.",
        "upload-form-label-usage-title": "ഉപയോഗം",
        "upload-form-label-usage-filename": "പ്രമാണത്തിന്റെ പേര്",
-       "foreign-structured-upload-form-label-own-work": "ഇതെന്റെ സ്വന്തം സൃഷ്ടി ആണ്",
-       "foreign-structured-upload-form-label-infoform-categories": "വർഗ്ഗങ്ങൾ",
-       "foreign-structured-upload-form-label-infoform-date": "തീയതി",
-       "foreign-structured-upload-form-label-own-work-message-local": "{{SITENAME}} സംരംഭത്തിലെ സേവന നിബന്ധനകൾക്കും ഉപയോഗാനുമതി നയങ്ങൾക്കും അനുസരിച്ചാണ് ഈ പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നതെന്ന് ഞാൻ സ്ഥിരീകരിക്കുന്നു.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "{{SITENAME}} സംരംഭത്തിലെ നയങ്ങളനുസരിച്ച് താങ്കൾക്ക് ഈ പ്രമാണം അപ്‌ലോഡ് ചെയ്യാൻ കഴിയില്ലെങ്കിൽ, ദയവായി ഇത് അടച്ച് മറ്റൊരു മാർഗ്ഗം ശ്രമിക്കുക.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "താങ്കൾക്ക് [[Special:Upload|സ്വതേ ഉള്ള അപ്‌ലോഡ് താളും]] പരിശോധിക്കാവുന്നതാണ്.",
-       "foreign-structured-upload-form-label-own-work-message-default": "ഈ പ്രമാണം പങ്ക് വെയ്ക്കപ്പെട്ടിരിക്കുന്ന ഒരു ശേഖരത്തിലോട്ടാണ് അപ്‌ലോഡ് ചെയ്യുന്നതെന്ന് ഞാൻ മനസ്സിലാക്കുന്നു. അവിടുത്തെ ഉപയോഗ നിബന്ധനകൾക്കും അനുമതി നയങ്ങൾക്കും അനുസൃതമായാണ് ഇത് ചെയ്യുന്നതെന്ന് ഞാൻ സ്ഥിരീകരിക്കുന്നു.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "പങ്ക് വെയ്ക്കപ്പെട്ടിരിക്കുന്ന ശേഖരത്തിന്റെ നയങ്ങളനുസരിച്ച് താങ്കൾക്ക് ഈ പ്രമാണം അപ്‌ലോഡ് ചെയ്യാൻ കഴിയില്ലെങ്കിൽ, ദയവായി ഇത് അടക്കുകയും മറ്റൊരു മാർഗ്ഗം ശ്രമിക്കുകയും ചെയ്യുക.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "ഈ പ്രമാണം അവരുടെ നയങ്ങളുമായി ചേർന്നുപോകുമെങ്കിൽ താങ്കൾക്ക് [[Special:Upload|{{SITENAME}} സംരംഭത്തിലെ അപ്‌ലോഡ് താൾ]] പരീക്ഷിച്ചു നോക്കാവുന്നതാണ്.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "ഈ പ്രമാണത്തിന്റെ പകർപ്പവകാശം എനിക്ക് സ്വന്തമാണെന്നും, ഈ പ്രമാണം വിക്കിമീഡിയ കോമൺസിൽ പിന്നീട് മാറ്റാനാവത്തവിധം [https://creativecommons.org/licenses/by-sa/4.0/ ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ-ഷെയർഎലൈക് 4.0] ഉപയോഗാനുമതിയിൽ പ്രസിദ്ധീകരിക്കാമെന്നും [https://wikimediafoundation.org/wiki/Terms_of_Use/ml ഉപയോഗനിബന്ധനകൾ] അംഗീകരിക്കുന്നുവെന്നും സാക്ഷ്യപ്പെടുത്തുന്നു.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "ഈ പ്രമാണത്തിന്റെ പകർപ്പവകാശം താങ്കളുടെ സ്വന്തമല്ലെങ്കിൽ അഥവാ മറ്റൊരു ഉപയോഗാനുമതിയിലാണ് പ്രമാണം പ്രസിദ്ധീകരിക്കാൻ ഉദ്ദേശിക്കുന്നതെങ്കിൽ [https://commons.wikimedia.org/wiki/Special:UploadWizard?uselang=ml കോമൺസിലെ അപ്‌ലോഡ് സഹായി] ഉപയോഗിക്കുന്നത് പരിഗണിക്കുക.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "ഈ പ്രമാണം അവരുടെ നയങ്ങൾക്കനുസൃതമായി അപ്‌ലോഡ് ചെയ്യാൻ സൈറ്റ് അനുവദിക്കുമെങ്കിൽ [[Special:Upload|{{SITENAME}} സംരംഭത്തിലെ അപ്‌ലോഡ് താൾ]] പരീക്ഷിച്ചു നോക്കാവുന്നതാണ്.",
+       "upload-form-label-own-work": "ഇതെന്റെ സ്വന്തം സൃഷ്ടി ആണ്",
+       "upload-form-label-infoform-categories": "വർഗ്ഗങ്ങൾ",
+       "upload-form-label-infoform-date": "തീയതി",
+       "upload-form-label-own-work-message-generic-local": "{{SITENAME}} സംരംഭത്തിലെ സേവന നിബന്ധനകൾക്കും ഉപയോഗാനുമതി നയങ്ങൾക്കും അനുസരിച്ചാണ് ഈ പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നതെന്ന് ഞാൻ സ്ഥിരീകരിക്കുന്നു.",
+       "upload-form-label-not-own-work-message-generic-local": "{{SITENAME}} സംരംഭത്തിലെ നയങ്ങളനുസരിച്ച് താങ്കൾക്ക് ഈ പ്രമാണം അപ്‌ലോഡ് ചെയ്യാൻ കഴിയില്ലെങ്കിൽ, ദയവായി ഇത് അടച്ച് മറ്റൊരു മാർഗ്ഗം ശ്രമിക്കുക.",
+       "upload-form-label-not-own-work-local-generic-local": "താങ്കൾക്ക് [[Special:Upload|സ്വതേ ഉള്ള അപ്‌ലോഡ് താളും]] പരിശോധിക്കാവുന്നതാണ്.",
+       "upload-form-label-own-work-message-generic-foreign": "ഈ പ്രമാണം പങ്ക് വെയ്ക്കപ്പെട്ടിരിക്കുന്ന ഒരു ശേഖരത്തിലോട്ടാണ് അപ്‌ലോഡ് ചെയ്യുന്നതെന്ന് ഞാൻ മനസ്സിലാക്കുന്നു. അവിടുത്തെ ഉപയോഗ നിബന്ധനകൾക്കും അനുമതി നയങ്ങൾക്കും അനുസൃതമായാണ് ഇത് ചെയ്യുന്നതെന്ന് ഞാൻ സ്ഥിരീകരിക്കുന്നു.",
+       "upload-form-label-not-own-work-message-generic-foreign": "പങ്ക് വെയ്ക്കപ്പെട്ടിരിക്കുന്ന ശേഖരത്തിന്റെ നയങ്ങളനുസരിച്ച് താങ്കൾക്ക് ഈ പ്രമാണം അപ്‌ലോഡ് ചെയ്യാൻ കഴിയില്ലെങ്കിൽ, ദയവായി ഇത് അടക്കുകയും മറ്റൊരു മാർഗ്ഗം ശ്രമിക്കുകയും ചെയ്യുക.",
+       "upload-form-label-not-own-work-local-generic-foreign": "ഈ പ്രമാണം അവരുടെ നയങ്ങളുമായി ചേർന്നുപോകുമെങ്കിൽ താങ്കൾക്ക് [[Special:Upload|{{SITENAME}} സംരംഭത്തിലെ അപ്‌ലോഡ് താൾ]] പരീക്ഷിച്ചു നോക്കാവുന്നതാണ്.",
        "backend-fail-stream": "$1 എന്ന പ്രമാണം സ്ട്രീം ചെയ്യാൻ കഴിഞ്ഞില്ല.",
        "backend-fail-backup": "$1 എന്ന പ്രമാണത്തിന്റെ ബാക്ക്അപ് എടുക്കാൻ കഴിഞ്ഞില്ല.",
        "backend-fail-notexists": "$1 എന്ന പ്രമാണം നിലവിലില്ല.",
index 5b990cc..4c28ba3 100644 (file)
        "noname": "Та хүчинтэй хэрэглэгчийн нэрийг өгөөгүй байна.",
        "loginsuccesstitle": "Амжилттай нэвтэрлээ",
        "loginsuccess": "'''Та {{SITENAME}} руу \"$1\" нэрээр нэвтэрлээ.'''",
-       "nosuchuser": "\"$1\" нэртэй хэрэглэгч олдсонгүй.\nХэрэглэгчийн нэрийн үсгүүд том жижгээр бичсэн байдлаар өөр байна.\nТа зөв бичсэн эсэхээ шалгах, эсвэл [[Special:UserLogin/signup| шинээр бүртгүүлнэ үү]].",
+       "nosuchuser": "\"$1\" нэртэй хэрэглэгч олдсонгүй.\nХэрэглэгчийн нэрийн үсгүүд том жижгээр бичсэн байдлаар өөр байна.\nТа зөв бичсэн эсэхээ шалгах, эсвэл [[Special:CreateAccount| шинээр бүртгүүлнэ үү]].",
        "nosuchusershort": "\"$1\" гэсэн нэртэй хэрэглэгч байхгүй байна.\nҮсгийн алдаагаа шалгана уу.",
        "nouserspecified": "Та хэрэглэгчийн нэрээ зааж өгөх хэрэгтэй.",
        "login-userblocked": "Энэхүү хэрэглэгчийг түгжсэн байна. Нэвтрэх боломжгүй.",
        "accmailtext": "[[User talk:$1|$1]]-н санамсаргүй байдлаар үүсгэгдсэн нууц үг $2 руу илгээгдлээ.\n\nНэвтэрч орсны дараа энэ шинэ бүртгэлийн нууц үгийг ''[[Special:ChangePassword|энэ хуудсаар]]'' солих боломжтой.",
        "newarticle": "(Шинэ)",
        "newarticletext": "Таны орохыг хүссэн хуудас байхгүй байна. Энэ хуудсыг шинээр үүсгэхийн тулд доорх талбарт мэдээллээ оруулна уу (дэлгэрэ\nнгүй мэдээллийг [$1 тусламжийн хуудсаас] авч болно).\nХэрэв та энд санамсаргүйгээр орж ирсэн бол броузерийнхаа '''back''' товчийг дар.",
-       "anontalkpagetext": "----''Энэ нь хараахан бүртгүүлээгүй байгаа буюу хэрэглэгчийн нэр хэрэглэхгүй байгаа хэрэглэгчийн хэлэлцүүлгийн хуудас болно.\nБид ийм хэрэглэгчдийг тогтоохын тулд IP хаягийг нь ашигладаг.\nНэг IP хаягийг хэд хэдэн хэрэглэгч хамтран ашиглаж байж болно.\nХэрвээ таны IP хаягт ирсэн санал хүсэлтүүд танд хандаагүй бөгөөд та цаашид ийм байдал үүсэхээс сэргийлье гэвэл [[Special:UserLogin/signup|бүртгүүлэх]] буюу [[Special:UserLogin|нэвтрэхийг]] хүсье.''",
+       "anontalkpagetext": "----''Энэ нь хараахан бүртгүүлээгүй байгаа буюу хэрэглэгчийн нэр хэрэглэхгүй байгаа хэрэглэгчийн хэлэлцүүлгийн хуудас болно.\nБид ийм хэрэглэгчдийг тогтоохын тулд IP хаягийг нь ашигладаг.\nНэг IP хаягийг хэд хэдэн хэрэглэгч хамтран ашиглаж байж болно.\nХэрвээ таны IP хаягт ирсэн санал хүсэлтүүд танд хандаагүй бөгөөд та цаашид ийм байдал үүсэхээс сэргийлье гэвэл [[Special:CreateAccount|бүртгүүлэх]] буюу [[Special:UserLogin|нэвтрэхийг]] хүсье.''",
        "noarticletext": "Одоогийн байдлаар энэ хуудсанд текст алга.\nТа нэг бол энэ хуудасны нэрээр бусад хуудсуудад [[Special:Search/{{PAGENAME}}|хайлт хийх]], <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": "\"<nowiki>$1</nowiki>\" хэрэглэгчийн бүртгэл бүртгэгдээгүй байна. Та энэ хуудсыг үүсгэх/засварлах тухайгаа дахин тунгааж бодно уу.",
index a4e9ab6..b2af24b 100644 (file)
        "noname": "आपण वैध सदस्यनाम नमूद केले नाही.",
        "loginsuccesstitle": "आपल्या सनोंद-प्रवेशाची नोंदणी यशस्वीरीत्या पूर्ण झाली",
        "loginsuccess": "'''तुम्ही {{SITENAME}} वर \"$1\" नावाने सनोंद प्रवेशित आहात.'''",
-       "nosuchuser": "\"$1\" या नावाचा कोणताही सदस्य नाही.तुमचे शुद्धलेखन तपासा, किंवा [[Special:UserLogin/signup|नवीन खाते]] तयार करा.",
+       "nosuchuser": "\"$1\" या नावाचा कोणताही सदस्य नाही.तुमचे शुद्धलेखन तपासा, किंवा [[Special:CreateAccount|नवीन खाते]] तयार करा.",
        "nosuchusershort": "\"$1\" या नावाचा सदस्य नाही. लिहीताना आपली चूक तर नाही ना झाली?",
        "nouserspecified": "तुम्हाला सदस्यनाव नमूद करावे लागेल.",
        "login-userblocked": "हा सदस्य ’प्रतिबंधित’ आहे. त्यास सनोंद-प्रवेशाची परवानगी नाही.",
        "accmailtext": "[[User talk:$1|$1]] यांसाठी अनियतक्रमाने निर्मित केलेला परवलीचा शब्द $2 यांना पाठवण्यात आला आहे.\n\nया नवीन खात्यासाठीचा परवलीचा शब्द,सनोंद-प्रवेश घेतल्यावर [[Special:ChangePassword|परवलीचा शब्द बदला]] येथे बदलता येईल.",
        "newarticle": "(नवीन लेख)",
        "newarticletext": "आपण सध्या अस्तित्त्वात नसलेल्या पानाच्या दुव्याचा मागोवा घेत आला आहात.\nहे पान नव्याने तयार करण्यासाठी खालील पेटीत टंकन करणे सुरु करा(अधिक माहितीसाठी [$1 साहाय्य पान] बघा).\n\nजर आपण येथे चुकून आला असाल तर ब्राउझरच्या  <strong>परत</strong>(बॅक) कळीवर टिचकी द्या.",
-       "anontalkpagetext": "---- ''हे चर्चापान अशा अज्ञात सदस्यासाठी आहे, ज्यांनी खाते तयार केलेले नाही किंवा त्याचा वापर करत नाहीत. त्यांच्या ओळखीसाठी आम्ही आंतरजाल अंकपत्ता वापरतो आहोत. असा अंकपत्ता बऱ्याच लोकांचा एकच असू शकतो. जर आपण अज्ञात सदस्य असाल आणि आपल्याला काही अप्रासंगिक संदेश मिळाला असेल तर कृपया [[Special:UserLogin| खाते तयार करा]] किंवा [[Special:UserLogin/signup|सनोंद-प्रवेश करा]] ज्यामुळे, पुढे असे गैरसमज होणार नाहीत.''",
+       "anontalkpagetext": "---- ''हे चर्चापान अशा अज्ञात सदस्यासाठी आहे, ज्यांनी खाते तयार केलेले नाही किंवा त्याचा वापर करत नाहीत. त्यांच्या ओळखीसाठी आम्ही आंतरजाल अंकपत्ता वापरतो आहोत. असा अंकपत्ता बऱ्याच लोकांचा एकच असू शकतो. जर आपण अज्ञात सदस्य असाल आणि आपल्याला काही अप्रासंगिक संदेश मिळाला असेल तर कृपया [[Special:UserLogin| खाते तयार करा]] किंवा [[Special:CreateAccount|सनोंद-प्रवेश करा]] ज्यामुळे, पुढे असे गैरसमज होणार नाहीत.''",
        "noarticletext": "या लेखात सध्या काहीही मजकूर नाही.\nतुम्ही विकिपीडियावरील इतर लेखांमध्ये या [[Special:Search/{{PAGENAME}}| मथळ्याचा शोध घेऊ शकता]], <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={{FULLPAGENAME}}}}आपण या लेखाच्या इतर नोंदी शोधा]</span>,परंतु, आपणास हा लेख लिहीण्याची परवानगी देण्यात येउ शकत नाही.",
        "missing-revision": "\"{{FULLPAGENAME}}\" या लेखाचे #$1 हे संस्करण अस्तित्वात नाही.वगळल्या गेलेल्या लेखपानाच्या जुन्या इतिहास-दुव्याचे अनुसरण केल्यामुळे असे होते.याबाबत विस्तृत माहिती  [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} वगळलेल्या नोंदी]येथे बघता येईल.",
        "upload-form-label-infoform-description": "वर्णन",
        "upload-form-label-usage-title": "वापर",
        "upload-form-label-usage-filename": "संचिका नाम",
-       "foreign-structured-upload-form-label-own-work": "हे माझे स्वत:चे काम आहे",
-       "foreign-structured-upload-form-label-infoform-categories": "वर्ग",
-       "foreign-structured-upload-form-label-infoform-date": "दिनांक",
+       "upload-form-label-own-work": "हे माझे स्वत:चे काम आहे",
+       "upload-form-label-infoform-categories": "वर्ग",
+       "upload-form-label-infoform-date": "दिनांक",
        "backend-fail-stream": "$1 या संचिकेचा स्त्रोत शोधता आला नाही.",
        "backend-fail-backup": "$1 या संचिकेची आधारप्रत बनविता आली नाही.",
        "backend-fail-notexists": "$1 ही संचिका अस्तित्वात नाही.",
        "logentry-protect-protect-cascade": "$1 ने $3 $4 [निपतन]ला {{GENDER:$2|सुरक्षित केले}}",
        "logentry-protect-modify": "$1 ने $3 $4 ची  सुरक्षा पातळी {{GENDER:$2|बदलली}}",
        "logentry-protect-modify-cascade": "$1 ने $3 $4 [निपतन]ची  सुरक्षा पातळी {{GENDER:$2|बदलली}}",
-       "logentry-rights-rights": "$1 ने $3 साठी $4 वरुन $5 ला गट सदस्यता{{GENDER:$2|बदलली}}",
+       "logentry-rights-rights": "$1 ने {{GENDER:$6|$3}} साठी $4 वरुन $5 ला गट सदस्यता{{GENDER:$2|बदलली}}",
        "logentry-rights-rights-legacy": "$1 ने $3 साठी गट सदस्यता {{GENDER:$2|बदलली}}",
        "logentry-rights-autopromote": "$1 ला स्वयंचलितरित्या $4 वरुन $5 ला {{GENDER:$2|बढती दिल्या गेली}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|अपभारीत केली}} $3",
        "api-error-nomodule": "अंतर्गत चूक: module set चढवलेला नाही",
        "api-error-ok-but-empty": "आंतरिक त्रुटी : विदादाता प्रतिक्रिया देत नहीं",
        "api-error-overwrite": "अस्तित्वात असलेल्या संचिकेवर पुनर्लेखन प्रतिबंधित आहे.",
+       "api-error-ratelimited": "आपण, हा विकी परवानगी देत असल्यापेक्षा अधिक संचिका, कमी कालावधीत अपभारणाचा प्रयत्न करीत आहात.\nकाही मिनिटांनी पुन्हा प्रयत्न करा.",
        "api-error-stashfailed": "इन्तरिक त्रुटी : विदादाता तात्पुरत्या स्वरूपाच्या संचिका जमा करण्यात अयशस्वी",
        "api-error-publishfailed": "अंतर्गत त्रुटी:विदादात्यास, या तात्पुरत्या संचिकेच्या प्रकाशनास अपयश आले.",
        "api-error-stasherror": "स्टॅचमध्ये ही संचिका अपभारणात त्रुटी आली.",
index d4b395e..59249b9 100644 (file)
        "noname": "Nama pengguna tidak sah.",
        "loginsuccesstitle": "Berjaya log masuk",
        "loginsuccess": "'''Anda telah log masuk ke dalam {{SITENAME}} sebagai \"$1\".'''",
-       "nosuchuser": "Pengguna \"$1\" tidak wujud. Nama pengguna adalah peka huruf besar. Sila semak ejaan anda, atau anda boleh [[Special:UserLogin/signup|membuka akaun baru]].",
+       "nosuchuser": "Pengguna \"$1\" tidak wujud. Nama pengguna adalah peka huruf besar. Sila semak ejaan anda, atau anda boleh [[Special:CreateAccount|membuka akaun baru]].",
        "nosuchusershort": "Pengguna \"$1\" tidak wujud. Sila semak ejaan anda.",
        "nouserspecified": "Sila nyatakan nama pengguna.",
        "login-userblocked": "Pengguna ini disekat. Log masuk tidak dibenarkan.",
        "accmailtext": "Kata laluan janaan rawak untuk [[User talk:$1|$1]] telah dikirim kepada $2. Anda boleh menukarnya di halaman ''[[Special:ChangePassword|tukar kata laluan]]'' sebaik sahaja log masuk.",
        "newarticle": "(Baru)",
        "newarticletext": "Anda telah mengikuti pautan ke laman yang belum wujud.\nUntuk mencipta laman ini, sila taip dalam kotak di bawah\n(lihat [$1 laman bantuan] untuk maklumat lanjut).\nJika anda tiba di sini secara tak sengaja, hanya klik butang '''back''' pada pelayar anda.",
-       "anontalkpagetext": "----''Ini ialah laman perbincangan bagi pengguna tanpa nama yang belum membuka akaun atau tidak log masuk.\nOleh itu kami terpaksa menggunakan alamat IP untuk mengenal pasti pengguna tersebut. Alamat IP ini boleh dikongsi oleh ramai pengguna.\nSekiranya anda adalah seorang pengguna tanpa nama dan berasa bahawa komen yang tidak kena mengena telah ditujukan kepada anda, sila [[Special:UserLogin/signup|buka akaun baru]] atau [[Special:UserLogin|log masuk]] untuk mengelakkan sebarang kekeliruan dengan pengguna tanpa nama yang lain.''",
+       "anontalkpagetext": "----''Ini ialah laman perbincangan bagi pengguna tanpa nama yang belum membuka akaun atau tidak log masuk.\nOleh itu kami terpaksa menggunakan alamat IP untuk mengenal pasti pengguna tersebut. Alamat IP ini boleh dikongsi oleh ramai pengguna.\nSekiranya anda adalah seorang pengguna tanpa nama dan berasa bahawa komen yang tidak kena mengena telah ditujukan kepada anda, sila [[Special:CreateAccount|buka akaun baru]] atau [[Special:UserLogin|log masuk]] untuk mengelakkan sebarang kekeliruan dengan pengguna tanpa nama yang lain.''",
        "noarticletext": "Laman ini buat masa sekarang tidak berteks. Anda boleh [[Special:Search/{{PAGENAME}}|cari tajuk bagi laman ini]] dalam laman-laman lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cari log-log yang berkaitan], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} sunting laman ini]</span>.",
        "noarticletext-nopermission": "Tiada teks dalam laman ini ketika ini.\nAnda boleh [[Special:Search/{{PAGENAME}}|mencari tajuk laman ini]] dalam laman lain,\natau <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log yang berkaitan]</span>.",
        "missing-revision": "Semakan #$1 pada halaman \"{{FULLPAGENAME}}\" tidak wujud.\n\nHal ini biasanya disebabkan oleh pautan sejarah yang lapuk ke halaman yang sudah dihapuskan.\nButirannya boleh didapati di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log penghapusan].",
        "upload-form-label-infoform-description": "Keterangan",
        "upload-form-label-usage-title": "Penggunaan",
        "upload-form-label-usage-filename": "Nama fail",
-       "foreign-structured-upload-form-label-own-work": "Ini ialah karya saya sendiri",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategori",
-       "foreign-structured-upload-form-label-infoform-date": "Tarikh",
-       "foreign-structured-upload-form-label-own-work-message-local": "Saya mengesahkan bahawa saya memuat naik fail ini dengan mengikut terma perkhidmatan dan dasar perlesenan di {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Anda mungkin juga mahu mencuba [[Special:Upload|laman muat naik yang asal]].",
+       "upload-form-label-own-work": "Ini ialah karya saya sendiri",
+       "upload-form-label-infoform-categories": "Kategori",
+       "upload-form-label-infoform-date": "Tarikh",
+       "upload-form-label-own-work-message-generic-local": "Saya mengesahkan bahawa saya memuat naik fail ini dengan mengikut terma perkhidmatan dan dasar perlesenan di {{SITENAME}}.",
+       "upload-form-label-not-own-work-local-generic-local": "Anda mungkin juga mahu mencuba [[Special:Upload|laman muat naik yang asal]].",
        "backend-fail-stream": "Fail $1 tidak dapat distrimkan.",
        "backend-fail-backup": "Fail $1 tidak dapat disandarkan.",
        "backend-fail-notexists": "Fail $1 tidak wujud.",
index 2993d36..f4e8742 100644 (file)
        "noname": "L-isem tal-utent li tajt mhuwiex validu.",
        "loginsuccesstitle": "Dħalt b'suċċess",
        "loginsuccess": "'''Irnexxielek taqbad mas-server ta' {{SITENAME}} bl-isem tal-utent \"$1\".'''",
-       "nosuchuser": "M'hemm l-ebda utent bl-isem ta' \"$1\".<br />\nL-ismijiet tal-utenti huma sensittivi fuq kif jinkitbu.<br />\nJekk jogħġbok kun żġur li ktibtu sew, jew minflok [[Special:UserLogin/signup|oħloq kont ġdid]].",
+       "nosuchuser": "M'hemm l-ebda utent bl-isem ta' \"$1\".<br />\nL-ismijiet tal-utenti huma sensittivi fuq kif jinkitbu.<br />\nJekk jogħġbok kun żġur li ktibtu sew, jew minflok [[Special:CreateAccount|oħloq kont ġdid]].",
        "nosuchusershort": "M'hemm l-ebda utent bl-isem \"$1\".\nAgħmel żġur li ktibta sew.",
        "nouserspecified": "Trid tispeċifika isem tal-utent.",
        "login-userblocked": "Dan l-utent huwa imblukkat. Mhuwiex possibbli li jsir il-login.",
        "accmailtext": "Intbagħtet lil $2 password iġġenerata każwalment għal [[User talk:$1|$1]] .\nTista' tinbidel fuq il-paġna għat-<em>[[Special:ChangePassword|tibdil tal-password]]</em> wara d-dħul fil-kont.",
        "newarticle": "(Ġdid)",
        "newarticletext": "Inti segwejt link għal paġna li għadha ma ġietx maħluqa.\nSabiex toħloq il-paġna, ikteb fil-kaxxa li tinsab hawn taħt (ara [$1 paġna tal-għajnuna] għal aktar informazzjoni).\nJekk wasalt hawn biż-żball, agħfas il-buttuna '''lura''' (''back'') fuq il-browser tiegħek.",
-       "anontalkpagetext": "----''Din hija l-paġna ta' diskussjoni ta' utent anonimu li għadu ma ħoloqx kont, jew inkella li ma jużahx.\nGħaldaqstant biex nidentifikawh ikollna nużaw l-indirizz tal-IP tiegħu/tagħha.\nL-istess indirizz tal-IP jista' jkun użat minn bosta utenti differenti.\nJekk int utent anonimu u tħoss li qiegħed tirċievi kummenti irrelevanti jew li ma jagħmlux sens, jekk jogħġbok [[Special:UserLogin|idħol fil-kont tiegħek]] jew [[Special:UserLogin/signup|oħloq wieħed]] sabiex tevita li fil-futur tiġi konfuż ma' utenti anonimi oħra.''",
+       "anontalkpagetext": "----''Din hija l-paġna ta' diskussjoni ta' utent anonimu li għadu ma ħoloqx kont, jew inkella li ma jużahx.\nGħaldaqstant biex nidentifikawh ikollna nużaw l-indirizz tal-IP tiegħu/tagħha.\nL-istess indirizz tal-IP jista' jkun użat minn bosta utenti differenti.\nJekk int utent anonimu u tħoss li qiegħed tirċievi kummenti irrelevanti jew li ma jagħmlux sens, jekk jogħġbok [[Special:UserLogin|idħol fil-kont tiegħek]] jew [[Special:CreateAccount|oħloq wieħed]] sabiex tevita li fil-futur tiġi konfuż ma' utenti anonimi oħra.''",
        "noarticletext": "Bħalissa m'hemm l-ebda test f'din il-paġna.\nTista' [[Special:Search/{{PAGENAME}}|tfittex it-titlu ta' din il-paġna]] f'paġni oħra, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tfittex ir-reġistri relatati], jew [{{fullurl:{{FULLPAGENAME}}|action=edit}} toħloq dil-paġna]</span>.",
        "noarticletext-nopermission": "Bħalissa m'hemm l-ebda test f'din il-paġna. Inti tista' [[Special:Search/{{PAGENAME}}|tfittex dan it-titlu tal-paġna]] f'paġni oħra, jew <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tfittex ir-reġistri relatati]</span>, imma m'għandikx permess toħloq dil-paġna.",
        "missing-revision": "Ir-reviżjoni #$1 tal-paġna bl-isem \"{{FULLPAGENAME}}\" ma teżistix.\n\nDan ħafna drabi jiġri minħabba li tkun segwejt ħolqa lejn paġna mħassra, f'kronoloġija li mhix aġġornata.\nId-detallji tista' ssibhom fir-[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} reġistru tat-tħassir].",
index c02ff03..56e90a0 100644 (file)
        "loginerror": "Erro de outenticaçon",
        "loginsuccesstitle": "Antreste cumo debe de ser",
        "loginsuccess": "'''Stás agora lhigado a {{SITENAME}} cumo \"$1\"'''.",
-       "nosuchuser": "Num eisiste nanhun outelizador cul nome \"$1\".\nLs nomes de outelizador son defrentes an lhetra grande ó pequeinha.\nBei cumo screbiste, ó [[Special:UserLogin/signup|cria ua nuoba cuonta]].",
+       "nosuchuser": "Num eisiste nanhun outelizador cul nome \"$1\".\nLs nomes de outelizador son defrentes an lhetra grande ó pequeinha.\nBei cumo screbiste, ó [[Special:CreateAccount|cria ua nuoba cuonta]].",
        "nosuchusershort": "Nun eisiste nanhun outelizador cul nome \"$1\".\nBei se l screbiste bien.",
        "nouserspecified": "Tenes que dezir un nome de outelizador.",
        "wrongpassword": "La palabra chabe ye ambálida.\nPor fabor, spurmenta outra beç.",
        "move-page-legend": "Mover página",
        "movepagetext": "Outelizando este formulário tu puodes renomear ua páigina, arrastrando to l stórico para l nuobo títalo. L títalo anterior será transformado nun ancaminamiento para l nuobo.\nYe possible amanhar de forma outomática ancaminamientos que lhigen un títalo oureginal.\nCauso scuolhas para que esso nun seia feito, bei se nun hai ancaminamientos [[Special:DoubleRedirects|dues bezes]] ó [[Special:BrokenRedirects|scachados]].\nYe de la tue respunsablidade tener la certeza de que las lhigaçones cuntinan a apuntar pa adonde dében.\n\nArrepara que la páigina '''nun''' será arrastrada se yá eisistir ua páigina cul nuobo títalo, a nun ser que steia bazio ó seia un ancaminamiento i nun tenga stórico de eidiçones. Esto quier dezir que puodes renomear outra beç ua páigina pa l nome que tenie antes de l anganho i que nun puodes subrescrebir ua páigina.\n\n<b>CUIDADO!</b>\nEsto puode ser ua altaraçon drástica i einesperada pa ua páigina popular; por fabor, ten la certeza de que antendes las cunsequéncias desto antes de cuntinar.",
        "movepagetalktext": "La páigina de \"çcusson\" associada, se eistir, será outomaticamente arrastrada, '''a nun ser que:'''\n*Ua páigina de çcusson cun contenido yá eisista subre l nuobo títalo, ou\n*Tu marques la caixa ambaixo.\n\nNestes causos, tu terás que arrastrar ou ajuntar la páigina a la mano, se assi quejires.",
-       "movearticle": "Arrastrar páigina",
        "newtitle": "Pa nuobo títalo:",
        "move-watch": "Begiar esta páigina",
        "movepagebtn": "Arrastrar páigina",
index 9f40bf4..97f653d 100644 (file)
        "noname": "Зярс эзить максо кемекстазь теицянь лем.",
        "loginsuccesstitle": "Совавить",
        "loginsuccess": "'''Тон совить {{SITENAME}}-с кода \"$1\".'''",
-       "nosuchuser": "$1 лем марто теиця арась.\nТеиця лемтне явозь тень корясь вишка эли покш тештинесэ сёрмадозь. Ваннык видестэ - а видестэ сёрмадык, эли [[Special:UserLogin/signup|тейть-шкак од совамо тарка]].",
+       "nosuchuser": "$1 лем марто теиця арась.\nТеиця лемтне явозь тень корясь вишка эли покш тештинесэ сёрмадозь. Ваннык видестэ - а видестэ сёрмадык, эли [[Special:CreateAccount|тейть-шкак од совамо тарка]].",
        "nosuchusershort": "\"$1\" лемсэ теиця арась. Варштака, кизды, аволь истя сёрмадозь.",
        "nouserspecified": "Теицянь лем эряви.",
        "login-userblocked": "Те теицясь аравтозь саймас. Совамонзо а мерить.",
index ddbb9a1..8a350a6 100644 (file)
        "noname": "Ahmo ōtiquihto cualli tlatequitiltilīlli tōcāitl.",
        "loginsuccesstitle": "Cualli calaquiliztli",
        "loginsuccess": "'''Ōticalac {{SITENAME}} quemeh \"$1\".'''",
-       "nosuchuser": "Ayāc tlatequitiltilīlli motōcāitia \"$1\".\nIn tlatequitiltilīltōcāitl quimati in huēyimachiyōtlahtōliztli.\nXiquitta in yēquihcuilōlli, ahnozo [[Special:UserLogin/signup|xicchīhua yancuīc cuenta]].",
+       "nosuchuser": "Ayāc tlatequitiltilīlli motōcāitia \"$1\".\nIn tlatequitiltilīltōcāitl quimati in huēyimachiyōtlahtōliztli.\nXiquitta in yēquihcuilōlli, ahnozo [[Special:CreateAccount|xicchīhua yancuīc cuenta]].",
        "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.",
        "categories": "Tlaìxmatkàtlàlilòmë",
        "categoriespagetext": "{{PLURAL:$1|Inìn tlaìxmatkàtlàlilòtl kimpia|Inîkë tlaìxmatkàtlàlilòmë kimpiâkë}} tlaìxtlapaltìn noso medios.\nÂmò monèxtiâkë nikàn in [[Special:UnusedCategories|tlaìxmatkàtlàlilòmë tlèn âmò mokìntekitìltia]].\nNò mà mỏta in tlèn [[Special:WantedCategories|ìpan kineki tlaìxmatkàtlàlilòtl]].",
        "categoriesfrom": "Mà monèxtìkàn tlaìxmatkàtlàlilòmë tlèn pèwâkë ìka:",
-       "special-categories-sort-count": "tlapōhualcopa",
-       "special-categories-sort-abc": "tlahtōlcopa",
        "linksearch": "Calān tzonhuiliztli tlatemoliztli",
        "linksearch-ns": "Tōcātzin:",
        "linksearch-ok": "Tictēmōz",
index 1bd4a2b..1e5f271 100644 (file)
        "noname": "Lí phah--ê iōng-chiá-miâ boē-sái.",
        "loginsuccesstitle": "Teng-ji̍p sêng-kong",
        "loginsuccess": "Lí hiān-chhú-sî í-keng teng-ji̍p {{SITENAME}} chò \"$1\".",
-       "nosuchuser": "Chia bô iōng-chiá hō-chò \"$1\".\nIiōng-chiá hō-chò ū hun toā-jī sè-jī.\nChhiáⁿ kiám-cha lí ê phèng-im, a̍h-sī  [[Special:UserLogin/signup|khui sin iōng-chiá ê kháu-chō.]]",
+       "nosuchuser": "Chia bô iōng-chiá hō-chò \"$1\".\nIiōng-chiá hō-chò ū hun toā-jī sè-jī.\nChhiáⁿ kiám-cha lí ê phèng-im, a̍h-sī  [[Special:CreateAccount|khui sin iōng-chiá ê kháu-chō.]]",
        "nosuchusershort": "Bô \"$1\" chit ê iōng-chiá miâ.\nTùi khoàⁿ-māi,  lí phah--ê.",
        "nouserspecified": "Lí ài chí-tēng chi̍t ê iōng-chiá miâ.",
        "login-userblocked": "這個用者已經予人封鎖,袂使登入。",
        "sp-contributions-submit": "Chhoē",
        "whatlinkshere": "Tó-ūi liân kàu chia",
        "whatlinkshere-title": "Liân khì \"$1\" ê ia̍h-bīn",
-       "whatlinkshere-page": "頁:",
+       "whatlinkshere-page": "Ia̍h:",
        "linkshere": "Í-hā '''[[:$1]]''' liân kàu chia:",
        "nolinkshere": "Bô poàⁿ ia̍h liân kàu '''[[:$1]]'''.",
        "isredirect": "choán-ia̍h",
        "tooltip-ca-viewsource": "Chit ia̍h pó-hō͘ tiâu leh.\nLí ē-sái khoàⁿ i ê goân-sú-bé.",
        "tooltip-ca-history": "Chit ia̍h ê chá-chêng pán-pún",
        "tooltip-ca-delete": "Thâi chit ia̍h",
-       "tooltip-ca-move": "徙這頁",
+       "tooltip-ca-move": "Sóa chit ia̍h",
        "tooltip-ca-watch": "共這頁加入去你的監視單",
        "tooltip-ca-unwatch": "Lí ê kàm-sī-toaⁿ soá tiàu chit ia̍h.",
        "tooltip-search": "Chhoé {{SITENAME}}",
        "tooltip-diff": "Hián-sī lí tùi bûn-pún só͘ chò ê kái-tōng",
        "tooltip-watch": "共這頁加入去你的監視單",
        "tooltip-rollback": "Ji̍h \"Hoê-choán\" ē-sái thè tńg-khì téng-chi̍t-ê kái ê lâng ê ia̍h.",
-       "tooltip-preferences-save": "保存設定",
+       "tooltip-preferences-save": "Pó-chûn siat-tēng",
        "tooltip-summary": "Siá chi̍t-ê kán-tan soat-bêng",
        "anonymous": "{{SITENAME}} bô kì-miâ ê iōng-chiá",
        "siteuser": "{{SITENAME}} iōng-chiá $1",
index a2ad536..cbf66c4 100644 (file)
        "noname": "Nun avite specificato nu nomme valido d'utente.",
        "loginsuccesstitle": "Acciesso affettuato",
        "loginsuccess": "'''Si stato cunnesso ô server 'e {{SITENAME}} cu 'o nomme utente 'e \"$1\".'''",
-       "nosuchuser": "Nun è riggistrato nisciuno utente c' 'o nomme \"$1\".\n'E nomme utente songo sensibbele a 'e maiuscole.\nCuntrolla 'o nomme nzertàto, o [[Special:UserLogin/signup|crìa n'utenza nova]].",
+       "nosuchuser": "Nun è riggistrato nisciuno utente c' 'o nomme \"$1\".\n'E nomme utente songo sensibbele a 'e maiuscole.\nCuntrolla 'o nomme nzertàto, o [[Special:CreateAccount|crìa n'utenza nova]].",
        "nosuchusershort": "Nun ce stanno utente cu o nòmme \"$1\". Cuntrolla si scrivìste buòno.",
        "nouserspecified": "Tiene 'a dìcere nu nomme pricìso.",
        "login-userblocked": "Chist'utente è bloccato. Nun se può effettuà 'o login.",
        "accmailtext": "'Na password gennerata casualmente ppe [[User talk:$1|$1]] è stata mannata a $2. Chista password può essere càgnata dint'â paggena ppe ''[[Special:ChangePassword|càgna 'a password]]'' subbeto doppo l'acciesso.",
        "newarticle": "(Nuovo)",
        "newarticletext": "Site ghiuto/a addò nu link 'e na paggena ca nun esiste ancora.\nP' 'a crià sta paggena, accummenciate a scrivere dint'a cascia cà abbascio (vedite 'a [$1 paggena d'aiuto] pe liegge cchiù nfurmazziune).\nSi site venuto/a ccà pe' sbaglio, vedite 'e sprémmere 'o buttòne '''Arreto''' d' 'o navigatóre.",
-       "anontalkpagetext": "----\n''Chest'è 'a paggena 'e discussione 'e n'utente anonimo, ca nun ave criàt' 'ancora n'utenza o ca nun sta ausanno. Pe' l'identificà avite 'e truvà 'o nummero d' 'o ndirizzo IP d' 'o sujo. L'indirizze IP se ponno spartì però sempe ausanno cunte differente. Si site n'utente anonimo e penzate ca 'e cummente ccà dint'a sta paggena nun parlano 'e vuje, allora [[Special:UserLogin/signup|criate n'utenza nnova]] o [[Special:UserLogin|trasite cu chella ca tenite già]] pe' nun sta' mmescato mmiez'a l'ati utente anonime n futuro.''",
+       "anontalkpagetext": "----\n''Chest'è 'a paggena 'e discussione 'e n'utente anonimo, ca nun ave criàt' 'ancora n'utenza o ca nun sta ausanno. Pe' l'identificà avite 'e truvà 'o nummero d' 'o ndirizzo IP d' 'o sujo. L'indirizze IP se ponno spartì però sempe ausanno cunte differente. Si site n'utente anonimo e penzate ca 'e cummente ccà dint'a sta paggena nun parlano 'e vuje, allora [[Special:CreateAccount|criate n'utenza nnova]] o [[Special:UserLogin|trasite cu chella ca tenite già]] pe' nun sta' mmescato mmiez'a l'ati utente anonime n futuro.''",
        "noarticletext": "Mo' mo' 'a paggena richiesta è abbacante. Se pò [[Special:Search/{{PAGENAME}}|ascià stu titolo]] dint'a l'ati paggene d' 'o sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ascià dint'e riggistre azzeccate] o pure [{{fullurl:{{FULLPAGENAME}}|action=edit}} crià 'a paggena mo']</span>.",
        "noarticletext-nopermission": "Mo' mo' 'a paggena richiesta è abbacante. Se pò [[Special:Search/{{PAGENAME}}|ascià stu titolo]] dint'a l'ati paggene d' 'o sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ascià dint'e riggistre azzeccate]</span>, però nun tenite 'o permesso 'a crià sta paggena.",
        "missing-revision": "'A verziona #$1 d' 'a paggena \"{{FULLPAGENAME}}\" nun esiste.\n\nChest'è causato quanno se và dint'a nu link a na paggena ch'è stata scancellata.\n'E dettaglie se ponno truvà dint'a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 'o riggistro 'e scancellamiente].",
        "upload-form-label-infoform-description-tooltip": "Facite 'a descriziona sintetica 'e tuttuquanto fosse degno 'e nota a proposito 'e st'opera. P' 'e foto, facite assapé 'e ccosi principale ca songo rappresentate, l'accasione e/o luogo dint' 'o quale so' state scattate.",
        "upload-form-label-usage-title": "Aúso",
        "upload-form-label-usage-filename": "Nomme d' 'o file",
-       "foreign-structured-upload-form-label-own-work": "Chest'è fatica mia",
-       "foreign-structured-upload-form-label-infoform-categories": "Categurìe",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Io cunfermo ca songh'io ca carrecanno stu file sto secutanno 'e tiermene 'e servizio e pulitiche 'e licienza dint'a {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Si nun site capace 'e carrecà stu file pe' bbìa d' 'e pulitiche 'e {{SITENAME}}, pe' piacere nchiurete sta casciulella e tentate n'ata maniera.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Forse vulite pure tentà [[Special:Upload|'a paggena 'e carreche predefinita]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Capisco ca sto a carrecà stu file a nu repositorio spartuto. Cunfermo ca facenno chesto sto secutanno 'e tèrmene 'e servizio e licienze llanno.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Si nun site capace 'e carrecà stu file pe' bbìa d' 'e pulitiche d' 'o repusitorio spartuto, pe' piacere nchiurete sta casciulella e tentate n'ata maniera.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Putite pure tentà 'ausà [[Special:Upload|'a paggena 'e carreche 'e {{SITENAME}}]], si stu file nun se putesse carrecà llanno pe' bbìa d' 'e pulitiche.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Faccio attestato ca songo 'o detentore d' 'o copyright 'e stu file, e so' d'accordo 'e lassà irrevocabbelmente stu file a Wikimedia Commons sott'a licienza [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribuziona-SparteEguale 4.0], e so' d'accordo cu sti [https://wikimediafoundation.org/wiki/Terms_of_Use Termene d'Uso].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Si nun tenite 'o copyright 'e stu file, o pure 'o vulite lassà libbero cu n'ata licienza, cunziderate 'ausà 'o [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Putite pure tentà 'e ausà [[Special:Upload|'a paggena 'e carreche 'e {{SITENAME}}]], si stu sito ve premmettesse 'e carrecà llanno pe' bbìa d' 'e pulitiche.",
+       "upload-form-label-own-work": "Chest'è fatica mia",
+       "upload-form-label-infoform-categories": "Categurìe",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Io cunfermo ca songh'io ca carrecanno stu file sto secutanno 'e tiermene 'e servizio e pulitiche 'e licienza dint'a {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Si nun site capace 'e carrecà stu file pe' bbìa d' 'e pulitiche 'e {{SITENAME}}, pe' piacere nchiurete sta casciulella e tentate n'ata maniera.",
+       "upload-form-label-not-own-work-local-generic-local": "Forse vulite pure tentà [[Special:Upload|'a paggena 'e carreche predefinita]].",
+       "upload-form-label-own-work-message-generic-foreign": "Capisco ca sto a carrecà stu file a nu repositorio spartuto. Cunfermo ca facenno chesto sto secutanno 'e tèrmene 'e servizio e licienze llanno.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Si nun site capace 'e carrecà stu file pe' bbìa d' 'e pulitiche d' 'o repusitorio spartuto, pe' piacere nchiurete sta casciulella e tentate n'ata maniera.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Putite pure tentà 'ausà [[Special:Upload|'a paggena 'e carreche 'e {{SITENAME}}]], si stu file nun se putesse carrecà llanno pe' bbìa d' 'e pulitiche.",
        "backend-fail-stream": "Nun se può mannà 'o file \"$1\".",
        "backend-fail-backup": "Nun se può ffà 'o backup d' 'o file \"$1\".",
        "backend-fail-notexists": "'O file $1 nun esiste.",
        "feedback-useragent": "Aggente utente:",
        "searchsuggest-search": "Truova",
        "searchsuggest-containing": "tène...",
+       "api-error-autoblocked": "Ll'indirizzo IP d' 'o vuosto è stato bloccato automaticamente, pecché a nu mumento l'ausaje n'utenza bloccata.",
        "api-error-badaccess-groups": "Tun putite carrecà file ncopp' 'a sta wiki.",
        "api-error-badtoken": "Errore interno: 'O token nun è buono.",
+       "api-error-blocked": "Site stato/a bloccato/a, nun putite ffà cagnamiente.",
        "api-error-copyuploaddisabled": "'A funzione carrcà 'e n'URL nun è appicciata dint'a stu server.",
        "api-error-duplicate": "Nce {{PLURAL:$1|sta è n'atu file|stanno ati file}} ncopp' 'o sito ch' 'e cuntenute eguale eguale.",
        "api-error-duplicate-archive": "Nce {{PLURAL:$1|steva n'atu file|stevano ati file}} già ncopp' 'o sito ch' 'e stisse cuntenute, però {{PLURAL:$1|è stato|so' state}} scancellate.",
        "api-error-nomodule": "Errore interno: Nisciuno modulo 'e carreca mpustato.",
        "api-error-ok-but-empty": "Errore interno: Nisciuna resposta 'a 'o server.",
        "api-error-overwrite": "Sovrascrivere nu file ch'esiste già nun è permesso.",
+       "api-error-ratelimited": "Vuje avite tntato 'e carrecà cchiù file dint'a nu mumento curt' 'e tiempo ca sta wiki premmettesse.\nPe' piacere, tentate n'ata vota int'a nu poch' 'e minute.",
        "api-error-stashfailed": "Errore interno: 'O server nun ngarraje a s'astipà 'o file temporaneo.",
        "api-error-publishfailed": "Errore interno: 'O server nun ngarraje a pubbrecà 'o file temporaneo.",
        "api-error-stasherror": "'A carreca d' 'o file 'n stash è asciuta male, ce sta n'errore.",
index d3ec649..d01ff9a 100644 (file)
        "noname": "Du har ikke oppgitt et gyldig brukernavn.",
        "loginsuccesstitle": "Du er nå logget inn",
        "loginsuccess": "Du er nå logget inn på {{SITENAME}} som «$1».",
-       "nosuchuser": "Det eksisterer ingen bruker ved navn «$1».\nMerk at det skilles mellom store og små bokstaver.\nSjekk stavemåten eller [[Special:UserLogin/signup|opprett en ny konto]].",
+       "nosuchuser": "Det eksisterer ingen bruker ved navn «$1».\nMerk at det skilles mellom store og små bokstaver.\nSjekk stavemåten eller [[Special:CreateAccount|opprett en ny konto]].",
        "nosuchusershort": "Det finnes ingen bruker ved navn «$1». Kontroller stavemåten.",
        "nouserspecified": "Du må oppgi et brukernavn.",
        "login-userblocked": "Brukeren er blokkert. Innlogging er ikke tillatt.",
        "accmailtext": "Et tilfeldig passord for [[User talk:$1|$1]] har blitt sendt til $2. Det kan endres på [[Special:ChangePassword|passordendringssiden]] under innlogging.",
        "newarticle": "(Ny)",
        "newarticletext": "Du har fulgt en lenke til en side som ikke finnes ennå.\nFor å opprette siden, begynn å skrive i boksen under (se [$1 hjelpesiden] for mer informasjon).\nOm du havnet her ved en feil, trykk '''tilbake''' i nettleseren.",
-       "anontalkpagetext": "----\n''Dette er en diskusjonsside for en uregistrert bruker som ikke har opprettet konto eller ikke er logget inn.\nVi er derfor nødt til å bruke den numeriske IP-adressen til å identifisere ham eller henne.\nEn IP-adresse kan være delt mellom flere brukere.\nHvis du er en uregistrert bruker og synes at du har fått irrelevante kommentarer på en slik side, [[Special:UserLogin/signup|opprett en konto]] eller [[Special:UserLogin|logg inn]] så vi unngår fremtidige forvekslinger med andre uregistrerte brukere.''",
+       "anontalkpagetext": "----\n''Dette er en diskusjonsside for en uregistrert bruker som ikke har opprettet konto eller ikke er logget inn.\nVi er derfor nødt til å bruke den numeriske IP-adressen til å identifisere ham eller henne.\nEn IP-adresse kan være delt mellom flere brukere.\nHvis du er en uregistrert bruker og synes at du har fått irrelevante kommentarer på en slik side, [[Special:CreateAccount|opprett en konto]] eller [[Special:UserLogin|logg inn]] så vi unngår fremtidige forvekslinger med andre uregistrerte brukere.''",
        "noarticletext": "Det er for tiden ingen tekst på denne siden.\nDu kan [[Special:Search/{{PAGENAME}}|søke etter denne sidetittelen]] på andre sider,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søke i relaterte logger],\neller [{{fullurl:{{FULLPAGENAME}}|action=edit}} opprette siden]</span>.",
        "noarticletext-nopermission": "Det er for tiden ingen tekst på denne siden.\nDu kan [[Special:Search/{{PAGENAME}}|søke etter sidens tittel]] blant andre sider, eller <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søke i relevante logger]</span>, men du har ikke tillatelse til å opprette denne siden.",
        "missing-revision": "Revisjonen #$1 av siden med navnet \"{{FULLPAGENAME}}\" eksisterer ikke.\n\nDette skyldes som regel at en gammel historikklenke er fulgt til en side som er slettet.\nDetaljer kan finnes i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sletteloggen].",
        "upload-form-label-infoform-description": "Beskrivelse",
        "upload-form-label-usage-title": "Bruk",
        "upload-form-label-usage-filename": "Filnavn",
-       "foreign-structured-upload-form-label-own-work": "Dette er mitt eget verk",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorier",
-       "foreign-structured-upload-form-label-infoform-date": "Dato",
-       "foreign-structured-upload-form-label-own-work-message-local": "Jeg bekrefter at jeg ved å laste opp denne filen følger bruksvilkårene og lisensieringspolitikken på {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Hvis filen ikke lar seg laste opp under {{SITENAME}}s politikk må du lukke denne dialogboksen og prøve en annen metode.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Du kan eventuelt forsøke [[Special:Upload|den ordinære opplastingssiden]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Jeg forstår at jeg laster opp denne filen til et delt arkiv. Jeg bekrefter at dette gjøres i tråd med bruksvilkårene og lisensieringspolitikken der.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Hvis filen ikke lar seg laste opp under arkivets politikk må du lukke denne dialogboksen og prøve en annen metode.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Du kan eventuelt forsøke [[Special:Upload|den ordinære opplastingssiden]] på {{SITENAME}} hvis filen kan lastes opp under politikken som gjelder der.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Jeg bekrefter at jeg har opphavsretten til denne filen, samtykker til å ugjenkallelig slippe filen til Wikimedia Commons under lisensen [https://creativecommons.org/licenses/by-sa/4.0/deed.no Creative Commons Navngivelse-DelPåSammeVilkår 4.0], og samtykker til [https://wikimediafoundation.org/wiki/Terms_of_Use bruksvilkårene].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Hvis du ikke sitter på opphavsretten til filen, eller ønsker å slippe den under en annen lisens, prøv [https://commons.wikimedia.org/wiki/Special:UploadWizard Opplastingsveiviseren på Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Du kan eventuelt forsøke [[Special:Upload|den ordinære opplastingssiden på {{SITENAME}}]] hvis filen kan lastes opp under politikken som gjelder der.",
+       "upload-form-label-own-work": "Dette er mitt eget verk",
+       "upload-form-label-infoform-categories": "Kategorier",
+       "upload-form-label-infoform-date": "Dato",
+       "upload-form-label-own-work-message-generic-local": "Jeg bekrefter at jeg ved å laste opp denne filen følger bruksvilkårene og lisensieringspolitikken på {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Hvis filen ikke lar seg laste opp under {{SITENAME}}s politikk må du lukke denne dialogboksen og prøve en annen metode.",
+       "upload-form-label-not-own-work-local-generic-local": "Du kan eventuelt forsøke [[Special:Upload|den ordinære opplastingssiden]].",
+       "upload-form-label-own-work-message-generic-foreign": "Jeg forstår at jeg laster opp denne filen til et delt arkiv. Jeg bekrefter at dette gjøres i tråd med bruksvilkårene og lisensieringspolitikken der.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Hvis filen ikke lar seg laste opp under arkivets politikk må du lukke denne dialogboksen og prøve en annen metode.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Du kan eventuelt forsøke [[Special:Upload|den ordinære opplastingssiden]] på {{SITENAME}} hvis filen kan lastes opp under politikken som gjelder der.",
        "backend-fail-stream": "Kunne ikke strømme filen $1.",
        "backend-fail-backup": "Kunne ikke sikkerhetskopiere filen $1.",
        "backend-fail-notexists": "Filen $1 finnes ikke.",
index 97f08d2..3f3cf2b 100644 (file)
        "noname": "Je mutten n gebrukersnaam opgeven.",
        "loginsuccesstitle": "An-emeld",
        "loginsuccess": "Je bin noen an-emeld bie {{SITENAME}} as \"$1\".",
-       "nosuchuser": "Der is gien gebruker mit de naam \"$1\".\nGebrukersnamen bin heufdlettergeveulig.\nKiek de schriefwieze effen nao of [[Special:UserLogin/signup|maak n nieje gebruker an]].",
+       "nosuchuser": "Der is gien gebruker mit de naam \"$1\".\nGebrukersnamen bin heufdlettergeveulig.\nKiek de schriefwieze effen nao of [[Special:CreateAccount|maak n nieje gebruker an]].",
        "nosuchusershort": "Der is gien gebruker mit de naam \"$1\". Kiek de spelling nao.",
        "nouserspecified": "Vul n naam in",
        "login-userblocked": "Disse gebruker is eblokkeerd.\nJe kunnen niet anmelden.",
        "accmailtext": "Der is n willekeurig wachtwoord veur [[User talk:$1|$1]] verstuurd naor $2. t Kan ewiezigd wörden op de zied ''[[Special:ChangePassword|wachtwoord wiezigen]]'' naoda'j an-emeld bin.",
        "newarticle": "(Niej)",
        "newarticletext": "Disse zied besteet nog niet.\nIn t veld hieronder ku'j wat schrieven um disse zied an te maken (meer informasie vie'j op de [$1 hulpzied]).\nA'j hier per ongelok terechtekeumen bin gebruuk dan de knoppe '''veurige''' um weerumme te gaon.",
-       "anontalkpagetext": "---- ''Disse overlegzied heurt bie n anonieme gebruker die nog gien gebrukersnaam hef, of t niet gebruukt. We gebruken daorumme t IP-adres um hum of heur te herkennen, mer t kan oek ween dat meerdere personen t zelfde IP-adres gebruken, en da'j hiermee berichten ontvangen die niet veur joe bedoeld bin. A'j dit veurkoemen willen, dan ku'j t best [[Special:UserLogin/signup|n gebrukersnaam anmaken]] of [[Special:UserLogin|anmelden]].''",
+       "anontalkpagetext": "---- ''Disse overlegzied heurt bie n anonieme gebruker die nog gien gebrukersnaam hef, of t niet gebruukt. We gebruken daorumme t IP-adres um hum of heur te herkennen, mer t kan oek ween dat meerdere personen t zelfde IP-adres gebruken, en da'j hiermee berichten ontvangen die niet veur joe bedoeld bin. A'j dit veurkoemen willen, dan ku'j t best [[Special:CreateAccount|n gebrukersnaam anmaken]] of [[Special:UserLogin|anmelden]].''",
        "noarticletext": "Der steet noen gien tekste op disse zied.\nJe kunnen [[Special:Search/{{PAGENAME}}|de titel opzeuken]] in aandere ziejen,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} zeuken in de logboeken],\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse zied anmaken]</span>.",
        "noarticletext-nopermission": "Op disse zied steet gien tekste.\nJe kunnen [[Special:Search/{{PAGENAME}}|zeuken naor disse term]] in aandere ziejen of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboeken deurzeuken]</span>, mer je hebben gien rechten um disse zied an te maken.",
        "missing-revision": "De versie #$1 van de zied \"{{FULLPAGENAME}} besteet niet.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].",
        "sectioneditnotsupported-text": "Je kunnen op disse zied gien seksies bewarken.",
        "permissionserrors": "Gien toestemming",
        "permissionserrorstext": "Je maggen of kunnen dit niet doon. De {{PLURAL:$1|reden|redens}} daorveur {{PLURAL:$1|is|bin}}:",
-       "permissionserrorstext-withaction": "Je hebben gien rech um $2, mit de volgende {{PLURAL:$1|reden|redens}}:",
+       "permissionserrorstext-withaction": "Je hebben gien recht um $2, mit de volgende {{PLURAL:$1|reden|redens}}:",
        "recreate-moveddeleted-warn": "'''Waorschuwing: je maken n zied an die eerder al vortedaon is.'''\n\nBedenk eerst of t neudig is um disse zied veerder te bewarken.\nVeur de dudelikheid steet hieronder  t vortdologboek en t herneumlogboek veur disse zied:",
        "moveddeleted-notice": "Disse zied is vortedaon.\nHieronder steet de informasie uut t vortdologboek en t herneumlogboek.",
        "log-fulllog": "t Hele logboek bekieken",
        "powersearch-togglelabel": "Selekteren:",
        "powersearch-toggleall": "Alle",
        "powersearch-togglenone": "Gien",
+       "powersearch-remember": "Keuze onthouwen veur toekomstige zeukopdrachten",
        "search-external": "Extern zeuken",
        "searchdisabled": "Zeuken in {{SITENAME}} is niet meugelik. Je kunnen gebruukmaken van Google. De gegevens over {{SITENAME}} bin misschien niet bie-ewörken.",
        "search-error": "Der is wat mis-egaon bie t zeuken: $1",
        "right-userrights": "Alle gebrukersrechten bewarken",
        "right-userrights-interwiki": "Gebrukersrechten van gebrukers in aandere wiki's wiezigen",
        "right-siteadmin": "De databanke blokkeren en weer vriegeven",
-       "right-override-export-depth": "Ziejen uutvoeren, oek de ziejen waor naor verwezen wörden, tot n diepte van 5",
+       "right-override-export-depth": "Ziejen exporteren, oek de ziejen waor naor verwezen wördt, tot n diepte van 5",
        "right-sendemail": "Bericht versturen naor aandere gebrukers",
        "right-passwordreset": "Bekiek netpostberichten veur t opniej instellen van joew wachtwoord",
        "newuserlogpage": "Logboek mit anwas",
        "sorbsreason": "Joew IP-adres is op-eneumen as open proxyserver in de zwarte lieste van DNS die'w veur {{SITENAME}} gebruken.",
        "sorbs_create_account_reason": "Joew IP-adres is op-eneumen as open proxyserver in de zwarte lieste van DNS, die'w veur {{SITENAME}} gebruken.\nJe kunnen gien gebrukerszied anmaken.",
        "xffblockreason": "n IP-adres dat jie gebruken is eblokkeerd. Dit steet in de kop 'X-Forwarded-For'. De oorspronkelike reden veur de blokkerings is: $1",
-       "cant-see-hidden-user": "De gebruker die'j proberen te blokkeren is al eblokkeerd en verbörgen.\nUmda'j gien rech hebben um gebrukers te verbargen, ku'j de blokkering van de gebruker niet bekieken of bewarken.",
+       "cant-see-hidden-user": "De gebruker die'j proberen te blokkeren is al eblokkeerd en verbörgen.\nUmda'j gien recht hebben um gebrukers te verbargen, ku'j de blokkering van de gebruker niet bekieken of bewarken.",
        "ipbblocked": "Je kunnen gien aandere gebrukers (de)blokkeren, umda'j zelf eblokkeerd bin",
        "ipbnounblockself": "Je maggen je eigen niet deblokkeren",
        "lockdb": "Databanke blokkeren",
        "semiprotectedpagemovewarning": "'''Waorschuwing:''' disse zied kan allinnig deur eregistreerden gebrukers herneumd wörden.\nDe leste logboekregel steet hieronder:",
        "move-over-sharedrepo": "== t Bestaand besteet al ==\n[[:$1]] besteet al in de edeelden mediadatabanke. A'j n bestaand naor disse titel herneumen, dan ku'j  t edeelden bestaand niet gebruken.",
        "file-exists-sharedrepo": "Disse bestaandsnaam besteet al in de edeelden mediadatabanke.\nKies n aandere bestaandsnaam.",
-       "export": "Ziejen uutvoeren",
-       "exporttext": "De tekste en geschiedenisse van n zied of n koppel ziejen kunnen in XML-formaot uutevoerd wörden. Dit bestaand ku'j daornao uutvoeren naor n aandere MediaWiki deur de [[Special:Import|invoerzied]] te gebruken.\n\nZet in t onderstaonde veld de namen van de ziejen die'j uutvoeren willen, één zied per regel, en gif an o'j alle versies mit de bewarkingssamenvatting uutvoeren willen of allinnig de leste versies mit de bewarkingssamenvatting.\n\nA'j dat leste doon willen dan ku'j oek n verwiezing gebruken, bieveurbeeld [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] veur de zied \"[[{{MediaWiki:Mainpage}}]]\".",
-       "exportall": "Alle ziejen uutvoeren",
+       "export": "Ziejen exporteren",
+       "exporttext": "De tekste en geschiedenisse van n zied of n koppel ziejen kunnen in XML-formaot uutevoerd wörden. Dit bestaand ku'j daornao exporteren naor n aandere MediaWiki deur de [[Special:Import|invoerzied]] te gebruken.\n\nZet in t onderstaonde veld de namen van de ziejen die'j exporteren willen, één zied per regel, en geef an o'j alle versies mit de bewarkingssamenvatting exporteren willen of allinnig de leste versies mit de bewarkingssamenvatting.\n\nA'j dat leste doon willen dan ku'j oek n verwiezing gebruken, bieveurbeeld [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] veur de zied \"[[{{MediaWiki:Mainpage}}]]\".",
+       "exportall": "Alle ziejen exporteren",
        "exportcuronly": "Allinnig de actuele versie, niet de veurgeschiedenisse",
-       "exportnohistory": "----\n'''NB:''' t uutvoeren van de hele geschiedenisse is uutezet vanwegen prestasieredens.",
+       "exportnohistory": "----\n'''NB:''' t exporteren van de hele geschiedenisse is uutezet vanwegen prestasieredens.",
        "exportlistauthors": "De hele auteurslieste opnemen veur elke zied",
-       "export-submit": "Uutvoeren",
+       "export-submit": "Exporteren",
        "export-addcattext": "Ziejen derbie doon uut de kategorie:",
        "export-addcat": "Derbie doon",
        "export-addnstext": "Ziejen uut de volgende naamruumte derbie doon:",
index 40f07fd..9ba39f7 100644 (file)
        "oct": "Okt",
        "nov": "Nov.",
        "dec": "Dez",
+       "january-date": "$1. Januar",
+       "february-date": "$1. Februar",
+       "march-date": "$1. März",
+       "april-date": "$1. April",
+       "may-date": "$1. Mai",
+       "june-date": "$1. Juni",
+       "july-date": "$1. Juli",
+       "august-date": "$1. August",
+       "september-date": "$1. September",
+       "october-date": "$1. Oktober",
+       "november-date": "$1. November",
+       "december-date": "$1. Dezember",
        "pagecategories": "{{PLURAL:$1|Kategorie|Kategorien}}",
        "category_header": "Sieden in de Kategorie „$1“",
        "subcategories": "Ünnerkategorien",
        "actions": "Akschonen",
        "namespaces": "Naamrüüm",
        "variants": "Varianten",
+       "navigation-heading": "Navigatschoonsmenü",
        "errorpagetitle": "Fehler",
        "returnto": "Trüch to $1.",
        "tagline": "Vun {{SITENAME}}",
        "hidetoc": "Nich wiesen",
        "collapsible-collapse": "Versteken",
        "collapsible-expand": "Wiesen",
+       "confirmable-yes": "Jo",
+       "confirmable-no": "Nee",
        "thisisdeleted": "Ankieken oder weerholen vun $1?",
        "viewdeleted": "$1 ankieken?",
        "restorelink": "{{PLURAL:$1|ene löschte Version|$1 löschte Versionen}}",
        "nstab-template": "Vörlaag",
        "nstab-help": "Hülp",
        "nstab-category": "Kategorie",
+       "mainpage-nstab": "Hööftsiet",
        "nosuchaction": "Disse Aktschoon gifft dat nich",
        "nosuchactiontext": "De in de URL angeven Akschoon warrt nich ünnerstütt.\nVillicht hest du in de URL en Tippfehler oder büst en verkehrten Lenk nagahn.\nDat kann aver ok op en Bug in de Software henwiesen, de op {{SITENAME}} bruukt warrt.",
        "nosuchspecialpage": "Disse Spezialsiet gifft dat nich",
        "virus-scanfailed": "Scan hett nich klappt (Code $1)",
        "virus-unknownscanner": "Unbekannten Virenscanner:",
        "logouttext": "'''Du büst nu afmellt.'''\n\nDu kannst {{SITENAME}} nu anonym wiederbruken oder di ünner dissen oder en annern Brukernaam wedder <span class='plainlinks'>[$1 anmellen]</span>.\nDenk dor an, dat welk Sieden ünner Ümstänn noch jümmer so wiest warrn köönt, as wenn du anmellt weerst. Dat ännert sik, wenn du den Cache vun dien Browser leddig maakst.",
+       "welcomeuser": "Willkamen, $1!",
        "yourname": "Dien Brukernaam",
        "userlogin-yourname": "Brukernaam",
        "yourpassword": "Dien Passwoort",
        "noname": "Du muttst en Brukernaam angeven.",
        "loginsuccesstitle": "Anmellen hett Spood",
        "loginsuccess": "Du büst nu as „$1“ bi {{SITENAME}} anmellt.",
-       "nosuchuser": "Den Brukernaam „$1“ gifft dat nich.\nBrukernaams maakt en Ünnerscheed twischen groot un lütt schrevene Bookstaven.\nKiek de Schrievwies na oder [[Special:UserLogin/signup|mell di as ne’en Bruker an]].",
+       "nosuchuser": "Den Brukernaam „$1“ gifft dat nich.\nBrukernaams maakt en Ünnerscheed twischen groot un lütt schrevene Bookstaven.\nKiek de Schrievwies na oder [[Special:CreateAccount|mell di as ne’en Bruker an]].",
        "nosuchusershort": "De Brukernaam „$1“ existeert nich. Prööv de Schrievwies.",
        "nouserspecified": "Du musst en Brukernaam angeven",
        "login-userblocked": "Disse Bruker is sperrt. Anmellen geiht nich.",
        "accmailtext": "En tofällig Passwoord för [[User talk:$1|$1]] is $2 tostüürt worrn.\n\nDat Passwoord för dit Brukerkonto kann na dat Anmellen ünner ''[[Special:ChangePassword|Passwoord ännern]]'' ännert warrn.",
        "newarticle": "(Nee)",
        "newarticletext": "Du büst op en Sied kamen, de dat noch nich gifft.\nWenn du disse Sied opstellen wullt, schriev dien Text in dat Finster ünnen  (kiek op de [$1 Hülpsied] för mehr Infos).\nWenn du de Sied gornich ännern wullst, denn klick op den '''Trügg'''-Knoop in dien Webkieker.",
-       "anontalkpagetext": "---- ''Dit is de Diskuschoonssiet vun en nich anmellt Bruker, de noch keen Brukerkonto anleggt hett oder dat jüst nich bruukt.\nWi mööt hier de numerische IP-Adress verwennen, üm den Bruker to identifizeern.\nSo en Adress kann vun verscheden Brukern bruukt warrn.\nWenn du en anonymen Bruker büst un meenst, dat disse Kommentaren nich an di richt sünd, denn [[Special:UserLogin/signup|legg di en Brukerkonto an]] oder [[Special:UserLogin|mell di an]], dat dat Problem nich mehr dor is.''",
-       "noarticletext": "Dor is opstunns keen Text op disse Sied. Du kannst [[Special:Search/{{PAGENAME}}|na dissen Utdruck in annere Sieden söken]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} in de Logböker söken],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse Sied ännern]</span>.",
+       "anontalkpagetext": "---- ''Dit is de Diskuschoonssiet vun en nich anmellt Bruker, de noch keen Brukerkonto anleggt hett oder dat jüst nich bruukt.\nWi mööt hier de numerische IP-Adress verwennen, üm den Bruker to identifizeern.\nSo en Adress kann vun verscheden Brukern bruukt warrn.\nWenn du en anonymen Bruker büst un meenst, dat disse Kommentaren nich an di richt sünd, denn [[Special:CreateAccount|legg di en Brukerkonto an]] oder [[Special:UserLogin|mell di an]], dat dat Problem nich mehr dor is.''",
+       "noarticletext": "Dor is opstunns keen Text op disse Siet. \nDu kannst [[Special:Search/{{PAGENAME}}|na dissen Utdruck in annere Sieden söken]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} in de Logböker söken], oder [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse Siet ännern]</span>.",
        "noarticletext-nopermission": "Disse Sied hett opstunns keen Text.\nDu kannst in annere Sieden [[Special:Search/{{PAGENAME}}|na dissen Titel söken]]\noder <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} in de Logböker söken]</span>, man du hest nich dat Recht, de Sied optostellen.",
        "userpage-userdoesnotexist": "Dat Brukerkonto „<nowiki>$1</nowiki>“ gifft dat noch nich. Överlegg, wat du disse Siet würklich nee opstellen/ännern wullt.",
        "userpage-userdoesnotexist-view": "Dat Brukerkonto „$1“ gifft dat nich.",
        "session_fail_preview_html": "'''Deit uns leed! Wi kunnen dien Ännern nich spiekern, de Sitzungsdaten sünd verloren gahn.'''\n\n''In {{SITENAME}} is dat Spiekern vun rein HTML verlöövt, dorvun is de Vörschau utblennt, dat JavaScript-Angrepen nich mööglich sünd.''\n\n'''Versöök dat noch wedder un klick noch wedder op „Siet spiekern“. Wenn dat Problem noch jümmer dor is, [[Special:UserLogout|mell di af]] un denn wedder an.'''",
        "token_suffix_mismatch": "'''Dien Ännern sünd afwiest worrn. Dien Browser hett welk Teken in de Kuntrull-Tekenreeg kaputt maakt.\nWenn dat so spiekert warrt, kann dat angahn, dat noch mehr Teken in de Sied kaputt gaht.\nDat kann to’n Bispeel dor vun kamen, dat du en anonymen Proxy-Deenst bruukst, de wat verkehrt maakt.'''",
        "editing": "Ännern vun $1",
+       "creating": "Opstellen vun $1",
        "editingsection": "Ännern vun $1 (Afsatz)",
        "editingcomment": "Ännern vun $1 (nee Afsnidd)",
        "editconflict": "Konflikt bi’t Sied ännern: $1",
        "currentrev": "Aktuelle Version",
        "currentrev-asof": "Aktuelle Version vun’n $1",
        "revisionasof": "Version vun $1",
-       "revision-info": "Verschoon vun'n $4, Klock $5 vun $2",
+       "revision-info": "Verschoon vun'n $4, Klock $5 vun {{GENDER:$6|$2}}$7",
        "previousrevision": "Nächstöllere Version→",
        "nextrevision": "Ne’ere Version →",
        "currentrevisionlink": "aktuelle Version",
        "shown-title": "Wies $1 {{PLURAL:$1|Resultat|Resultaten}} per Sied",
        "viewprevnext": "Wies ($1 {{int:pipe-separator}} $2) ($3).",
        "searchmenu-exists": "* Sied '''[[$1]]'''",
-       "searchmenu-new": "'''Stell de Sied „[[:$1]]“ in dit Wiki nee op!'''",
+       "searchmenu-new": "<strong>Stell de Siet „[[:$1]]“ in dit Wiki nee op!</strong> {{PLURAL:$2|0=|Süh ok de Siet mit dien Sökresultat.|Süh ok de funnen Sökresultaten.}}",
        "searchprofile-articles": "Inholdsieden",
        "searchprofile-images": "Datein",
        "searchprofile-everything": "Allens",
        "powersearch-togglelabel": "Utwählen:",
        "powersearch-toggleall": "All",
        "powersearch-togglenone": "Keen",
+       "powersearch-remember": "Utwahl för latere sökanfragen marken",
        "search-external": "Externe Söök",
        "searchdisabled": "<p>De Vulltextsöök is wegen Överlast en Stoot deaktiveert. In disse Tied kannst du disse Google-Söök verwennen,\nde aver nich jümmer den aktuellsten Stand weerspegelt.<p>",
        "preferences": "Instellen",
        "action-userrights-interwiki": "de Rechten vun Brukers op annere Wikis to ännern",
        "action-siteadmin": "de Datenbank to sperren oder freetogeven",
        "nchanges": "{{PLURAL:$1|Een Ännern|$1 Ännern}}",
+       "enhancedrc-history": "Historie",
        "recentchanges": "Toletzt ännert",
        "recentchanges-legend": "Optionen för toletzt ännert",
        "recentchanges-summary": "Op disse Sied warrt de Sieden wiest, de toletzt ännert worrn sünd.",
        "recentchanges-label-minor": "Dat is en lütte Ännern",
        "recentchanges-label-bot": "Düsse Ännern worr maakt vun en Bot",
        "recentchanges-label-unpatrolled": "Düsse Ännern is noch nich kontrolleert worrn",
+       "recentchanges-label-plusminus": "Disse Siedengrött is mit dit Antall Bytes ännert",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (süh ok de [[Special:NewPages|List mit ne'e Sieden]])",
        "rcnotefrom": "Dit sünd de Ännern siet <b>$2</b> (bet to <b>$1</b> wiest).",
        "rclistfrom": "Wies ne’e Ännern siet $3 $2",
-       "rcshowhideminor": "$1 lütte Ännern",
-       "rcshowhidebots": "$1 Bots",
-       "rcshowhideliu": "$1 inloggte Brukers",
-       "rcshowhideanons": "$1 anonyme Brukers",
+       "rcshowhideminor": "lütte Ännern $1",
+       "rcshowhideminor-show": "wiesen",
+       "rcshowhideminor-hide": "versteken",
+       "rcshowhidebots": "Bots $1",
+       "rcshowhidebots-show": "wiesen",
+       "rcshowhidebots-hide": "versteken",
+       "rcshowhideliu": "registreerte Brukers $1",
+       "rcshowhideliu-show": "wiesen",
+       "rcshowhideliu-hide": "versteken",
+       "rcshowhideanons": "anonyme Brukers $1",
+       "rcshowhideanons-show": "wiesen",
+       "rcshowhideanons-hide": "versteken",
        "rcshowhidepatr": "$1 nakekene Ännern",
-       "rcshowhidemine": "$1 miene Ännern",
+       "rcshowhidepatr-show": "wiesen",
+       "rcshowhidepatr-hide": "versteken",
+       "rcshowhidemine": "miene Ännern $1",
+       "rcshowhidemine-show": "wiesen",
+       "rcshowhidemine-hide": "versteken",
+       "rcshowhidecategorization": "kategoriserung vun Sieden $1",
        "rclinks": "Wies de letzten '''$1''' Ännern vun de letzten '''$2''' Daag. ('''N''' - Ne’e Sieden; '''L''' - Lütte Ännern)<br />$3",
        "diff": "Ünnerscheed",
        "hist": "Versionen",
        "hide": "Nich wiesen",
-       "show": "Wiesen",
+       "show": "wiesen",
        "minoreditletter": "L",
        "newpageletter": "N",
        "boteditletter": "B",
        "pager-older-n": "{{PLURAL:$1|vörige|vörige $1}}",
        "suppress": "Oversight",
        "apisandbox-examples": "Bispeel",
-       "apisandbox-results": "Resultat",
+       "apisandbox-results": "Resultaten",
        "booksources": "Bookhannel",
        "booksources-search-legend": "Na Böker bi Bookhökers söken",
        "booksources-search": "Söken",
        "wlheader-showupdated": "Sieden, de siet dien letzten Besöök ännert worrn sünd, warrt '''fett''' wiest.",
        "wlnote": "Ünnen {{PLURAL:$1|steiht de letzte Ännern|staht de letzten $1 Ännern}} vun de {{PLURAL:$2|letzte Stünn|letzten '''$2''' Stünnen}}.",
        "wlshowlast": "Wies de letzten $1 Stünnen $2 Daag",
+       "watchlist-hide": "Versteken",
        "watchlist-options": "Optionen för de Oppasslist",
        "watching": "warrt op de Oppasslist ropsett...",
        "unwatching": "warrt vun de Oppasslist rünnernahmen...",
        "undelete-show-file-submit": "Jo",
        "namespace": "Naamruum:",
        "invert": "Utwahl ümkehren",
+       "namespace_association": "Tohörige Naamruum",
        "blanknamespace": "(Hööft-)",
-       "contributions": "Bidrääg vun den Bruker",
+       "contributions": "Bidrääg vun den {{GENDER:$1|Bruker}}",
        "contributions-title": "Brukerbidrääg vun „$1“",
        "mycontris": "Mien Arbeid",
        "contribsub2": "För $1 ($2)",
        "whatlinkshere-prev": "{{PLURAL:$1|vörige|vörige $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|nächste|nächste $1}}",
        "whatlinkshere-links": "← Lenken",
-       "whatlinkshere-hideredirs": "Redirects $1",
+       "whatlinkshere-hideredirs": "Redirects versteken",
        "whatlinkshere-hidetrans": "Vörlageninbinnungen $1",
-       "whatlinkshere-hidelinks": "Lenken $1",
+       "whatlinkshere-hidelinks": "Lenken versteken",
        "whatlinkshere-hideimages": "Dateilenken $1",
        "whatlinkshere-filters": "Filters",
        "autoblockid": "Autoblock #$1",
        "importlogpagetext": "Administrativen Import vun Sieden mit Versionsgeschicht vun annere Wikis.",
        "import-logentry-upload-detail": "{{PLURAL:$1|ene Version|$1 Versionen}}",
        "import-logentry-interwiki-detail": "{{PLURAL:$1|ene Version|$1 Versionen}} vun $2",
-       "tooltip-pt-userpage": "Dien Brukersied",
+       "tooltip-pt-userpage": "{{GENDER:|Dien}} Brukersiet",
        "tooltip-pt-anonuserpage": "De Brukersiet för de IP-Adress ünner de du schriffst",
-       "tooltip-pt-mytalk": "Dien Diskuschoonssied",
+       "tooltip-pt-mytalk": "{{GENDER:|Dien}} Diskuschoonssiet",
        "tooltip-pt-anontalk": "Diskuschoon över Ännern vun disse IP-Adress",
-       "tooltip-pt-preferences": "Mien Instellen",
+       "tooltip-pt-preferences": "{{GENDER:|Dien}} Instellen",
        "tooltip-pt-watchlist": "Mien Oppasslist",
-       "tooltip-pt-mycontris": "List vun dien Bidrääg",
+       "tooltip-pt-mycontris": "List vun {{GENDER:|dien}} Bidrääg",
        "tooltip-pt-login": "Du kannst di geern anmellen, dat is aver nich nödig, dat du Sieden ännern kannst.",
        "tooltip-pt-logout": "Afmellen",
        "tooltip-ca-talk": "Diskuschoon över disse Siet",
-       "tooltip-ca-edit": "Du kannst disse Siet ännern. Bruuk dat vör dat Spiekern.",
+       "tooltip-ca-edit": "Disse Siet ännern",
        "tooltip-ca-addsection": "En Afsnidd tofögen",
        "tooltip-ca-viewsource": "Disse Siet is schuult. Du kannst den Borntext ankieken.",
        "tooltip-ca-history": "Historie vun disse Siet.",
        "tooltip-t-recentchangeslinked": "Verlinkte Sieden",
        "tooltip-feed-rss": "RSS-Feed för disse Siet",
        "tooltip-feed-atom": "Atom-Feed för disse Siet",
-       "tooltip-t-contributions": "List vun de Bidreeg vun dissen Bruker",
+       "tooltip-t-contributions": "List vun de Bidrääg vun {{GENDER:$1|dissen Bruker}}",
        "tooltip-t-emailuser": "Dissen Bruker en E-Mail tostüren",
        "tooltip-t-upload": "Biller oder Mediendatein hoochladen",
        "tooltip-t-specialpages": "List vun alle Spezialsieden",
        "tooltip-ca-nstab-main": "Siet ankieken",
        "tooltip-ca-nstab-user": "Brukersiet ankieken",
        "tooltip-ca-nstab-media": "Mediensiet ankieken",
-       "tooltip-ca-nstab-special": "Dit is en Spezialsiet, du kannst disse Siet nich ännern.",
+       "tooltip-ca-nstab-special": "Dit is en Spezialsiet un kann nich ännert worrn.",
        "tooltip-ca-nstab-project": "Portalsiet ankieken",
        "tooltip-ca-nstab-image": "Bildsiet ankieken",
        "tooltip-ca-nstab-mediawiki": "Systemnarichten ankieken",
        "spambot_username": "MediaWiki Spam-Oprümen",
        "spam_reverting": "Trüchdreiht na de letzte Version ahn Lenken na $1.",
        "spam_blanking": "All Versionen harrn Lenken na $1, rein maakt.",
-       "simpleantispam-label": "Antispam-Kuntrull. Hier '''nix''' indragen!",
+       "simpleantispam-label": "Antispam-Kuntrull. \nHier '''nix''' indragen!",
        "pageinfo-title": "Informatschoon för \"$1\"",
        "pageinfo-article-id": "Sied-ID",
+       "pageinfo-toolboxlink": "Sieteninformatschonen",
        "pageinfo-redirectsto-info": "Info",
        "pageinfo-contentpage-yes": "Jo",
        "pageinfo-protect-cascading-yes": "Jo",
        "file-info-size": "$1 × $2 Pixel, Grött: $3, MIME-Typ: $4",
        "file-nohires": "Gifft dat Bild nich grötter.",
        "svg-long-desc": "SVG-Datei, Utgangsgrött: $1 × $2 Pixel, Dateigrött: $3",
-       "show-big-image": "Dat Bild wat grötter",
+       "show-big-image": "Originaldatei",
        "show-big-image-size": "$1 × $2 Pixels",
        "file-info-gif-looped": "löppt as Slööp",
        "file-info-gif-frames": "$1 {{PLURAL:$1|Bild|Biller}}",
        "htmlform-selectorother-other": "Annere",
        "sqlite-has-fts": "$1 mit Stöhn för Vulltext-Söök",
        "sqlite-no-fts": "$1 ahn Stöhn för Vulltext-Söök",
+       "logentry-delete-delete": "$1 {{GENDER:$2|wegsmeten}} Siet $3",
        "revdelete-restricted": "Inschränkungen för Administraters instellt",
        "revdelete-unrestricted": "Inschränkungen för Administraters rutnahmen",
        "logentry-block-block": "$1 {{GENDER:$2|block}} {{GENDER:$4|$3}} för en Tiedruum vun $5 $6",
        "expand_templates_ok": "Los",
        "expand_templates_remove_comments": "Kommentaren rutnehmen",
        "expand_templates_generate_xml": "XML-Parser-Boom wiesen",
-       "expand_templates_preview": "Vörschau"
+       "expand_templates_preview": "Vörschau",
+       "pagelang-language": "Spraak"
 }
index 470c288..abc600d 100644 (file)
        "noname": "तपाईंले सही प्रयोगकर्ता नाम दिनु भएन।",
        "loginsuccesstitle": "प्रवेश सफल",
        "loginsuccess": "'''तपाईंले {{SITENAME}}मा  \"$1\" को रुपमा प्रवेश गर्नुभएकोछ।'''",
-       "nosuchuser": "\"$1\" को नामबाट कुनै पनि प्रयोगकर्ता भेटिएनन् ।\nप्रयोगकर्ता नाम वर्णसंवेदनशील हुन्छन् ।\nहिज्जे जाँच्नुहोस् , या [[Special:UserLogin/signup|नयाँ खाता बनाउनुहोस्]].",
+       "nosuchuser": "\"$1\" को नामबाट कुनै पनि प्रयोगकर्ता भेटिएनन् ।\nप्रयोगकर्ता नाम वर्णसंवेदनशील हुन्छन् ।\nहिज्जे जाँच्नुहोस् , या [[Special:CreateAccount|नयाँ खाता बनाउनुहोस्]].",
        "nosuchusershort": " \"$1\" नामको कुनै पनि प्रयोगकर्ता भेटिएन।\n तपाईँको हिज्जे जाँच्नुहोस् ।",
        "nouserspecified": "तपाँईले प्रयोगकर्ताको नाम जनाउनुपर्छ।",
        "login-userblocked": "यस प्रयोगकर्तालाई रोक लगाइएको छ। प्रवेश गर्ने अनुमति छैन।",
        "accmailtext": "जथाभावीरूपमा सृजना गरिएको प्रवेशशब्द प्रयोगकर्ता [[User talk:$1|$1]] को  $2 मा पठाइएको छ।\n\nयो नयाँ खाताको प्रवेशशब्द  ''[[Special:ChangePassword|change password]]'' मा प्रवेश गरेर परिवर्तन गर्न सकिन्छ ।",
        "newarticle": "(नयाँ)",
        "newarticletext": "तपाईँले अहिले सम्म नभएको पृष्ठको लिंङ्क पहिल्याउनु भएको छ।\nयो पृष्ठ निर्माण गर्न तलको कोष्ठमा टाइप गर्नुहोस्  ।(थप जानकारीको लागि [$1 help page] हेर्नुहोस् )।\nयहाँ त्यत्तिकै आइपुग्नु भएको हो भने , ब्राउजरको  '''back''' बटन थिच्नुहोस ।",
-       "anontalkpagetext": "----''यो वार्तालाप पृष्ठ अज्ञात प्रयोगकर्ताको हो जसले अहिलेसम्म खाता बनाएकै छैन, अथवा जसले यस पृष्ठको उपयोग गर्दैन।\nयस कारण हामीले उसलाई उसको आइ पी (IP) ठेगानाले चिन्न सक्छौं। \nयस्तो आइ पी (IP) ठेगाना धेरै प्रयोगकर्ताहरूको साझा हुनसक्छ।\nयदि तपाईं अज्ञात प्रयोगकर्ता हुनुहुन्छ र तपाईंमाथि अचाहिँदो टिप्पणी भएको अनुभव गर्नुहुन्छ भने भविष्यमा अन्य अज्ञात प्रयोगकर्तासितको भ्रमबाट बाँच्न कृपया [[Special:UserLogin/signup|खाता खोल्नुहोस्]] अथवा [[Special:UserLogin|प्रवेश गर्नुहोस्]] ''",
+       "anontalkpagetext": "----''यो वार्तालाप पृष्ठ अज्ञात प्रयोगकर्ताको हो जसले अहिलेसम्म खाता बनाएकै छैन, अथवा जसले यस पृष्ठको उपयोग गर्दैन।\nयस कारण हामीले उसलाई उसको आइ पी (IP) ठेगानाले चिन्न सक्छौं। \nयस्तो आइ पी (IP) ठेगाना धेरै प्रयोगकर्ताहरूको साझा हुनसक्छ।\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}}|पृष्ठ={{FULLPAGENAMEE}}}} सम्बन्धित लगहरू खोज्न सक्नुहुनेछ ]</span> तर तपाईंलाई नयाँ पृष्ठ बनाउने अधिकार छैन।",
        "missing-revision": "\"{{FULLPAGENAME}}\" पृष्ठको अवतरण #$1 रहेको छैन।\n\nसामान्य रूपमा यसो एउटा हटाइएको पृष्ठको पुरानो लिङ्कमा क्लिक गर्दा हुन्छ।\nअधिक जानकारीको लागि तपाईं [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटाएको लग] हेर्न सक्नुहुन्छ।",
        "upload-form-label-infoform-description": "वर्णन",
        "upload-form-label-usage-title": "प्रयोग",
        "upload-form-label-usage-filename": "फाइल नाम",
-       "foreign-structured-upload-form-label-infoform-categories": "श्रेणीहरू",
-       "foreign-structured-upload-form-label-infoform-date": "मिति",
+       "upload-form-label-infoform-categories": "श्रेणीहरू",
+       "upload-form-label-infoform-date": "मिति",
        "backend-fail-stream": "फाइल ''$1'' प्रवाह गर्न सकिएन ।",
        "backend-fail-backup": "फाइल ''$1'' जगेडा संग्रह गर्न सकिएन ।",
        "backend-fail-notexists": "फाइल $1 पृष्ठ अस्तित्वमा छैन ।",
index 9156377..172abe7 100644 (file)
        "noname": "U hebt geen geldige gebruikersnaam opgegeven.",
        "loginsuccesstitle": "Aangemeld",
        "loginsuccess": "<strong>U bent nu aangemeld bij {{SITENAME}} als \"$1\".</strong>",
-       "nosuchuser": "De gebruiker \"$1\" bestaat niet.\nGebruikersnamen zijn hoofdlettergevoelig.\nControleer de schrijfwijze of [[Special:UserLogin/signup|maak een nieuw gebruiker aan]].",
+       "nosuchuser": "De gebruiker \"$1\" bestaat niet.\nGebruikersnamen zijn hoofdlettergevoelig.\nControleer de schrijfwijze of [[Special:CreateAccount|maak een nieuw gebruiker aan]].",
        "nosuchusershort": "De gebruiker \"$1\" bestaat niet.\nControleer de schrijfwijze.",
        "nouserspecified": "Geef een gebruikersnaam op.",
        "login-userblocked": "Deze gebruiker is geblokkeerd.\nAanmelden is niet mogelijk.",
        "accmailtext": "Een willekeurig gegenereerd wachtwoord voor [[User talk:$1|$1]] is verzonden naar $2. Het kan worden gewijzigd op de pagina \"[[Special:ChangePassword|wachtwoord wijzigen]]\" na het aanmelden.",
        "newarticle": "(Nieuw)",
        "newarticletext": "Deze pagina bestaat niet.\nTyp in het onderstaande veld om de pagina aan te maken (meer informatie staat op de [$1 hulppagina]).\nGebruik de knop <strong>Terug</strong> in uw browser als u hier per ongeluk terecht bent gekomen.",
-       "anontalkpagetext": "----\n<em>Deze overlegpagina hoort bij een anonieme gebruiker die geen gebruikersnaam heeft of deze niet gebruikt.</em>\nDaarom wordt het IP-adres ter identificatie gebruikt.\nHet is mogelijk dat meerdere personen hetzelfde IP-adres gebruiken.\nMogelijk ontvangt u hier berichten die niet voor u bedoeld zijn.\nAls u dat wilt voorkomen, [[Special:UserLogin/signup|registreer u]] of [[Special:UserLogin|meld u aan]] om verwarring met andere anonieme gebruikers te voorkomen.",
+       "anontalkpagetext": "----\n<em>Deze overlegpagina hoort bij een anonieme gebruiker die geen gebruikersnaam heeft of deze niet gebruikt.</em>\nDaarom wordt het IP-adres ter identificatie gebruikt.\nHet is mogelijk dat meerdere personen hetzelfde IP-adres gebruiken.\nMogelijk ontvangt u hier berichten die niet voor u bedoeld zijn.\nAls u dat wilt voorkomen, [[Special:CreateAccount|registreer u]] of [[Special:UserLogin|meld u aan]] om verwarring met andere anonieme gebruikers te voorkomen.",
        "noarticletext": "Deze pagina bevat geen tekst.\nU kunt [[Special:Search/{{PAGENAME}}|naar deze term zoeken]] in andere pagina's, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboeken doorzoeken] of [{{fullurl:{{FULLPAGENAME}}|action=edit}} deze pagina aanmaken]</span>.",
        "noarticletext-nopermission": "Deze pagina bevat geen tekst.\nU kunt [[Special:Search/{{PAGENAME}}|naar deze term zoeken]] in andere pagina's of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboeken doorzoeken]</span>, maar u mag de pagina niet aanmaken.",
        "missing-revision": "De versie #$1 van de pagina \"{{FULLPAGENAME}}\" bestaat niet.\n\nDit wordt meestal veroorzaakt door het volgen van een verouderde koppeling naar een pagina die is verwijderd.\nMeer gegevens zijn mogelijk te vinden in het [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} verwijderingslogboek].",
        "upload-form-label-infoform-description-tooltip": "Beschrijf kort alles wat voor het werk van belang is.\nBenoem voor een afbeelding de belangrijkste zaken die zijn afgebeeld, alsmede de plaats of de gelegenheid.",
        "upload-form-label-usage-title": "Gebruik",
        "upload-form-label-usage-filename": "Bestandsnaam",
-       "foreign-structured-upload-form-label-own-work": "Dit is mijn eigen werk",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorieën",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Ik bevestig dat ik dit bestand upload onder de voorwaarden en het licentiebeleid van {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Als u niet in staat bent dit bestand te uploaden onder het beleid van {{SITENAME}}, sluit dit venster dan alstublieft en kies een andere methode.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "U kunt ook de [[Special:Upload|standaard uploadpagina]] gebruiken.",
-       "foreign-structured-upload-form-label-own-work-message-default": "Ik begrijp dat ik dit bestand upload naar een gedeelde repository. Ik bevestig dat ik voldoe aan de voorwaarden en het licentiebeleid daar.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Als u niet in staat bent dit bestand te uploaden onder het beleid van de gedeelde repository, sluit dit venster dan alstublieft en kies een andere methode.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "U kunt ook proberen de [[Special:Upload|uploadpagina van {{SITENAME}}]] te gebruiken als dit bestand geüpload kan worden onder hun beleid.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Ik verklaar dat ik de auteursrechten bezit op dit bestand en ik ga onherroepbaar akkoord met het vrijgeven van dit bestand aan Wikimedia Commons onder de licentie [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Naamsvermelding-GelijkDelen 4.0] en ik ga akkoord met de [https://wikimediafoundation.org/wiki/Terms_of_Use Gebruiksvoorwaarden].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Als u geen eigenaar bent van de auteursrechten van dit bestand, of als u het onder een andere licentie wilt vrijgeven, overweeg dan gebruik te maken van de [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "U kunt ook de [[Special:Upload|uploadpagina op {{SITENAME}}]] gebruiken, als de site het uploaden van dit bestand onder hun beleid toestaat.",
+       "upload-form-label-own-work": "Dit is mijn eigen werk",
+       "upload-form-label-infoform-categories": "Categorieën",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Ik bevestig dat ik dit bestand upload onder de voorwaarden en het licentiebeleid van {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Als u niet in staat bent dit bestand te uploaden onder het beleid van {{SITENAME}}, sluit dit venster dan alstublieft en kies een andere methode.",
+       "upload-form-label-not-own-work-local-generic-local": "U kunt ook de [[Special:Upload|standaard uploadpagina]] gebruiken.",
+       "upload-form-label-own-work-message-generic-foreign": "Ik begrijp dat ik dit bestand upload naar een gedeelde repository. Ik bevestig dat ik voldoe aan de voorwaarden en het licentiebeleid daar.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Als u niet in staat bent dit bestand te uploaden onder het beleid van de gedeelde repository, sluit dit venster dan alstublieft en kies een andere methode.",
+       "upload-form-label-not-own-work-local-generic-foreign": "U kunt ook proberen de [[Special:Upload|uploadpagina van {{SITENAME}}]] te gebruiken als dit bestand geüpload kan worden onder hun beleid.",
        "backend-fail-stream": "Het was niet mogelijk het bestand \"$1\" te streamen.",
        "backend-fail-backup": "Het was niet mogelijk een reservekopie van het bestand $1 te maken.",
        "backend-fail-notexists": "Het bestand $1 bestaat niet.",
        "whatlinkshere-prev": "{{PLURAL:$1|vorige|vorige $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|volgende|volgende $1}}",
        "whatlinkshere-links": "← koppelingen",
-       "whatlinkshere-hideredirs": "doorverwijzingen $1",
-       "whatlinkshere-hidetrans": "Transclusies $1",
-       "whatlinkshere-hidelinks": "koppelingen $1",
+       "whatlinkshere-hideredirs": "Verberg doorverwijzingen",
+       "whatlinkshere-hidetrans": "Verberg transclusies",
+       "whatlinkshere-hidelinks": "Verberg links",
        "whatlinkshere-hideimages": "Bestandskoppelingen $1",
        "whatlinkshere-filters": "Filters",
        "whatlinkshere-submit": "OK",
        "tooltip-ca-nstab-category": "Categoriepagina bekijken",
        "tooltip-minoredit": "Deze wijziging als een kleine wijziging markeren",
        "tooltip-save": "Wijzigingen opslaan",
+       "tooltip-publish": "Uw wijzigingen publiceren",
        "tooltip-preview": "Een voorvertoning maken. Gebruik dit voordat u opslaat!",
        "tooltip-diff": "Weergeven welke wijzigingen u aan de tekst hebt gemaakt",
        "tooltip-compareselectedversions": "De verschillen tussen de geselecteerde versies van deze pagina bekijken.",
        "mw-widgets-titleinput-description-redirect": "doorverwijzing naar $1",
        "api-error-blacklisted": "Kies een andere, beschrijvende naam.",
        "sessionmanager-tie": "Het is niet mogelijk om meerdere authenticatietypen voor verzoeken te combineren: $1.",
-       "sessionprovider-generic": "$1 sessies",
-       "sessionprovider-mediawiki-session-cookiesessionprovider": "sessies gebaseerd op cookies",
+       "sessionprovider-generic": "$1-sessies",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "op cookies gebaseerde sessies",
        "sessionprovider-nocookies": "Cookies kunnen uitgeschakeld zijn. Zorg ervoor dat u cookies hebt ingeschakeld en probeer het opnieuw.",
        "randomrootpage": "Willekeurige hoofdpagina",
        "log-action-filter-block": "Soort blokkade:",
index c01b64a..c5c1570 100644 (file)
                        "Gaute",
                        "Macofe",
                        "Chameleon222",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "Mortensson"
                ]
        },
        "tog-underline": "Strek under lenkjer:",
        "tog-hideminor": "Gøym småplukk i lista over siste endringar",
        "tog-hidepatrolled": "Gøym patruljerte endringar i lista over siste endringar",
        "tog-newpageshidepatrolled": "Gøym patruljerte sider frå lista over nye sider",
+       "tog-hidecategorization": "Løyn sidekategoriseringa",
        "tog-extendwatchlist": "Utvid overvakingslista til å vise alle endringane, ikkje berre dei siste",
        "tog-usenewrc": "Grupper endringar etter side i siste endringane og på overvakingslista",
        "tog-numberheadings": "Vis nummererte overskrifter",
@@ -41,6 +43,8 @@
        "tog-watchdefault": "Legg til sidene og filene eg endrar på overvakingslista mi",
        "tog-watchmoves": "Legg til sidene og filene eg flytter på overvakingslista mi",
        "tog-watchdeletion": "Legg til sidene og filene eg slettar på overvakingslista mi",
+       "tog-watchuploads": "Overvak nye filer som eg lastar opp",
+       "tog-watchrollback": "Overvak sider som eg har attra",
        "tog-minordefault": "Merk endringar som «småplukk» som standard",
        "tog-previewontop": "Vis førehandsvisinga før endringsboksen",
        "tog-previewonfirst": "Førehandsvis første endring",
        "tog-watchlisthidebots": "Gøym endringar gjorde av robotar i overvakingslista",
        "tog-watchlisthideminor": "Gøym småplukk i overvakingslista",
        "tog-watchlisthideliu": "Gøym endringar av innlogga brukarar i overvakingslista.",
+       "tog-watchlistreloadautomatically": "Oppdater overvakingslista automatisk når eit filter vert endra (krev JavaScript)",
        "tog-watchlisthideanons": "Gøym endringar av anonyme brukarar i overvakingslista.",
        "tog-watchlisthidepatrolled": "Gøym patruljerte endringar i overvakingslista",
+       "tog-watchlisthidecategorization": "Løyn sidekategoriseringa",
        "tog-ccmeonemails": "Send meg kopi av e-postane eg sender til andre brukarar",
        "tog-diffonly": "Ikkje vis sideinnhaldet under skilnadene mellom versjonane",
        "tog-showhiddencats": "Vis gøymde kategoriar",
        "october-date": "$1. oktober",
        "november-date": "$1. november",
        "december-date": "$1. desember",
+       "period-am": "f.m.",
+       "period-pm": "e.m.",
        "pagecategories": "{{PLURAL:$1|Kategori|Kategoriar}}",
        "category_header": "Artiklar i kategorien «$1»",
        "subcategories": "Underkategoriar",
        "morenotlisted": "Lista er ikkje heil.",
        "mypage": "Sida mi",
        "mytalk": "Diskusjon",
-       "anontalk": "Diskusjonside for denne IP-adressa",
+       "anontalk": "Diskusjon",
        "navigation": "Navigering",
        "and": "&#32;og",
        "qbfind": "Finn",
        "noname": "Du har ikkje oppgjeve gyldig brukarnamn.",
        "loginsuccesstitle": "Du er no innlogga",
        "loginsuccess": "Du er no innlogga som «$1».",
-       "nosuchuser": "Det finst ikkje nokon brukar med brukarnamnet «$1».\nBrukarnamn skil mellom stor og liten bokstav. Sjekk at du har skrive brukarnamet rett eller [[Special:UserLogin/signup|opprett ein ny konto]].",
+       "nosuchuser": "Det finst ikkje nokon brukar med brukarnamnet «$1».\nBrukarnamn skil mellom stor og liten bokstav. Sjekk at du har skrive brukarnamet rett eller [[Special:CreateAccount|opprett ein ny konto]].",
        "nosuchusershort": "Det finst ikkje nokon brukar med brukarnamnet «$1». Sjekk at du har skrive rett.",
        "nouserspecified": "Du må oppgje eit brukarnamn.",
        "login-userblocked": "Denne brukaren er blokkert. Innlogging er ikkje tillate.",
        "accmailtext": "Eit tilfeldig laga passord for [[User talk:$1|$1]] er sendt til $2.\n\nPassordet for den nye kontoen kan verta endra på ''[[Special:ChangePassword|endra passord]]''-sida etter innlogging.",
        "newarticle": "(Ny)",
        "newarticletext": "Du har følgt ei lenkje til ei side som ikkje finst enno.\nFor å opprette sida, kan du skrive i boksen under (sjå [$1 hjelpesida] for meir informasjon).\nHamna du her ved ein feil, klikk på '''attende'''-knappen i nettlesaren din.",
-       "anontalkpagetext": "----''Dette er ei diskusjonsside for ein anonym brukar som ikkje har oppretta konto eller ikkje har logga inn.\nVi er difor nøydde til å bruke den numeriske IP-adressa til å identifisere brukaren. Same IP-adresse kan vere knytt til fleire brukarar. Om du er ein anonym brukar og meiner at du har fått irrelevante kommentarar på ei slik side, [[Special:UserLogin/signup|opprett ein brukarkonto]] eller [[Special:UserLogin|logg inn]] slik at vi unngår framtidige forvekslingar med andre anonyme brukarar.''",
+       "anontalkpagetext": "----''Dette er ei diskusjonsside for ein anonym brukar som ikkje har oppretta konto eller ikkje har logga inn.\nVi er difor nøydde til å bruke den numeriske IP-adressa til å identifisere brukaren. Same IP-adresse kan vere knytt til fleire brukarar. Om du er ein anonym brukar og meiner at du har fått irrelevante kommentarar på ei slik side, [[Special:CreateAccount|opprett ein brukarkonto]] eller [[Special:UserLogin|logg inn]] slik at vi unngår framtidige forvekslingar med andre anonyme brukarar.''",
        "noarticletext": "Det er nett no ikkje noko tekst på denne sida.\nDu kan [[Special:Search/{{PAGENAME}}|søkja etter sidetittelen]] i andre sider, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søkja i dei relaterte loggane]\neller [{{fullurl:{{FULLPAGENAME}}|action=edit}} endra denne sida]</span>.",
        "noarticletext-nopermission": "Der er nett no ikkje noko tekst på denne sida.\nDu kan [[Special:Search/{{PAGENAME}}|søkja etter sidetittelen]] i andre sider\neller <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søkja i dei relaterte loggane]</span>, men du har ikkje løyve til å oppretta denne sida.",
        "missing-revision": "Versjonen #$1 av sida med namnet «{{FULLPAGENAME}}» finst ikkje.\n\nDette skriv seg som oftast frå at ei forelda historikklenkje vart fylgd til ei side som er sletta.\nDetaljar kan ein finna i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sletteloggen].",
index 36a22e4..79c3ea9 100644 (file)
        "noname": "Gawa fana ka leina la mošomiši la go loka.",
        "loginsuccesstitle": "O tsene ka katlego",
        "loginsuccess": "'''Bjale o tsene go {{SITENAME}} bjalo ka \"$1\".'''",
-       "nosuchuser": "Ga gona mošomiši wa leina la \"$1\".\nMaina a huduetša ke ditlhaka.\nLebele mopeleto wa gago goba [[Special:UserLogin/signup|o tlhome mošomiši yo mophsa]].",
+       "nosuchuser": "Ga gona mošomiši wa leina la \"$1\".\nMaina a huduetša ke ditlhaka.\nLebele mopeleto wa gago goba [[Special:CreateAccount|o tlhome mošomiši yo mophsa]].",
        "nosuchusershort": "Ga gona mošomiši wa leina la \"$1\". Hlokomela mopeleto wa gago.",
        "nouserspecified": "O swanela ke go fana ka leina la mošomiši.",
        "wrongpassword": "O loketše ditlhaka-tša-siphiri tšeo e sego tšona. Ka kgopelo, leka gape.",
index e16a822..d67458b 100644 (file)
        "noname": "Avètz pas picat de nom d'utilizaire valid.",
        "loginsuccesstitle": "Identificacion capitada.",
        "loginsuccess": "Sètz actualament connectat(ada) sus {{SITENAME}} en tant que « $1 ».",
-       "nosuchuser": "L'utilizaire « $1 » existís pas.\nLo nom d'utilizaire es sensible a la cassa.\nVerificatz qu'avètz plan ortografiat lo nom, o [[Special:UserLogin/signup|creatz-vos un compte novèl]].",
+       "nosuchuser": "L'utilizaire « $1 » existís pas.\nLo nom d'utilizaire es sensible a la cassa.\nVerificatz qu'avètz plan ortografiat lo nom, o [[Special:CreateAccount|creatz-vos un compte novèl]].",
        "nosuchusershort": "I a pas de contributor amb lo nom « $1 ». Verificatz l’ortografia.",
        "nouserspecified": "Vos cal especificar vòstre nom d'utilizaire.",
        "login-userblocked": "Aqueste utilizaire es blocat. Connexion pas autorizada.",
        "accmailtext": "Un senhal generat aleatòriament per [[User talk:$1|$1]] es estat mandat a $2.\nLo senhal per aqueste compte novèl pòt èsser cambiat sus la pagina ''[[Special:ChangePassword|Cambiament de senhal]]'' aprèp s'èsser connectat.",
        "newarticle": "(Novèl)",
        "newarticletext": "Avètz seguit un ligam cap a una pagina qu’existís pas encara o qu'es estada [{{fullurl:Special:Log|type=delete&page={{FULLPAGENAMEE}}}} escafada].\nPer crear aquesta pagina, picatz vòstre tèxte dins la bóstia çaijós (podètz consultar [$1 la pagina d’ajuda] per mai d’entresenhas).\nSe sètz arribat(ada) aicí per error, clicatz sul boton '''retorn''' de vòstre navigador.",
-       "anontalkpagetext": "---- ''Sètz sus la pagina de discussion d'un utilizaire anonim qu'a pas encara creat un compte o que n'utiliza pas.\nPer aquesta rason, devèm utilizar son adreça IP per l'identificar. Una adreça d'aqueste tipe pòt èsser partejada entre mantun utilizaire. Se sètz un utilizaire anonim e se constatatz que de comentaris que vos concernisson pas vos son estats adreçats, podètz [[Special:UserLogin/signup|crear un compte]] o [[Special:UserLogin|vos connectar]] per evitar tota confusion venenta amb d’autres contributors anonims.''",
+       "anontalkpagetext": "---- ''Sètz sus la pagina de discussion d'un utilizaire anonim qu'a pas encara creat un compte o que n'utiliza pas.\nPer aquesta rason, devèm utilizar son adreça IP per l'identificar. Una adreça d'aqueste tipe pòt èsser partejada entre mantun utilizaire. Se sètz un utilizaire anonim e se constatatz que de comentaris que vos concernisson pas vos son estats adreçats, podètz [[Special:CreateAccount|crear un compte]] o [[Special:UserLogin|vos connectar]] per evitar tota confusion venenta amb d’autres contributors anonims.''",
        "noarticletext": "Pel moment, i a pas cap de tèxte sus aquesta pagina ;\npodètz [[Special:Search/{{PAGENAME}}|aviar una recèrca sul títol d'aqueste títol de pagina]] dins las autras pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} recercar dins las operacions ligadas]\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear aquesta pagina]</span>.",
        "noarticletext-nopermission": "Actualament i a pas cap de tèxte dins aquesta pagina.\nPodètz [[Special:Search/{{PAGENAME}}|far una recèrca sul títol de la pagina]] dins las autras paginas,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} recercar dins los jornals associats]</span>.",
        "missing-revision": "La revision n° $1 de la pagina intitulada « {{FULLPAGENAME}} » existís pas.\n\nAquò se produsís en general en seguent un ligam istoric obsolèt cap a una pagina qu'es estada suprimida.\nPodètz trobar mai de detalhs dins lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornal de las supressions].",
index 486d5e9..97c6921 100644 (file)
@@ -4,7 +4,8 @@
                        "Denö",
                        "Hiloin Natoi",
                        "Ilja.mos",
-                       "Mashoi7"
+                       "Mashoi7",
+                       "Misosoof"
                ]
        },
        "tog-underline": "Linkien alleviivuamine:",
        "resetpass_submit": "Azeta peittosana da kirjuttai sistiemah:",
        "changepassword-success": "Sinun peittosana on vaihtettu!",
        "changepassword-throttled": "Olet oppinuh kirjuttuakseh liijan moni kerdua. Ole hyvä, vuota $1 enne ku opit uvvessah.",
+       "botpasswords-label-create": "Luaji",
+       "botpasswords-label-update": "Päivitä",
+       "botpasswords-label-cancel": "Hylgiä",
+       "botpasswords-label-delete": "Poista",
        "resetpass_forbidden": "Ei voi vaihtua peittosanua",
        "resetpass-no-info": "Et voi nähtä tädä sivuu kuni et ole kirjutannuhes.",
        "resetpass-submit-loggedin": "Vaihta peittosana",
        "upload-form-label-infoform-description": "Kuvavus",
        "upload-form-label-usage-title": "Käyttö",
        "upload-form-label-usage-filename": "Failunimi",
-       "foreign-structured-upload-form-label-own-work": "Tämä on minun oma ruado",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategouriet",
+       "upload-form-label-own-work": "Tämä on minun oma ruado",
+       "upload-form-label-infoform-categories": "Kategouriet",
        "license-header": "Licenzii",
        "imgfile": "tiijosto",
        "listfiles_name": "Nimi",
index d711ecd..3d5bc9c 100644 (file)
        "noname": "ଆପଣ ଗୋଟିଏ ବୈଧ ଇଉଜର ନାମ ଦେଇନାହାନ୍ତି ।",
        "loginsuccesstitle": "ଠିକଭାବେ ଲଗ-ଇନ ହେଲା",
        "loginsuccess": "'''ଆପଣ {{SITENAME}}ରେ \"$1\" ନାମରେ ଲଗ-ଇନ କରିଛନ୍ତି ।'''",
-       "nosuchuser": "\"$1\" ନାମରେ କେହି ଜଣେ ବି ସଭ୍ୟ ନାହାନ୍ତି ।\nଇଉଜର ନାମ ଇଂରାଜୀ ଛୋଟ ଓ ବଡ଼ ଅକ୍ଷର ପ୍ରତି ସମ୍ବେଦନଶୀଳ ।\nଆପଣ ନିଜର ବନାନ ପରଖି ନିଅନ୍ତୁ, ଅଥବା [[Special:UserLogin/signup|ନୂଆ ଖାତାଟିଏ ତିଆରି କରନ୍ତୁ]] ।",
+       "nosuchuser": "\"$1\" ନାମରେ କେହି ଜଣେ ବି ସଭ୍ୟ ନାହାନ୍ତି ।\nଇଉଜର ନାମ ଇଂରାଜୀ ଛୋଟ ଓ ବଡ଼ ଅକ୍ଷର ପ୍ରତି ସମ୍ବେଦନଶୀଳ ।\nଆପଣ ନିଜର ବନାନ ପରଖି ନିଅନ୍ତୁ, ଅଥବା [[Special:CreateAccount|ନୂଆ ଖାତାଟିଏ ତିଆରି କରନ୍ତୁ]] ।",
        "nosuchusershort": "\"$1\" ନାମରେ କେହି ଜଣେ ବି ସଭ୍ୟ ନାହାନ୍ତି ।\nଆପଣ ବନାନ ପରଖି ନିଅନ୍ତୁ ।",
        "nouserspecified": "ଆପଣଙ୍କୁ ଇଉଜର ନାମଟିଏ ଦେବାକୁ ପଡ଼ିବ ।",
        "login-userblocked": "ଏହି ସଭ୍ୟଙ୍କୁ ଅଟକାଯାଇଛି । ଲଗ ଇନ କରିବାକୁ ଅନୁମତି ନାହିଁ ।",
        "accmailtext": "[[User talk:$1|$1]] ପାଇଁ $2କୁ ଏକ ଆପେ ଆପେ ତିଆରି ପାସୱାର୍ଡ଼ ପଠାଗଲା । ଏହା ଲଗ ଇନ କଲା ପରେ<em>[[Special:ChangePassword|ପାସୱାର୍ଡ଼ ବଦଳ]]</em> ପୃଷ୍ଠାରେ ବଦଳାଯାଇପାରିବ ।",
        "newarticle": "(ନୁଆ)",
        "newarticletext": "ଆପଣ ଖୋଲିଥିବା ଲିଙ୍କଟିରେ ଏଯାଏଁ କିଛିବି ପୃଷ୍ଠା ନାହିଁ ।\nଏହି ପୃଷ୍ଠାଟିକୁ ତିଆରି କରିବା ପାଇଁ ତଳ ବାକ୍ସରେ ଟାଇପ କରନ୍ତୁ (ଅଧିକ ଜାଣିବା ପାଇଁ [$1 ସାହାଯ୍ୟ ପୃଷ୍ଠା] ଦେଖନ୍ତୁ) ।\nଯଦି ଆପଣ ଏଠାକୁ ଭୁଲରେ ଆସିଯାଇଥାନ୍ତି ତେବେ ଆପଣଙ୍କ ବ୍ରାଉଜରର '''Back''' ପତିଟି ଦବାନ୍ତୁ ।",
-       "anontalkpagetext": "----''ଏହା ଏକ ଖାତା ଖୋଲିନଥିବା ବା ଖାତା ବ୍ୟବହାର କରିନଥିବା ଜଣେ ବେନାମି ସଭ୍ୟଙ୍କର ଆଲୋଚନା ପୃଷ୍ଠା ।''\nତେଣୁ ଆମ୍ଭେ ସଂଖ୍ୟା ଦେଇ ସୂଚୀତ IP ଠିକଣା ଦେଇ ତାଙ୍କୁ ଜାଣିବା ।\nଏହି ପ୍ରକାରର ଗୋଟିଏ IP ଠିକଣା ବହୁ ସଭ୍ୟଙ୍କ ଦେଇ ବ୍ୟବହାର କରାଯାଇପାରେ । \nଯଦି ଆପଣ ଜଣେ ଅଜଣା ସଭ୍ୟ ଓ ଭାବୁଛନ୍ତି ଇଆଡୁ ସିଆଡୁ ମତାମତ ସବୁ ଆପଣଙ୍କ ପାଇଁ ଦିଆଯାଇଛି ତେବେ ଦୟାକରି [[Special:UserLogin/signup|ନୂଆ ଖାତାଟିଏ ଖୋଲନ୍ତୁ]] କିମ୍ବା [[Special:UserLogin|ଆଗରୁ ଥିବା ଖାତାରେ ଲଗ ଇନ କରନ୍ତୁ]] ଯାହା ବେନାମି ସଭ୍ୟଙ୍କୁ ନେଇ ଉପୁଜିଥିବା ଦ୍ଵନ୍ଦର ସମାଧାନ କରିବ ।",
+       "anontalkpagetext": "----''ଏହା ଏକ ଖାତା ଖୋଲିନଥିବା ବା ଖାତା ବ୍ୟବହାର କରିନଥିବା ଜଣେ ବେନାମି ସଭ୍ୟଙ୍କର ଆଲୋଚନା ପୃଷ୍ଠା ।''\nତେଣୁ ଆମ୍ଭେ ସଂଖ୍ୟା ଦେଇ ସୂଚୀତ IP ଠିକଣା ଦେଇ ତାଙ୍କୁ ଜାଣିବା ।\nଏହି ପ୍ରକାରର ଗୋଟିଏ IP ଠିକଣା ବହୁ ସଭ୍ୟଙ୍କ ଦେଇ ବ୍ୟବହାର କରାଯାଇପାରେ । \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</span>, କିନ୍ତୁ ଏହି ପୃଷ୍ଠାଟିକୁ ଆପଣ ତିଆରି କରିପାରିବେ ନାହିଁ ।",
        "missing-revision": "\"{{FULLPAGENAME}}\" ନାମରେ ଥିବା ପୃଷ୍ଠାଟିର #$1 ପୁନରାବୃତ୍ତି ନାହିଁ ।\n\nପୁରୁଣା ହୋଇଯାଇଥିବା ଇତିହାସ ଲିଙ୍କ ଯାହା ଏକ ଲିଭାଯାଇଥିବା ପୃଷ୍ଠାକୁ ଦିଆଯାଇଥିବାରୁ ଏହା ସାଧାରଣତଃ ହୋଇଥାଏ ।\nଅଧିକ ବିବରଣୀ [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log]ରେ ମିଳିପାରିବ ।",
        "preferences": "ପସନ୍ଦ",
        "mypreferences": "ପସନ୍ଦ",
        "prefs-edits": "ସମ୍ପାଦନା ସଂଖ୍ୟା:",
-       "prefsnologintext2": "ନିà¬\9cର à¬ªà¬¸à¬¨à­\8dଦ à¬¬à¬¦à¬²ାଇବା ପାଇଁ ଲଗ ଇନ କରନ୍ତୁ ।",
+       "prefsnologintext2": "ନିà¬\9cର à¬ªà¬¸à¬¨à­\8dଦ à¬¬à¬¦à¬³ାଇବା ପାଇଁ ଲଗ ଇନ କରନ୍ତୁ ।",
        "prefs-skin": "ବହିରାବରଣ",
        "skin-preview": "ସାଇତା ଆଗରୁ ଦେଖଣା",
        "datedefault": "କୌଣସି ପସନ୍ଦ ନାହିଁ",
index 460665e..76d9abf 100644 (file)
        "noname": "Раст фæсномыг нæ ныффыстай.",
        "loginsuccesstitle": "Бахызтæ",
        "loginsuccess": "'''Ныр ды дæ хыст {{grammar:genitive|{{SITENAME}}}} куыд \"$1\".'''",
-       "nosuchuser": "Нæй ахæм архайæг \"$1\" номимæ.\nАрхайджыты нæмттæ хатынц дамгъæты регистр.\nСбæрæг æй кæн, раст ныффыстай ном, æви [[Special:UserLogin/signup|бакæн ног аккаунт]].",
+       "nosuchuser": "Нæй ахæм архайæг \"$1\" номимæ.\nАрхайджыты нæмттæ хатынц дамгъæты регистр.\nСбæрæг æй кæн, раст ныффыстай ном, æви [[Special:CreateAccount|бакæн ног аккаунт]].",
        "nosuchusershort": "Нæй архайæг \"$1\" фæсномыгимæ.\nФен, фæсномыг раст ныффыстай, æви нæ.",
        "nouserspecified": "Ды хъуамæ зæгъай дæ фæсномыг.",
        "login-userblocked": "Ацы архайæг хъодыгонд у. Нæй гæнæн бахизын.",
        "accmailtext": "[[User talk:$1|{{grammar:dative|$1}}]] халæй ист пароль æрвыст æрцыд ацы адрисмæ: $2. Ацы ног аккаунты пароль гæнæн ис фæивын <em>[[Special:ChangePassword|пароль ивæн фарсыл]]</em> бахизыны фæстæ.",
        "newarticle": "(Ног)",
        "newarticletext": "Ды ныххæцыдтæ ахæм æрвитæныл, кæй фарс нырмæ нæй.\nФарс бакæнынæн байдай фыссын дæлдæр цы къæртт ис, уым (кæс [$1 æххуысы фарс] фылдæр базонынæн).",
-       "anontalkpagetext": "----''Ай у æнæном архайæджы ныхасы фарс. Ацы архайæг нырмæ нæ срегистраци кодта, кæнæ та йæ аккаунтæй нæ архайы.\nУый тыххæй мах пайда кæнæм йæ IP адрисæй, цæмæй-иу æй бæрæг кæнæм.\nАхæм IP адристæй гæнæн ис архайой цалдæр архайæджы.\nКæд ды æнæном архайæг дæ æмæ дæм цыдæр зæгъæлы фыстæджытæ цæуы, уæд, дæ хорзæхæй, [[Special:UserLogin/signup|бакæн аккаунт]] кæнæ [[Special:UserLogin|бахиз системæмæ]], цæмæй дæ мауал хæццæ кæной æндæр æнæном архайджытимæ.''",
+       "anontalkpagetext": "----''Ай у æнæном архайæджы ныхасы фарс. Ацы архайæг нырмæ нæ срегистраци кодта, кæнæ та йæ аккаунтæй нæ архайы.\nУый тыххæй мах пайда кæнæм йæ IP адрисæй, цæмæй-иу æй бæрæг кæнæм.\nАхæм IP адристæй гæнæн ис архайой цалдæр архайæджы.\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}}|бацагурын ацы фарсы ном]] æндæр фæрсты, кæнæ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} агурын йæ кой логты]</span>, фæлæ дын йæ саразыны бар нæй.",
        "missing-revision": "\"{{grammar:genitive|{{FULLPAGENAME}}}}\" фарсæн $1-æм фæлтæр нæй.\n\nАй арæх æрцæуы, исчи хафт фарсы зæронд историйы æрвитæны фæдыл куы ацæуы.\nФылдæр гæнæн ис базонын [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} хафыны логы].",
        "cachedspecial-refresh-now": "Фæстаг фенын.",
        "categories": "Категоритæ",
        "categoriespagetext": "Ацы {{PLURAL:$1|категорийы|категориты}} ис фæрстæ кæнæ файлтæ.\n[[Special:UnusedCategories|Нæ пайдагонд категоритæ]] æвдыст не сты.\nНоджы кæс [[Special:WantedCategories|хъæугæ категоритæ]].",
-       "special-categories-sort-count": "нымæцмæ гæсгæ равæр",
-       "special-categories-sort-abc": "алфавитмæ гæсгæ равæр",
        "sp-deletedcontributions-contribs": "бавæрд",
        "linksearch": "Æддаг æрвитæнтæ агурын",
        "linksearch-ns": "Номдон:",
index 9bb2e21..1085b31 100644 (file)
        "noname": "ਤੁਸੀਂ ਇੱਕ ਸਹੀ ਯੂਜ਼ਰ-ਨਾਂ ਨਹੀਂ ਦਿੱਤਾ।",
        "loginsuccesstitle": "ਲਾਗਇਨ ਸਫ਼ਲ",
        "loginsuccess": "'''ਤੁਸੀਂ {{SITENAME}} ਉੱਤੇ \"$1\" ਵਜੋਂ ਲਾਗਇਨ ਹੋ ਚੁੱਕੇ ਹੋ।'''",
-       "nosuchuser": "!\"$1\" ਨਾਂ ਨਾਲ਼ ਕੋਈ ਵਰਤੋਂਕਾਰ ਨਹੀਂ ਹੈ। ਵੱਡੇ ਅਤੇ ਛੋਟੇ ਅੱਖਰ ਵਰਤਣ ਨਾਲ ਫ਼ਰਕ ਪੈਂਦਾ ਹੈ।\nਆਪਣੇ ਸਪੈਲਿੰਗ ਨੂੰ ਧਿਆਨ ਨਾਲ ਚੈੱਕ ਕਰੋ ਜਾਂ [[Special:UserLogin/signup|ਨਵਾਂ ਖਾਤਾ ਬਣਾਓ]]",
+       "nosuchuser": "!\"$1\" ਨਾਂ ਨਾਲ਼ ਕੋਈ ਵਰਤੋਂਕਾਰ ਨਹੀਂ ਹੈ। ਵੱਡੇ ਅਤੇ ਛੋਟੇ ਅੱਖਰ ਵਰਤਣ ਨਾਲ ਫ਼ਰਕ ਪੈਂਦਾ ਹੈ।\nਆਪਣੇ ਸਪੈਲਿੰਗ ਨੂੰ ਧਿਆਨ ਨਾਲ ਚੈੱਕ ਕਰੋ ਜਾਂ [[Special:CreateAccount|ਨਵਾਂ ਖਾਤਾ ਬਣਾਓ]]",
        "nosuchusershort": "\"$1\" ਨਾਂ ਨਾਲ ਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਨਹੀਂ ਹੈ। ਆਪਣੇ ਸਪੈਲਿੰਗ ਧਿਆਨ ਨਾਲ ਚੈੱਕ ਕਰੋ।",
        "nouserspecified": "ਤੁਹਾਨੂੰ ਇੱਕ ਯੂਜ਼ਰ-ਨਾਂ ਦੇਣਾ ਪਵੇਗਾ।",
        "login-userblocked": "ਇਹ ਯੂਜ਼ਰ-ਨਾਂ ਪਾਬੰਦੀਸ਼ੁਦਾ ਹੈ। ਲਾਗਇਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।",
        "accmailtext": "[[User talk:$1|$1]] ਲਈ ਰਲ਼ਵੇਂ ਤੌਰ ’ਤੇ ਬਣਿਆ ਪਾਸਵਰਡ $2 ਨੂੰ ਭੇਜਿਆ ਜਾ ਚੁੱਕਾ ਹੈ।\nਇਸ ਨਵੇਂ ਖਾਤੇ ਲਈ ਲਾਗਇਨ ਕਰਨ ਤੋਂ ਬਾਅਦ ''[[Special:ChangePassword|ਪਾਸਵਰਡ ਬਦਲੋ]]'' ’ਤੇ ਜਾ ਕੇ ਪਾਸਵਰਡ ਬਦਲਿਆ ਜਾ ਸਕਦਾ ਹੈ।",
        "newarticle": "(ਨਵਾਂ)",
        "newarticletext": "ਤੁਸੀਂ ਕਿਸੇ ਅਜਿਹੇ ਸਫ਼ੇ ਦੇ ਕੜੀ ’ਤੇ ਹੋ ਜੋ ਹਾਲੇ ਬਣਾਇਆ ਨਹੀਂ ਗਿਆ।\nਸਫ਼ਾ ਬਣਾਉਣ ਲਈ ਹੇਠ ਦਿੱਤੇ ਖਾਨੇ ਵਿਚ ਲਿਖਣਾ ਸ਼ੁਰੂ ਕਰੋ। (ਹੋਰ ਮਦਦ ਲਈ [$1 ਮਦਦ ਸਫ਼ਾ] ਵੇਖੋ।)\nਜੇ ਤੁਸੀਂ ਗ਼ਲਤੀ ਨਾਲ ਇੱਥੇ ਆਏ ਹੋ ਤਾਂ ਆਪਣੇ ਬ੍ਰਾਊਜ਼ਰ ਦੇ '''ਪਿੱਛੇ''' ਬਟਨ ’ਤੇ ਕਲਿੱਕ ਕਰੋ।",
-       "anontalkpagetext": "----''ਇਹ ਇਕ ਗੁਮਨਾਮ ਮੈਂਬਰ ਲਈ ਇਕ ਚਰਚਾ ਸਫ਼ਾ ਹੈ ਜਿਸਨੇ ਹਾਲੇ ਖਾਤਾ ਨਹੀ ਬਣਾਇਆ ਜਾਂ ਉਸਨੂੰ ਵਰਤ ਨਹੀਂ ਰਿਹਾ।\nਇਸ ਵਾਸਤੇ ਸਾਡੇ ਕੋਲ ਉਸਨੂੰ ਪਛਾਨਣ ਲਈ IP ਪਤਾ ਹੈ।\nਇਕ IP ਪਤਾ ਕਈ ਵਰਤਣ ਵਾਲ਼ਿਆਂ ਦੁਆਰਾ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ।\nਜੇ ਤੁਸੀਂ ਇੱਕ ਗੁਮਨਾਮ ਮੈਂਬਰ ਹੋ ਅਤੇ ਸਮਝਦੇ ਹੋ ਕਿ ਇਹ ਟਿੱਪਣੀਆਂ ਤੁਹਾਡੇ ਲਈ ਹਨ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਹੋਰਾਂ ਗੁਮਨਾਮ ਮੈਂਬਰਾਂ ਨਾਲ਼ ਪੈਦਾ ਹੋਣ ਵਾਲ਼ੀ ਉਲਝਣ ਤੋਂ ਬਚਣ ਲਈ [[Special:UserLogin/signup|ਖਾਤਾ ਬਣਾਓ]] ਜਾਂ [[Special:UserLogin|ਲਾਗਇਨ ਕਰੋ]]।''",
+       "anontalkpagetext": "----''ਇਹ ਇਕ ਗੁਮਨਾਮ ਮੈਂਬਰ ਲਈ ਇਕ ਚਰਚਾ ਸਫ਼ਾ ਹੈ ਜਿਸਨੇ ਹਾਲੇ ਖਾਤਾ ਨਹੀ ਬਣਾਇਆ ਜਾਂ ਉਸਨੂੰ ਵਰਤ ਨਹੀਂ ਰਿਹਾ।\nਇਸ ਵਾਸਤੇ ਸਾਡੇ ਕੋਲ ਉਸਨੂੰ ਪਛਾਨਣ ਲਈ IP ਪਤਾ ਹੈ।\nਇਕ IP ਪਤਾ ਕਈ ਵਰਤਣ ਵਾਲ਼ਿਆਂ ਦੁਆਰਾ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ।\nਜੇ ਤੁਸੀਂ ਇੱਕ ਗੁਮਨਾਮ ਮੈਂਬਰ ਹੋ ਅਤੇ ਸਮਝਦੇ ਹੋ ਕਿ ਇਹ ਟਿੱਪਣੀਆਂ ਤੁਹਾਡੇ ਲਈ ਹਨ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਹੋਰਾਂ ਗੁਮਨਾਮ ਮੈਂਬਰਾਂ ਨਾਲ਼ ਪੈਦਾ ਹੋਣ ਵਾਲ਼ੀ ਉਲਝਣ ਤੋਂ ਬਚਣ ਲਈ [[Special:CreateAccount|ਖਾਤਾ ਬਣਾਓ]] ਜਾਂ [[Special:UserLogin|ਲਾਗਇਨ ਕਰੋ]]।''",
        "noarticletext": "ਫ਼ਿਲਹਾਲ ਇਸ ਸਫ਼ੇ ’ਤੇ ਕੋਈ ਲਿਖਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਦੂਜੇ ਸਫ਼ਿਆਂ ’ਤੇ [[Special:Search/{{PAGENAME}}|ਇਸ ਸਿਰਲੇਖ ਦੀ ਖੋਜ]] ਕਰ ਸਕਦੇ ਹੋ, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ਸਬੰਧਤ ਚਿੱਠੇ ਲੱਭ] ਸਕਦੇ ਹੋ ਜਾਂ ਇਸ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ਸਫ਼ੇ ਵਿਚ ਲਿਖ] ਸਕਦੇ ਹੋ</span>।",
        "noarticletext-nopermission": "ਫ਼ਿਲਹਾਲ ਇਸ ਪੰਨੇ ’ਤੇ ਕੋਈ ਲਿਖਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਦੂਸਰੇ ਪੰਨਿਆਂ ’ਤੇ [[Special:Search/{{PAGENAME}}|ਇਸ ਸਿਰਲੇਖ ਦੀ ਖੋਜ]] ਕਰ ਸਕਦੇ ਹੋ, ਸਬੰਧਤ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ਚਿੱਠੇ] ਖੋਜ ਸਕਦੇ ਹੋ ਜਾਂ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ਇਸ ਪੰਨੇ ਵਿੱਚ ਲਿਖ] ਸਕਦੇ ਹੋ।</span>",
        "userpage-userdoesnotexist": "ਵਰਤੋਂਕਾਰ ਖਾਤਾ \"$1\" ਰਜਿਸਟਰ ਨਹੀਂ ਹੈ।\nਜੇ ਤੁਸੀਂ ਇਸਨੂੰ ਬਣਾਉਣਾ/ਸੋਧਣਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਮਿਰਬਾਨੀ ਕਰਕੇ ਜਾਂਚ ਕਰ ਲਓ।",
        "upload-file-error": "ਅੰਦਰੂਨੀ ਗਲਤੀ",
        "upload-misc-error": "ਅਣਪਛਾਤੀ ਅੱਪਲੋਡ ਗਲਤੀ",
        "upload-http-error": "ਇੱਕ HTTP ਗ਼ਲਤੀ ਹੋਈ: $1",
-       "foreign-structured-upload-form-label-infoform-date": "ਤਾਰੀਖ਼",
+       "upload-form-label-infoform-date": "ਤਾਰੀਖ਼",
        "backend-fail-notexists": "ਫ਼ਾਈਲ $1 ਮੌਜੂਦ ਨਹੀਂ ਹੈ।",
        "backend-fail-delete": "ਫ਼ਾਈਲ \"$1\" ਮਿਟਾਈ ਨਹੀਂ ਜਾ ਸਕੀ।",
        "backend-fail-alreadyexists": "ਫ਼ਾਈਲ \"$1\" ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।",
index 17f7f4c..7aa422d 100644 (file)
        "tog-ccmeonemails": "Przesyłaj mi kopie wiadomości, które wysyłam do innych użytkowników",
        "tog-diffonly": "Nie pokazuj treści stron pod porównaniami zmian",
        "tog-showhiddencats": "Pokazuj ukryte kategorie",
-       "tog-norollbackdiff": "Pomiń pokazywanie zmian po użyciu funkcji „cofnij”",
+       "tog-norollbackdiff": "Nie pokazuj zmian po użyciu funkcji „cofnij”",
        "tog-useeditwarning": "Ostrzegaj mnie, gdy opuszczam stronę edycji bez zapisania zmian",
        "tog-prefershttps": "Zawsze używaj bezpiecznego połączenia po zalogowaniu",
        "underline-always": "Zawsze",
        "createacct-reason-ph": "Dlaczego zakładasz kolejne konto",
        "createacct-submit": "Utwórz konto",
        "createacct-another-submit": "Utwórz konto",
+       "createacct-another-continue-submit": "Kontynuuj tworzenie konta",
        "createacct-benefit-heading": "{{grammar:B.lp|{{SITENAME}}}} tworzą ludzie tacy jak Ty.",
        "createacct-benefit-body1": "{{PLURAL:$1|edycja|edycje|edycji}}",
        "createacct-benefit-body2": "{{PLURAL:$1|strona|strony|stron}}",
        "noname": "To nie jest poprawna nazwa użytkownika.",
        "loginsuccesstitle": "Zalogowano",
        "loginsuccess": "'''{{GENDER:|Zalogowałeś się|Zalogowałaś się|Zalogowano}} do {{GRAMMAR:D.lp|{{SITENAME}}}} jako „$1”.'''",
-       "nosuchuser": "Brak użytkownika o nazwie „$1”.\nW nazwie użytkownika ma znaczenie wielkość znaków.\nSprawdź pisownię lub [[Special:UserLogin/signup|utwórz nowe konto]].",
+       "nosuchuser": "Brak użytkownika o nazwie „$1”.\nW nazwie użytkownika ma znaczenie wielkość znaków.\nSprawdź pisownię lub [[Special:CreateAccount|utwórz nowe konto]].",
        "nosuchusershort": "Brak użytkownika o nazwie „$1”.\nSprawdź poprawność pisowni.",
        "nouserspecified": "Musisz podać nazwę użytkownika.",
        "login-userblocked": "Ten użytkownik jest zablokowany. Zalogowanie się jest niemożliwe.",
        "passwordreset-emailsentusername": "Jeśli z tym kontem powiązany jest adres e‐mail, zostanie na niego wysłany e-mail do odzyskiwania hasła.",
        "passwordreset-emailsent-capture": "Wyświetlony poniżej e‐mail pozwalający na zresetowanie hasła został wysłany.",
        "passwordreset-emailerror-capture": "Poniżej wyświetlony e‐mail pozwalający na zresetowanie hasła został wygenerowany, ale nie udało się wysłać go do {{GENDER:$2|użytkownika|użytkowniczki}}: $1",
+       "passwordreset-invalideamil": "Nieprawidłowy adres e-mail",
+       "passwordreset-nodata": "Nie podano ani nazwy użytkownika, ani adresu e-mail",
        "changeemail": "Zmiana lub usunięcie adresu e‐mail",
        "changeemail-header": "Wypełnij ten formularz, aby zmienić swój adres e-mail. Jeśli chcesz usunąć swój adres e-mail, to przy wypełnianiu formularza pozostaw puste pole nowego adresu e-mail.",
        "changeemail-passwordrequired": "Musisz podać swoje hasło, aby potwierdzić tę zmianę.",
        "minoredit": "To jest drobna zmiana",
        "watchthis": "Obserwuj",
        "savearticle": "Zapisz",
+       "publishpage": "Opublikuj stronę",
        "preview": "Podgląd",
        "showpreview": "Pokaż podgląd",
        "showdiff": "Podgląd zmian",
        "accmailtext": "Losowo wygenerowane hasło dla [[User talk:$1|$1]] zostało wysłane do $2.\n\nHasło dla tego nowego konta po zalogowaniu można zmienić na stronie ''[[Special:ChangePassword|zmiana hasła]]''.",
        "newarticle": "(Nowy)",
        "newarticletext": "Brak strony o tym tytule.\nJeśli chcesz ją utworzyć, wpisz treść strony w poniższym polu (więcej informacji odnajdziesz [$1 na stronie pomocy]).\nJeśli utworzenie nowej strony nie było Twoim zamiarem, wciśnij ''Wstecz'' w swojej przeglądarce.",
-       "anontalkpagetext": "---- ''To jest strona dyskusji anonimowego użytkownika – takiego, który nie ma jeszcze swojego konta lub nie chce go w tej chwili używać.\nBy go identyfikować, używamy adresów IP.\nJednak adres IP może być współdzielony przez wielu użytkowników.\nJeśli jesteś anonimowym użytkownikiem i uważasz, że zamieszczone tu komentarze nie są skierowane do Ciebie, [[Special:UserLogin/signup|utwórz konto]] lub [[Special:UserLogin|zaloguj się]] – dzięki temu unikniesz w przyszłości podobnych nieporozumień.''",
+       "anontalkpagetext": "---- ''To jest strona dyskusji anonimowego użytkownika – takiego, który nie ma jeszcze swojego konta lub nie chce go w tej chwili używać.\nBy go identyfikować, używamy adresów IP.\nJednak adres IP może być współdzielony przez wielu użytkowników.\nJeśli jesteś anonimowym użytkownikiem i uważasz, że zamieszczone tu komentarze nie są skierowane do Ciebie, [[Special:CreateAccount|utwórz konto]] lub [[Special:UserLogin|zaloguj się]] – dzięki temu unikniesz w przyszłości podobnych nieporozumień.''",
        "noarticletext": "Obecnie ta strona nie ma zawartości.\nMożesz [[Special:Search/{{PAGENAME}}|wyszukać ten tytuł na innych stronach]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przeszukać rejestr] \nlub [{{fullurl:{{FULLPAGENAME}}|action=edit}} utworzyć tę stronę]</span>.",
        "noarticletext-nopermission": "Ta strona nie posiada jeszcze zawartości.\nMożesz [[Special:Search/{{PAGENAME}}|wyszukać ten tytuł]] w treści innych stron\nlub <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przeszukać powiązane rejestry]</span>, ale nie masz uprawnień do utworzenia tej strony",
        "missing-revision": "Wersja #$1 strony \"{{FULLPAGENAME}}\" nie istnieje.\n\nZazwyczaj jest to spowodowane przestarzałym linkiem do usuniętej strony. Powód usunięcia znajduje się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze].",
        "right-override-export-depth": "Eksport stron wraz z linkowanymi do głębokości 5 linków",
        "right-sendemail": "Wysyłanie e‐maili do innych użytkowników",
        "right-passwordreset": "Sprawdzanie treści e‐maila o resetowaniu hasła",
-       "right-managechangetags": "Tworzenie i usuwanie [[Special:Tags|znaczników]] z bazy danych",
+       "right-managechangetags": "Tworzenie i (dez)aktywowanie [[Special:Tags|znaczników]]",
        "right-applychangetags": "Wprowadzanie [[Special:Tags|znaczników]] wraz z własnymi zmianami",
        "right-changetags": "Dodawanie i usuwanie dowolnych [[Special:Tags|znaczników]] z poszczególnych wersji i wpisów w rejestrze",
+       "right-deletechangetags": "Usuwanie [[Special:Tags|znaczników]] z bazy danych",
        "grant-group-page-interaction": "Interakcja ze stronami",
        "grant-group-file-interaction": "Interakcja z plikami multimedialnymi",
        "grant-group-watchlist-interaction": "Interakcja z listą obserwowanych",
        "action-viewmyprivateinfo": "zobaczenia swoich prywatnych danych",
        "action-editmyprivateinfo": "edycji swoich prywatnych danych",
        "action-editcontentmodel": "edycji modelu zawartości strony",
-       "action-managechangetags": "utwórz lub usuń znaczniki z bazy danych",
+       "action-managechangetags": "tworzenia i de(aktywowania) znaczników",
        "action-applychangetags": "wprowadzania znaczników wraz z własnymi zmianami",
        "action-changetags": "dodawania i usuwania dowolnych znaczników z poszczególnych wersji i wpisów w rejestrze",
+       "action-deletechangetags": "usuwania znaczników z bazy danych",
        "nchanges": "$1 {{PLURAL:$1|zmiana|zmiany|zmian}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od ostatniej wizyty}}",
        "enhancedrc-history": "historia",
        "upload-form-label-infoform-description-tooltip": "Krótko opisz wszystko istotne, co dotyczy tej pracy.\nW przypadku zdjęcia wymień najważniejsze ujęte obiekty, sytuację lub miejsce.",
        "upload-form-label-usage-title": "Korzystanie",
        "upload-form-label-usage-filename": "Nazwa pliku",
-       "foreign-structured-upload-form-label-own-work": "To moja własna praca",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorie",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Potwierdzam, że wysyłam ten plik zgodnie z warunkami i zasadami licencjonowania obowiązującymi na {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Jeśli nie możesz wysłać tego pliku zgodnie z zasadami obowiązującymi na {{SITENAME}}, zamknij ten komunikat i spróbuj innej metody.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Możesz skorzystać też z [[Special:Upload|domyślnej strony przesyłania plików]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Rozumiem, że przesyłam ten plik do współdzielonego repozytorium. Potwierdzam, że robię to zgodnie z warunkami i zasadami licencjonowania tam obowiązującymi.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Jeśli nie jesteś w stanie przesłać tego pliku zgodnie z zasadami współdzielonego repozytorium, zamknij to okno i spróbuj innej metody.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Możesz spróbować użyć [[Special:Upload|strony przesyłania plików {{GRAMMAR:D.lp|{{SITENAME}}}}]], jeżeli zasady tej strony dopuszczają publikację tego pliku.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Oświadczam, że mam prawa autorskie do tego pliku, nieodwołalnie publikuję go na Wikimedia Commons na licencji [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0] i zgadzam się na [https://wikimediafoundation.org/wiki/Terms_of_Use/pl warunki użytkowania].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Jeżeli nie masz praw autorskich do tego pliku albo chcesz go opublikować na innej licencji, rozważ użycie [https://commons.wikimedia.org/wiki/Special:UploadWizard kreatora przesyłania plików].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Możesz spróbować użyć [[Special:Upload|strony przesyłania plików {{GRAMMAR:D.lp|{{SITENAME}}}}]], jeżeli zasady tej strony dopuszczają publikację tego pliku.",
+       "upload-form-label-own-work": "To moja własna praca",
+       "upload-form-label-infoform-categories": "Kategorie",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Potwierdzam, że wysyłam ten plik zgodnie z warunkami i zasadami licencjonowania obowiązującymi na {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Jeśli nie możesz wysłać tego pliku zgodnie z zasadami obowiązującymi na {{SITENAME}}, zamknij ten komunikat i spróbuj innej metody.",
+       "upload-form-label-not-own-work-local-generic-local": "Możesz skorzystać też z [[Special:Upload|domyślnej strony przesyłania plików]].",
+       "upload-form-label-own-work-message-generic-foreign": "Rozumiem, że przesyłam ten plik do współdzielonego repozytorium. Potwierdzam, że robię to zgodnie z warunkami i zasadami licencjonowania tam obowiązującymi.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Jeśli nie jesteś w stanie przesłać tego pliku zgodnie z zasadami współdzielonego repozytorium, zamknij to okno i spróbuj innej metody.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Możesz spróbować użyć [[Special:Upload|strony przesyłania plików {{GRAMMAR:D.lp|{{SITENAME}}}}]], jeżeli zasady tej strony dopuszczają publikację tego pliku.",
        "backend-fail-stream": "Nie można odczytać pliku $1.",
        "backend-fail-backup": "Nie można utworzyć kopii zapasowej pliku  $1 .",
        "backend-fail-notexists": "Plik  $1  nie istnieje.",
        "whatlinkshere-prev": "{{PLURAL:$1|poprzednie|poprzednie $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|następne|następne $1}}",
        "whatlinkshere-links": "← linkujące",
-       "whatlinkshere-hideredirs": "$1 przekierowania",
-       "whatlinkshere-hidetrans": "$1 dołączenia",
-       "whatlinkshere-hidelinks": "$1 linki",
-       "whatlinkshere-hideimages": "$1 linki z plików",
+       "whatlinkshere-hideredirs": "Ukryj przekierowania",
+       "whatlinkshere-hidetrans": "Ukryj dołączenia",
+       "whatlinkshere-hidelinks": "Ukryj linki",
+       "whatlinkshere-hideimages": "Ukryj linki z plików",
        "whatlinkshere-filters": "Filtry",
        "whatlinkshere-submit": "Dalej",
        "autoblockid": "automatyczna blokada nr $1",
        "lockdbsuccesstext": "Baza danych została zablokowana.<br />\nPamiętaj by [[Special:UnlockDB|zdjąć blokadę]] po zakończeniu działań administracyjnych.",
        "unlockdbsuccesstext": "Baza danych została odblokowana.",
        "lockfilenotwritable": "Nie można zapisać pliku blokady bazy danych.\nBlokowanie i odblokowywanie bazy danych, wymaga by plik mógł być zapisywany przez web serwer.",
+       "databaselocked": "Baza danych jest już zablokowana.",
        "databasenotlocked": "Baza danych nie jest zablokowana.",
        "lockedbyandtime": "(przez $1 dnia $2 o $3)",
        "move-page": "Przenieś $1",
        "feedback-useragent": "Aplikacja klienta:",
        "searchsuggest-search": "Szukaj",
        "searchsuggest-containing": "zawierające...",
+       "api-error-autoblocked": "Twój adres IP został automatycznie zablokowany, ponieważ był używany przez zablokowanego użytkownika.",
        "api-error-badaccess-groups": "Nie masz uprawnień aby przesyłać pliki do tej wiki.",
        "api-error-badtoken": "Błąd wewnętrzny – nieprawidłowy kod weryfikacyjny (token).",
+       "api-error-blocked": "Została ci zablokowana możliwość edycji.",
        "api-error-copyuploaddisabled": "Przesyłanie poprzez podanie adresu URL zostało na tym serwerze wyłączone.",
        "api-error-duplicate": "{{PLURAL:$1|Jest już inny plik|Są już inne pliki}} o tej samej zawartości",
        "api-error-duplicate-archive": "{{PLURAL:$1|Był już inny plik|Były już inne pliki}} o takiej samej zawartości, ale {{PLURAL:$1|został usunięty|zostały usunięte}}.",
        "log-action-filter-rights-rights": "Ręczna zmiana",
        "log-action-filter-rights-autopromote": "Automatyczna zmiana",
        "log-action-filter-upload-upload": "Nowe przesłane",
-       "log-action-filter-upload-overwrite": "Przesłane ponownie"
+       "log-action-filter-upload-overwrite": "Przesłane ponownie",
+       "authmanager-create-disabled": "Utworzenie konta jest wyłączone.",
+       "authmanager-create-from-login": "Aby utworzyć konto, wypełnij poniższe pola.",
+       "authmanager-authplugin-setpass-denied": "Wtyczka uwierzytelniania nie zezwala na zmianę haseł.",
+       "authmanager-authplugin-setpass-bad-domain": "Niepoprawna domena.",
+       "authmanager-autocreate-noperm": "Automatyczne tworzenie konta jest niedozwolone.",
+       "authmanager-autocreate-exception": "Automatyczne tworzenie konta tymczasowo wyłączone z powodu wcześniejszych błędów.",
+       "authmanager-userdoesnotexist": "Konto użytkownika „$1” nie jest zarejestrowane.",
+       "authmanager-password-help": "Hasło do uwierzytelniania.",
+       "authmanager-email-label": "E-mail",
+       "authmanager-email-help": "Adres e‐mail",
+       "authmanager-realname-help": "Prawdziwe imię i nazwisko użytkownika",
+       "authmanager-provider-password": "Uwierzytelnianie oparte na haśle",
+       "authmanager-provider-temporarypassword": "Hasło tymczasowe",
+       "authprovider-resetpass-skip-label": "Pomiń",
+       "authform-newtoken": "Brakujący token. $1",
+       "authform-notoken": "Brakujący token",
+       "authform-wrongtoken": "Nieprawidłowy token",
+       "specialpage-securitylevel-not-allowed": "Niestety, nie możesz korzystać z tej strony, ponieważ twoja tożsamość nie może zostać zweryfikowana.",
+       "authpage-cannot-login-continue": "Nie można kontynuować logowania. Sesja najprawdopodobniej wygasła.",
+       "cannotauth-not-allowed-title": "Brak dostępu",
+       "changecredentials-submit-cancel": "Anuluj",
+       "removecredentials-submit": "Usuń",
+       "removecredentials-submit-cancel": "Anuluj",
+       "credentialsform-account": "Nazwa konta:"
 }
index f127930..52c8279 100644 (file)
        "noname": "A l'ha nen ëspessificà në stranòm vàlid.",
        "loginsuccesstitle": "Compliment! A l'é pen-a rintrà ant ël sistema.",
        "loginsuccess": "'''Adess a l'é colegà a {{SITENAME}} con lë stranòm «$1».'''",
-       "nosuchuser": "A-i é pa gnun utent con lë stranòm «$1».\nJë stranòm ëd j'utent a son sensìbij a le majùscole.\nCh'a contròla ël nòm che a l'ha batù, o [[Special:UserLogin/signup|ch'a crea un neuv cont]].",
+       "nosuchuser": "A-i é pa gnun utent con lë stranòm «$1».\nJë stranòm ëd j'utent a son sensìbij a le majùscole.\nCh'a contròla ël nòm che a l'ha batù, o [[Special:CreateAccount|ch'a crea un neuv cont]].",
        "nosuchusershort": "A-i é pa gnun utent che as ciama «$1». Për piasì, che a contròla se a l'ha scrit tut giust.",
        "nouserspecified": "A venta che a specìfica në stranòm d'utent",
        "login-userblocked": "St'utent-sì a l'é blocà. A peul pa intré ant ël sistema.",
        "accmailtext": "Na ciav generà a l'ancàpit për [[User talk:$1|$1]] a l'é stàita mandà a $2.\nA peul esse modificà an sla pàgina ''[[Special:ChangePassword|modìfica dla ciav]]'' apress esse rintrà ant ël sistema.",
        "newarticle": "(Neuv)",
        "newarticletext": "A l'é andaje dapress a na liura a na pàgina che a esist ancor nen.\nPër creé la pàgina, ch'a ancamin-a a scrive ant lë spassi sì-sota (vëdde la [$1 pàgina d'agiut] për savèjne ëd pì).\nS'a l'é rivà sì për eror, ch'a sgnaca ël boton '''andaré''' ëd sò navigador.",
-       "anontalkpagetext": "----''Costa a l'é la pàgina ëd ciaciarade për n'utent anònim che a l'é ancó pa dorbusse un cont, ò pura che a lo deuvra nen. Alora i l'oma da dovré ël nùmer d'adrëssa IP për deje n'identificassion a chiel o chila.\nN'adrëssa IP përparèj a peul esse partagià da vàire utent.\nSe chiel a l'é n'utent anònim e a l'ha l'impression d'arsèive dij coment sensa sust, për piasì [[Special:UserLogin/signup|ch'a crea un cont]] o [[Special:UserLogin|ch'a rintra ant ël sistema]] për evité dë fé confusion con d'àutri utent anònim.''",
+       "anontalkpagetext": "----\n<em>Costa a l'é la pàgina ëd ciaciarade për n'utent anònim che a l'é ancó pa duvertasse un cont, ò pura che a lo deuvra nen.</em>\nAlora i l'oma da dovré ël nùmer d'adrëssa IP për deje n'identificassion a chiel o chila.\nN'adrëssa IP përparèj a peul esse partagià da vàire utent.\nSe chiel a l'é n'utent anònim e a l'ha l'impression d'arsèive dij coment sensa sust, për piasì [[Special:CreateAccount|ch'a crea un cont]] o [[Special:UserLogin|ch'a rintra ant ël sistema]] për evité dë fé confusion con d'àutri utent anònim.''",
        "noarticletext": "Al moment costa pàgina a l'é veuida.\nA peul [[Special:Search/{{PAGENAME}}|sërché cost tìtol]] andrinta a d'àutre pàgine, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sërché ant ij registr colegà],\no purament [{{fullurl:{{FULLPAGENAME}}|action=edit}} modìfiché sta pàgina]</span>.",
        "noarticletext-nopermission": "Al moment a-i é gnun test ansima a costa pàgina.\nA peul [[Special:Search/{{PAGENAME}}|sërché ës tìtol ëd pàgina]] an d'àutre pàgine,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sërché ant j'argistr colegà]</span>, ma a l'ha pa ël përmess ëd creé costa pàgina.",
        "missing-revision": "La revision nùmer $1 dla pàgina antitolà «{{FULLPAGENAME}}» a esist pa.\n\nSòn a l'é normalment causà da l'andèje dapress a na vej liura stòrica a na pàgina ch'a l'é stàita scancelà. Ij detaj a peulo esse trovà ant ël [registr ëd jë scancelament ëd {{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}}].",
        "whatlinkshere-prev": "{{PLURAL:$1|d'un andré|andré ëd $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|d'un anans|anans ëd $1}}",
        "whatlinkshere-links": "← anliure",
-       "whatlinkshere-hideredirs": "$1 le ridiression",
-       "whatlinkshere-hidetrans": "$1 anclusion",
-       "whatlinkshere-hidelinks": "$1 anliura",
+       "whatlinkshere-hideredirs": "Stërmé ridiression",
+       "whatlinkshere-hidetrans": "Stërmé transclusion",
+       "whatlinkshere-hidelinks": "Stërmé anliure",
        "whatlinkshere-hideimages": "$1 j'archivi lijà",
        "whatlinkshere-filters": "Filtr",
        "autoblockid": "Blocagi automàtich #$1",
        "javascripttest": "Preuva ëd JavaScript",
        "javascripttest-pagetext-unknownaction": "Assion nen conossùa «$1».",
        "javascripttest-qunit-intro": "Vëdde [$1 la documentassion dle preuve] dzora a mediawiki.org.",
-       "tooltip-pt-userpage": "Soa pàgina utent",
+       "tooltip-pt-userpage": "{{GENDER:|Soa}} pàgina utent",
        "tooltip-pt-anonuserpage": "La pàgina utent për l'IP con ël qual chiel a contribuiss",
        "tooltip-pt-mytalk": "Soa pàgina ëd discussion e ciaciarade",
        "tooltip-pt-anontalk": "La pàgina ëd ciaciarade an sle contribussion da costa adrëssa IP",
        "tooltip-pt-preferences": "Coma che i veuj mia {{SITENAME}}.",
        "tooltip-pt-watchlist": "Lista dle pàgine che chiel as ten sot euj.",
-       "tooltip-pt-mycontris": "Lista ëd soe contribussion",
+       "tooltip-pt-mycontris": "Lista ëd {{GENDER:|soe}} contribussion",
        "tooltip-pt-login": "Un a l'é nen obligà a rintré ant al sistema, ma se a lo fa a l'é mej",
        "tooltip-pt-logout": "Seurte da",
        "tooltip-pt-createaccount": "I-j consejoma ëd creé un cont e ëd rintré ant ël sistema; però a l'é nen obligatòri",
index 3ef60e7..21de22e 100644 (file)
        "noname": "تسی کوئی پکا ورتن آلا ناں نئیں رکھ رۓ۔",
        "loginsuccesstitle": "تسی لاگن ہوگۓ او",
        "loginsuccess": "'''ہن تسی {{SITENAME}} تے \"$1\" دے ناں توں لاگ ان او'''",
-       "nosuchuser": "اس $1 ناں نال کوئی ورتن آلا نہیں۔\nاپنی لکھائی درست کرو یا نیا [[Special:UserLogin/signup|کھاتہ بناؤ]]۔",
+       "nosuchuser": "اس $1 ناں نال کوئی ورتن آلا نہیں۔\nاپنی لکھائی درست کرو یا نیا [[Special:CreateAccount|کھاتہ بناؤ]]۔",
        "nosuchusershort": "اس \"$1\" ناں دا کوئی ورتن آلا نہيں اے۔\n\nاپنی الف، بے چیک کرو۔",
        "nouserspecified": "توانوں اپنا ورتن آلا ناں دسنا ہوۓ گا۔",
        "login-userblocked": "اے ورتن آلے روکیا ہویا اے۔ اے لاگ ان نئیں کرسکدا۔",
        "accmailtext": "اک کنجی [[User talk:$1|$1]] $2 نوں پیج دتی گئی اے۔\nایس نویں کھاتے دی کنجی بدلی جاسکدی  اے ''[[Special:ChangePassword|change password]]'' صفے تے لاگ ان ہون تے۔",
        "newarticle": "(نواں)",
        "newarticletext": "تسی ایسے صفحے دے جوڑ توں ایتھے پہنچے او جیڑا ھلے تک نہیں بنیا۔<br />\nاس صفحہ بنانے آسطے تھلے دتے گۓ ڈبے وچ لکھنا شروع کر دیو(زیادہ رہنمائی آستے اے ویکھو [$1 <br />مدد دا صفحہ])۔\nاگر تسی ایتھے غلطی نال پہنچے او تے اپنے کھوجی توں \"بیک\" دا بٹن دبا دیو۔",
-       "anontalkpagetext": "----'' ایہ اک گمنام ورتن والے دا گل بات دا صفہ اے جینے ہلے کھاتہ نئیں کھولیا یا او اینون ورتدا نئیں۔\nسانوں فیر نمبراں والا آئی پی پتہ ورتنا پوے گا اونوں لئی. ایہو جیا آئی پی پتہ گئی ورتن والے ورت سکدے نیں۔ \nاگر تسیں اک گمنام ورتن والے او تے اے مسوس کردے او جے پیڑی گل بات تواڈی بارے ہوئی اے، مہربانی کرکے [[Special:UserLogin/signup|create an account]] یا [[Special:UserLogin|log in]] اگے کسے مسلے توں بچن گمنام ورتن والیاں کولوں",
+       "anontalkpagetext": "----'' ایہ اک گمنام ورتن والے دا گل بات دا صفہ اے جینے ہلے کھاتہ نئیں کھولیا یا او اینون ورتدا نئیں۔\nسانوں فیر نمبراں والا آئی پی پتہ ورتنا پوے گا اونوں لئی. ایہو جیا آئی پی پتہ گئی ورتن والے ورت سکدے نیں۔ \nاگر تسیں اک گمنام ورتن والے او تے اے مسوس کردے او جے پیڑی گل بات تواڈی بارے ہوئی اے، مہربانی کرکے [[Special:CreateAccount|create an account]] یا [[Special:UserLogin|log in]] اگے کسے مسلے توں بچن گمنام ورتن والیاں کولوں",
        "noarticletext": "اس ویلے اس صفے تے کج نہیں لکھیا ہویا تسیں [[Special:Search/{{PAGENAME}}|اس صفے دے ناں نوں دوجے صفیاں تے کھوج سکدے او]] یا فیر [{{fullurl:{{FULLPAGENAME}}|action=edit}} اس صفے نوں لکھ سکدے او۔]",
        "noarticletext-nopermission": "ایس ویلے ایس صفے تے کوئی لکھت نئیں۔ \nتسیں [[Special:Search/{{PAGENAME}}|search for this page title]] دوسریاں صفیاں تے،\nیا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>۔",
        "userpage-userdoesnotexist": "ورتن کھاتہ \"$1\" رجسٹر نئیں ہویا۔\nمہربانی کرکے دسو جے تسیں ای ھفا بنانا/بدلنا چاندے او۔",
index bf6af7a..16f214a 100644 (file)
        "noname": "'Κ έβαλατε καλόν όνεμαν χρήστε.",
        "loginsuccesstitle": "Έντον τ' εσέβεμαν",
        "loginsuccess": "'''Εσήβετεν σο {{SITENAME}} με τ'όνεμαν \"$1\".'''",
-       "nosuchuser": "Αδαπές 'κ εχ' χρήστεν με τ' όνομαν \"$1\".\nΤερέστεν τα γράμματα τη ονοματί, τερέστεν τα τρανογράμματα και τα μικρογράμματα να είναι τογρία.\nΤερέστεν την ορθογραφίαν ή [[Special:UserLogin/signup|ποισέστεν καινούρεον λογαρίαν]].",
+       "nosuchuser": "Αδαπές 'κ εχ' χρήστεν με τ' όνομαν \"$1\".\nΤερέστεν τα γράμματα τη ονοματί, τερέστεν τα τρανογράμματα και τα μικρογράμματα να είναι τογρία.\nΤερέστεν την ορθογραφίαν ή [[Special:CreateAccount|ποισέστεν καινούρεον λογαρίαν]].",
        "nosuchusershort": "Αδαπές 'κ εχ' χρήστεν με τ' όνομαν \"$1\".\nΤ'όνομαν γραφέστεν ατο τογρία.",
        "nouserspecified": "Πρέπ' να ψιλίζετε έναν όνεμαν.",
        "login-userblocked": "Το χρήστεν έδεξαν ατον. Να εμπαίν 'κ ίνεται.",
        "deleteotherreason": "Άλλον/αλλομίαν λόγον:",
        "deletereasonotherlist": "Άλλον λόγον",
        "rollback": "Φέρον ξαν σην υστερναίαν",
-       "rollback_short": "Επαναφοράν",
        "rollbacklink": "φέρον ξαν σην υστερναίαν",
        "protectlogpage": "Αρχείον ασπαλιγματίων",
        "protectedarticle": "ασπαλιζμένον \"[[$1]]\"",
        "move-page-legend": "Ετεροχλάεμαν σελίδας",
        "movepagetext": "Εάν εφτάτε το ψαλαφίον αφκά θα δείτε άλλον όνομαν σ' έναν σελίδαν και θα παίρτεν τ' ιστορικόνατς εκαικά. Το παλαιόν η σελίδαν θα μεταβάλκεται σε σύνδεσμον σην καινούραιαν.\n\nΕπορείτε να μεταβάλκετε τα συνδέσμαι που δεκνίζνε σο παλαιόν τη σελίδαν αυτόματα. Εάν 'κ φτάτε αέτς,\nευρέστεν [[Special:DoubleRedirects|διπλά]] για [[Special:BrokenRedirects|τσακωμένα συνδέσμ]].\nΈχετ' ευθύνην τα παλαιά τα συνδέσμαι να δεκνίζνε σο σωστόν τη σελίδαν.\n\nΗ σελίδαν ''''κ θ' αλλάζ'''' τη θέσηνατς όντες έχ' άλλον σελίδαν με το νέον τ' όνεμαν. Εξαίρεσην εν τ' εύκαιρα τα σελίδας και τα συνδέσμαι, ντο 'κ έχνε ιστορικόν.\nΕπορείτε δηλαδή να παίρετε τη σελίδαν σ' όνομαν ντ' είχεν προτεσνά. Άμα 'κ επορείτε με το ετεροχλάεμαν να σβήετε άλλον σελίδαν.\n\n'''ΩΡΙΑ!'''\nΑβούτεν η ενέργειαν επορεί να φέρει τρανά διαφοράς σ' έναν σελίδαν που δεβάζνε πολλοί.\nΝουνίστενατο καλά πριχού να εφτάτε τ' άλλαγμαν τ' ονοματί.",
        "movepagetalktext": "Η σελίδαν καλατσεματί αυτόματα θα πηγαίν' εντάμαν, '''εξόν:'''\n*Έχ' άλλον σελίδαν καλατσεματί ντο 'κ εν εύκαιρον άμα έχ' το ίδιον τ' όνεμαν\n*θα ευκαιρώνετε το χουτίν αφκά.\n\nΕάν θέλετε να εφτάτε τα ένωμαν, να εφτάτε ατό με copy και paste.",
-       "movearticle": "Ετεροχλάεμαν σελίδας:",
        "newtitle": "Νέον τίτλον:",
        "move-watch": "Ωρίαγμαν τη σελίδας",
        "movepagebtn": "Ετεροχλάεμαν σελίδας",
        "movelogpage": "Αρχείον ετεροχλαεματί",
        "movereason": "Λόγον:",
        "revertmove": "κλώσιμον",
-       "delete_and_move": "Σβήσον και ετεροχλάεψον",
        "export": "Εξαγωγήν σελίδιων",
        "export-addcattext": "Βαλέστεν σελίδας ασήν κατηγορίαν:",
        "export-addcat": "Βαλέστεν",
        "importstart": "Έμπαζμαν σελιδίων...",
        "import-noarticle": "'Κ εχ' σελίδαν για έμπαζμαν!",
        "importlogpage": "Αρχείον εμπαζματίων",
-       "import-logentry-interwiki": "εγέντον εισαγωγήν transwiki σην σελίδαν $1",
        "tooltip-pt-userpage": "Τ' εσόν η σελίδαν",
        "tooltip-pt-mytalk": "Τ' εσόν το καλάτσεμαν",
        "tooltip-pt-preferences": "Τ' εμά τα προτιμήσεις",
        "htmlform-selectorother-other": "Άλλον",
        "rightsnone": "(τιδέν)",
        "revdelete-summary": "σύνοψην",
-       "searchsuggest-search": "Αράεμαν"
+       "searchsuggest-search": "Αράεμαν",
+       "special-characters-group-ipa": "ΔΦΑ",
+       "special-characters-group-telugu": "Τελούγκου"
 }
index 698cbc1..c1e7cb9 100644 (file)
        "noname": "Sta ni ast tikrōmiska tērpautajas pabilisnā.",
        "loginsuccesstitle": "Enēisenis izpalla",
        "loginsuccess": "'''Assei teinū engūbun {{SITENAME}} kāigi \"$1\".'''",
-       "nosuchuser": "Ni ast tērpautajs sen pabilīsnan \"$1\".\nZentlin debban en tērpautajas pabilīsnai ast zentlawingi.\nIzbandais peisāsnan anga [[Special:UserLogin/signup|teīkeis nāunan rekkenan]].",
+       "nosuchuser": "Ni ast tērpautajs sen pabilīsnan \"$1\".\nZentlin debban en tērpautajas pabilīsnai ast zentlawingi.\nIzbandais peisāsnan anga [[Special:CreateAccount|teīkeis nāunan rekkenan]].",
        "nosuchusershort": "Ni ast tērpautajs sen pabilīsnan \"$1\".\nIzbandais peisāsnan.",
        "nouserspecified": "Tu turri enpeisātun tērpautajas pabilīsnan.",
        "wrongpassword": "Nitikrōmiskas kliptaswīrds. Bandais dabber rēizan.",
        "accmailtitle": "Kliptaswīrds tengīntan.",
        "newarticle": "(Nāuns)",
        "newarticletext": "Tu assei autengīntan pra autengīnsenin en dabber niekzistīntin pāusan.\nKāi teīklai šin pāusan, pagaūneis enpeisātun en zemmaišasmu lāukan (wīdais [$1 help page] per tūls infōrmaciōnis).\nIk tū ni kwaitīwuns(si) teīktun nāunan pāusan, gnetteis \"Etwārtai\" knuppan en twajjai lasātlin.",
-       "anontalkpagetext": "----''Sta ast anōnimas tērpautajas diskusiōnis pāusan - stawīdan, kawīds ni turri dabber swajjan rekkenan anga ni tērpaui din.\nKāi identificīlai tennan, tērpawimai IP adressins.\nAdder IP adressi mazzi būtwei dallautan pra tūlin tērpautajans.\nIk tu assei anōnims tērpautajs be tu mīri, kāi kumentārai stwi ni ast wartīntan prei tin, [[Special:UserLogin/signup|teīkeis rekkenan]] anga [[Special:UserLogin|enēis]] kāi aulānktun perejīngins kurtīsenins sen kittans anōnimans tērpautajs.''",
+       "anontalkpagetext": "----''Sta ast anōnimas tērpautajas diskusiōnis pāusan - stawīdan, kawīds ni turri dabber swajjan rekkenan anga ni tērpaui din.\nKāi identificīlai tennan, tērpawimai IP adressins.\nAdder IP adressi mazzi būtwei dallautan pra tūlin tērpautajans.\nIk tu assei anōnims tērpautajs be tu mīri, kāi kumentārai stwi ni ast wartīntan prei tin, [[Special:CreateAccount|teīkeis rekkenan]] anga [[Special:UserLogin|enēis]] kāi aulānktun perejīngins kurtīsenins sen kittans anōnimans tērpautajs.''",
        "noarticletext": "Tēnti šin pāusan ni turri ēn sen tekstan.\nTu mazzi [[Special:Search/{{PAGENAME}}|laukītun šisse pāusas tītelin]] en kitēimans pāusans,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pralaukītun registerin],\nanga [{{fullurl:{{FULLPAGENAME}}|action=edit}} redigītun šin pāusan]</span>.",
        "userpage-userdoesnotexist": "Tērpautajs \"<nowiki>$1</nowiki>\" ni ast registrītan.\nIzbāndais, anga tū perarwi kwaitīwuns teīktun/redigītun šin pāusan.",
        "userpage-userdoesnotexist-view": "Tērpautajas rekkens \"$1\" ni ast registrītan.",
index 8e677db..cc3f168 100644 (file)
@@ -6,7 +6,8 @@
                        "Umherirrender",
                        "아라",
                        "عثمان خان شاہ",
-                       "Macofe"
+                       "Macofe",
+                       "Amire80"
                ]
        },
        "tog-underline": "کرښنې تړنې:",
        "noname": "تاسې تر اوسه پورې کوم کره کارن نوم نه دی ځانگړی کړی.",
        "loginsuccesstitle": "غونډال کې ورننوتلۍ",
        "loginsuccess": "'''تاسې اوس {{SITENAME}} کې د \"$1\" په نوم ننوتي ياست.'''",
-       "nosuchuser": "د \"$1\" په نوم هېڅ کارن نشته.\nد کارنانو نومونه د غټو او واړو تورو سره حساس دي.\nخپل حجا وڅارۍ، او يا هم [[Special:UserLogin/signup|يو نوی گڼون جوړ کړی]].",
+       "nosuchuser": "د \"$1\" په نوم هېڅ کارن نشته.\nد کارنانو نومونه د غټو او واړو تورو سره حساس دي.\nخپل حجا وڅارۍ، او يا هم [[Special:CreateAccount|يو نوی گڼون جوړ کړی]].",
        "nosuchusershort": "د \"$1\" په نوم هېڅ کوم گڼون نشته. لطفاً خپل د نوم ليکلې بڼې ته ځير شی چې پکې تېروتنه نه وي.",
        "nouserspecified": "تاسې ځان ته کوم کارن نوم نه دی ځانگړی کړی.",
        "login-userblocked": "په دې کارن بنديز لگېدلی. غونډال کې ننوتلو ته پرې نه ښودلی شو.",
        "minoredit": "دا يو وړوکی سمون دی",
        "watchthis": "همدا مخ کتل",
        "savearticle": "مخ خوندي کول",
+       "publishpage": "مخ خپرول",
        "preview": "مخليدنه",
        "showpreview": "مخليدنه",
        "showdiff": "بدلونونه ښکاره کول",
        "accmailtitle": "پټنوم ولېږل شو.",
        "newarticle": "(نوی)",
        "newarticletext": "تاسې د يوې داسې تړنې څارنه کړې چې لا تر اوسه پورې نه شته.\nکه همدا مخ ليکل غواړۍ، نو په لانديني چوکاټ کې خپل متن وټاپئ (د لا نورو مالوماتو لپاره د [$1 لارښود مخ] وگورئ).\nکه چېرته تاسې دلته په تېروتنه راغلي ياست، نو يواځې د خپل د کتنمل '''مخ پر شا''' تڼۍ مو وټوکئ.",
-       "anontalkpagetext": "----''دا د يوه ورکنومي کارن چې کارن-نوم نه لري او يا خپل کارن-نوم نه کاروي، د سکالو يوه پاڼه ده. نو د يوه کس د پېژندلو پخاطر موږ د هماغه کارن د انټرنېټ شمېره يا IP پته دلته ثبتوؤ. داسې يوه IP پته د ډېرو کارنانو لخوا هم کارېدلی شي. که تاسې يو ورکنومی کارن ياست او تاسې ته دا څرگندېږي چې تاسې ته نااړونده پېغامونه او تبصرې اشاره شوي، نو د نورو بې نومو کارنانو او ستاسې ترمېنځ د ټکنتوب د مخ نيونې لپاره لطفاً [[Special:UserLogin/signup|يو گڼون جوړ کړۍ]] او يا هم [[Special:UserLogin|غونډال ته ورننوځۍ]].''",
-       "noarticletext": "دم مهال په دې مخ کې څه نشته.\nتاسې کولای شی چې په نورو مخونو کې [[Special:Search/{{PAGENAME}}|د دې مخ د سرليک پلټنه]] يا\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} د اړوندو يادښتونو پلټنه] وکړی.\nاو يا [{{fullurl:{{FULLPAGENAME}}|action=edit}} همدا مخ سم کړی]</span>.",
+       "anontalkpagetext": "----''دا د يوه ورکنومي کارن چې کارن-نوم نه لري او يا خپل کارن-نوم نه کاروي، د سکالو يوه پاڼه ده. نو د يوه کس د پېژندلو پخاطر موږ د هماغه کارن د انټرنېټ شمېره يا IP پته دلته ثبتوؤ. داسې يوه IP پته د ډېرو کارنانو لخوا هم کارېدلی شي. که تاسې يو ورکنومی کارن ياست او تاسې ته دا څرگندېږي چې تاسې ته نااړونده پېغامونه او تبصرې اشاره شوي، نو د نورو بې نومو کارنانو او ستاسې ترمېنځ د ټکنتوب د مخ نيونې لپاره لطفاً [[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}}|همدا سرليک په نورو مخونو کې وپلټۍ]], يا هم <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} اړونده يادښتونه وپلټۍ]</span>، خو تاسې د دې مخ د جوړولو اجازه نه لرۍ.",
        "userpage-userdoesnotexist": "د \"<nowiki>$1</nowiki>\" گڼون نه دی ثبت شوی.\nلطفاً ځان ډاډه کړئ چې آيا تاسې په رښتيا همدا مخ جوړول/سمول غواړئ.",
        "userpage-userdoesnotexist-view": "د \"$1\" گڼون نه دی ثبت شوی.",
        "upload-form-label-infoform-description": "څرگندونه",
        "upload-form-label-usage-title": "کارېدنې",
        "upload-form-label-usage-filename": "د دوتنې نوم",
-       "foreign-structured-upload-form-label-own-work": "دا زما خپل کار دی",
-       "foreign-structured-upload-form-label-infoform-categories": "وېشنيزې",
-       "foreign-structured-upload-form-label-infoform-date": "نېټه",
+       "upload-form-label-own-work": "دا زما خپل کار دی",
+       "upload-form-label-infoform-categories": "وېشنيزې",
+       "upload-form-label-infoform-date": "نېټه",
        "backend-fail-notexists": "د $1 په نوم دوتنه نشته.",
        "backend-fail-delete": "د \"$1\" دوتنه ړنګه نه شوه.",
        "backend-fail-alreadyexists": "د $1 دوتنه له پخوا نه شته.",
        "listgrouprights-rights": "رښتې",
        "listgrouprights-helppage": "Help:د ډلې رښتې",
        "listgrouprights-members": "(د غړو لړليک)",
-       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code dir=\"ltr\">($2)</code></span>",
        "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "{{PLURAL:$2|ډله|ډلې}} ورگډول: $1",
        "listgrouprights-removegroup": "{{PLURAL:$2|ډله|ډلې}} ليري کول: $1",
        "ipb-unblock": "له يوه کارن-نوم يا IP پتې بنديز ليري کول",
        "ipb-blocklist": "شته بنديزونه کتل",
        "ipb-blocklist-contribs": "د {{GENDER:$1|$1}} ونډې",
+       "ipb-blocklist-duration-left": "$1 پاتې دی",
        "unblockip": "کارن له بنديزه وېستل",
        "unblockiptext": "د لاندې فورمې په کارولو سره يو بنديز شوي کارن يا آی پي پتې ته د ليکلو لاسرسی ورکولی شی.",
        "ipusubmit": "دا بنديز ليرې کول",
        "watchlistedit-raw-done": "ستاسې کتنلړ اوسمهاله شو.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 سرليک ورگډ شو|$1 سرليکونه ورگډ شوه}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 سرليک ليرې شو|$1 سرليکونه ليري شوه}}:",
-       "watchlistedit-clear-title": "کتنلړ سپين شو",
+       "watchlistedit-clear-title": "کتنلړ سپينول",
        "watchlistedit-clear-legend": "کتنلړ سپينول",
        "watchlistedit-clear-titles": "سرليکونه:",
        "watchlistedit-clear-submit": "کتنلړ سپينول (دا دايمي ده!)",
index 2ceb7e5..de3a243 100644 (file)
        "noname": "Você não colocou um nome de usuário válido.",
        "loginsuccesstitle": "Autenticado",
        "loginsuccess": "'''Agora você está {{GENDER:autenticado|autenticada}} ao wiki {{SITENAME}} como \"$1\"'''.",
-       "nosuchuser": "Não existe nenhum usuário com o nome \"$1\".\nOs nomes de usuário são sensíveis a letras maiúsculas.\nVerifique o que foi digitado ou [[Special:UserLogin/signup|crie uma nova conta]].",
+       "nosuchuser": "Não existe nenhum usuário com o nome \"$1\".\nOs nomes de usuário são sensíveis a letras maiúsculas.\nVerifique o que foi digitado ou [[Special:CreateAccount|crie uma nova conta]].",
        "nosuchusershort": "Não existe um usuário com o nome \"$1\". Verifique o nome que introduziu.",
        "nouserspecified": "Você precisa especificar um nome de usuário.",
        "login-userblocked": "Este usuário está bloqueado. Entrada proibida.",
        "accmailtext": "Uma senha gerada aleatoriamente para [[User talk:$1|$1]] foi enviada para $2.\n\nEla pode ser alterada na página ''[[Special:ChangePassword|de troca de senha]]'', após o início de sessão.",
        "newarticle": "(Nova)",
        "newarticletext": "Você seguiu um link para uma página que ainda não existe.\nPara criá-la, comece escrevendo na caixa abaixo (veja [$1 a página de ajuda] para mais informações).\nSe você chegou aqui por engano, clique no botão '''voltar''' do seu navegador.",
-       "anontalkpagetext": "---- ''Esta é a página de discussão para um usuário anônimo que ainda não criou uma conta ou que não a usa, de forma que temos de utilizar o endereço de IP para identificá-lo(a). Tal endereço de IP pode ser compartilhado por vários usuários. Se você é um usuário anônimo e acha que comentários irrelevantes foram direcionados a você, por gentileza, [[Special:UserLogin/signup|crie uma conta]] ou [[Special:UserLogin|autentique-se]], a fim de evitar futuras confusões com outros usuários anônimos.''",
+       "anontalkpagetext": "---- ''Esta é a página de discussão para um usuário anônimo que ainda não criou uma conta ou que não a usa, de forma que temos de utilizar o endereço de IP para identificá-lo(a). Tal endereço de IP pode ser compartilhado por vários usuários. Se você é um usuário anônimo e acha que comentários irrelevantes foram direcionados a você, por gentileza, [[Special:CreateAccount|crie uma conta]] ou [[Special:UserLogin|autentique-se]], a fim de evitar futuras confusões com outros usuários anônimos.''",
        "noarticletext": "Não há conteúdo nesta página no momento.\nVocê pode [[Special:Search/{{PAGENAME}}|pesquisar pelo título desta página]] em outras páginas, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar por registros relacionados],\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} criar esta página]</span>.",
        "noarticletext-nopermission": "No momento, não há conteúdo nesta página\nVocê pode [[Special:Search/{{PAGENAME}}|pesquisar pelo título desta página]] em outras páginas,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar por registros relacionados] </span>. Note que, no entanto, você não tem permissão para criar esta página.",
        "missing-revision": "A revisão #$1 da página denominada \"{{FULLPAGENAME}}\" não existe.\n\nIsto é geralmente causado por seguir um link de histórico desatualizado para uma página que foi eliminada.\nOs detalhes podem ser encontrados no [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de eliminação].",
        "upload-form-label-infoform-description": "Descrição",
        "upload-form-label-usage-title": "Uso",
        "upload-form-label-usage-filename": "Nome do arquivo",
-       "foreign-structured-upload-form-label-own-work": "Isto é o meu próprio trabalho",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorias",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Você pode também querer tentar [[Special:Upload|the default upload page]]",
+       "upload-form-label-own-work": "Isto é o meu próprio trabalho",
+       "upload-form-label-infoform-categories": "Categorias",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-not-own-work-local-generic-local": "Você pode também querer tentar [[Special:Upload|the default upload page]]",
        "backend-fail-stream": "Não foi possível transmitir o arquivo  $1.",
        "backend-fail-backup": "Não foi possível fazer backup do arquivo  $1 .",
        "backend-fail-notexists": "O arquivo $1 não existe.",
index 430713d..4881cf8 100644 (file)
        "noname": "Não especificou um nome de utilizador válido.",
        "loginsuccesstitle": "Autenticação bem sucedida",
        "loginsuccess": "'''Encontra-se agora ligado à {{SITENAME}} como \"$1\"'''.",
-       "nosuchuser": "Não existe nenhum utilizador com o nome \"$1\".\nOs nomes de utilizador são sensíveis à capitalização.\nVerifique a ortografia, ou [[Special:UserLogin/signup|crie uma nova conta]].",
+       "nosuchuser": "Não existe nenhum utilizador com o nome \"$1\".\nOs nomes de utilizador são sensíveis à capitalização.\nVerifique a ortografia, ou [[Special:CreateAccount|crie uma nova conta]].",
        "nosuchusershort": "Não existe um utilizador com o nome \"$1\". Verifique o nome que introduziu.",
        "nouserspecified": "Precisa de especificar um nome de utilizador.",
        "login-userblocked": "Este utilizador está bloqueado. Não é permitido o acesso.",
        "accmailtext": "Uma palavra-passe gerada aleatoriamente para [[User talk:$1|$1]] foi enviada para $2.\n\nEla pode ser alterada na página [[Special:ChangePassword|de alteração da palavra-passe]] após iniciar sessão.",
        "newarticle": "(Nova)",
        "newarticletext": "Seguiu uma ligação para uma página que ainda não existe.\nPara criá-la, escreva o seu conteúdo na caixa abaixo (consulte a [$1 página de ajuda] para mais detalhes).\nSe chegou aqui por engano, clique o botão '''voltar''' (ou ''back'') do seu navegador.",
-       "anontalkpagetext": "----''Esta é a página de discussão de um utilizador anónimo que ainda não criou uma conta ou não a utiliza, pelo que temos de utilizar o endereço IP para identificá-lo(a).\nUm endereço IP pode ser partilhado por vários utilizadores.\nSe é um utilizador anónimo e sente que lhe foram direcionados comentários irrelevantes, por favor [[Special:UserLogin/signup|crie uma conta]] ou [[Special:UserLogin|inicie sessão]] para evitar futuras confusões com outros utilizadores anónimos.''",
+       "anontalkpagetext": "----''Esta é a página de discussão de um utilizador anónimo que ainda não criou uma conta ou não a utiliza, pelo que temos de utilizar o endereço IP para identificá-lo(a).\nUm endereço IP pode ser partilhado por vários utilizadores.\nSe é um utilizador anónimo e sente que lhe foram direcionados comentários irrelevantes, por favor [[Special:CreateAccount|crie uma conta]] ou [[Special:UserLogin|inicie sessão]] para evitar futuras confusões com outros utilizadores anónimos.''",
        "noarticletext": "Ainda não existe texto nesta página.\nPode [[Special:Search/{{PAGENAME}}|pesquisar o título desta página]] noutras páginas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} procurar registos relacionados]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta página]</span>.",
        "noarticletext-nopermission": "Ainda não existe texto nesta página.\nPode [[Special:Search/{{PAGENAME}}|pesquisar o título desta página]] noutras páginas, ou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} procurar nos registos relacionados]</span>, mas não tem permissão para criar esta página.",
        "missing-revision": "A revisão #$1 da página denominada \"{{FULLPAGENAME}}\" não existe.\n\nIsto é geralmente causado por seguir uma ligação de histórico desatualizada para uma página que foi eliminada.\nOs detalhes podem ser encontrados no [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registo de eliminação].",
        "upload-form-label-infoform-description": "Descrição",
        "upload-form-label-usage-title": "Uso",
        "upload-form-label-usage-filename": "Nome do ficheiro",
-       "foreign-structured-upload-form-label-own-work": "Este é minha obra própria",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorias",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que estou a carregar este ficheiro segundo as condições de serviço e política de licenças de {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Se não é capaz de carregar este ficheiro sob as políticas de {{SITENAME}}, por favor feche esta janela e tente outro método.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Poderá querer experimentar [[Special:Upload|a página padrão de carregamento]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Entendo que estou a carregar este ficheiro em um repositório partilhado. Confirmo que estou a fazê-lo cumprindo com os termos de serviço e com as políticas de licenciamento.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Se não é capaz de carregar este ficheiro sob as políticas do repositório partilhado, por favor feche esta janela e tente outro método.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Pode querer tentar utilizar [[Special:Upload|a página de carregamento em {{SITENAME}}]], se este ficheiro puder ser carregado de acordo com suas as políticas.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Confirmo que sou o proprietário dos direitos de autor deste ficheiro, e aceito partilhar irrevogavelmente este ficheiro para o Wikimedia Commons nos termos da licença [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Atribuição-CompartilhaIgual 4.0], e concordo com os [https://wikimediafoundation.org/wiki/Terms_of_Use Termos de Utilização].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Se não é o proprietário dos direitos de autor deste ficheiro, ou caso deseje partilhá-lo sob uma licença diferente, considere utilizar o [https://commons.wikimedia.org/wiki/Special:UploadWizard Assistente de Envio de Ficheiros do Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Pode querer tentar utilizar [[Special:Upload|a página de carregamento em {{SITENAME}}]], se o sítio aceitar o carregamento deste ficheiro de acordo com as suas políticas.",
+       "upload-form-label-own-work": "Este é minha obra própria",
+       "upload-form-label-infoform-categories": "Categorias",
+       "upload-form-label-infoform-date": "Data",
+       "upload-form-label-own-work-message-generic-local": "Confirmo que estou a carregar este ficheiro segundo as condições de serviço e política de licenças de {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Se não é capaz de carregar este ficheiro sob as políticas de {{SITENAME}}, por favor feche esta janela e tente outro método.",
+       "upload-form-label-not-own-work-local-generic-local": "Poderá querer experimentar [[Special:Upload|a página padrão de carregamento]].",
+       "upload-form-label-own-work-message-generic-foreign": "Entendo que estou a carregar este ficheiro em um repositório partilhado. Confirmo que estou a fazê-lo cumprindo com os termos de serviço e com as políticas de licenciamento.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se não é capaz de carregar este ficheiro sob as políticas do repositório partilhado, por favor feche esta janela e tente outro método.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Pode querer tentar utilizar [[Special:Upload|a página de carregamento em {{SITENAME}}]], se este ficheiro puder ser carregado de acordo com suas as políticas.",
        "backend-fail-stream": "Não foi possível transmitir o ficheiro \"$1\".",
        "backend-fail-backup": "Não foi possível fazer cópia de segurança do ficheiro \"$1\".",
        "backend-fail-notexists": "O ficheiro $1 não existe.",
index 5c49430..f6d2f41 100644 (file)
                        "Robin van der Vliet",
                        "Conquistador",
                        "Frigory",
-                       "Psychoslave"
+                       "Psychoslave",
+                       "Guycn2"
                ]
        },
        "sidebar": "{{notranslate}}",
        "password-change-forbidden": "Error message shown when an external authentication source does not allow the password to be changed.",
        "externaldberror": "This message is thrown when a valid attempt to change the wiki password for a user fails because of a database error or an error from an external system.",
        "login": "{{Doc-special|UserLogin|unlisted=1}}\n{{Identical|Log in}}",
+       "login-security": "Used as the title of the login page when the user is already logged in but sent to reauthenticate before getting access to a feature with elevated security.",
        "nav-login-createaccount": "Shown to anonymous users in the upper right corner of the page. When you can't create an account, the message {{msg-mw|login}} is shown.\n{{Identical|Log in / create account}}",
        "loginprompt": "{{ignored}}",
        "userlogin": "Since 1.22 no longer used in core, but may still be used by extensions. DEPRECATED\n\n{{Identical|Log in / create account}}",
        "helplogin-url": "{{doc-important|Do not translate the namespace name <code>Help</code>.}}\nUsed as name of the page that provides information about logging into the wiki.\n\nUsed as a link target in the message {{msg-mw|Userlogin-helplink}}.",
        "userlogin-helplink2": "Label for a link to login help.\n\nSee example: [[Special:UserLogin]]\n\nSee also:\n* {{msg-mw|Helplogin-url}}",
        "userlogin-loggedin": "Used as warning on [[Special:UserLogin]] when the current user is already logged in.\n\nFollowed by the Login form.\n\nSee example: [[Special:UserLogin]].\n\nParameters:\n* $1 - user name (used for display and for gender support)\nSee also:\n* {{msg-mw|Mobile-frontend-userlogin-loggedin-register}}",
+       "userlogin-reauth": "Used as an explanatory message on [[Special:UserLogin]] when the user is redirected there to log in again when trying to use a security-sensitive page.\n\nParameters:\n* $1 - user name (used for display and for gender support)",
        "userlogin-createanother": "Used as label for the button on [[Special:UserLogin]] shown when the current user is already logged in.\n{{Identical|Create another account}}",
        "createacct-emailrequired": "Label in create account form for email field when it is required.\n\nSee also:\n* {{msg-mw|Createacct-emailoptional}}\n{{Identical|E-mail address}}",
        "createacct-emailoptional": "Label in vertical-layout create account form for email field when it is optional.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n\nSee also:\n* {{msg-mw|Createacct-emailrequired}}",
        "createacct-email-ph": "Placeholder in vertical-layout create account form for email field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
        "createacct-another-email-ph": "Placeholder in create account form for email field when one user creates an account for another.",
-       "createaccountmail": "The label for the checkbox for creating a new account and sending the new password to the specified email address directly, as used on [[Special:UserLogin/signup]] when one user creates an account for another (if creating accounts by email is allowed).\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
+       "createaccountmail": "The label for the checkbox for creating a new account and sending the new password to the specified email address directly, as used on [[Special:CreateAccount]] when one user creates an account for another (if creating accounts by email is allowed).\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
+       "createaccountmail-help": "Account creation API help message for the <code>mailpassword</code> parameter.",
        "createacct-realname": "In vertical-layout create account form, label for field to enter optional real name.",
        "createaccountreason": "Since 1.22 no longer used in core, but may be used by some extensions. DEPRECATED\n\n{{Identical|Reason}}",
        "createacct-reason": "In create account form, label for field to enter reason to create an account when already logged-in.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Reason}}",
        "createacct-reason-ph": "Placeholder in vertical-layout create account form for reason field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
+       "createacct-reason-help": "Account creation API help message for the <code>reason</code> parameter.",
        "createacct-imgcaptcha-help": "{{Optional}} Optional help text in vertical-layout create account form for image CAPTCHA input field when repositioned by JavaScript.\n\nBlank by default.",
        "createacct-submit": "Submit button on vertical-layout create account form.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
-       "createacct-another-submit": "Submit button of  [[Special:UserLogin/signup]] ([[Special:CreateAccount]]) when accessed by a registered user.\n\nThe original means \"create an account in addition to the one you already have\"; sometimes, but not always, it means you are going to \"Create the account on behalf of somebody else\" or \"Create account for another\".\n{{Identical|Create another account}}",
+       "createacct-another-submit": "Submit button of  [[Special:CreateAccount]] when accessed by a registered user.\n\nThe original means \"create an account in addition to the one you already have\"; sometimes, but not always, it means you are going to \"Create the account on behalf of somebody else\" or \"Create account for another\".\n{{Identical|Create another account}}",
+       "createacct-continue-submit": "Submit button on create account form in second and later steps of a multistep account creation.",
+       "createacct-another-continue-submit": "Submit button on create account form in second and later steps of a multistep account creation, when done by a registered user.",
        "createacct-benefit-heading": "In vertical-layout create account form, the heading for the section describing the benefits of creating an account. See example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n\nIf in your language you need to know the gender of the name for the wiki (which is the subject of the English sentence), please adapt the sentence as much as you need for your translation to fit.",
        "createacct-benefit-icon1": "In vertical-layout create account form, the CSS style for the div next to the first benefit. If you replace this you will need probably need to adjust CSS.\n\nUsed as a CSS class name.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup&useNew=1}} Special:UserLogin?type=signup&useNew=1]",
        "createacct-benefit-head1": "In vertical-layout create account form, the text in the heading for the first benefit. Do not edit the magic word; if you replace it you will probably need to adjust CSS.\n\nFollowed by the message {{msg-mw|Createacct-benefit-body1}}.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup&useNew=1}} Special:UserLogin?type=signup&useNew=1]",
        "nocookieslogin": "This message is displayed when someone tried to login, but the browser doesn't accept cookies.",
        "nocookiesfornew": "This message is displayed when the user tried to create a new account, but it failed the cross-site request forgery (CSRF) check. It could be blocking an attack, but most likely, the browser isn't  accepting cookies.",
        "nocookiesforlogin": "{{optional}}\nThis message is displayed when someone tried to login and the CSRF failed (most likely, the browser doesn't accept cookies).\n\nDefault:\n* {{msg-mw|Nocookieslogin}}",
+       "createacct-loginerror": "This message is displayed after a successful registration when there is a server-side error with logging the user in. This is not expected to happen.",
        "noname": "Error message.",
        "loginsuccesstitle": "The title of the page saying that you are logged in. The content of the page is the message {{msg-mw|Loginsuccess}}.\n{{Identical|Log in}}",
        "loginsuccess": "The content of the page saying that you are logged in. The title of the page is {{msg-mw|Loginsuccesstitle}}.\n\nParameters:\n* $1 - the name of the logged in user\n{{Gender}}",
        "createacct-another-realname-tip": "{{doc-singularthey}}\nUsed on the account creation form when creating another user's account. Similar to {{msg-mw|prefs-help-realname}}.\n{{Identical|Real name attribution}}",
        "pt-login": "Shown to anonymous users in the upper right corner of the page when they can't create an account (otherwise the message {{msg-mw|nav-login-createaccount}} is shown there).\n{{Identical|Log in}}",
        "pt-login-button": "Shown as the caption of the button at [[Special:UserLogin]].\n{{Identical|Log in}}",
+       "pt-login-continue-button": "Shown as the caption of the button at [[Special:UserLogin]] in second and later steps of a multipage login.",
        "pt-createaccount": "Used on the top of the page for logged out users, where it appears next to {{msg-mw|login}}, so consider making them similar.\n{{Identical|Create account}}",
        "pt-userlogout": "{{Doc-actionlink}}\n{{Identical|Log out}}",
        "pear-mail-error": "{{notranslate}}\nParameters:\n* $1 - error message which is returned by PEAR mailer.",
        "botpasswords-invalid-name": "Error message when a username lacking the separator character is passed to BotPassword. Parameters:\n* $1 - The separator character.",
        "botpasswords-not-exist": "Error message when a username exists but does not a bot password for the given \"bot name\". Parameters:\n* $1 - username\n* $2 - bot name",
        "resetpass_forbidden": "Used as error message in changing password. Maybe the external auth plugin won't allow local password changes.",
+       "resetpass_forbidden-reason": "Like {{msg-mw|resetpass_forbidden}} but the auth provider gave a reason.\n\nParameters:\n* $1 - reason given by auth provider",
        "resetpass-no-info": "Error message for [[Special:ChangePassword]].\n\nParameters:\n* $1 (unused) - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description",
        "resetpass-submit-loggedin": "Button on [[Special:ResetPass]] to submit new password.\n\n{{Identical|Change password}}",
        "resetpass-submit-cancel": "Used on [[Special:ResetPass]].\n{{Identical|Cancel}}",
        "passwordreset-emailsentusername": "Used in [[Special:PasswordReset]].\n\nSee also:\n* {{msg-mw|Passwordreset-emailsent-capture}}\n* {{msg-mw|Passwordreset-emailerror-capture}}",
        "passwordreset-emailsent-capture": "Used in [[Special:PasswordReset]].\n\nSee also:\n* {{msg-mw|Passwordreset-emailsentemail}}\n* {msg-mw|Passwordreset-emailsentusername}}\n* {{msg-mw|Passwordreset-emailerror-capture}}",
        "passwordreset-emailerror-capture": "Error message displayed in [[Special:PasswordReset]] when sending an email fails. Parameters:\n* $1 - error message\n* $2 - username, used for GENDER\nSee also:\n* {{msg-mw|Passwordreset-emailsentemail}}\n* {msg-mw|Passwordreset-emailsentusername}}\n* {{msg-mw|Passwordreset-emailsent-capture}}",
+       "passwordreset-emailsent-capture2": "Used in [[Special:PasswordReset]].\n\nParameters:\n* $1 - number of accounts notified\n\nSee also:\n* {{msg-mw|Passwordreset-emailsentemail}}\n* {msg-mw|Passwordreset-emailsentusername}}\n* {{msg-mw|Passwordreset-emailerror-capture}}",
+       "passwordreset-emailerror-capture2": "Error message displayed in [[Special:PasswordReset]] when sending an email fails. Parameters:\n* $1 - error message\n* $2 - username, used for GENDER\n* $3 - number of accounts notified\n\nSee also:\n* {{msg-mw|Passwordreset-emailsentemail}}\n* {msg-mw|Passwordreset-emailsentusername}}\n* {{msg-mw|Passwordreset-emailsent-capture}}",
+       "passwordreset-nocaller": "Shown when a password reset was requested but the caller was not provided. This is an internal error.",
+       "passwordreset-nosuchcaller": "Shown when a password reset was requested but the username of the caller could not be resolved to a user. This is an internal error.\n\nParameters:\n* $1 - username of the caller",
+       "passwordreset-ignored": "Shown when password reset was unsuccessful due to configuration problems.",
+       "passwordreset-invalideamil": "Returned when the email address is syntatically invalid.",
+       "passwordreset-nodata": "Returned when no data was provided.",
        "changeemail": "Title of [[Special:ChangeEmail|special page]]. This page also allows removing the user's email address.",
        "changeemail-summary": "{{ignored}}",
        "changeemail-header": "Text of [[Special:ChangeEmail]].",
        "image_tip": "This is the text that appears when you hover the mouse over the sixth (middle) button on the edit toolbar.\n\n{{Identical|Embedded file}}",
        "media_sample": "{{optional}}\n{{Identical|Example}}",
        "media_tip": "This is the text that appears when you hover the mouse over the fifth button from the right in the edit toolbar.\n{{Identical|File link}}",
+       "sig-text": "{{notranslate}} This is the text that appears when you click on the signature button (second button from the right) on the edit toolbar. $1 will be replaced with four tildes (which cannot be included directly in the message for technical reasons).",
        "sig_tip": "This is the text that appears when you hover the mouse over the second key from the right on the edit toolbar.\n{{Identical|Signature with timestamp}}",
        "hr_tip": "This is the text that appears when you hover the mouse over the first button on the right on the edit toolbar.",
        "summary": "The Summary text beside the edit summary field\n\nSee also:\n* {{msg-mw|Subject}}\nSee also:\n* {{msg-mw|Accesskey-summary}}\n* {{msg-mw|Tooltip-summary}}\n{{Identical|Summary}}",
        "showpreview": "The text of the button to preview the page you are editing. See also {{msg-mw|showdiff}} and {{msg-mw|savearticle}} for the other buttons.\n\nSee also:\n* {{msg-mw|Showpreview}}\n* {{msg-mw|Accesskey-preview}}\n* {{msg-mw|Tooltip-preview}}\n{{Identical|Show preview}}",
        "showdiff": "Button below the edit page. See also {{msg-mw|Showpreview}} and {{msg-mw|Savearticle}} for the other buttons.\n\nSee also:\n* {{msg-mw|Showdiff}}\n* {{msg-mw|Accesskey-diff}}\n* {{msg-mw|Tooltip-diff}}\n{{Identical|Show change}}",
        "blankarticle": "Notice displayed once after the user tries to save an empty page.",
-       "anoneditwarning": "Shown when editing a page anonymously.\n\nParameters:\n* $1 – A link to log in, <nowiki>{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}</nowiki>\n* $2 – A link to sign up, <nowiki>{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}</nowiki>\n\nSee also:\n* {{msg-mw|Mobile-frontend-editor-anonwarning}}",
+       "anoneditwarning": "Shown when editing a page anonymously.\n\nParameters:\n* $1 – A link to log in, <nowiki>{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}</nowiki>\n* $2 – A link to sign up, <nowiki>{{fullurl:Special:CreateAccount|returnto={{FULLPAGENAMEE}}}}</nowiki>\n\nSee also:\n* {{msg-mw|Mobile-frontend-editor-anonwarning}}",
        "anonpreviewwarning": "See also:\n* {{msg-mw|Anoneditwarning}}",
        "missingsummary": "The text \"edit summary\" is in {{msg-mw|Summary}}.\n\nSee also:\n* {{msg-mw|Missingcommentheader}}\n* {{msg-mw|Savearticle}}",
        "selfredirect": "Notice displayed once after the user tries to create a redirect to the same article.",
        "yourtext": "Used in Diff Preview page. The diff is between {{msg-mw|currentrev}} and {{msg-mw|yourtext}}.\n\nAlso used in Edit Conflict page; the diff between {{msg-mw|yourtext}} and {{msg-mw|storedversion}}.",
        "storedversion": "This is used in an edit conflict as the label for the top revision that has been stored, as opposed to your version {{msg-mw|yourtext}} that has not been stored which is shown at the bottom of the page.",
        "nonunicodebrowser": "Used as warning when editing page.",
-       "editingold": "Used as warning when editing page.",
+       "editingold": "Used as warning when editing an old revision of a page.",
        "yourdiff": "Used as h2 header for the diff of the current version of a page with the user's version in case there is an edit conflict or a spam filter hit.",
        "copyrightwarning": "Copyright warning displayed under the edit box in editor. Parameters:\n* $1 - link\n* $2 - license name",
        "copyrightwarning2": "Copyright warning displayed under the edit box in editor\n*$1 - license name",
        "undo-summary-username-hidden": "Edit summary for an undo action where the username of the old revision is hidden.\n\nParameters:\n* $1 - the revision ID being undone\nSee also:\n* {{msg-mw|Undo-summary}}",
        "cantcreateaccounttitle": "Used as title of the error message {{msg-mw|Cantcreateaccount-text}}.",
        "cantcreateaccount-text": "Used as error message, with the title {{msg-mw|Cantcreateaccounttitle}}.\n* $1 - target IP address\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\nSee also:\n* {{msg-mw|Cantcreateaccount-range-text}}",
-       "cantcreateaccount-range-text": "Used as more detailed version of the {{msg-mw|Cantcreateaccount-text}} error message, with the title {{msg-mw|Cantcreateaccounttitle}}.\n* $1 - target IP address range\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\n* $4 - current user's IP address",
+       "cantcreateaccount-range-text": "Used instead of the {{msg-mw|Cantcreateaccount-text}} when the block is a range block.\n* $1 - target IP address range\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\n* $4 - current user's IP address",
        "createaccount-hook-aborted": "Placeholder message to return with API errors on account create; passes through the message from a hook {{notranslate}}",
        "viewpagelogs": "Link displayed in history of pages",
        "nohistory": "Message shown when there are no history to list. See [{{canonicalurl:x|action=history}} example history].\n----\nAlso used as title of error message when the feed is empty. See [{{canonicalurl:x|action=history&feed=atom}} example feed].\n\nSee the error message:\n* {{msg-mw|history-feed-empty}}",
        "right-managechangetags": "{{doc-right|managechangetags}}",
        "right-applychangetags": "{{doc-right|applychangetags}}",
        "right-changetags": "{{doc-right|changetags}}",
+       "right-deletechangetags": "{{doc-right|deletechangetags}}",
        "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}",
        "grant-group-page-interaction": "{{Related|grant-group}}",
        "grant-group-file-interaction": "{{Related|grant-group}}",
        "action-managechangetags": "{{doc-action|managechangetags}}",
        "action-applychangetags": "{{doc-action|applychangetags}}",
        "action-changetags": "{{doc-action|changetags}}",
+       "action-deletechangetags": "{{doc-action|deletechangetags}}",
        "nchanges": "Appears on enhanced watchlist and recent changes when page has more than one change on given date, linking to a diff of the changes.\n\nParameters:\n* $1 - the number of changes on that day (2 or more)\nThree messages are shown side-by-side: ({{msg-mw|Nchanges}} | {{msg-mw|Enhancedrc-since-last-visit}} | {{msg-mw|Enhancedrc-history}}).",
        "enhancedrc-since-last-visit": "Appears on enhanced watchlist and recent changes when page has more than one change on given date and at least one that the user hasn't seen yet, linking to a diff of the unviewed changes.\n\nParameters:\n* $1 - the number of unviewed changes (1 or more)\nThree messages are shown side-by-side: ({{msg-mw|nchanges}} | {{msg-mw|enhancedrc-since-last-visit}} | {{msg-mw|enhancedrc-history}}).",
        "enhancedrc-history": "Appears on enhanced watchlist and recent changes when page has more than one change on given date, linking to its history.\n\nThis is the same as {{msg-mw|hist}}, but not abbreviated.\n\nThree messages are shown side-by-side: ({{msg-mw|nchanges}} | {{msg-mw|enhancedrc-since-last-visit}} | {{msg-mw|enhancedrc-history}}).\n{{Identical|History}}",
        "upload-form-label-infoform-description-tooltip": "The tooltip documenting the description fields on the details page.\n\nIdentical to {{msg-mw|mwe-upwiz-tooltip-description}}.",
        "upload-form-label-usage-title": "Title for the insert form showing how to use the uploaded item.\n{{Identical|Usage}}",
        "upload-form-label-usage-filename": "Label for the file name input\n{{Identical|Filename}}",
-       "foreign-structured-upload-form-label-own-work": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Label for own work confirmation checkbox",
-       "foreign-structured-upload-form-label-infoform-categories": "Label for category selector input\n{{Identical|Category}}",
-       "foreign-structured-upload-form-label-infoform-date": "Label for date input\n{{Identical|Date}}",
-       "foreign-structured-upload-form-label-own-work-message-local": "Message shown by local when a user affirms that their file upload to the local wiki follows the terms of service and licensing policies of the local wiki.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Message shown by local when a user cannot upload a file to the local wiki.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Suggests uploading a file via Special:Upload instead of using whatever method they're currently using.",
-       "foreign-structured-upload-form-label-own-work-message-default": "Message shown by default when a user affirms that they are allowed to upload a file to a remote wiki.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Message shown by default when a user cannot upload a file to a remote wiki.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Suggests uploading a file locally instead of to a remote wiki.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Legal message, confirming that the user is allowed to upload the file.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Explains alternatives when the copyright isn't owned by the uploader.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Message suggesting the user might want to upload a file locally instead of to Wikimedia Commons.",
+       "upload-form-label-own-work": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Label for own work confirmation checkbox",
+       "upload-form-label-infoform-categories": "Label for category selector input\n{{Identical|Category}}",
+       "upload-form-label-infoform-date": "Label for date input\n{{Identical|Date}}",
+       "upload-form-label-own-work-message-generic-local": "Message shown by local when a user affirms that their file upload to the local wiki follows the terms of service and licensing policies of the local wiki.",
+       "upload-form-label-not-own-work-message-generic-local": "Message shown by local when a user cannot upload a file to the local wiki.",
+       "upload-form-label-not-own-work-local-generic-local": "Suggests uploading a file via Special:Upload instead of using whatever method they're currently using.",
+       "upload-form-label-own-work-message-generic-foreign": "Message shown by default when a user affirms that they are allowed to upload a file to a remote wiki.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Message shown by default when a user cannot upload a file to a remote wiki.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Suggests uploading a file locally instead of to a remote wiki.",
        "backend-fail-stream": "Parameters:\n* $1 - a filename",
        "backend-fail-backup": "Parameters:\n* $1 - a filename",
        "backend-fail-notexists": "Parameters:\n* $1 - a filename",
        "changecontentmodel-success-text": "Message telling user that their change has been successfully done.\n* $1 - Target page title",
        "changecontentmodel-cannot-convert": "Error message shown if the content model cannot be changed to the specified type. $1 is the page title, $2 is the localized content model name.",
        "changecontentmodel-nodirectediting": "Error message shown if the content model does not allow for direct editing. $1 is the localized name of the content model.",
+       "changecontentmodel-emptymodels-title": "Title of the error page shown if the content model cannot be changed to any of the available types.",
+       "changecontentmodel-emptymodels-text": "Text of the error page shown if the content model cannot be changed to any of the available types. $1 is the page title.",
        "log-name-contentmodel": "{{doc-logpage}}\n\nTitle of [[Special:Log/contentmodel]].",
        "log-description-contentmodel": "Text in [[Special:Log/contentmodel]].",
        "logentry-contentmodel-new": "{{Logentry}}\n$4 is not used.\n$5 is the new content model.",
        "whatlinkshere-prev": "This is part of the navigation message on the top and bottom of Whatlinkshere pages, where it is used as the first argument of {{msg-mw|Viewprevnext}}.\n\nParameters:\n* $1 - the number of items shown per page. It is not used when $1 is zero; not sure what happens when $1 is one.\nSpecial pages use {{msg-mw|Prevn}} instead (still as an argument to {{msg-mw|Viewprevnext}}).\n\nSee also:\n* {{msg-mw|Whatlinkshere-next}}\n{{Identical|Previous}}",
        "whatlinkshere-next": "This is part of the navigation message on the top and bottom of Whatlinkshere pages, where it is used as the second argument of {{msg-mw|Viewprevnext}}.\n\nParameters:\n* $1 - the number of items shown per page. It is not used when $1 is zero; not sure what happens when $1 is one.\nSpecial pages use {{msg-mw|Nextn}} instead (still as an argument to {{msg-mw|Viewprevnext}}).\n\nSee also:\n* {{msg-mw|Whatlinkshere-prev}}\n{{Identical|Next}}",
        "whatlinkshere-links": "Used on [[Special:WhatLinksHere]]. It is a link to the WhatLinksHere page of that page.\n\nExample line:\n* [[Main Page]] ([[Special:WhatLinksHere/Main Page|{{int:whatlinkshere-links}}]])\n{{Identical|Link}}",
-       "whatlinkshere-hideredirs": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}\n{{Identical|Redirect}}",
-       "whatlinkshere-hidetrans": "First filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}\n{{Identical|Transclusion}}",
-       "whatlinkshere-hidelinks": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}\n{{Identical|Link}}",
+       "whatlinkshere-hideredirs": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}",
+       "whatlinkshere-hidetrans": "First filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}",
+       "whatlinkshere-hidelinks": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}",
        "whatlinkshere-hideimages": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 - the {{msg-mw|Hide}} or {{msg-mw|Show}}\n\nSee also:\n*{{msg-mw|Isimage}}\n*{{msg-mw|Media tip}}",
        "whatlinkshere-filters": "{{Identical|Filter}}",
        "whatlinkshere-submit": "Label for submit button in [[Special:WhatLinksHere]]\n{{Identical|Go}}",
        "ipb-blocklist": "Used as link text in [[Special:Block]].\n\nThe link points to Specil:BlockList.",
        "ipb-blocklist-contribs": "Used in [[Special:Block]].\n* $1 - target username",
        "ipb-blocklist-duration-left": "Used on [[Special:BlockList]] to show the remaining time (years, months, days, hours, minutes) until the block expires.\n$1 - The duration left",
-       "unblockip": "Used as legend for the form in [[Special:Unblock]].",
+       "unblockip": "Used as title and legend for the form in [[Special:Unblock]].",
        "unblockiptext": "Used in the {{msg-mw|Unblockip}} form on [[Special:Unblock]].",
        "ipusubmit": "Used as button text on [{{canonicalurl:Special:BlockList|action=unblock}} Special:BlockList?action=unblock]. To see the message:\n* Go to [[Special:BlockList]]\n* Click \"unblock\" for any block (but you can only see \"unblock\" if you have administrator rights)\n* It is now the button below the form",
        "unblocked": "{{doc-important|Do not translate the namespace \"User:\".}}\nParameters:\n* $1 - the username that was unblocked\nSee also:\n* {{msg-mw|Unblocked-range}}\n* {{msg-mw|Unblocked-id}}\n*{{msg-mw|Unblocked-ip}}",
        "unlockdbsuccesssub": "Used as subtitle in [[Special:UnlockDB]].\n\nSee also:\n* {{msg-mw|Lockdbsuccesssub|subtitle}}\n* {{msg-mw|Lockdbsuccesstext|text}}\n* {{msg-mw|Unlockdbsuccesssub|subtitle}}\n* {{msg-mw|Unlockdbsuccesstext|text}}",
        "lockdbsuccesstext": "Used as message text in [[Special:LockDB]].\n\nSee also:\n* {{msg-mw|Lockdbsuccesssub|subtitle}}\n* {{msg-mw|Lockdbsuccesstext|text}}\n* {{msg-mw|Unlockdbsuccesssub|subtitle}}\n* {{msg-mw|Unlockdbsuccesstext|text}}",
        "unlockdbsuccesstext": "Used as message text in [[Special:UnlockDB]].\n\nSee also:\n* {{msg-mw|Lockdbsuccesssub|subtitle}}\n* {{msg-mw|Lockdbsuccesstext|text}}\n* {{msg-mw|Unlockdbsuccesssub|subtitle}}\n* {{msg-mw|Unlockdbsuccesstext|text}}",
-       "lockfilenotwritable": "'No longer needed' on wikipedia.",
+       "lockfilenotwritable": "Used as error message in [[Special:LockDB]].",
+       "databaselocked": "Used as error message in [[Special:LockDB]].\nThe title of this error message is {{msg-mw|Lockdb}}.\n\nSee also:\n* {{msg-mw|Lockdb|title}}\n* {{msg-mw|Databaselocked|message}}",
        "databasenotlocked": "Used as error message in [[Special:UnlockDB]].\nThe title of this error message is {{msg-mw|Lockdb}}.\n\nSee also:\n* {{msg-mw|Lockdb|title}}\n* {{msg-mw|Databasenotlocked|message}}",
        "lockedbyandtime": "Used as part of the message when a database is locked through [[Special:LockDB]]. Parameters:\n* $1 is the user that locked the database.\n* $2 is the date on which the lock was made\n* $3 is the time at which the lock was made",
        "move-page": "Used as page title of [[Special:MovePage]] to move pages.\n\nSee example: [[Special:MovePage/Portal:En]].\n\nParameters:\n* $1 - the name of the page to be moved (without link)\n{{Identical|Move}}",
        "variantname-gan": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
        "variantname-sr-ec": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-ec</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-cyrl</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
        "variantname-sr-el": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-el</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-latn</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
-       "variantname-sr": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
+       "variantname-sr": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
        "variantname-kk-kz": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
        "variantname-kk-tr": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
        "variantname-kk-cn": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
        "timezone-local": "Label to indicate that a time is in the user's local timezone.\n{{Identical|Local}}",
        "duplicate-defaultsort": "See definition of [[w:Sorting|sort key]] on Wikipedia. Parameters:\n* $1 - old default sort key\n* $2 - new default sort key",
        "duplicate-displaytitle": "Warning shown when a page has its display title set multiple times. Parameters:\n* $1 - old display title\n* $2 - new display title",
+       "restricted-displaytitle": "Warning shown a display title is ignored because it is not equivalent to its actual title. Parameters:\n* $1 - the ignored display title",
        "invalid-indicator-name": "Warning shown when the [https://www.mediawiki.org/wiki/Help:Page_status_indicators &lt;indicator name=\"''unique-identifier''\">''content''&lt;/indicator>] parser tag is used incorrectly.",
        "version": "{{doc-special|Version}}\n{{Identical|Version}}",
        "version-summary": "{{doc-specialpagesummary|version}}",
        "tags-delete-not-found": "Error message on [[Special:Tags]]",
        "tags-delete-too-many-uses": "Error message on [[Special:Tags]]",
        "tags-delete-warnings-after-delete": "Warning shown after deleting a tag.\n\nParameters:\n* $1 - the code name of the tag that was deleted\n* $2 - the number of warnings",
+       "tags-delete-no-permission": "Error message on [[Special:Tags]]",
        "tags-activate-title": "The title of a page used to activate a tag. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
        "tags-activate-question": "An explanation to tell users what they are about to do.\n\nParameters:\n* $1 - the code name of the tag that is about to be activated",
        "tags-activate-reason": "{{Identical|Reason}}",
        "feedback-useragent": "A label denoting the user agent in the feedback that is posted to the feedback page.\n{{Identical|User agent}}",
        "searchsuggest-search": "Greyed out default text in the simple search box in the Vector skin. (It disappears and lets the user enter the requested search terms when the search box receives focus.)\n\n{{Identical|Search}}",
        "searchsuggest-containing": "Label used in the special item of the search suggestions list which gives the user an option to perform a full text search for the term.",
+       "api-error-autoblocked": "API error message that can be used for client side localisation of API errors.\n\nCf. {{msg-mw|Autoblockedtext}}.",
        "api-error-badaccess-groups": "API error message that can be used for client side localisation of API errors.",
        "api-error-badtoken": "API error message that can be used for client side localisation of API errors.",
+       "api-error-blocked": "API error message that can be used for client side localisation of API errors.",
        "api-error-copyuploaddisabled": "API error message that can be used for client side localisation of API errors.",
        "api-error-duplicate": "API error message that can be used for client side localisation of API errors. Parameters:\n* $1 - a number of files",
        "api-error-duplicate-archive": "API error message that can be used for client side localisation of API errors. Parameters:\n* $1 - a number of files",
        "api-error-nomodule": "API error message that can be used for client side localisation of API errors.",
        "api-error-ok-but-empty": "API error message that can be used for client side localisation of API errors.",
        "api-error-overwrite": "API error message that can be used for client side localisation of API errors.",
+       "api-error-ratelimited": "API error message that can be used for client side localisation of API errors.\n\nCf. {{msg-mw|Actionthrottledtext}}",
        "api-error-stashfailed": "API error message that can be used for client side localisation of API errors.",
        "api-error-publishfailed": "API error message that can be used for client side localisation of API errors.",
        "api-error-stasherror": "API error message that can be used for client side localisation of API errors.",
        "log-action-filter-suppress-block": "{{doc-log-action-filter-action|suppress|block}}",
        "log-action-filter-suppress-reblock": "{{doc-log-action-filter-action|suppress|reblock}}",
        "log-action-filter-upload-upload": "{{doc-log-action-filter-action|upload|upload}}",
-       "log-action-filter-upload-overwrite": "{{doc-log-action-filter-action|upload|overwrite}}"
+       "log-action-filter-upload-overwrite": "{{doc-log-action-filter-action|upload|overwrite}}",
+       "authmanager-authn-not-in-progress": "Error message when AuthManager session data is lost during authentication, or the user hits the \"continue\" endpoint without an active authentication attempt.",
+       "authmanager-authn-no-primary": "Error message when no AuthenticationProvider handles the AuthenticationRequests for login. This might mean the user needs to fill out all the form fields.",
+       "authmanager-authn-no-local-user": "Error message when authentication somehow succeeds without a username being known. This probably should never happen.",
+       "authmanager-authn-no-local-user-link": "Error message when federated authentication (e.g. \"login with Google\") succeeds, but no account is associated.",
+       "authmanager-authn-autocreate-failed": "Error message when auto-creation fails during login. Parameters:\n* $1 - Error message from the account creation attempt, as wikitext.",
+       "authmanager-change-not-supported": "Error message when all PrimaryAuthenticationProviders ignore the change request.",
+       "authmanager-create-disabled": "Message displayed when account creation is disabled.",
+       "authmanager-create-from-login": "Message displayed when moving from login to account creation and additional data must be collected from the user.",
+       "authmanager-create-not-in-progress": "Error message when AuthManager session data is lost during account creation, or the user hits the \"continue\" endpoint without an active account creation attempt.",
+       "authmanager-create-no-primary": "Error message when no AuthenticationProvider handles the AuthenticationRequests for account creation. This might mean the user needs to fill out all the form fields.",
+       "authmanager-link-no-primary": "Error message when no AuthenticationProvider handles the AuthenticationRequests for account linking. This might mean the user needs to fill out all the form fields.",
+       "authmanager-link-not-in-progress": "Error message when AuthManager session data is lost during account linking, or the user hits the \"continue\" endpoint without an active account link attempt.",
+       "authmanager-authplugin-setpass-failed-title": "Title of error page from AuthManager if AuthPlugin returns false from its setPassword() method.",
+       "authmanager-authplugin-setpass-failed-message": "Text of error page from AuthManager if AuthPlugin returns false from its setPassword() method.",
+       "authmanager-authplugin-create-fail": "Error message from AuthManager if the AuthPlugin returns false from its addUser() method.",
+       "authmanager-authplugin-setpass-denied": "Error message from AuthManager if the AuthPlugin returns false from its allowPasswordChange() method.",
+       "authmanager-authplugin-setpass-bad-domain": "Error message from AuthManager if the AuthPlugin rejects the passed domain.",
+       "authmanager-autocreate-noperm": "Error message when auto-creation fails due to lack of permission.",
+       "authmanager-autocreate-exception": "Error message when auto-creation fails because we tried recently and an exception was thrown, so we're not going to try again yet.",
+       "authmanager-userdoesnotexist": "Error message when a user account does not exist. Parameters:\n* $1 - User name.",
+       "authmanager-userlogin-remembermypassword-help": "Description of the field with label {{msg-mw|userlogin-remembermypassword}}.",
+       "authmanager-username-help": "Description of the field with label {{msg-mw|userlogin-yourname}}.",
+       "authmanager-password-help": "Description of the field with label {{msg-mw|userlogin-yourpassword}}.",
+       "authmanager-domain-help": "Description of the field with label {{msg-mw|yourdomainname}}.",
+       "authmanager-retype-help": "Description of the field with label {{msg-mw|createacct-yourpasswordagain}}.",
+       "authmanager-email-label": "Label for the email field.\n{{Identical|E-mail}}",
+       "authmanager-email-help": "Description of the field with label {{msg-mw|authmanager-email-label}}.\n{{Identical|E-mail address}}",
+       "authmanager-realname-label": "Label for the realname field.\n{{Identical|Real name}}",
+       "authmanager-realname-help": "Description of the field with label {{msg-mw|authmanager-realname-label}}.",
+       "authmanager-provider-password": "Description for PasswordAuthenticationRequest. Will be used as $1 in messages such as {{msg-mw|authprovider-confirmlink-option}}.",
+       "authmanager-provider-password-domain": "Description for PasswordDomainAuthenticationRequest. Will be used as $1 in messages such as {{msg-mw|authprovider-confirmlink-option}}.",
+       "authmanager-account-password-domain": "Format to display username and domain for PasswordDomainAuthenticationRequest. Will be used as $2 in messages such as {{msg-mw|authprovider-confirmlink-option}}. Parameters:\n* $1 - Username\n* $2 - Domain",
+       "authmanager-provider-temporarypassword": "Description for TemporaryPasswordAuthenticationRequest. Will be used as $1 in messages such as {{msg-mw|authprovider-confirmlink-option}}.",
+       "authprovider-confirmlink-message": "Message from ConfirmLinkSecondaryAuthenticationProvider to indicate that credentials may be linked.",
+       "authprovider-confirmlink-option": "Used to format linkable credentials in ConfirmLinkSecondaryAuthenticationProvider. Parameters:\n* $1 - Credential provider (e.g. the name of the third-party authentication service).\n* $2 - Credential account (e.g. the email address).",
+       "authprovider-confirmlink-request-label": "Form field label for the list of linkable credentials",
+       "authprovider-confirmlink-request-help": "Form field help text",
+       "authprovider-confirmlink-success-line": "Line to display that credentials were linked successfully. Parameters:\n* $1 - Linked credentials, formatted with {{msg-mw|authprovider-confirmlink-option}}\n\nSee also:\n* {{msg-mw|authprovider-confirmlink-failed}}\n* {{msg-mw|authprovider-confirmlink-failed-line}}",
+       "authprovider-confirmlink-failed-line": "Line to display that credentials were not linked successfully. Parameters:\n* $1 - Credentials that failed, formatted with {{msg-mw|authprovider-confirmlink-option}}\n* $2 - Failure message text.\n\nSee also:\n* {{msg-mw|authprovider-confirmlink-failed}}\n* {{msg-mw|authprovider-confirmlink-success-line}}",
+       "authprovider-confirmlink-failed": "Used to prefix the list of individual link statuses when some did not succeed. Parameters:\n* $1 - Failure message, or a wikitext bulleted list of failure messages.\n\nSee also:\n* {{msg-mw|authprovider-confirmlink-success-line}}\n* {{msg-mw|authprovider-confirmlink-failed-line}}",
+       "authprovider-confirmlink-ok-help": "Description of the \"ok\" field when ConfirmLinkSecondaryAuthenticationProvider needs to display link failure messages to the user.",
+       "authprovider-resetpass-skip-label": "Label for the \"Skip\" button when it's possible to skip resetting a password in ResetPasswordSecondaryAuthenticationProvider.\n{{Identical|Skip}}",
+       "authprovider-resetpass-skip-help": "Description of the option to skip resetting a password in ResetPasswordSecondaryAuthenticationProvider.",
+       "authform-nosession-login": "Error message shown when the login was successful, but the session could not be reestablished on the next request. $1 is an explanation which depends on what session handler is being used, such as {{msg-mw|sessionprovider-nocookies}}.",
+       "authform-nosession-signup": "Error message shown when the account creation was successful, but the session could not be reestablished on the next request. $1 is an explanation which depends on what session handler is being used, such as {{msg-mw|sessionprovider-nocookies}}.",
+       "authform-newtoken": "Error message shown on the auth form when the session has no CSRF token. This can be caused by session expiry but it is more likely that the client does not support sessions for some reason (e.g. a browser with all cookies diabled). $1 is an explanation (in the form of full sentences) given by the session provider of why sessions might not work (usually this will be {{msg-mw|sessionprovider-nocookies}}).",
+       "authform-notoken": "Error message shown on the auth form when the submitted data has no CSRF token.",
+       "authform-wrongtoken": "Error message shown on the auth form when the submitted CSRF token value is invalid.",
+       "specialpage-securitylevel-not-allowed-title": "Error page title shown when the user visits a special page but the authentication security check fails.",
+       "specialpage-securitylevel-not-allowed": "Error message shown when the user visits a special page but the authentication security check fails.",
+       "authpage-cannot-login": "Error message shown on authentication-related special pages when login cannot start. This is not supposed to happen unless the site is misconfigured.",
+       "authpage-cannot-login-continue": "Error message shown on authentication-related special pages when login cannot continue. This most likely means a session timeout.",
+       "authpage-cannot-create": "Error message shown on authentication-related special pages when account creation cannot start. This is not supposed to happen unless the site is misconfigured.",
+       "authpage-cannot-create-continue": "Error message shown on authentication-related special pages when account creation cannot continue. This most likely means a session timeout.",
+       "authpage-cannot-link": "Error message shown on authentication-related special pages when account linking cannot start. This is not supposed to happen unless the site is misconfigured.",
+       "authpage-cannot-link-continue": "Error message shown on authentication-related special pages when account linking cannot continue. This most likely means a session timeout.",
+       "cannotauth-not-allowed-title": "Title of the error page shown when the user tries to use an authentication-related page they should not have access to.",
+       "cannotauth-not-allowed": "Text of the error page shown when the user tries t use an authentication-related page they should not have access to.",
+       "changecredentials": "Title of the special page [[Special:ChangeCredentials]] which allows changing authentication credentials (such as the password).",
+       "changecredentials-submit": "Used on [[Special:ChangeCredentials]].\n{{Identical|Change}}",
+       "changecredentials-submit-cancel": "Used on [[Special:ChangeCredentials]].\n{{Identical|Cancel}}",
+       "changecredentials-invalidsubpage": "Error message shown when using [[Special:ChangeCredentials]] with an invalid type.\n\nParameters:\n* $1 - subpage name.",
+       "changecredentials-success": "Success message after using [[Special:ChangeCredentials]].",
+       "removecredentials": "Title of the special page [[Special:RemoveCredentials]] which allows removing authentication credentials (such as a two-factor token).",
+       "removecredentials-submit": "Used on [[Special:RemoveCredentials]].\n{{Identical|Remove}}",
+       "removecredentials-submit-cancel": "Used on [[Special:RemoveCredentials]].\n{{Identical|Cancel}}",
+       "removecredentials-invalidsubpage": "Error message shown when using [[Special:RemoveCredentials]] with an invalid type.\n\nParameters:\n* $1 - subpage name.",
+       "removecredentials-success": "Success message after using [[Special:RemoveCredentials]].",
+       "credentialsform-provider": "Shown on [[Special:ChangeCredentials]]/[[Special:RemoveCredentials]] as the label for the authentication type (e.g. \"password\", \"English Wikipedia via OAuth\")",
+       "credentialsform-account": "Shown on [[Special:ChangeCredentials]]/[[Special:RemoveCredentials]] as the label for the account name",
+       "cannotlink-no-provider-title": "Error page title shown when the user visits [[Special:LinkAccounts]] but there is no external account provider that could be linked.",
+       "cannotlink-no-provider": "Error message shown when the user visits [[Special:LinkAccounts]] but there is no external account provider that could be linked.",
+       "linkaccounts": "Title of the special page [[Special:LinkAccounts]] which allows the user to connect the local user accounts with external ones such as Google or Facebook.",
+       "linkaccounts-success-text": "Text shown on top of the form after a successful action.",
+       "linkaccounts-submit": "Text of the main submit button on [[Special:LinkAccounts]] (when there is one)",
+       "unlinkaccounts": "Title of the special page [[Special:UnlinkAccounts]] which allows the user to remove linked remote accounts.",
+       "unlinkaccounts-success": "Account unlinking form success message"
 }
index 65d39e5..a77384d 100644 (file)
        "noname": "Manam niwarqankichu ruraqpa allin sutinta.",
        "loginsuccesstitle": "Llamk'apuy tiyayqa qallarisqañam",
        "loginsuccess": "Llamk'apuy tiyayniykiqa qallarisqam {{SITENAME}}-pi \"$1\" sutiyuq kaspa.",
-       "nosuchuser": "Nisqayki \"$1\" sutiyuq ruraqqa manam kanchu.\nRuraqpa sutinqa uchuypas hatunpas sanampakunayuqmi kayta atin.\nAllin qillqasqaykita llanchiriy, ichataq urapi kaq hunt'ana p'anqata llamk'achiy [[Special:UserLogin/signup|musuq rakiqunata kicharinaykipaq]].",
+       "nosuchuser": "Nisqayki \"$1\" sutiyuq ruraqqa manam kanchu.\nRuraqpa sutinqa uchuypas hatunpas sanampakunayuqmi kayta atin.\nAllin qillqasqaykita llanchiriy, ichataq urapi kaq hunt'ana p'anqata llamk'achiy [[Special:CreateAccount|musuq rakiqunata kicharinaykipaq]].",
        "nosuchusershort": "Nisqayki \"$1\" sutiyuq ruraqqa manam kanchu.\nAllin qillqasqaykita llanchiriy.",
        "nouserspecified": "Ruraqpa sutiykitam qunayki.",
        "login-userblocked": "Kay ruraqqa hark'asqam. Manam yaykuyta atinchu.",
        "accmailtext": "Kikinmanta kamarisqa [[User talk:$1|$1]]-paq yaykuna rimaqa $2-manmi kachasqaña.\n\nYaykurqaspaqa ''[[Special:ChangePassword|yaykuna rima hukchana]]'' p'anqapi kay yaykuna rimata hukchaytam atinki.",
        "newarticle": "(Musuq)",
        "newarticletext": "Manaraq kachkaq p'anqatam llamk'apuchkanki. Musuq p'anqata kamariyta munaspaykiqa, qillqarillay. Astawan ñawiriyta munaspaykiqa, [$1 yanapana p'anqata] qhaway. Mana munaspaykitaq, ñawpaq p'anqaman ripuy.",
-       "anontalkpagetext": "---- ''Kayqa huk sutinnaq icha mana sutinta llamk'achiq ruraqpa rimanakuyninmi. IP huchhantam hallch'asunchik payta sutinchanapaq. Achka ruraqkunam huklla IP huchhanta llamk'achiyta atin. Sutinnaq ruraq kaspaykiqa, mana qampa rurasqaykimanta willamusqakunata rikuspaykiqa, ama hina kaspa [[Special:UserLogin/signup|rakiqunaykita kamariy]] icha [[Special:UserLogin|yaykuy]] huk sutinnaq ruraqkunawan ama pantasqa kanaykipaq.''",
+       "anontalkpagetext": "---- ''Kayqa huk sutinnaq icha mana sutinta llamk'achiq ruraqpa rimanakuyninmi. IP huchhantam hallch'asunchik payta sutinchanapaq. Achka ruraqkunam huklla IP huchhanta llamk'achiyta atin. Sutinnaq ruraq kaspaykiqa, mana qampa rurasqaykimanta willamusqakunata rikuspaykiqa, ama hina kaspa [[Special:CreateAccount|rakiqunaykita kamariy]] icha [[Special:UserLogin|yaykuy]] huk sutinnaq ruraqkunawan ama pantasqa kanaykipaq.''",
        "noarticletext": "Kunanqa kay p'anqa ch'usaqmi kachkan.\nKaytam rurayta atinkiman: kay p'anqap sutinta [[Special:Search/{{PAGENAME}}|huk p'anqakunapi maskay]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hallch'ankunapi maskay]\nicha [{{fullurl:{{FULLPAGENAME}}|action=edit}} kay p'anqata llamk'apuy]</span>.",
        "noarticletext-nopermission": "Kunanqa kay p'anqa ch'usaqmi kachkan.\nKaytam rurayta atinkiman: kay p'anqap sutinta [[Special:Search/{{PAGENAME}}|huk p'anqakunapi maskay]]\nicha payman kapuq <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hallch'akunapi maskay]</span>,\nichataq kay p'anqata kamariyta manam saqillasunkichu.",
        "missing-revision": "\"{{FULLPAGENAME}}\" nisqa p'anqapaq #$1 musuqchasqaqa manam kanchu.\n\nKayqa tukurqanman qullusqa p'anchaman t'inkimuq mawk'ayasqa wiñay kawsay t'inkiraykuchá.\nImaymanata [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} qulluy hallch'apim] tariykiman.",
index 73f33af..eb3ee7c 100644 (file)
        "noname": "Ti n'has betg inditgà in num d'utilisader valid.",
        "loginsuccesstitle": "T'annunzià cun success",
        "loginsuccess": "'''Ti es t'annunzia tar {{SITENAME}} sco \"$1\".'''",
-       "nosuchuser": "I n'exista nagin utilisader cun il num \"$1\".\nFa stim dad utilisar correctamain maiusclas e minusclas.\nCurregia il num u [[Special:UserLogin/signup|creescha in nov conto]].",
+       "nosuchuser": "I n'exista nagin utilisader cun il num \"$1\".\nFa stim dad utilisar correctamain maiusclas e minusclas.\nCurregia il num u [[Special:CreateAccount|creescha in nov conto]].",
        "nosuchusershort": "I na dat nagin utilisader cun il num \"$1\".\nCurregia ti'endataziun.",
        "nouserspecified": "Inditgescha per plaschair in num d'utilisader.",
        "login-userblocked": "Quest utilisader è bloccà. Betg pussaivel da t'annunziar.",
        "accmailtext": "In pled-clav casual per [[User talk:$1|$1]] è vegnì tramess a $2. El po vegnir midà sin la pagina ''[[Special:ChangePassword|midar pled-clav]]'' suenter che ti t'es annunzià.",
        "newarticle": "(Nov)",
        "newarticletext": "Ti has cliccà ina colliaziun ad ina pagina che n'exista anc betg. Per crear ina pagina, entschaiva a tippar en la stgaffa sutvart (guarda [$1 la pagina d'agid] per t'infurmar).",
-       "anontalkpagetext": "----''Quai è la pagina da discussiun per in utilisader anomim che n'ha anc betg creà in conto d'utilisader u che n'al utilisescha betg.\nPerquai avain nus d'utilisar l'adressa dad IP per l'identifitgar.\nIna tala adressa dad IP po vegnir utilisada da differents utilisaders.\nSche ti es in utilisaders anonim e pensas che commentaris che na pertutgan betg tai vegnan adressads a tai, lura [[Special:UserLogin/signup|creescha in conto]] u [[Special:UserLogin|t'annunzia]] per evitar en futur che ti vegns sbaglià cun auters utilisaders.''",
+       "anontalkpagetext": "----''Quai è la pagina da discussiun per in utilisader anomim che n'ha anc betg creà in conto d'utilisader u che n'al utilisescha betg.\nPerquai avain nus d'utilisar l'adressa dad IP per l'identifitgar.\nIna tala adressa dad IP po vegnir utilisada da differents utilisaders.\nSche ti es in utilisaders anonim e pensas che commentaris che na pertutgan betg tai vegnan adressads a tai, lura [[Special:CreateAccount|creescha in conto]] u [[Special:UserLogin|t'annunzia]] per evitar en futur che ti vegns sbaglià cun auters utilisaders.''",
        "noarticletext": "Quest artitgel na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar il term]] sin in'autra pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols],\nu [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear questa pagina]</span>.",
        "noarticletext-nopermission": "Questa pagina na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar quest titel]] en autras paginas u <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols correspundents]</span>, ma ti n'has betg ils dretgs da crear questa pagina.",
        "missing-revision": "La versiun #$1 da la pagina cun il num \"{{FULLPAGENAME}}\" n'exista betg.\n\nQuai capita savnes sche ti cliccas sin ina colliaziun antiquada en la cronologia per ina pagina ch'è vegnida stizzada.\nDetagls pon vegnri chattads en il [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protocol da stizzar].",
index af03e7f..cecb1be 100644 (file)
        "noname": "Numele de utilizator pe care l-ați introdus nu este valid.",
        "loginsuccesstitle": "Autentificat(ă)",
        "loginsuccess": "'''Ați fost autentificat la {{SITENAME}} ca „$1”.'''",
-       "nosuchuser": "Nu există nici un utilizator cu numele „$1”.\nNumele de utilizatori sunt sensibile la majuscule.\nVerifică dacă ai scris corect sau [[Special:UserLogin/signup|creează un nou cont de utilizator]].",
+       "nosuchuser": "Nu există nici un utilizator cu numele „$1”.\nNumele de utilizatori sunt sensibile la majuscule.\nVerifică dacă ai scris corect sau [[Special:CreateAccount|creează un nou cont de utilizator]].",
        "nosuchusershort": "Nu există niciun utilizator cu numele „$1”.\nVerificați ortografierea.",
        "nouserspecified": "Trebuie să specificați un nume de utilizator.",
        "login-userblocked": "Acest utilizator este blocat. Autentificarea nu este permisă.",
        "accmailtext": "O parolă generată aleator pentru [[User talk:$1|$1]] a fost trimisă la $2. Parola poate fi schimbată după autentificare din pagina ''[[Special:ChangePassword|schimbare parolă]]''.",
        "newarticle": "(Nou)",
        "newarticletext": "Ați încercat să ajungeți la o pagină care nu există. Pentru a o crea, începeți să scrieți în caseta de mai jos (vedeți [$1 pagina de ajutor] pentru mai multe informații). Dacă ați ajuns aici din greșeală, întoarceți-vă folosind controalele navigatorului dumneavoastră.",
-       "anontalkpagetext": "---- ''Aceasta este pagina de discuții pentru un utilizator care nu și-a creat un cont încă, sau care nu s-a autentificat.\nDe aceea trebuie să folosim adresă IP pentru a identifica această persoană.\nO adresă IP poate fi folosită în comun de mai mulți utilizatori.\nDacă sunteți un astfel de utilizator și credeți că vă sunt adresate mesaje irelevante, vă rugăm să [[Special:UserLogin/signup|vă creați un cont]] sau să [[Special:UserLogin|vă autentificați]] pentru a evita confuzii cu alți utilizatori anonimi în viitor.''",
+       "anontalkpagetext": "---- ''Aceasta este pagina de discuții pentru un utilizator care nu și-a creat un cont încă, sau care nu s-a autentificat.\nDe aceea trebuie să folosim adresă IP pentru a identifica această persoană.\nO adresă IP poate fi folosită în comun de mai mulți utilizatori.\nDacă sunteți un astfel de utilizator și credeți că vă sunt adresate mesaje irelevante, vă rugăm să [[Special:CreateAccount|vă creați un cont]] sau să [[Special:UserLogin|vă autentificați]] pentru a evita confuzii cu alți utilizatori anonimi în viitor.''",
        "noarticletext": "Actualmente, această pagină este lipsită de conținut.\nPuteți [[Special:Search/{{PAGENAME}}|căuta titlul acestei pagini]] în alte pagini, puteți <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} căuta înregistrări în jurnalele relevante]\nsau puteți [{{fullurl:{{FULLPAGENAME}}|action=edit}} crea această pagină]</span>.",
        "noarticletext-nopermission": "Actualmente, această pagină este lipsită de conținut.\nPuteți [[Special:Search/{{PAGENAME}}|căuta acest titlu]] în alte pagini sau puteți <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} căuta înregistrări în jurnale]</span>; nu aveți însă permisiunea de a crea această pagină.",
        "missing-revision": "Versiunea nr. $1 a paginii „{{FULLPAGENAME}}” nu există.\n\nAcest lucru se întâmplă de obicei atunci când se accesează o legătură expirată către istoricul unei pagini șterse.\nDetalii se pot găsi în [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jurnalul ștergerilor].",
        "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 cu succes propriile drepturi. Ca urmare, nu mai puteți accesa această pagină.",
+       "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",
        "upload-form-label-infoform-description-tooltip": "Descrieți pe scurt orice este notabil despre lucrare.\nPentru o fotografie, menționați principalele lucruri care sunt reprezentate, evenimentul sau locul.",
        "upload-form-label-usage-title": "Utilizare",
        "upload-form-label-usage-filename": "Numele fișierului",
-       "foreign-structured-upload-form-label-own-work": "Aceasta este propria mea operă",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorii",
-       "foreign-structured-upload-form-label-infoform-date": "Dată",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirm că încarc acest fișier în concordanță cu termenii serviciului și politicile de licențiere de la {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Dacă nu puteți încărca acest fișier în conformitate cu politicile de la {{SITENAME}}, închideți această casetă de dialog și încercați altă metodă.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Poate doriți să încercați [[Special:Upload|pagina de încărcare implicită]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Înțeleg că încarc acest fișier într-un depozit partajat. Confirm că fac acest lucru conform termenilor serviciului și politicilor de licențiere de acolo.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Dacă nu puteți încărca acest fișier în conformitate cu politicile depozitului partajat, închideți această casetă de dialog și încercați altă metodă.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Poate doriți să încercați [[Special:Upload|pagina de încărcare de la {{SITENAME}}]], în cazul în care acest fișier poate fi încărcat acolo în conformitate cu politicele lor.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Declar că dețin drepturile de autor asupra acestui fișier, accept să public irevocabil acest fișier la Wikimedia Commons sub licența [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Atribuire și distribuire în condiții identice 4.0] și sunt de acord cu [https://wikimediafoundation.org/wiki/Terms_of_Use Termenii de utilizare].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Dacă nu dețineți drepturile de autor asupra acestui fișier sau doriți să-l publicați sub o altă licență, puteți utiliza [https://commons.wikimedia.org/wiki/Special:UploadWizard Expertul de încărcare de la Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Poate doriți să încercați [[Special:Upload|pagina de încărcare de la {{SITENAME}}]], în cazul în care acest site permite încărcarea acestui fișier în conformitate cu politicele lor.",
+       "upload-form-label-own-work": "Aceasta este propria mea operă",
+       "upload-form-label-infoform-categories": "Categorii",
+       "upload-form-label-infoform-date": "Dată",
+       "upload-form-label-own-work-message-generic-local": "Confirm că încarc acest fișier în concordanță cu termenii serviciului și politicile de licențiere de la {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Dacă nu puteți încărca acest fișier în conformitate cu politicile de la {{SITENAME}}, închideți această casetă de dialog și încercați altă metodă.",
+       "upload-form-label-not-own-work-local-generic-local": "Poate doriți să încercați [[Special:Upload|pagina de încărcare implicită]].",
+       "upload-form-label-own-work-message-generic-foreign": "Înțeleg că încarc acest fișier într-un depozit partajat. Confirm că fac acest lucru conform termenilor serviciului și politicilor de licențiere de acolo.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Dacă nu puteți încărca acest fișier în conformitate cu politicile depozitului partajat, închideți această casetă de dialog și încercați altă metodă.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Poate doriți să încercați [[Special:Upload|pagina de încărcare de la {{SITENAME}}]], în cazul în care acest fișier poate fi încărcat acolo în conformitate cu politicele lor.",
        "backend-fail-stream": "Imposibil de citit fișierul $1.",
        "backend-fail-backup": "Imposibil de efectuat o copie de rezervă a fișierului $1.",
        "backend-fail-notexists": "Fișierul $1 nu există.",
        "special-characters-group-ipa": "AFI",
        "special-characters-group-symbols": "Simboluri",
        "special-characters-group-greek": "Greacă",
+       "special-characters-group-greekextended": "Greacă extinsă",
        "special-characters-group-cyrillic": "Chirilică",
        "special-characters-group-arabic": "Arabă",
        "special-characters-group-arabicextended": "Arabă extinsă",
index f9515d4..dd73eab 100644 (file)
        "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:UserLogin/signup|ccreje n'utende nuève]].",
+       "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]].",
        "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è.",
        "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:UserLogin/signup|ccreje 'nu cunde utende]] o [[Special:UserLogin|tràse]] pe no fà confusione jndr'à 'u future cu otre utinde anoneme.''",
+       "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.''",
        "noarticletext": "Non ge stè scritte ninde jndr'à sta pàgene.\nTu puè [[Special:Search/{{PAGENAME}}|cercà pe quiste titele]] jndr'à otre pàggene, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}] oppure [{{fullurl:{{FULLPAGENAME}}|action=edit}} cange 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].",
        "upload-form-label-infoform-description": "Descrizione",
        "upload-form-label-usage-title": "Ause",
        "upload-form-label-usage-filename": "Nome d'u file",
-       "foreign-structured-upload-form-label-infoform-categories": "Categorije",
-       "foreign-structured-upload-form-label-infoform-date": "Date",
+       "upload-form-label-infoform-categories": "Categorije",
+       "upload-form-label-infoform-date": "Date",
        "backend-fail-stream": "Non ge pozze trasmettere 'u file $1.",
        "backend-fail-backup": "Non ge pozze cupià 'u file $1.",
        "backend-fail-notexists": "'U file $1 non g'esiste.",
index 8ce6c2e..98bb3ce 100644 (file)
        "createacct-reason-ph": "Зачем вы создаёте другую учетную запись",
        "createacct-submit": "Создать учётную запись",
        "createacct-another-submit": "Создать учётную запись",
+       "createacct-continue-submit": "Продолжить создание учётной записи",
+       "createacct-another-continue-submit": "Продолжить создание учётной записи",
        "createacct-benefit-heading": "{{SITENAME}} — совместный труд таких же людей, как вы.",
        "createacct-benefit-body1": "{{PLURAL:$1|правка|правки|правок}}",
        "createacct-benefit-body2": "{{PLURAL:$1|статья|статьи|статей}}",
        "noname": "Вы не указали допустимого имени участника.",
        "loginsuccesstitle": "Вход произведён",
        "loginsuccess": "Теперь вы работаете под именем $1.",
-       "nosuchuser": "Участника с именем «$1» не существует.\nИмена участников чувствительны к регистру букв.\nПроверьте правильность написания имени или [[Special:UserLogin/signup|создайте новую учётную запись]].",
+       "nosuchuser": "Участника с именем «$1» не существует.\nИмена участников чувствительны к регистру букв.\nПроверьте правильность написания имени или [[Special:CreateAccount|создайте новую учётную запись]].",
        "nosuchusershort": "Не существует участника с именем «$1». Проверьте написание имени.",
        "nouserspecified": "Вы должны указать имя участника.",
        "login-userblocked": "Этот участник заблокирован. Вход в систему не разрешён.",
        "botpasswords-invalid-name": "Указанное имя участника не содержит разделителя для пароля бота («$1»).",
        "botpasswords-not-exist": "У участника «$1» нет пароля для бота с названием «$2».",
        "resetpass_forbidden": "Пароль не может быть изменён",
+       "resetpass_forbidden-reason": "Пароли не могут быть изменены: $1",
        "resetpass-no-info": "Чтобы обращаться непосредственно к этой странице, вам следует представиться системе.",
        "resetpass-submit-loggedin": "Изменить пароль",
        "resetpass-submit-cancel": "Отмена",
        "passwordreset-emailsentusername": "Если есть адрес электронной почты, связанный с этим именем участника, то будет отправлено письмо для восстановления пароля.",
        "passwordreset-emailsent-capture": "Отправлено электронное письмо с информацией о сбросе пароля, текст которого можно увидеть ниже.",
        "passwordreset-emailerror-capture": "Было создано электронное письмо с информацией о сбросе пароля, текст которого можно увидеть ниже, однако его не удалось отправить {{GENDER:$2|участнику|участнице}} по следующей причине: $1",
+       "passwordreset-invalideamil": "Недопустимый адрес электронной почты",
        "changeemail": "Изменить или удалить адрес электронной почты",
        "changeemail-header": "Заполните эту форму, чтобы изменить свой адрес электронной почты. Если вы хотите отвязать свой адрес электронной почты от учётной записи, то при заполнении формы оставьте пустым поле нового адреса электронной почты.",
        "changeemail-passwordrequired": "Чтобы подтвердить это изменение, вам нужно будет ввести свой пароль.",
        "accmailtext": "Сгенерированный случайным образом пароль для [[User talk:$1|$1]] выслан на адрес $2.\n\nПосле авторизации можно будет сменить пароль для этой учётной записи на ''[[Special:ChangePassword|специальной странице смены пароля]]''.",
        "newarticle": "(Новая)",
        "newarticletext": "Вы перешли по ссылке на страницу, которой пока не существует.\nЧтобы её создать, наберите текст в окне, расположенном ниже (подробнее см. [$1 справочную страницу]).\nЕсли вы оказались здесь по ошибке, просто нажмите кнопку '''назад''' своего браузера.",
-       "anontalkpagetext": "----''Эта страница обсуждения принадлежит анонимному участнику, который ещё не создал учётной записи, или не использует её.\nПоэтому для идентификации используется цифровой IP-адрес.\nЭтот же адрес может соответствовать нескольким другим участникам.\nЕсли вы анонимный участник и полагаете, что получили сообщения, адресованные не вам, пожалуйста, [[Special:UserLogin/signup|создайте учётную запись]] или [[Special:UserLogin|представьтесь системе]], чтобы впредь избежать возможной путаницы с другими анонимными участниками.''",
+       "anontalkpagetext": "----\n<em>Эта страница обсуждения анонимного участника, который ещё не создал учётной записи или не использует её.</em>\nПоэтому мы вынуждены для его/её идентификации использовать цифровой IP-адрес.\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}}}} найти соответствующие записи журналов].</span> У вас нет разрешения создать данную страницу.",
        "missing-revision": "Версия $1 страницы «{{FULLPAGENAME}}» не существует.\n\nЭто обычно бывает, если последовать по устаревшей ссылке на страницу, которая была удалена.\nПодробности могут быть в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале удалений].",
        "userpage-userdoesnotexist": "Учётной записи «<nowiki>$1</nowiki>» не существует. Убедитесь, что вы действительно желаете создать или изменить эту страницу.",
        "userpage-userdoesnotexist-view": "Не зарегистрировано учётной записи «$1».",
        "blocked-notice-logextract": "Этот участник в данный момент заблокирован.\nНиже приведена последняя запись из журнала блокировок:",
-       "clearyourcache": "'''Замечание.''' Возможно, после сохранения вам придётся очистить кэш своего браузера, чтобы увидеть изменения.\n* '''Firefox / Safari:''' Удерживая клавишу ''Shift'', нажмите на панели инструментов ''Обновить'' либо нажмите ''Ctrl-F5'' или ''Ctrl-R'' (''⌘-R'' на Mac)\n* '''Google Chrome:''' Нажмите ''Ctrl-Shift-R'' (''⌘-Shift-R'' на Mac)\n* '''Internet Explorer:''' Удерживая ''Ctrl'', нажмите ''Обновить'' либо нажмите ''Ctrl-F5''\n* '''Opera:''' Выберите очистку кэша в меню ''Инструменты → Настройки''",
+       "clearyourcache": "<strong>Замечание.</strong> Возможно, после сохранения вам придётся очистить кэш своего браузера, чтобы увидеть изменения.\n* <strong>Firefox / Safari:</strong> Удерживая клавишу <em>Shift</em>, нажмите на панели инструментов <em>Обновить</em> либо нажмите <em>Ctrl-F5</em> или <em>Ctrl-R</em> (<em>⌘-R</em> на Mac)\n* <strong>Google Chrome:</strong> Нажмите <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> на Mac)\n* <strong>Internet Explorer:</strong> Удерживая <em>Ctrl</em>, нажмите <em>Обновить</em> либо нажмите <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Перейдите в <em>Menu → Настройки</em> (<em>Opera → Настройки</em> на Mac), а затем <em>Безопасность → Очистить историю посещений → Кэшированные изображения и файлы</em>",
        "usercssyoucanpreview": "'''Подсказка.''' Нажмите кнопку «{{int:showpreview}}», чтобы проверить свой новый CSS-файл перед сохранением.",
        "userjsyoucanpreview": "'''Подсказка.''' Нажмите кнопку «{{int:showpreview}}», чтобы проверить свой новый JS-файл перед сохранением.",
        "usercsspreview": "'''Помните, что это только предварительный просмотр вашего CSS-файла, он ещё не сохранён!'''",
        "right-override-export-depth": "экспортирование страниц, включая связанные страницы с глубиной до 5",
        "right-sendemail": "отправка электронной почты другим участникам",
        "right-passwordreset": "просмотр электронных писем с изменением пароля",
-       "right-managechangetags": "создание и удаление [[Special:Tags|меток]] из базы данных",
+       "right-managechangetags": "Создание и (де)активация [[Special:Tags|меток]]",
        "right-applychangetags": "применение [[Special:Tags|меток]] вместе со своими правками",
        "right-changetags": "добавление и удаление произвольных [[Special:Tags|меток]] на отдельных правках и записях в журнале",
+       "right-deletechangetags": "Удаление [[Special:Tags|меток]] из базы данных",
        "grant-generic": "Набор прав «$1»",
        "grant-group-page-interaction": "Взаимодействие со страницами",
        "grant-group-file-interaction": "Взаимодействие с медиафайлами",
        "action-viewmyprivateinfo": "просмотр вашей частной информации",
        "action-editmyprivateinfo": "редактирование вашей частной информации",
        "action-editcontentmodel": "редактирование контентной модели страницы",
-       "action-managechangetags": "создание и удаление меток из базы данных",
+       "action-managechangetags": "создание и (де)активацию меток",
        "action-applychangetags": " применять теги наряду с Вашими изменениями",
        "action-changetags": "Добавлять и удалять произвольные теги на отдельных изменениях и записях в журнале",
+       "action-deletechangetags": "удаление меток из базы данных",
        "nchanges": "$1 {{PLURAL:$1|изменение|изменения|изменений}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|с последнего посещения}}",
        "enhancedrc-history": "история",
        "upload-form-label-infoform-description-tooltip": "Коротко опишите всё самое важное об этом произведении. Для фото — укажите, что главное изображено, обстоятельства съёмки или место.",
        "upload-form-label-usage-title": "Использование",
        "upload-form-label-usage-filename": "Имя файла",
-       "foreign-structured-upload-form-label-own-work": "Это моя собственная работа",
-       "foreign-structured-upload-form-label-infoform-categories": "Категории",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
-       "foreign-structured-upload-form-label-own-work-message-local": "Я подтверждаю, что загружаю этот файл в соответствиями с правилами и лицензионной политикой сайта {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Если Вы не можете загрузить этот файл в соответствиями с правилами сайта {{SITENAME}}, пожалуйста, закройте это диалоговое окно и попробуйте другой метод.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Возможно, вы также захотите попробовать [[Special:Upload|страницу загрузки по умолчанию]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Я понимаю, что загружаю этот файл в общий репозиторий. Я подтверждаю, что я делаю это в соответствии с пользовательским соглашением и лицензионной политикой.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Если Вы не можете загрузить этот файл в соответствиями с правилами общего хранилища, пожалуйста, закройте это диалоговое окно и попробуйте другой метод.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "В том случае, если этот файл может быть загружен в соответствии с правилами сайта {{SITENAME}}, вы также можете попробовать использовать его [[Special:Upload|страницу загрузки]].",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Я подтверждаю, что являюсь владельцем авторских прав на этот файл, и соглашаюсь на безотзывной основе разместить этот файл на Викискладе под лицензией [https://creativecommons.org/licenses/by-sa/4.0/deed.ru Creative Commons Attribution-ShareAlike 4.0], а также соглашаюсь с [https://wikimediafoundation.org/wiki/Условия_использования Условиями использования].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Если вы не являетесь владельцем авторских прав на этот файл, или вы хотите выпустить его под другой лицензией, рассмотрите возможность использования [https://commons.wikimedia.org/wiki/Special:UploadWizard Мастера загрузки на Викисладе].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "В том случае, если этот файл может быть загружен в соответствии с правилами сайта {{SITENAME}}, вы также можете попробовать использовать его [[Special:Upload|страницу загрузки]].",
+       "upload-form-label-own-work": "Это моя собственная работа",
+       "upload-form-label-infoform-categories": "Категории",
+       "upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work-message-generic-local": "Я подтверждаю, что загружаю этот файл в соответствиями с правилами и лицензионной политикой сайта {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Если Вы не можете загрузить этот файл в соответствиями с правилами сайта {{SITENAME}}, пожалуйста, закройте это диалоговое окно и попробуйте другой метод.",
+       "upload-form-label-not-own-work-local-generic-local": "Возможно, вы также захотите попробовать [[Special:Upload|страницу загрузки по умолчанию]].",
+       "upload-form-label-own-work-message-generic-foreign": "Я понимаю, что загружаю этот файл в общий репозиторий. Я подтверждаю, что я делаю это в соответствии с пользовательским соглашением и лицензионной политикой.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Если Вы не можете загрузить этот файл в соответствиями с правилами общего хранилища, пожалуйста, закройте это диалоговое окно и попробуйте другой метод.",
+       "upload-form-label-not-own-work-local-generic-foreign": "В том случае, если этот файл может быть загружен в соответствии с правилами сайта {{SITENAME}}, вы также можете попробовать использовать его [[Special:Upload|страницу загрузки]].",
        "backend-fail-stream": "Не удалось транслировать файл $1.",
        "backend-fail-backup": "Невозможно сделать резервную копию файла $1.",
        "backend-fail-notexists": "Файл $1 не существует.",
        "changecontentmodel-success-text": "Модель содержимого [[:$1]] была изменена.",
        "changecontentmodel-cannot-convert": "Содержимое [[:$1]] не может быть преобразовано к типу $2.",
        "changecontentmodel-nodirectediting": "Модель содержимого $1 не поддерживает прямое редактирование",
+       "changecontentmodel-emptymodels-title": "Нет доступных моделей содержимого",
+       "changecontentmodel-emptymodels-text": "Содержимое на [[:$1]] не может быть преобразовано ни к одному типу.",
        "log-name-contentmodel": "Журнал изменения моделей содержимого",
        "log-description-contentmodel": "События, связанные с моделями содержимого страниц",
        "logentry-contentmodel-new": "$1 создал{{GENDER:$2||а}} страницу $3 с использованием нестандартной модели содержимого «$5»",
        "whatlinkshere-prev": "{{PLURAL:$1|1=предыдущая|предыдущие}} $1",
        "whatlinkshere-next": "{{PLURAL:$1|1=следующая|следующие}} $1",
        "whatlinkshere-links": "← ссылки",
-       "whatlinkshere-hideredirs": "$1 перенаправления",
-       "whatlinkshere-hidetrans": "$1 включения",
-       "whatlinkshere-hidelinks": "$1 ссылки",
-       "whatlinkshere-hideimages": "$1 файловые ссылки",
+       "whatlinkshere-hideredirs": "Скрыть перенаправления",
+       "whatlinkshere-hidetrans": "Скрыть включения",
+       "whatlinkshere-hidelinks": "Скрыть ссылки",
+       "whatlinkshere-hideimages": "Скрыть файловые ссылки",
        "whatlinkshere-filters": "Фильтры",
        "whatlinkshere-submit": "Выполнить",
        "autoblockid": "Автоблокировка #$1",
        "lockdbsuccesstext": "База данных проекта была заблокирована.<br />\nНе забудьте [[Special:UnlockDB|убрать блокировку]] после завершения процедуры обслуживания.",
        "unlockdbsuccesstext": "База данных проекта была разблокирована.",
        "lockfilenotwritable": "Нет права на запись в файл блокировки базы данных. Чтобы заблокировать или разблокировать базу данных, веб-сервер должен иметь разрешение на запись в этот файл.",
+       "databaselocked": "База данных уже заблокирована.",
        "databasenotlocked": "База данных не была заблокирована.",
        "lockedbyandtime": "($1 $2 $3)",
        "move-page": "$1 — переименование",
        "tooltip-ca-nstab-category": "Страница категории",
        "tooltip-minoredit": "Отметить это изменение как незначительное",
        "tooltip-save": "Сохранить ваши изменения",
+       "tooltip-publish": "Опубликовать ваши изменения",
        "tooltip-preview": "Предварительный просмотр страницы; пожалуйста, используйте его перед сохранением!",
        "tooltip-diff": "Показать изменения, сделанные по отношению к исходному тексту.",
        "tooltip-compareselectedversions": "Посмотреть разницу между двумя выбранными версиями этой страницы.",
        "timezone-local": "Местное",
        "duplicate-defaultsort": "Внимание. Ключ сортировки по умолчанию «$2» переопределяет прежний ключ сортировки по умолчанию «$1».",
        "duplicate-displaytitle": "<strong>Внимание:</strong> Отображаемое название «$2» переопределяет ранее заданное отображаемое название «$1».",
+       "restricted-displaytitle": "<strong>Внимание:</strong> Отображаемое название «$1» было проигнорировано, поскольку она не соответствует актуальному названию страницы.",
        "invalid-indicator-name": "<strong>Ошибка:</strong> Атрибут <code>name</code> индикаторов состояния страницы не должен быть пустым.",
        "version": "Версия",
        "version-extensions": "Установленные расширения",
        "tags-delete-not-found": "Метка «$1» не существует.",
        "tags-delete-too-many-uses": "Метка «$1» применяется в более чем $2 {{PLURAL:$2|версии|версиям}}, что означает, что она не может быть удалена.",
        "tags-delete-warnings-after-delete": "Метка «$1» была удалена, но {{PLURAL:$2|было обнаружено следующее предупреждение|были обнаружены следующие предупреждения}}:",
+       "tags-delete-no-permission": "У вас нет прав на удаление изменений меток.",
        "tags-activate-title": "Активировать метку",
        "tags-activate-question": "Вы собираетесь активировать метку «$1».",
        "tags-activate-reason": "Причина:",
        "feedback-useragent": "Браузер:",
        "searchsuggest-search": "Поиск",
        "searchsuggest-containing": "содержащие…",
+       "api-error-autoblocked": "Ваш IP-адрес был автоматически заблокирован, потому что он был использован заблокированным участником.",
        "api-error-badaccess-groups": "Вам не разрешено загружать файлы в эту вики.",
        "api-error-badtoken": "Внутренняя ошибка:  некорректный токен.",
+       "api-error-blocked": "Редактирование было для вас заблокировано.",
        "api-error-copyuploaddisabled": "Загрузка по URL-адресу отключена на этом сервере.",
        "api-error-duplicate": "Уже {{PLURAL:$1|существует другой файл|существуют другие файлы}} с таким же содержимым.",
        "api-error-duplicate-archive": "Раньше на сайте {{PLURAL:$1|1=уже был файл|были файлы}} с точно таким же содержанием, но {{PLURAL:$1|1=он был удалён|они были удалены}}.",
        "api-error-nomodule": "Внутренняя ошибка: не настроен модуль загрузки.",
        "api-error-ok-but-empty": "Внутренняя ошибка: нет ответа от сервера.",
        "api-error-overwrite": "Не допускается замена существующего файла.",
+       "api-error-ratelimited": "Вы пытаетесь загрузить несколько файлов за более короткий промежуток времени, чем это позволено.\nПожалуйста, попробуйте ещё раз через несколько минут.",
        "api-error-stashfailed": "Внутренняя ошибка: сервер не смог сохранить временный файл.",
        "api-error-publishfailed": "Внутренняя ошибка: сервер не смог сохранить временный файл.",
        "api-error-stasherror": "При загрузке файла в хранилище произошла ошибка.",
        "log-action-filter-suppress-block": "Сокрытие пользователя через блокировки",
        "log-action-filter-suppress-reblock": "Сокрытие пользователя через повторное блокирование",
        "log-action-filter-upload-upload": "Новая загрузка",
-       "log-action-filter-upload-overwrite": "Повторно загрузить"
+       "log-action-filter-upload-overwrite": "Повторно загрузить",
+       "authmanager-authn-autocreate-failed": "Автоматическое создание локальной учётной записи не удалось: $1",
+       "authmanager-authplugin-setpass-bad-domain": "Неверный домен.",
+       "authmanager-autocreate-exception": "Автоматическое создание учётной записи временно отключено из-за предыдущих ошибок.",
+       "authmanager-userdoesnotexist": "Не зарегистрировано учётной записи «$1».",
+       "authmanager-email-label": "Электронная почта",
+       "authmanager-email-help": "Адрес электронной почты",
+       "authmanager-realname-label": "Настоящее имя",
+       "authmanager-realname-help": "Настоящее имя участника",
+       "authmanager-provider-temporarypassword": "Временный пароль",
+       "authprovider-resetpass-skip-label": "Пропустить",
+       "authprovider-resetpass-skip-help": "Пропустить сброс пароля.",
+       "changecredentials-submit": "Изменить",
+       "changecredentials-submit-cancel": "Отмена",
+       "removecredentials-submit-cancel": "Отмена"
 }
index 60b2992..b7f8c44 100644 (file)
        "noname": "Мусите увести мено свого конта.",
        "loginsuccesstitle": "Успішне приголошіня",
        "loginsuccess": "'''Теперь працуєте {{grammar:locative|{{SITENAME}}}} під меном $1.'''",
-       "nosuchuser": "Не екзістує хоснователь з меном «$1». У хосновательскых мен ся розлишують малы/великы писмена. Сконтролюйте запис, або собі [[Special:UserLogin/signup|зареґіструйте нове конто]].",
+       "nosuchuser": "Не екзістує хоснователь з меном «$1». У хосновательскых мен ся розлишують малы/великы писмена. Сконтролюйте запис, або собі [[Special:CreateAccount|зареґіструйте нове конто]].",
        "nosuchusershort": "Хоснователь з меном $1 не екзістує.\nПеревірте правилность написаня мена.",
        "nouserspecified": "Мусите задати мено хоснователя.",
        "login-userblocked": "Тот хоснователь є заблокованый. Приголошіня не є дозволене.",
        "accmailtext": "Трафунково выґенероване гесло про хоснователя [[User talk:$1|$1]] было послане на $2.\nОно може быти змінене на  [[Special:ChangePassword|сторінцї про зміну гесла]].",
        "newarticle": "(Нова)",
        "newarticletext": "Перешли сте на сторінку, котра іщі не екзістує.\nНову сторінку створите так, же зачнете писати в окнї ниже (вид. [$1 сторінка помочі], про вецей інформації).\nКідь сте ту помылково, просто кликните в переглядачу на '''назад'''",
-       "anontalkpagetext": "----''Тото є діскузна сторінка анонімного хоснователя, котрый іщі не має конто або го не хоснує. Про&nbsp;ёго ідентіфікацію прото мусиме хосновати IP адресу. Таку IP адресу може хосновати декілько хоснователїв. Покы сьте анонімный хоснователь і&nbsp;і уважуєте, же вам суть адресованы ірелевантны коментарї, просиме, [[Special:UserLogin/signup|створьте собі конто]] або [[Special:UserLogin|ся приголосте]], уникнете тым будучій замінї з&nbsp;іншыма анонімныма хоснователями.''",
+       "anontalkpagetext": "----''Тото є діскузна сторінка анонімного хоснователя, котрый іщі не має конто або го не хоснує. Про&nbsp;ёго ідентіфікацію прото мусиме хосновати IP адресу. Таку IP адресу може хосновати декілько хоснователїв. Покы сьте анонімный хоснователь і&nbsp;і уважуєте, же вам суть адресованы ірелевантны коментарї, просиме, [[Special:CreateAccount|створьте собі конто]] або [[Special:UserLogin|ся приголосте]], уникнете тым будучій замінї з&nbsp;іншыма анонімныма хоснователями.''",
        "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}}|глядати тоту назву]] в іншых сторінках, або <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} глядати в лоґах]</span>, но вы не мате права створити тоту сторінку.",
        "missing-revision": "Ревізія #$1 сторінкы з назвов „{{FULLPAGENAME}}“ не є.\n\nГевсе звычайно запрічінене так, же наслїдовали сьте застарїлый історічный одказ на сторінку, котра была уж змазана.\nДетайлы можуть быти найджены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} книзї змазаных сторінок].",
index 6bd7f31..8b5d51c 100644 (file)
        "noname": "भवता/भवत्या योग्यं सदस्यनाम न प्रदत्तम् ।",
        "loginsuccesstitle": "स्वागतं ! प्रवेशः सिद्धः ।",
        "loginsuccess": "भवता/भवत्या {{SITENAME}} इत्यत्र \"$1\"-योजकत्वेन प्रवेशः प्राप्तः ।",
-       "nosuchuser": "'''$1''' नाम्नः न कोऽपि योजकः विद्यते ।\n\nप्रयोक्तृनामानि पक्षानुगुणं (case sensitive) भवन्ति ।\n\nयत् टङ्कितं, तत् पश्यतु अथवा [[Special:UserLogin/signup|नूतनसदस्यता प्राप्यताम्]] ।",
+       "nosuchuser": "'''$1''' नाम्नः न कोऽपि योजकः विद्यते ।\n\nप्रयोक्तृनामानि पक्षानुगुणं (case sensitive) भवन्ति ।\n\nयत् टङ्कितं, तत् पश्यतु अथवा [[Special:CreateAccount|नूतनसदस्यता प्राप्यताम्]] ।",
        "nosuchusershort": "'''$1''' नाम्नः न कोऽपि सदस्यः विद्यते ।\n\nयत् टङ्कितं, तत् पश्यतु ।",
        "nouserspecified": "भवता/भवत्या एकं योग्यं सदस्यनाम अवश्यमेव दातव्यम् ।",
        "login-userblocked": "एषः सदस्यः प्रतिबन्धितः । प्रवेष्टुम् अनुमतिः नास्ति ।",
        "accmailtext": "[[User talk:$1|$1]] कृते अशृङ्खलितरीत्या (randomly) उत्पादितः कूटशब्दः $2 वि-पत्रसङ्केतं प्रति प्रेषितः अस्ति । <em>[[Special:ChangePassword|कूटशब्दः परिवर्त्यताम्]]</em> अत्र तत् परिर्तयितुं शक्यते ।",
        "newarticle": "(नूतनम्)",
        "newarticletext": "भवान्/भवती अनिर्मिते पृष्ठे अस्ति । \nपृष्ठं स्रष्टुम् अधः प्रदत्तायां पेटिकायां टङ्कनं प्रारभताम् (साहाय्यार्थं [$1 अत्र]) नुदतु ।\nभवान्/भवती यदि क्षतिकारणात् एतत् पृष्ठं प्रति आगच्छत्, तर्हि अस्य गवेषकस्य (browser) Back नुदतु ।",
-       "anontalkpagetext": "----\n<em>एतत् सम्भाषणपृष्ठम् अनामकसदस्येभ्यः अस्ति । एतत् तेभ्यः अनामकसदस्येभ्यः रचितमस्ति, यैः सदस्यता न प्राप्ता अस्ति तथा च अस्य पृष्ठस्य उपयोगं न कुर्वन्तः सन्ति ।</em>\nतेषां व्यक्तिगतसूचनां प्राप्तुमेव वयं तस्य/तस्याः अन्तर्जालसंविदः उपयोगं कुर्मः । केचन सदस्याः स्वस्य अन्तर्जालसंविदम् अन्यान् सदस्यान् कथयन्ति । \nयद्यपि अनामकसदस्यः अहं नास्मि, तथापि अयोग्यसूचनाः मम पार्श्वे आगच्छन्त्यः सन्ति इति यदि भवान्/भवती शङ्कते, तर्हि एतत् [[Special:UserLogin/signup|create an account]] एतत् [[Special:UserLogin|log in]] वा कृत्वा भविष्यस्य अनामकसदस्यानां सन्देशेभ्यः स्वस्य रक्षणं करोतु ।",
+       "anontalkpagetext": "----\n<em>एतत् सम्भाषणपृष्ठम् अनामकसदस्येभ्यः अस्ति । एतत् तेभ्यः अनामकसदस्येभ्यः रचितमस्ति, यैः सदस्यता न प्राप्ता अस्ति तथा च अस्य पृष्ठस्य उपयोगं न कुर्वन्तः सन्ति ।</em>\nतेषां व्यक्तिगतसूचनां प्राप्तुमेव वयं तस्य/तस्याः अन्तर्जालसंविदः उपयोगं कुर्मः । केचन सदस्याः स्वस्य अन्तर्जालसंविदम् अन्यान् सदस्यान् कथयन्ति । \nयद्यपि अनामकसदस्यः अहं नास्मि, तथापि अयोग्यसूचनाः मम पार्श्वे आगच्छन्त्यः सन्ति इति यदि भवान्/भवती शङ्कते, तर्हि एतत् [[Special:CreateAccount|create an account]] एतत् [[Special:UserLogin|log in]] वा कृत्वा भविष्यस्य अनामकसदस्यानां सन्देशेभ्यः स्वस्य रक्षणं करोतु ।",
        "noarticletext": "अस्मिन् पृष्ठे अधुना किमपि न विद्यते । [[Special:Search/{{PAGENAME}}|एषः शब्दः]] येषु पृष्ठेषु अन्तर्भवति, तानि पृष्ठानि अन्वेष्टुं शक्यन्ते । \n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}  सम्बद्धेषु पृष्ठेषु अन्वेषणं]\n[{{fullurl:{{FULLPAGENAME}}|action=edit}} अस्य पृष्ठस्य निर्माणं] वा  शक्यम्</span>.",
        "noarticletext-nopermission": "अस्मिन् पृष्ठे अधुना किमपि न विद्यते । [[Special:Search/{{PAGENAME}}|एषः शब्दः]] येषु पृष्ठेषु अन्तर्भवति, तानि पृष्ठानि अन्वेष्टुं शक्यन्ते । \n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}  सम्बद्धेषु पृष्ठेषु अन्वेषणं]\n[{{fullurl:{{FULLPAGENAME}}|action=edit}} अस्य पृष्ठस्य सम्पादनं] वा  शक्यम्</span>.",
        "missing-revision": "\"{{FULLPAGENAME}}\" पृष्ठस्य संस्करणं #$1 नोपलभ्यम् ।\nयस्य पृष्ठस्य इतिहासे परिसन्धयः कालातीताः सन्ति, तेषु पृष्ठेषु एवं भवति ।\nअधिकसूचनाः अत्र प्राप्तुं शक्यते [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} अपाकृतानाम् आवलिः].",
        "restriction-level-autoconfirmed": "अर्धसंरक्षितम्",
        "restriction-level-all": "कोऽपि स्तरः ।",
        "undelete": "अपमर्जितपुटानि अवलोकयतु ।",
-       "undeletepage": "à¤\85पमरà¥\8dà¤\9cितपà¥\81à¤\9fानि à¤¦à¥\83षà¥\8dà¤\9fà¥\8dवा à¤ªà¥\81नसà¥\8dथापयतà¥\81 à¥¤",
+       "undeletepage": "à¤\85पमरà¥\8dà¤\9cितपà¥\81षà¥\8dठानि à¤¦à¥\83षà¥\8dà¤\9fà¥\8dवा à¤ªà¥\81नसà¥\8dथापयतà¥\81",
        "undeletepagetitle": "'''अधः [[:$1|$1]] इत्येतेषाम् अपनीतावृत्तीनां दर्शनं भवति ।",
        "viewdeletedpage": "अपमर्जितपुटानि अवलोकयतु ।",
        "undeletepagetext": "{{PLURAL:$1|$1 पृष्ठं|$1 पृष्ठानि}} इत्येतानि अपनीतानि किन्तु एतानि लेखागारे सन्ति अपि च पुनस्थापितानि कर्तुं शक्यते ।",
index 6a155bb..deaa0d6 100644 (file)
        "noname": "Эн тиһилик билэр аатын киллэрбэтэххин.",
        "loginsuccesstitle": "Киирдиҥ",
        "loginsuccess": "'''Билигин бу аатынан үлэлиигин: \"$1\".'''",
-       "nosuchuser": "Маннык - \"$1\" - ааттаах кыттааччы суох.\nУлахан кыра буукубалар атыннаахтар.\nАатыҥ сөпкө суруллубутун көр эбэтэр [[Special:UserLogin/signup|саҥаттан бэлиэтэн]].",
+       "nosuchuser": "Маннык - \"$1\" - ааттаах кыттааччы суох.\nУлахан кыра буукубалар атыннаахтар.\nАатыҥ сөпкө суруллубутун көр эбэтэр [[Special:CreateAccount|саҥаттан бэлиэтэн]].",
        "nosuchusershort": "Маннык - \"$1\" - ааттаах кыттааччы суох. Аатыҥ сөпкө суруллубутун көр.",
        "nouserspecified": "Кыттааччы аатын киллэриэхтээххин.",
        "login-userblocked": "Бу кыттааччы бобуллубут. Тиһиккэ киирии көҥүллэммэт.",
        "accmailtext": "[[User talk:$1|$1]] кыттааччыга түбэспиччэ бэлиэлэртэн оҥоһуллубут киирии тыл бу аадырыска $2 ыытылынна.\nТиһиккэ бэлиэтэнэн баран киирии тылгын ''[[Special:ChangePassword|уларытыаххын]]'' сөп.",
        "newarticle": "(Саҥа ыстатыйа)",
        "newarticletext": "Эн суох сирэйгэ киирэ сатаатыҥ.\nМаннык ааттаах саҥа ыстатыйаны оҥорор буоллаххына, аллара баар түннүккэ суруй\n(сиһ. [$1 көмөнү] көрүөххүн сөп).\nӨскө манна сыыһа киирбит буоллаххына интэриниэтиҥ бырагыраамматын \"төнүн\" диэххин сөп.",
-       "anontalkpagetext": "----''Бу аатын эппэтэх кыттааччы ырытар сирэйэ.\nIP-аадырыһа эрэ көстөр.\nБиир IP-аадырыс хас да киһиэхэ бэриллиэн сөп. Өскө атын киһиэхэ суруллубут суругу алҕас туппут буоллаххына, бэйэҥ [[Special:UserLogin/signup|ааккын билиһиннэр]] эбэтэр [[Special:UserLogin|киир]], оччоҕо кэлин да булкуур тахсыа суоҕа.''",
+       "anontalkpagetext": "----''Бу аатын эппэтэх кыттааччы ырытар сирэйэ.\nIP-аадырыһа эрэ көстөр.\nБиир IP-аадырыс хас да киһиэхэ бэриллиэн сөп. Өскө атын киһиэхэ суруллубут суругу алҕас туппут буоллаххына, бэйэҥ [[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}}}} сурунаалларга манна сыһыаннаах суруктары булуоххун сөп].</span> Бу сирэйи айар кыаҕыҥ суох.",
        "missing-revision": "«{{FULLPAGENAME}}» сирэй $1 барыла суох.\n\nМаннык үксүн хайыы-үйэ сотуллубут билэҕэ эргэрбит сигэнэн бардахха буолааччы.\nСиһилии баҕар [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} сотуу сурунаалыгар] баара буолуо.",
        "upload-form-label-infoform-description-tooltip": "Кылгастык бу айымньы туһунан сүрүннээн этиҥ. Хаартыскаҕа сүнньүнэн туох ойууламмытый, ханна түһэриллибитий.",
        "upload-form-label-usage-title": "Туһаныы",
        "upload-form-label-usage-filename": "Билэ аата",
-       "foreign-structured-upload-form-label-own-work": "Бу бэйэм оҥоруум",
-       "foreign-structured-upload-form-label-infoform-categories": "Категорията",
-       "foreign-structured-upload-form-label-infoform-date": "Күнэ-дьыла",
-       "foreign-structured-upload-form-label-own-work-message-local": "{{SITENAME}} быраабылатын уонна лиссиэнсийэлиир бэлиитикэтин тутуһан бу билэни киллэрэрбин бигэргэтэбин.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Бу билэҕин {{SITENAME}} быраабылатынан угар кыаҕыҥ суох буоллаҕына, маны сап уонна атын ньыманан туһанан көр.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Баҕар [[Special:Upload|киллэрии сүрүн ньыматын]] туһаныаххын баҕарыаҥ.",
-       "foreign-structured-upload-form-label-own-work-message-default": "Уопсай репозиторийга угарбын өйдөөн туран угабын. Туһаныы сиэрин уонна лиссиэнсийэлиир бэлиитикэни кытта сөп түбэһэрин мэктиэлиибин.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Бу билэҕин уопсай репозиторий быраабылатынан угар кыаҕыҥ суох буоллаҕына, маны сап уонна атын ньыманы туһанан көр.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Өскө, {{SITENAME}} быраабылатынан угар сатанар буоллаҕына, кини [[Special:Upload|киллэрии тэрилин]] туһаныаххын сөп.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Бу билэни бас билэрбин уонна Биики Ыскылаакка төнүннэрбэттии [https://creativecommons.org/licenses/by-sa/4.0/deed.ru Creative Commons Attribution-ShareAlike 4.0] лиссиэнсийэннэн угары бигэргэтэбин. Ону тэҥэ [https://wikimediafoundation.org/wiki/Условия_использования Туһаныы усулуобуйатын кытта] сөбүлэһэбин.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Өскөтө билэни бас билбэт буоллаххына, биитэр атын лиссиэнсийэннэн угуоххун баҕарар буоллаххына, манна баар ньыманы туһаныаххын сөп: [https://commons.wikimedia.org/wiki/Special:UploadWizard Биики Ыскылаакка угуу маастара].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Өскө, {{SITENAME}} быраабылатынан угар сатанар буоллаҕына, кини [[Special:Upload|киллэрии тэрилин]] туһаныаххын эмиэ сөп.",
+       "upload-form-label-own-work": "Бу бэйэм оҥоруум",
+       "upload-form-label-infoform-categories": "Категорията",
+       "upload-form-label-infoform-date": "Күнэ-дьыла",
+       "upload-form-label-own-work-message-generic-local": "{{SITENAME}} быраабылатын уонна лиссиэнсийэлиир бэлиитикэтин тутуһан бу билэни киллэрэрбин бигэргэтэбин.",
+       "upload-form-label-not-own-work-message-generic-local": "Бу билэҕин {{SITENAME}} быраабылатынан угар кыаҕыҥ суох буоллаҕына, маны сап уонна атын ньыманан туһанан көр.",
+       "upload-form-label-not-own-work-local-generic-local": "Баҕар [[Special:Upload|киллэрии сүрүн ньыматын]] туһаныаххын баҕарыаҥ.",
+       "upload-form-label-own-work-message-generic-foreign": "Уопсай репозиторийга угарбын өйдөөн туран угабын. Туһаныы сиэрин уонна лиссиэнсийэлиир бэлиитикэни кытта сөп түбэһэрин мэктиэлиибин.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Бу билэҕин уопсай репозиторий быраабылатынан угар кыаҕыҥ суох буоллаҕына, маны сап уонна атын ньыманы туһанан көр.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Өскө, {{SITENAME}} быраабылатынан угар сатанар буоллаҕына, кини [[Special:Upload|киллэрии тэрилин]] туһаныаххын сөп.",
        "backend-fail-stream": "$1 билэни ыытар табыллыбата.",
        "backend-fail-backup": "Бу билэ $1 резервнэй куопуйатын оҥорор табыллыбата.",
        "backend-fail-notexists": "Маннык $1 билэ суох эбит.",
index 332e9f6..5a1c117 100644 (file)
        "noname": "Su nùmene impitadore insertadu no est vàlidu.",
        "loginsuccesstitle": "Ti ses identificadu",
        "loginsuccess": "'''Immoe ses intradu in {{SITENAME}} cun su nùmene impitadore \"$1\".'''",
-       "nosuchuser": "Non b'at impitadore cun su nùmene \"$1\".\nIs nùmenes impitadore sunt sensìbiles a is lìteras mannas.\nVerìfica su nùmene insertadu o [[Special:UserLogin/signup|crea unu contu nou]].",
+       "nosuchuser": "Non b'at impitadore cun su nùmene \"$1\".\nIs nùmenes impitadore sunt sensìbiles a is lìteras mannas.\nVerìfica su nùmene insertadu o [[Special:CreateAccount|crea unu contu nou]].",
        "nosuchusershort": "Non b'est perunu impitadore chi tenet \"$1\" comente nùmene.\nCòmpida su nùmene insertadu.",
        "nouserspecified": "Depes ispetzificare unu nùmene impitadore.",
        "login-userblocked": "{{GENDER:$1|Custu impitadore est arreadu|Custa impidadora est arreada}}. Atzessu no adduidu.",
        "accmailtext": "Una password ingendrada a manera casuale pro [[User talk:$1|$1]] est istada imbiada a $2. Podet èssere cambiada in sa pàgina de <em>[[Special:ChangePassword|càmbiu password]]</em> a pustis de èssere intradu in su contu tuo.",
        "newarticle": "(Nou)",
        "newarticletext": "Custa pàgina no esistit galu.\nPro creare sa pàgina, iscrie in sa casella a suta (càstia sa [$1 pàgina de agiudu] pro àteras informatziones).\nSi ses intradu inoghe pro isbàlliu, carca su butone <strong>back/indietro</strong> in su navigadore tuo.",
-       "anontalkpagetext": "----\n<em>Custa est sa pàgina de cuntierra de unu impitadore anònimu ki no at creadu unu contu galu, o ki non dd'usat.</em>\nPro custu impreamus su nùmeru de indiritzos IP pro ddu identificare. Is indiritzos IP podent perou èsser cundivìdidos dae unos cantos impitadores. Si ses unu impitadore anònimu e ritenes ki custos cummentos non sunt diretos a tue, pro praxere [[Special:UserLogin/signup|crea unu contu]] o [[Special:UserLogin|identifica·ti (log in)]] pro evitare cunfusione cun àteros impitadore anònimos.''",
+       "anontalkpagetext": "----\n<em>Custa est sa pàgina de cuntierra de unu impitadore anònimu ki no at creadu unu contu galu, o ki non dd'usat.</em>\nPro custu impreamus su nùmeru de indiritzos IP pro ddu identificare. Is indiritzos IP podent perou èsser cundivìdidos dae unos cantos impitadores. Si ses unu impitadore anònimu e ritenes ki custos cummentos non sunt diretos a tue, pro praxere [[Special:CreateAccount|crea unu contu]] o [[Special:UserLogin|identifica·ti (log in)]] pro evitare cunfusione cun àteros impitadore anònimos.''",
        "noarticletext": "In custu momentu sa pàgina est bùida.\nPodes [[Special:Search/{{PAGENAME}}|chircare custu tìtulu]] in àteras pàginas, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chircare in is registros ligados] o puru [{{fullurl:{{FULLPAGENAME}}|action=edit}} modificare sa pàgina]</span>.",
        "noarticletext-nopermission": "In custu tempu sa pàgina rechesta est bùida.\nPodes [[Special:Search/{{PAGENAME}}|chircare custu tìtulu]] in is àteras pàginas, o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chircare in is regìstros ligados]</span>, ma non tenes su permissu de creare custa pàgina.",
        "userpage-userdoesnotexist": "Su contu de s'impitadore \"<nowiki>$1</nowiki>\" no est stadu registradu.\nPro praxere abbàida si boles a sèriu creare/cambiare custa pàgina.",
index b50b5b1..ef529ee 100644 (file)
        "noname": "Nun spicificasti nu nomu utenti vàlidu.",
        "loginsuccesstitle": "Trasuta rinisciuta",
        "loginsuccess": "<strong>Ora trasisti nta {{SITENAME}} comu \"$1\".</strong>",
-       "nosuchuser": "Nun è riggistratu nuddu utenti di nomu \"$1\".\nLi nomi di l'utenti fannu diffirenza tra maiùsculi e minùsculi.\nCuntrolla chi scrivisti lu nomu bonu, o puru [[Special:UserLogin/signup|crea un cuntu novu]].",
+       "nosuchuser": "Nun è riggistratu nuddu utenti di nomu \"$1\".\nLi nomi di l'utenti fannu diffirenza tra maiùsculi e minùsculi.\nCuntrolla chi scrivisti lu nomu bonu, o puru [[Special:CreateAccount|crea un cuntu novu]].",
        "nosuchusershort": "Nun è riggistratu nuddu utenti di nomu \"$1\".\nCuntrolla chi scrivisti lu nomu bonu.",
        "nouserspecified": "Hai a spicificari un nomu utenti.",
        "login-userblocked": "St'utenti è bluccatu. Nun è pussìbbili di tràsiri.",
        "accmailtext": "Na password ginirata casualmenti pi [[User talk:$1|$1]] fu spiduta a $2. Si pò canciari dâ pàggina di <em>[[Special:ChangePassword|canciamentu dâ password]]</em> comu unu trasi.",
        "newarticle": "(Novu)",
        "newarticletext": "Siguisti na lijami a na pàggina c'ancora nun esisti.\nPi criari sta pàggina, accumenza a scrìviri ccassutta (talìa la [$1 pàggina d'aiutu] p'aviri maiuri nfurmazzioni).\nSi agghicasti ccà pi sbagghiu, carca lu buttuni <strong>n arreri</strong> dû tò browser.",
-       "anontalkpagetext": "----''Chista è la pàggina di discussioni di n’utenti anònimu, ca nun criau ancora n’accessu o ca nun l’usa.\nP’idintificàrilu è pirciò nicissariu usari lu nùmmiru di lu sò nnirizzu IP.\nLi nnirizzi IP ponnu pirò èssiri spartuti di cchiù utenti.\nSiddu sî n’utenti anònimu e riteni ca li cummenti prisenti nta sta pàggina nun si rifirìscinu a tia, [[Special:UserLogin/signup|crea n’accessu novu]] o [[Special:UserLogin|trasi]] cu chiddu ca già hai p’evitari d’èssiri cunfusu cu àutri utenti anònimi ‘n futuru.''",
+       "anontalkpagetext": "----''Chista è la pàggina di discussioni di n’utenti anònimu, ca nun criau ancora n’accessu o ca nun l’usa.\nP’idintificàrilu è pirciò nicissariu usari lu nùmmiru di lu sò nnirizzu IP.\nLi nnirizzi IP ponnu pirò èssiri spartuti di cchiù utenti.\nSiddu sî n’utenti anònimu e riteni ca li cummenti prisenti nta sta pàggina nun si rifirìscinu a tia, [[Special:CreateAccount|crea n’accessu novu]] o [[Special:UserLogin|trasi]] cu chiddu ca già hai p’evitari d’èssiri cunfusu cu àutri utenti anònimi ‘n futuru.''",
        "noarticletext": "Nta stu mumentu la pàggina addumannata è vacanti. È pussìbbili [[Special:Search/{{PAGENAME}}|circari stu tìtulu]] nta l'àutri pàggini dû situ oppuru <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|action=edit}} circari ntê riggistri culligati] oppuru [{{fullurl:{{FULLPAGENAME}}|action=edit}} canciari la pàggina ora]</span>.",
        "noarticletext-nopermission": "Nta stu mumentu la pàggina addumannata è vacanti. È pussìbbili [[Special:Search/{{PAGENAME}}|circari stu tìtulu]] nti àutri pàggini dû situ o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} circari ntê riggistri culligati]</span>, ma nun hai li pirmissi pi criari sta pàggina.",
        "missing-revision": "La virsioni #$1 dâ paggina ntitulata \"{{FULLPAGENAME}}\" nun esisti.\n\nStu fattu di sòlitu succedi quannu si segui nu lijami di crunuluggìa versu na pàggina chi fu cancillata.\nSi ponnu vìdiri li dittagghî ntô [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} riggistru dî cancillazzioni].",
index 4b69dfe..6f6ec3b 100644 (file)
        "noname": "Ye'v na speceefie'd ae valid uisername.",
        "loginsuccesstitle": "Login fine",
        "loginsuccess": "<strong>Ye're nou loggit in tae {{SITENAME}} aes \"$1\".</strong>",
-       "nosuchuser": "Thaur's nae sic uiser aes \"$1\".\nUiser names ar case-sensiteeve.\nCheck yer speelin, or [[Special:UserLogin/signup|mak ae new accoont]].",
+       "nosuchuser": "Thaur's nae sic uiser aes \"$1\".\nUiser names ar case-sensiteeve.\nCheck yer speelin, or [[Special:CreateAccount|mak ae new accoont]].",
        "nosuchusershort": "Thaur's nae sic uiser aes \"$1\". Check yer spellin.",
        "nouserspecified": "Ye hae tae merk up ae uisername.",
        "login-userblocked": "Uiser \"$1\" is blockit. Log-in naw permitit.",
        "accmailtext": "Ae randomly generated passwaird fer [[User talk:$1|$1]] haes been sent til $2. It can be chynged oan the <em>[[Special:ChangePassword|chynge passwaird]]</em> page upo loggin in.",
        "newarticle": "(New)",
        "newarticletext": "Ye'v follaed aen airtin til ae page that disna exeest yet. Tae cræft the page, stairt typin in the kist ablo (see the [$1 heelp page] fer mair info). Gif ye'r here bi mistak, jist clap yer brouser's <strong>back</strong> button.",
-       "anontalkpagetext": "----\n<em>This is the discussion page fer aen anonymoos uiser that's naw cræftit aen accoont yet, or that disna uise it.</em>\nWe maun therefore uise the numerical IP address tae identifie him/her.\nSic aen IP address can be shaired bi several uisers.\nGif ye'r aen anonymos uiser n feel that onreelavant comments hae been directed at ye, please [[Special:UserLogin/signup|cræft aen accoont]] or [[Special:UserLogin|log in]] tae avoid futur confusion wi ither anonymoos uisers.",
+       "anontalkpagetext": "----\n<em>This is the discussion page fer aen anonymoos uiser that's naw cræftit aen accoont yet, or that disna uise it.</em>\nWe maun therefore uise the numerical IP address tae identifie him/her.\nSic aen IP address can be shaired bi several uisers.\nGif ye'r aen anonymos uiser n feel that onreelavant comments hae been directed at ye, please [[Special:CreateAccount|cræft aen accoont]] or [[Special:UserLogin|log in]] tae avoid futur confusion wi ither anonymoos uisers.",
        "noarticletext": "Thaur's naw tex oan this page the nou. \nYe can [[Special:Search/{{PAGENAME}}|rake fer this page teitle]] in ither pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rake the related logs],\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} eedit this page].</span>",
        "noarticletext-nopermission": "Thaur's nae tex in this page the nou.\nYe can [[Special:Search/{{PAGENAME}}|rake fer this page title]] in ither pages, or <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rake the relatit logs]</span>, but ye dinna hae permeession tae cræft this page.",
        "missing-revision": "The reveesion #$1 o the page named \"{{FULLPAGENAME}}\" disna exeest.\n\nThis is uissuallie caused bi follaein aen ootdated histerie airtin til ae page that haes been delytit.\nDetails can be foond in the [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} delytion log].",
index 23ddbe9..23a7daa 100644 (file)
        "noname": "توهان جو ڄاڻايل يُوزرنانءُ ناقابل ڪار آهي.",
        "loginsuccesstitle": "لاگ اِن ڪامياب",
        "loginsuccess": "'''هاڻي توهان {{SITENAME}} تي بطور \"$1\" لاگ اِن ٿيل آهيو.'''",
-       "nosuchuser": "\"$1\" نالي سان ڪو بہ يوزر نہ آهي.  \"$1\".\n ننڍن وڏن اکرن ۾ امتياز ڪرڻ لازمي آهي. \nهِجي چڪاسيو،يا [[Special:UserLogin/signup|نئون کاتو تخليق ڪريو]]",
+       "nosuchuser": "\"$1\" نالي سان ڪو بہ يوزر نہ آهي.  \"$1\".\n ننڍن وڏن اکرن ۾ امتياز ڪرڻ لازمي آهي. \nهِجي چڪاسيو،يا [[Special:CreateAccount|نئون کاتو تخليق ڪريو]]",
        "nosuchusershort": "\"$1\" نالي ڪو بہ يُوزر ناهي.\nهِجي جي پڪ ڪندا.",
        "nouserspecified": "توهان کي ڪو يوزرنانءُ ڄاڻائڻو پوندو.",
        "login-userblocked": "هيءُ يُوزر بندشيل آهي. لاگ اِن جي اجازت نہ ٿي ڏجي.",
        "upload-form-label-infoform-description": "تشريح",
        "upload-form-label-usage-title": "استعمال",
        "upload-form-label-usage-filename": "فائيل نانءُ",
-       "foreign-structured-upload-form-label-own-work": "هيءُ منهنجو پنهنجو ڪم آهي.",
-       "foreign-structured-upload-form-label-infoform-categories": "زمرا",
-       "foreign-structured-upload-form-label-infoform-date": "تاريخ",
+       "upload-form-label-own-work": "هيءُ منهنجو پنهنجو ڪم آهي.",
+       "upload-form-label-infoform-categories": "زمرا",
+       "upload-form-label-infoform-date": "تاريخ",
        "backend-fail-notexists": "فائيل ''$1'' وجود نٿو رکي.",
        "backend-fail-delete": "\"$1\" فائيل ڊهي نہ سگھيو.",
        "backend-fail-alreadyexists": "\"$1\" فائيل اڳ ئي وجود رکي ٿو.",
index 2488e06..e2adcf4 100644 (file)
@@ -10,6 +10,7 @@
        "tog-hideminor": "Fasal kayney kaŋ hun barmayyaŋ korawey ra tugu",
        "tog-hidepatrolled": "Fasalyan kurantey tugu barmay korawey ra",
        "tog-newpageshidepatrolled": "Moo kurantey tugu moo taaga maašeedaa ra",
+       "tog-hidecategorization": "Moɲey kanandiyanoo tugu",
        "tog-extendwatchlist": "Hawgay maašeedaa hayandi ka barmawey kul cebe, manti ikokorantaa hinne",
        "tog-usenewrc": "Barmawey marga moo bande barmay korawey nda hawgayhayey ra",
        "tog-numberheadings": "Boŋdekerey boŋkabuyan",
@@ -20,6 +21,7 @@
        "tog-watchdefault": "Moɲey nda tukey kaŋ ay g'i fasal tonton ay hawgayhayey ga",
        "tog-watchmoves": "Moɲey nda tukey kaŋ ay g'i ganandi tonton ay hawgayhayey ga",
        "tog-watchdeletion": "Moɲey nda tukey kaŋ ay g'i tuusu tonton ay hawgayhayey ga",
+       "tog-watchuploads": "Tuku taagey kaŋ g'i zijandi tonton hawgayhayey ga",
        "tog-watchrollback": "Moɲey kaŋ ay n'i taagandi tonton ay hawgayhayey ga",
        "tog-minordefault": "Fasalyaney kul šilbay sanda ikaynayaŋ nda tilasu",
        "tog-previewontop": "Moofuryan cebe jina fasal bataa ra",
@@ -31,8 +33,8 @@
        "tog-shownumberswatching": "Goykey kaŋ ga moɲoo hawgay hinnaa cebe",
        "tog-oldsig": "Kanbežeeri barantaa:",
        "tog-fancysig": "Kanbežeero tee sanda wikihantum (bila nda nga boŋdobu)",
-       "tog-uselivepreview": "Moofuryan goywaati ra (šiiyan)",
-       "tog-forceeditsummary": "Ay šaawar nda ya na fasal durandiyan dam",
+       "tog-uselivepreview": "Cebe kaŋ a ga dira",
+       "tog-forceeditsummary": "Ay šaawar nda fasal durandiyan koonu ga huru",
        "tog-watchlisthideown": "Ay boŋ fasalyaney tugu hawgayhayey ra",
        "tog-watchlisthidebots": "Maršin fasalyaney tugu hawgayhayey ra",
        "tog-watchlisthideminor": "Fasalyan kayney tugu hawgayhayey ra",
        "noname": "War mana goykaw maa henna kayandi.",
        "loginsuccesstitle": "Huryanoo boori",
        "loginsuccess": "<strong>War huru {{SITENAME}} ra sohõ sanda \"$1\".</strong>",
-       "nosuchuser": "Goykaw kul šii kaŋ ti \"$1\".  \nGoykawmaaɲey ga kula nda harfu azzaati.\nWar hantumoo koroši, wala [[Special:UserLogin/signup|kontu taaga tee]].",
+       "nosuchuser": "Goykaw kul šii kaŋ ti \"$1\".  \nGoykawmaaɲey ga kula nda harfu azzaati.\nWar hantumoo koroši, wala [[Special:CreateAccount|kontu taaga tee]].",
        "nosuchusershort": "Goykaw kul šii kaŋ ti \"$1\".\nHantum-tenjiyan koroši.",
        "nouserspecified": "War ga hima ka goykawmaa kayandi.",
        "login-userblocked": "Goykaw gagayandi. Huruyan šii nda fondo.",
        "accmailtext": "Ɲaami-ra šennikufal kaŋ tee  [[User talk:$1|$1]] sanbandi $2 do. A ga hin ka barmay  <em>[[Special:ChangePassword|šennikufal barmay]] moɲoo ga </em> nda war ga huru.",
        "newarticle": "(Itaaga)",
        "newarticletext": "War hanga dobu kaŋ ka fatta moo foo kaŋ ši bara jina ga.\nKa moɲoo tee, soobay ka hantum ganda bataa ra ([$1 faaba moɲoo] guna ka bay ka tonton.\nNda war n' ka dere ka kaa ne, war ceecikaa <strong>banda</strong>butoŋoo naagu.",
-       "anontalkpagetext": "----\n<em>Kakaw moɲoo woo goo goykaw kaŋ maaɲoo ši bangay se, boro kaɲ mana kontu tee jina, wal'a ši a ka goy.</em>\nAdiši kal'ir ma goy nda hinna IP aderesu ka boraa alhaaloo tabatandi.\nIP aderesu dumoo woo ga hin ka žemnandi goykaw booboyaŋ game.\nNda war ti goykaw kaŋ maaɲoo ši bangay nda war ga tammahã kaŋ war ši kula nda šenney wey, [[Special:UserLogin/signup|kontu tee]] wala [[Special:UserLogin|huru]] ka ganji hiino war nda goykaw taney kaŋ šii nda maa ma birji cere ra. \\",
+       "anontalkpagetext": "----\n<em>Kakaw moɲoo woo goo goykaw kaŋ maaɲoo ši bangay se, boro kaɲ mana kontu tee jina, wal'a ši a ka goy.</em>\nAdiši kal'ir ma goy nda hinna IP aderesu ka boraa alhaaloo tabatandi.\nIP aderesu dumoo woo ga hin ka žemnandi goykaw booboyaŋ game.\nNda war ti goykaw kaŋ maaɲoo ši bangay nda war ga tammahã kaŋ war ši kula nda šenney wey, [[Special:CreateAccount|kontu tee]] wala [[Special:UserLogin|huru]] ka ganji hiino war nda goykaw taney kaŋ šii nda maa ma birji cere ra. \\",
        "noarticletext": "Hantum kul šii moɲoo woo ga sohõda.\nWar ga hin ka [Special:Search/{{PAGENAME}}|moɲoo woo maaɲoo ceeci]] moɲe jerey ra,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|moo={{FULLPAGENAMEE}}}} hantum himantey guna],\nwala [{{fullurl:{{FULLPAGENAME}}|teera=fasal}} moɲoo woo fasal]</span>.",
        "noarticletext-nopermission": "Hantum kul šii moɲoo woo ra sohõda.\nWar ga hin ka [[Special:Search/{{PAGENAME}}|moɲoo woo maaɲoo ceeci]] moɲey jerey ra wala <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|moo={{FULLPAGENAMEE}}}} cceci hantum himantey ra]</span>, amma war ši nda fondo ka moɲoo woo tee.",
        "missing-revision": "Filla #$1 moɲoo kaŋ maaɲoo ti \"{{FULLPAGENAME}}\" se ši bara.\n\nWoo ga doona ka tee nda boro hanga taariki dobu žeena banda kaŋ ga fatta moo tuusante ga.\nŠilbaywey ga hin ka duwandi [{{fullurl:{{#Special:Log}}/tuusu|moo={{FULLPAGENAMEE}}}} tuusuyan taariki] ra.",
index b994d26..61607db 100644 (file)
        "nocookieslogin": "{{SITENAME}} nauduo pakavukus (''cookies''), ka prėkergtom nauduotuojus. Tamsta esat ėšjongis anūs. Prašuom ijongtė pakavukus ė pamiegītė apent.",
        "loginsuccesstitle": "Gerā prėsėjongiet",
        "loginsuccess": "'''Dabā Tamsta esat prėsėjongis prī {{SITENAME}} kāp „$1“.'''",
-       "nosuchuser": "Nier anėjuokė nauduotuojė pavadėnta „$1“.\nPatikrinkat rašība, aba [[Special:UserLogin/signup|padėrbkat naujė paskīra]].",
+       "nosuchuser": "Nier anėjuokė nauduotuojė pavadėnta „$1“.\nPatikrinkat rašība, aba [[Special:CreateAccount|padėrbkat naujė paskīra]].",
        "nosuchusershort": "Nier juokė nauduotuojė, pavadėnta „$1“. Patėkrinkat rašība.",
        "nouserspecified": "Tamstā rēk nuruodītė nauduotuojė varda.",
        "login-userblocked": "Nauduotuos ožgints īr. Pakliūtė nie galama.",
        "accmailtext": "Bikāp padėrbts slaptažuodis, katros prėgol prī [[User talk:$1|$1]] bova siōsts pošto $2. Kāp prėsėjongsat, galat <em>[[Special:ChangePassword|anon parkeistė]]</em>.",
        "newarticle": "(Naus)",
        "newarticletext": "Tamsta pakliovat poslapin, katros dā nie padėrbts.\nJēgo nuorat anon padėrbtė, rašīkat laukė, katros ī apatiuo\n(veiziekat [$1 pagelbas poslapi]).\nJēgo pakliovat čė netīčiuom, paprastiausē paspauskat naršīklės mīgtoka '''atgal'''.",
-       "anontalkpagetext": "----''Tas īr anonimėnė nauduotuojė, katros nier sosėkūrės aba nenauduo paskīruos, aptarėmu poslapis.\nDielē tuo nauduojams IP adresos anuo atpažėnėmō.\nTas IP adresos gal būtė dalinams keletō nauduotuoju.\nJēgo Tamsta esat anonimėnis nauduotuos ėr veizėt, kū kuomentarā nier skėrtė Tamstā, [[Special:UserLogin/signup|sokorkėt paskīra]] aba [[Special:UserLogin|prisėjonkėt]], ė nebūsėt maišuoms so kėtās anonimėnēs nauduotuojās.''",
+       "anontalkpagetext": "----''Tas īr anonimėnė nauduotuojė, katros nier sosėkūrės aba nenauduo paskīruos, aptarėmu poslapis.\nDielē tuo nauduojams IP adresos anuo atpažėnėmō.\nTas IP adresos gal būtė dalinams keletō nauduotuoju.\nJēgo Tamsta esat anonimėnis nauduotuos ėr veizėt, kū kuomentarā nier skėrtė Tamstā, [[Special:CreateAccount|sokorkėt paskīra]] aba [[Special:UserLogin|prisėjonkėt]], ė nebūsėt maišuoms so kėtās anonimėnēs nauduotuojās.''",
        "noarticletext": "Nūnā tamė poslapie nie nė juokė teksta.\nTamsta galat [[Special:Search/{{PAGENAME}}|ėiškuotė ton poslapė pavadėnėma]] terp kėtū poslapiū,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ėiškuotė prėgolontiu īrašu],\naba [{{fullurl:{{FULLPAGENAME}}|action=edit}} keistė ton poslapi]</span>.",
        "noarticletext-nopermission": "Nūnā tamė poslapie nier anėjuokė teksta.\nTamsta galat [[Special:Search/{{PAGENAME}}|ėiškuotė šėtuo poslapė pavadėnėma]] kėtūs poslapiūs,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ėiškuotė prėgolontiūm ragėstru]</span>.",
        "userpage-userdoesnotexist": "Nauduotuojė paskīra „<nowiki>$1</nowiki>“ nier ožregėstrouta. Prašuom patikrėntė, a Tamsta nuorėt kortė/keistė ta poslapi.",
index 26fea82..e8e1631 100644 (file)
        "noname": "Niste izabrali ispravno korisničko ime.",
        "loginsuccesstitle": "Prijavljivanje uspješno",
        "loginsuccess": "Trenutno ste prijavljeni na {{SITENAME}} kao \"$1\".",
-       "nosuchuser": "Ne postoji korisnik sa imenom \"$1\".\nKorisnička imena razlikuju velika i mala slova.\nProvjerite vaše kucanje ili [[Special:UserLogin/signup|napravite novi korisnički račun]].",
+       "nosuchuser": "Ne postoji korisnik sa imenom \"$1\".\nKorisnička imena razlikuju velika i mala slova.\nProvjerite vaše kucanje ili [[Special:CreateAccount|napravite novi korisnički račun]].",
        "nosuchusershort": "Ne postoji korisnik sa imenom \"$1\".\nProvjerite da li ste dobro ukucali.",
        "nouserspecified": "Morate izabrati korisničko ime.",
        "login-userblocked": "Ovaj korisnik je blokiran. Prijava nije dozvoljena.",
        "accmailtext": "Nasumično odabrana šifra za [[User talk:$1|$1]] je poslata na adresu $2.\n\nŠifra/lozinka za ovaj novi račun može biti promijenjena na stranici ''[[Special:ChangePassword|izmjene šifre]]'' nakon prijave.",
        "newarticle": "(Novi)",
        "newarticletext": "Preko linka ste došli na stranicu koja još uvijek ne postoji.\n* Ako želite stvoriti stranicu, počnite tipkati u okviru dolje (v. [$1 stranicu za pomoć] za više informacija).\n* Ukoliko ste došli greškom, pritisnike dugme '''Nazad''' ('''back''') na vašem pregledniku.",
-       "anontalkpagetext": "----''Ovo je stranica za razgovor za anonimnog korisnika koji još nije napravio račun ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo identifikovali njega ili nju.\nTakvu adresu može dijeliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene nebitne primjedbe, molimo Vas da [[Special:UserLogin/signup|napravite račun]] ili se [[Special:UserLogin|prijavite]] da biste izbjegli buduću zabunu sa ostalim anonimnim korisnicima.''",
+       "anontalkpagetext": "----''Ovo je stranica za razgovor za anonimnog korisnika koji još nije napravio račun ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo identifikovali njega ili nju.\nTakvu adresu može dijeliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene nebitne primjedbe, molimo Vas da [[Special:CreateAccount|napravite račun]] ili se [[Special:UserLogin|prijavite]] da biste izbjegli buduću zabunu sa ostalim anonimnim korisnicima.''",
        "noarticletext": "Na ovoj stranici trenutno nema teksta.\nMožete [[Special:Search/{{PAGENAME}}|tražiti naslov ove stranice]] u drugim stranicama,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretraživati srodne registre],\nili [{{fullurl:{{FULLPAGENAME}}|action=edit}} urediti ovu stranicu]</span>.",
        "noarticletext-nopermission": "Trenutno nema teksta na ovoj stranici.\nMožete [[Special:Search/{{PAGENAME}}|tražiti ovaj naslov stranice]] na drugim stranicama ili <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane registre]</span>. alio nemate dozvolu za stvaranje ove stranice.",
        "missing-revision": "Ne mogu da pronađem izmenu br. $1 na stranici pod nazivom „{{FULLPAGENAME}}“.\n\nOvo se obično dešava kada pratite zastarjelu vezu do stranice koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} evidenciji brisanja].",
        "upload-form-label-infoform-description": "Opis",
        "upload-form-label-usage-title": "Korištenje",
        "upload-form-label-usage-filename": "Ime datoteke",
-       "foreign-structured-upload-form-label-own-work": "Ovo je moje djelo",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorije",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Potvrđujem kako postavljam ovu datoteku u skladu sa uvjetima korištenja i politikom licenciranja na {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Ukoliko niste u stanju postaviti ovu datoteku pod politikom {{SITENAME}}, molimo zatvorite ovaj dijalog i pokušajte drugom metodom.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Također možete pokušati [[Special:Upload|na standarnoj stranici za postavljanje]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Razumijem kako postavljam ovu datoteku na dijeljeno skladište. Potvrđujem kako to činim u skladu sa uvjetima korištenja i ovdašnjom politikom licenciranja.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Ukoliko niste u stanju postaviti ovu datoteku pod politikom dijeljene ostave, molimo zatvorite ovaj dijalog i pokušajte drugu metodu.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Možete također pokušati koristeći  [[Special:Upload|stranicu za postavljanje na  {{SITENAME}}]], ukoliko se ova datoteka može postaviti pod tamošnjom politikom.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Potvrđujem da posjedujem autorska prava za ovu datoteku i slažem se da ću je neopozivo postaviti na Wikimedia Commons pod licencom [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], te se slažem s [https://wikimediafoundation.org/wiki/Terms_of_Use Uvjetima korištenja].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Ako ne posjedujete autorska prava za ovu datoteku ili je želite postaviti pod drugom licencom, imajte na umu da možete koristiti [https://commons.wikimedia.org/wiki/Special:UploadWizard čarobnjak za postavljanje datoteka na Commonsu].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Također možete koristiti [[Special:Upload|stranicu za postavljanje datoteka na projektu {{SITENAME}}]] ako politika stranice dozvoljava postavljanje ove datoteke.",
+       "upload-form-label-own-work": "Ovo je moje djelo",
+       "upload-form-label-infoform-categories": "Kategorije",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Potvrđujem kako postavljam ovu datoteku u skladu sa uvjetima korištenja i politikom licenciranja na {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Ukoliko niste u stanju postaviti ovu datoteku pod politikom {{SITENAME}}, molimo zatvorite ovaj dijalog i pokušajte drugom metodom.",
+       "upload-form-label-not-own-work-local-generic-local": "Također možete pokušati [[Special:Upload|na standarnoj stranici za postavljanje]].",
+       "upload-form-label-own-work-message-generic-foreign": "Razumijem kako postavljam ovu datoteku na dijeljeno skladište. Potvrđujem kako to činim u skladu sa uvjetima korištenja i ovdašnjom politikom licenciranja.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Ukoliko niste u stanju postaviti ovu datoteku pod politikom dijeljene ostave, molimo zatvorite ovaj dijalog i pokušajte drugu metodu.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Možete također pokušati koristeći  [[Special:Upload|stranicu za postavljanje na  {{SITENAME}}]], ukoliko se ova datoteka može postaviti pod tamošnjom politikom.",
        "backend-fail-stream": "Ne mogu da emitujem datoteku $1.",
        "backend-fail-backup": "Ne mogu da napravim rezervu datoteke $1.",
        "backend-fail-notexists": "Datoteka $1 ne postoji.",
index b02efbb..71a8157 100644 (file)
        "createaccounterror": "$1 ur as tufit at kcmt",
        "loginsuccesstitle": "Tkcmt mzyan (tllit ɣil ɣ ifalan)",
        "loginsuccess": "Tllit ɣilad ɣ ifalan (tzdit d tuqqna) {{GENDER:$1|||}} ar {{SITENAME}} zund « $1 ».",
-       "nosuchuser": "Asqdac « $1 » ur illi.\nUssaɣ n isqdacn ḥiln hlli.\nẒṛ daɣ ist turit mzyan mayad, niɣd [[Special:UserLogin/signup|tmmurẓmt amiḍan amaynu]].",
+       "nosuchuser": "Asqdac « $1 » ur illi.\nUssaɣ n isqdacn ḥiln hlli.\nẒṛ daɣ ist turit mzyan mayad, niɣd [[Special:CreateAccount|tmmurẓmt amiḍan amaynu]].",
        "nosuchusershort": "Ur illa umsaws lli ilan assaɣ « $1 ». Ẓṛ ist turit mzyan mayad.",
        "nouserspecified": "Illa fllak ad tarat assaɣ nk.",
        "login-userblocked": "Asqdac ad ur as yufi ad ikcm. Tazdayt ɣ ifalan uras ttuyskar",
index e4c3484..6844955 100644 (file)
@@ -31,7 +31,8 @@
                        "Macofe",
                        "Roonyh",
                        "Matma Rex",
-                       "SusithCM"
+                       "SusithCM",
+                       "Sandaru"
                ]
        },
        "tog-underline": "සබැඳි යටීර කිරීම:",
        "tog-newpageshidepatrolled": "විමසුමට ලක්කෙරුණු පිටු, අළුත් පිටු ලැයිස්තුව තුල නොපෙන්වන්න",
        "tog-hidecategorization": "පිටුවේ ප්‍රවර්ගීකරණය සගවන්න",
        "tog-extendwatchlist": "මෑත වෙනස්වීම් පමණක් නොව, අදාළ සියළු වෙනස්වීම් දක්වා පෙන්වන අයුරින් මුර-ලැයිස්තුව පුළුල් කරන්න",
-       "tog-usenewrc": "මෑත වෙනස්වීම් සහ මුර ලැයිස්තුව හී පිටුව අනුව සමූහ වෙනස්වීම් (ජාවාස්ක්‍රිප්ට් ඇවැසිය)",
+       "tog-usenewrc": "මෑත වෙනස්වීම් සහ මුර ලැයිස්තුව හී පිටුව අනුව සමූහ වෙනස්වීම්",
        "tog-numberheadings": "ශීර්ෂ-නාම ස්වයංක්‍රීයව අංකනය කරන්න",
        "tog-showtoolbar": "සංස්කරණ මෙවලම්තීරුව පෙන්වන්න",
        "tog-editondblclick": "ද්විත්ව-ක්ලික් කිරීම මගින් පිටු සංස්කරණය අරඹන්න",
-       "tog-editsectiononrightclick": "ඡේද ශීර්ෂ මත දකුණු-ක්ලික් කිරීමෙන් ඡේද සංස්කරණය සක්‍රීය කරන්න (ජාවාස්ක්‍රිප්ට්)",
+       "tog-editsectiononrightclick": "ඡේද ශීර්ෂ මත දකුණු-ක්ලික් කිරීමෙන් ඡේද සංස්කරණය සක්‍රීය කරන්න",
        "tog-watchcreations": "මම තනන පිටු හා මම උඩුගත කරන ගොනු මාගේ මුරලැයිස්තුවට එක් කරන්න",
        "tog-watchdefault": "මම සංස්කරණය කරන පිටු හා ගොනු මාගේ මුර ලැයිස්තුවට එක් කරන්න",
        "tog-watchmoves": "මම ගෙනයන පිටු හා ගොනු මාගේ මුර ලැයිස්තුවට එක් කරන්න",
        "noname": "වලංගු පරිශීලක-නාමයක් සඳහන් කිරීමට ඔබ අසමත් වී ඇත.",
        "loginsuccesstitle": "පිවිසුම සාර්ථකයි!",
        "loginsuccess": "'''දැන් ඔබ , \"$1\" ලෙස, {{SITENAME}} වෙත පිවිස සිටී.'''",
-       "nosuchuser": "\"$1\" යන නමැති පරිශීලකයෙකු නොමැත.\nපරිශීලක නාමයන්හි මහාප්‍රාණ ආදිය සැලකේ (case sensitive).\nඔබගේ අක්ෂර-වින්‍යාසය පිරික්සා බැලීම හෝ, [[Special:UserLogin/signup|නව ගිණුමක් තැනීම]] හෝ සිදුකරන්න.",
+       "nosuchuser": "\"$1\" යන නමැති පරිශීලකයෙකු නොමැත.\nපරිශීලක නාමයන්හි මහාප්‍රාණ ආදිය සැලකේ (case sensitive).\nඔබගේ අක්ෂර-වින්‍යාසය පිරික්සා බැලීම හෝ, [[Special:CreateAccount|නව ගිණුමක් තැනීම]] හෝ සිදුකරන්න.",
        "nosuchusershort": "\"$1\" නමින් පරිශීලකයෙකු නොමැත.\nඅක්‍ෂර-වින්‍යාසය පිරික්සා බලන්න.",
        "nouserspecified": "ඔබ විසින් පරිශීලක-නාමයක් සඳහන් කල යුතු වේ.",
        "login-userblocked": "මෙම පරිශීලකයා වාරණය කොට ඇත. පිවිසීමට ඉඩ දෙනු නොලැබේ.",
        "accmailtext": "[[User talk:$1|$1]] සඳහා අහඹු ලෙස ජනනය කරන ලද මුරපදයක් $2 වෙත යවා ඇත.\n\nමෙම නව ගිණුම සඳහා මුරපදය, ප්‍රවිෂ්ට වීමෙන් අනතුරුව, ''[[Special:ChangePassword|මුර පදය වෙනස් කරන්න]]''  පිටුව තුලදී වෙනස් කල හැක.",
        "newarticle": "(නව)",
        "newarticletext": "බැඳියක් ඔස්සේ පැමිණ ඔබ පිවිස ඇත්තේ දැනට නොපවතින පිටුවකටයි.\nමෙම ලිපිය තැනීමට අවශ්‍ය නම්, පහත ඇති කොටුව තුල අකුරු ලිවීම අරඹන්න (වැඩිදුර තොරතුරු සඳහා [$1 උදවු පිටුව] බලන්න).\nඔබ මෙහි පිවිස ඇත්තේ අත්වැරැද්දකින් නම්, ඔබගේ ගවේෂකයෙහි '''ආපසු''' බොත්තම ඔබන්න.",
-       "anontalkpagetext": "----''මෙම සංවාද පිටුව අයත් වන්නේ තවමත් ගිණුමක් තනා නැති හෝ එසේ කොට එනමුදු එය භාවිතා නොකරන හෝ නිර්නාමික පරිශීලකයෙකුටය.\nඑබැවින්, ඔහු/ඇය හැඳින්වීමට සංඛ්‍යාත්මක IP ලිපිනය භාවිතා කිරීමට අප හට සිදුවේ.\nපරිශීලකයන් කිහිප දෙනෙකු විසින් මෙවැනි IP ලිපිනයක් හවුලේ පරිහරණය කරනවා විය හැක.\nඔබ නිර්නාමික පරිශීලකයෙකු නම් හා ඔබ පිළිබඳ අනනුකූල පරිකථනයන් සිදුවෙන බවක් ඔබට හැ‍ඟේ නම්, අනෙකුත් නිර්නාමික පරිශීලකයන් හා සමග  මෙවැනි සංකූලතා ඇතිවීම වලක්වනු වස්,  කරුණාකර  [[Special:UserLogin/signup|ගිණුමක් තැනීමට]] හෝ [[Special:UserLogin|ප්‍රවිෂ්ට වීමට]]  කාරුණික වන්න.''",
+       "anontalkpagetext": "----''මෙම සංවාද පිටුව අයත් වන්නේ තවමත් ගිණුමක් තනා නැති හෝ එසේ කොට එනමුදු එය භාවිතා නොකරන හෝ නිර්නාමික පරිශීලකයෙකුටය.\nඑබැවින්, ඔහු/ඇය හැඳින්වීමට සංඛ්‍යාත්මක IP ලිපිනය භාවිතා කිරීමට අප හට සිදුවේ.\nපරිශීලකයන් කිහිප දෙනෙකු විසින් මෙවැනි IP ලිපිනයක් හවුලේ පරිහරණය කරනවා විය හැක.\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}}|මෙම පිටු ශීර්ෂය සඳහා ගවේශනය කිරීම]] හෝ, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}අදාළ ලඝු-සටහන් සඳහා ගවේෂණය කිරීම]</span>, හෝ මෙම පිටුව සංස්කරණය කිරීම හෝ ඔබට කල හැක.",
        "missing-revision": "සංශෝධනය නම් පිටුවේ #$1 \"{{FULLPAGENAME}}\" නොපවතියි.\n\nමෙය සාමාන්යයෙන් මකා දැමූ පිටුවක ඉතිහාසය සබැඳියන් යල් පැනගිය පහත සඳහන් හේතු වේ [{{fullurl:{{#Special:Log}}/මකන්න|page={{FULLPAGENAMEE}}}} මැකීමේ ලොගය].",
        "upload-form-label-infoform-description": "විස්තරය",
        "upload-form-label-usage-title": "භාවිතයන්",
        "upload-form-label-usage-filename": "ගොනු නාමය",
-       "foreign-structured-upload-form-label-own-work": "මෙය මගේ ස්වකීය නිර්මාණයකි",
-       "foreign-structured-upload-form-label-infoform-categories": "ප්‍රවර්ග",
-       "foreign-structured-upload-form-label-infoform-date": "දිනය",
+       "upload-form-label-own-work": "මෙය මගේ ස්වකීය නිර්මාණයකි",
+       "upload-form-label-infoform-categories": "ප්‍රවර්ග",
+       "upload-form-label-infoform-date": "දිනය",
        "backend-fail-stream": "$1 ගොනුව ප්‍රවාහ කල නොහැක.",
        "backend-fail-backup": "$1 ගොනුව උපස්ථ කල නොහැක.",
        "backend-fail-notexists": "$1 ගොනුව නොපවතියි.",
index 1fcafaa..24dd68c 100644 (file)
        "noname": "Nezadali ste platné používateľské meno.",
        "loginsuccesstitle": "Prihlásenie úspešné",
        "loginsuccess": "'''Teraz ste prihlásený do {{GRAMMAR:genitív|{{SITENAME}}}} ako „$1“.'''",
-       "nosuchuser": "Používateľské meno „$1“ neexistuje.\nV používateľských menách sa rozlišuje veľkosť písmen.\nSkontrolujte preklepy alebo sa [[Special:UserLogin/signup|zaregistrujte ako nový používateľ]].",
+       "nosuchuser": "Používateľské meno „$1“ neexistuje.\nV používateľských menách sa rozlišuje veľkosť písmen.\nSkontrolujte preklepy alebo sa [[Special:CreateAccount|zaregistrujte ako nový používateľ]].",
        "nosuchusershort": "V súčasnosti neexistuje používateľ s menom „$1“. Skontrolujte preklepy.",
        "nouserspecified": "Musíte uviesť meno používateľa.",
        "login-userblocked": "Tento používateľ je zablokovaný. Nie je mu dovolené prihlásiť sa.",
        "accmailtext": "Náhodne vytvorené heslo pre používateľa [[User talk:$1|$1]] bolo poslané na $2. Je možné ho zmeniť na stránke ''[[Special:ChangePassword|zmena hesla]]'' po prihlásení.",
        "newarticle": "(Nový)",
        "newarticletext": "Sledovali ste odkaz na stránku, ktorá zatiaľ neexistuje.\nStránku vytvoríte tak, že začnete písať do poľa nižšie (viac informácií nájdete na stránkach [$1 nápovedy]).\nAk ste sa sem dostali nechtiac, kliknite na tlačidlo <strong>späť</strong> vo svojom prehliadači.",
-       "anontalkpagetext": "----''Toto je diskusná stránka anonymného používateľa, ktorý nemá vytvorené svoje konto alebo ho nepoužíva.\nPreto musíme na jeho identifikáciu použiť numerickú IP adresu. Je možné, že takúto IP adresu používajú viacerí používatelia.\nAk ste anonymný používateľ a máte pocit, že vám boli adresované irelevantné diskusné príspevky, [[Special:UserLogin/signup|vytvorte si konto]] alebo sa [[Special:UserLogin|prihláste]], aby sa zamedzilo budúcim zámenám s inými anonymnými používateľmi.''",
+       "anontalkpagetext": "----''Toto je diskusná stránka anonymného používateľa, ktorý nemá vytvorené svoje konto alebo ho nepoužíva.\nPreto musíme na jeho identifikáciu použiť numerickú IP adresu. Je možné, že takúto IP adresu používajú viacerí používatelia.\nAk ste anonymný používateľ a máte pocit, že vám boli adresované irelevantné diskusné príspevky, [[Special:CreateAccount|vytvorte si konto]] alebo sa [[Special:UserLogin|prihláste]], aby sa zamedzilo budúcim zámenám s inými anonymnými používateľmi.''",
        "noarticletext": "Na tejto stránke sa momentálne nenachádza žiadny text.\nMôžete [[Special:Search/{{PAGENAME}}|vyhľadávať názov tejto stránky]] v obsahu iných stránok,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} vyhľadávať v súvisiacich záznamoch] alebo [{{fullurl:{{FULLPAGENAME}}|action=edit}} vytvoriť túto stránku]</span>.",
        "noarticletext-nopermission": "Táto stránka momentálne neobsahuje žiadny text.\nMôžete [[Special:Search/{{PAGENAME}}|hľadať názov tejto stránky]] v texte iných stránok\nalebo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hľadať v súvisiacich záznamoch]</span>, ale nemáte oprávnenie túto stránku vytvoriť.",
        "missing-revision": "Revízia #$1 stránky s názvom „{{FULLPAGENAME}}“ neexistuje.\n\nPravdepodobne ste nasledovali zastaraný odkaz na historickú verziu stránky, ktorá bola medzičasom odstránená.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname zmazaní].",
index 2bfa128..ac9345e 100644 (file)
@@ -54,7 +54,7 @@
        "tog-ccmeonemails": "Pošlji mi kopijo e-pošt, ki jih pošljem drugim uporabnikom",
        "tog-diffonly": "Pod primerjavo ne prikaži vsebine strani",
        "tog-showhiddencats": "Prikaži skrite kategorije",
-       "tog-norollbackdiff": "Ne prikaži primerjave po izvedeni vrnitvi",
+       "tog-norollbackdiff": "Po izvedeni vrnitvi ne prikaži primerjave",
        "tog-useeditwarning": "Opozori me, ko skušam zapreti urejevalno polje z neshranjenimi spremembami",
        "tog-prefershttps": "Med prijavo vedno uporabljaj varno povezavo",
        "underline-always": "Vedno",
        "noname": "Niste vnesli veljavnega uporabniškega imena.",
        "loginsuccesstitle": "Sedaj ste prijavljeni",
        "loginsuccess": "'''Zdaj ste prijavljeni v {{GRAMMAR:tožilnik|{{SITENAME}}}} kot »$1«.'''",
-       "nosuchuser": "Uporabnik z imenom »$1« ne obstaja.\nUporabniška imena so občutljiva na velikost črk.\nPreverite črkovanje ali pa si [[Special:UserLogin/signup|ustvarite nov uporabniški račun]].",
+       "nosuchuser": "Uporabnik z imenom »$1« ne obstaja.\nUporabniška imena so občutljiva na velikost črk.\nPreverite črkovanje ali pa si [[Special:CreateAccount|ustvarite nov uporabniški račun]].",
        "nosuchusershort": "Uporabnik z imenom »$1« ne obstaja.\nPreverite črkovanje.",
        "nouserspecified": "Prosimo, vpišite uporabniško ime.",
        "login-userblocked": "Ta uporabnik je blokiran. Prijava ni dovoljena.",
        "accmailtext": "Naključno generirano geslo za [[User talk:$1|$1]] smo poslali na $2. Po prijavi ga lahko spremenite na strani za ''[[Special:ChangePassword|spremembo gesla]]''.",
        "newarticle": "(Nov)",
        "newarticletext": "Sledili ste povezavi na stran, ki še ne obstaja.\nDa bi stran ustvarili, vnesite v spodnji obrazec besedilo\n(za več informacij glej [$1 pomoč]).\nČe ste sem prišli po pomoti, v svojem brskalniku kliknite gumb ''Nazaj''.",
-       "anontalkpagetext": "---- ''To je pogovorna stran brezimnega uporabnika, ki si še ni ustvaril računa ali pa ga ne uporablja. Zaradi tega moramo uporabiti IP-naslov za njegovo/njeno ugotavljanje istovetnosti. Takšen IP-naslov si lahko deli več uporabnikov. Če ste brezimni uporabnik in menite, da so nepomembne pripombe namenjene vam, prosimo [[Special:UserLogin|ustvarite račun]] ali pa se [[Special:UserLogin/signup|vpišite]], da preprečite zmedo z drugimi nepodpisanimi uporabniki.''",
+       "anontalkpagetext": "---- ''To je pogovorna stran brezimnega uporabnika, ki si še ni ustvaril računa ali pa ga ne uporablja. Zaradi tega moramo uporabiti IP-naslov za njegovo/njeno ugotavljanje istovetnosti. Takšen IP-naslov si lahko deli več uporabnikov. Če ste brezimni uporabnik in menite, da so nepomembne pripombe namenjene vam, prosimo [[Special:UserLogin|ustvarite račun]] ali pa se [[Special:CreateAccount|vpišite]], da preprečite zmedo z drugimi nepodpisanimi uporabniki.''",
        "noarticletext": "Na tej strani ni trenutno nobenega besedila. Naslov strani lahko poskusite [[Special:Search/{{PAGENAME}}|poiskati]] na drugih straneh, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} v dnevniških zapisih] ali pa [{{fullurl:{{FULLPAGENAME}}|action=edit}} stran ustvarite]</span>.",
        "noarticletext-nopermission": "Na strani trenutno ni nobenega besedila.\nLahko poskusite [[Special:Search/{{PAGENAME}}|poiskati naslov strani]] na drugih straneh ali <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} v povezanih dnevniških zapisih]</span>, vendar za ustvarjanje strani nimate zadostnih dovoljenj.",
        "missing-revision": "Redakcija št. $1 strani »{{FULLPAGENAME}}« ne obstaja.\n\nPo navadi se to zgodi, ko sledite zastareli povezavi na zgodovino strani, ki jo je nekdo izbrisal.\nPodrobnosti lahko najdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} dnevniku brisanja].",
        "right-override-export-depth": "Izvoz strani, vključno s povezaimi straneh do globine 5",
        "right-sendemail": "Pošiljanje e-pošte drugim uporabnikom",
        "right-passwordreset": "Ogled e-pošt ponastavitve gesel",
-       "right-managechangetags": "Ustvarjanje in brisanje [[Special:Tags|oznak]] iz zbirke podatkov",
+       "right-managechangetags": "Ustvarjanje in (dez)aktivacijo [[Special:Tags|oznak]]",
        "right-applychangetags": "Uveljavitev [[Special:Tags|oznak]] skupaj s spremembami",
        "right-changetags": "Dodajanje in odstranjevanje poljubnih [[Special:Tags|oznak]] na posameznih redakcijah in dnevniških vnosih",
+       "right-deletechangetags": "Izbris [[Special:Tags|oznak]] iz zbirke podatkov",
        "grant-generic": "Snov pravic »$1«",
        "grant-group-page-interaction": "Interakcija s stranmi",
        "grant-group-file-interaction": "Interakcija s predstavnostjo",
        "action-viewmyprivateinfo": "ogled svojih zasebnih informacij",
        "action-editmyprivateinfo": "urejanje svojih zasebnih informacij",
        "action-editcontentmodel": "urejanje vsebinskega modela strani",
-       "action-managechangetags": "ustvarjanje in brisanje oznak iz zbirke podatkov",
+       "action-managechangetags": "ustvarjanje in (dez)aktivacijo oznak",
        "action-applychangetags": "uveljavitev oznak skupaj z vašimi spremembami",
        "action-changetags": "dodajanje in odstranjevanje poljubnih oznak na posameznih redakcijah in dnevniških vnosih",
+       "action-deletechangetags": "izbris oznak iz zbirke podatkov",
        "nchanges": "$1 {{PLURAL:$1|sprememba|spremembi|spremembe|sprememb|sprememb}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od zadnjega obiska}}",
        "enhancedrc-history": "zgodovina",
        "upload-form-label-infoform-description-tooltip": "Na kratko opišite vse opazno o delu.\nPri fotografijah omenite glavne stvari, ki so upodobljene, priložnost ali kraj.",
        "upload-form-label-usage-title": "Uporaba",
        "upload-form-label-usage-filename": "Ime datoteke",
-       "foreign-structured-upload-form-label-own-work": "To je moje lastno delo",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorije",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Potrjujem, da datoteko nalagam v skladu s pogoji uporabe in pravili o licenciranju na {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Če datoteke ne morete naložiti pod pogoji {{SITENAME}}, zaprite to okno in poskusite drugo metodo.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Morda želite poskusiti [[Special:Upload|privzeto stran za nalaganje]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Razumem, da datoteko nalagam v deljeno hrambo. Potrjujem, da to počnem v skladu s tukajšnjimi pogoji uporabe in pravili za licenciranje.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Če datoteke ne morete naložiti pod pogoji deljene hrambe, zaprite to okno in poskusite drugo metodo.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Morda želite datoteko poskusiti naložiti na [[Special:Upload|strani za nalaganje na {{SITENAME}}]], če jo lahko naložite pod njihovimi pravili.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Izjavljam, da sem lastnik avtorskih pravic te datoteke, strinjam se z nepreklicno objavo datoteke v Wikimedijini Zbirki pod dovoljenjem [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Priznanje avtorstva-Deljenje pod enakimi pogoji 4.0] in strinjam se s [https://wikimediafoundation.org/wiki/Terms_of_Use Pogoji uporabe].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Če niste lastnik avtorskih pravic datoteke ali jo želite objaviti pod drugačnim dovoljenje, uporabite [https://commons.wikimedia.org/wiki/Special:UploadWizard Čarovnik za nalaganje v Zbirko].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Morda želite datoteko poskusiti naložiti na [[Special:Upload|strani za nalaganje na {{SITENAME}}]], če stran dovoljuje nalaganje datoteke pod njihovimi pravili.",
+       "upload-form-label-own-work": "To je moje lastno delo",
+       "upload-form-label-infoform-categories": "Kategorije",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Potrjujem, da datoteko nalagam v skladu s pogoji uporabe in pravili o licenciranju na {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Če datoteke ne morete naložiti pod pogoji {{SITENAME}}, zaprite to okno in poskusite drugo metodo.",
+       "upload-form-label-not-own-work-local-generic-local": "Morda želite poskusiti [[Special:Upload|privzeto stran za nalaganje]].",
+       "upload-form-label-own-work-message-generic-foreign": "Razumem, da datoteko nalagam v deljeno hrambo. Potrjujem, da to počnem v skladu s tukajšnjimi pogoji uporabe in pravili za licenciranje.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Če datoteke ne morete naložiti pod pogoji deljene hrambe, zaprite to okno in poskusite drugo metodo.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Morda želite datoteko poskusiti naložiti na [[Special:Upload|strani za nalaganje na {{SITENAME}}]], če jo lahko naložite pod njihovimi pravili.",
        "backend-fail-stream": "Ne morem pretakati datoteke $1.",
        "backend-fail-backup": "Ne morem varnostno kopirati datoteke $1.",
        "backend-fail-notexists": "Datoteka $1 ne obstaja.",
        "changecontentmodel-success-text": "Spremenili smo vrsto vsebine [[:$1]].",
        "changecontentmodel-cannot-convert": "Vsebine na [[:$1]] ni mogoče pretvoriti v vrsto $2.",
        "changecontentmodel-nodirectediting": "Model vsebine $1 ne podpira neposrednega urejanja",
+       "changecontentmodel-emptymodels-title": "Na voljo ni noben model vsebine",
+       "changecontentmodel-emptymodels-text": "Vsebine na [[:$1]] ni mogoče pretvoriti v katero koli vrsto.",
        "log-name-contentmodel": "Dnevnik sprememb modela vsebine",
        "log-description-contentmodel": "Dogodki, povezani z modeli vsebin strani",
        "logentry-contentmodel-new": "$1 je {{GENDER:$2|ustvaril|ustvarila|ustvaril(-a)}} stran $3 z neprivzetim modelom vsebine »$5«",
        "whatlinkshere-prev": "{{PLURAL:$1|prejšnji|prejšnja $1|prejšnji $1|prejšnjih $1|prejšnjih $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|naslednji|naslednja $1|naslednji $1|naslednjih $1|naslednjih $1}}",
        "whatlinkshere-links": "← povezave",
-       "whatlinkshere-hideredirs": "$1 preusmeritve",
-       "whatlinkshere-hidetrans": "$1 vključitve",
-       "whatlinkshere-hidelinks": "$1 povezave",
-       "whatlinkshere-hideimages": "$1 povezave datotek",
+       "whatlinkshere-hideredirs": "Skrij preusmeritve",
+       "whatlinkshere-hidetrans": "Skrij vključitve",
+       "whatlinkshere-hidelinks": "Skrij povezave",
+       "whatlinkshere-hideimages": "Skrij povezave datotek",
        "whatlinkshere-filters": "Filtri",
        "whatlinkshere-submit": "Pojdi",
        "autoblockid": "Samodejna blokada št. $1",
        "lockdbsuccesstext": "Podatkovna baza je bila zaklenjena.<br />\nNe pozabite je [[Special:UnlockDB|odkleniti]], ko boste končali z vzdrževanjem.",
        "unlockdbsuccesstext": "Zbirka podatkov {{GRAMMAR:rodilnik|{{SITENAME}}}} je spet odklenjena.",
        "lockfilenotwritable": "Datoteka zaklepanja zbirke podatkov ni zapisljiva.\nZa zaklepanje in odklepanje zbirke podatkov mora biti ta datoteka zapisljiva s strani spletnega strežnika.",
+       "databaselocked": "Zbirka podatkov je že zaklenjena.",
        "databasenotlocked": "Zbirka podatkov ni zaklenjena.",
        "lockedbyandtime": "($1 dne $2 ob $3)",
        "move-page": "Preimenuj $1",
        "timezone-local": "Krajevno",
        "duplicate-defaultsort": "'''Opozorilo:''' Privzeti ključ razvrščanja »$2« prepiše prejšnji privzeti ključ razvrščanja »$1«.",
        "duplicate-displaytitle": "<strong>Opozorilo:</strong> Prikazni naslov »$2« prepiše prejšnji prikazni naslov »$1«.",
+       "restricted-displaytitle": "<strong>Opozorilo:</strong> Prikazni naslov »$1« smo prezrli, saj ni enak dejanskemu naslovu strani.",
        "invalid-indicator-name": "<strong>Napaka:</strong> Atribut <code>name</code> indikatorjev stanja strani ne sme biti prazen.",
        "version": "Različica",
        "version-extensions": "Nameščene razširitve",
        "tags-delete-not-found": "Oznaka »$1« ne obstaja.",
        "tags-delete-too-many-uses": "Oznaka »$1« je uporabljena pri več kot $2 {{PLURAL:$2|redakciji|redakcijah}}, kar pomeni, da je ni mogoče izbrisati.",
        "tags-delete-warnings-after-delete": "Oznako »$1« smo izbrisali, vendar smo naleteli na {{PLURAL:$2|naslednjo težavo|naslednji težavi|naslednje težave}}:",
+       "tags-delete-no-permission": "Nimate dovoljenja za izbris oznak sprememb.",
        "tags-activate-title": "Aktiviraj oznako",
        "tags-activate-question": "Aktivirali boste oznako »$1«.",
        "tags-activate-reason": "Razlog:",
        "feedback-useragent": "Uporabniški agent:",
        "searchsuggest-search": "Iskanje",
        "searchsuggest-containing": "vsebujoč ...",
+       "api-error-autoblocked": "Vaš IP-naslov smo samodejno blokirali, saj ga je uporabljal blokiran uporabnik.",
        "api-error-badaccess-groups": "Nalaganje datotek na ta wiki vam ni dovoljeno.",
        "api-error-badtoken": "Notranja napaka: slab žeton.",
+       "api-error-blocked": "Urejanje vam je preprečeno.",
        "api-error-copyuploaddisabled": "Nalaganje preko URL je na tem strežniku onemogočeno.",
        "api-error-duplicate": "Na strani že {{PLURAL:$1|obstaja druga datoteka|obstajata drugi datoteki|obstajajo druge datoteke}} z enako vsebino.",
        "api-error-duplicate-archive": "Na strani {{PLURAL:$1|je že bila druga datoteka|sta že bili drugi datoteki|so že bile nekatere druge datoteke}} z enako vsebino, vendar {{PLURAL:$1|je bila izbrisana|sta bili izbrisani|so bile izbrisane}}.",
        "api-error-nomodule": "Notranja napaka: modul nalaganja ni izbran.",
        "api-error-ok-but-empty": "Notranja napaka: strežnik se ne odziva.",
        "api-error-overwrite": "Prepisovanje obstoječe datoteke ni dovoljeno.",
+       "api-error-ratelimited": "Poskušate naložiti več datotek v kratkem časovnem obdobju kot to dovoljuje ta wiki. Prosimo, poskusite znova čez nekaj minut.",
        "api-error-stashfailed": "Notranja napaka: strežnik ni uspel shraniti začasne datoteke.",
        "api-error-publishfailed": "Notranja napaka: strežnik ni uspel objaviti začasne datoteke.",
        "api-error-stasherror": "Pri nalaganju datoteke v hrambo je prišlo do napake.",
index 25e1648..693b6c5 100644 (file)
        "noname": "Du muußt enn giltiga Nutzernoama oangahn.",
        "loginsuccesstitle": "Oameldung erfolgreich",
        "loginsuccess": "Du biest jitz ols „$1“ bei {{SITENAME}} oagemeldet.",
-       "nosuchuser": "Dar Nutzernoame „$1“ existiert ne.\nIeberpriefe de Schreibweise (Gruß-/Kleenschreibung beachta) oder [[Special:UserLogin/signup|melde diech ols neuer Benutzer oa]].",
+       "nosuchuser": "Dar Nutzernoame „$1“ existiert ne.\nIeberpriefe de Schreibweise (Gruß-/Kleenschreibung beachta) oder [[Special:CreateAccount|melde diech ols neuer Benutzer oa]].",
        "nosuchusershort": "Dar Nutzernoame „$1“ existiert ne. Bitte ieberpriefe de Schreibweise.",
        "nouserspecified": "Bitte gieb enn Benutzernoamen oa.",
        "wrongpassword": "Doas Passwurt ies foalsch (oder fehlt). Bitte versuche is erneut.",
        "accmailtitle": "Passwurt wourde verschickt",
        "newarticle": "(Neu)",
        "newarticletext": "Du best an'm Link zu anner Seite gefulgt, de nee vorhanden ies.\nIm de Seite oazulega, trage denn Text ei de undastehende Box a (siehe de [$1 Hilfeseite] fier meh Informationen).\nBest du fälschlicherweise hier, klicke de '''Zerricke'''-Schaltfläche dennes Browsers.",
-       "anontalkpagetext": "----''Diese Seite dient dazu, a'm nee oagemeldeta Benutzer Noachrichta zu hinterlassa. 'S werd senne IP-Atresse zur Identifizierung verwendet. IP-Atressen kinna vu mehrera Nutzern gemeensam verwendet waan. Wenn du miet dann Kommentarn uff dieser Seite nischt oafanga koast, richta se siech vermutlich oa an'n friehera Inhaber denner IP-Atresse und du koast se ignorieren. Du koast dir au a [[Special:UserLogin/signup|Nutzerkonto erstalla]] oder dich [[Special:UserLogin|oamelda]], im kinftig Verwechslunga miet andern anonyma Nutzern zu vermeida.''",
+       "anontalkpagetext": "----''Diese Seite dient dazu, a'm nee oagemeldeta Benutzer Noachrichta zu hinterlassa. 'S werd senne IP-Atresse zur Identifizierung verwendet. IP-Atressen kinna vu mehrera Nutzern gemeensam verwendet waan. Wenn du miet dann Kommentarn uff dieser Seite nischt oafanga koast, richta se siech vermutlich oa an'n friehera Inhaber denner IP-Atresse und du koast se ignorieren. Du koast dir au a [[Special:CreateAccount|Nutzerkonto erstalla]] oder dich [[Special:UserLogin|oamelda]], im kinftig Verwechslunga miet andern anonyma Nutzern zu vermeida.''",
        "noarticletext": "Diese Seite enthält momentan noo kenn Text.\nDu koast diesen Tittel uffa andern Seita [[Special:Search/{{PAGENAME}}|sucha]],\n<span class=\"plainlinks\"> ei dan zugeheeriga [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Logbichern sucha] oder diese Seite [{{fullurl:{{FULLPAGENAME}}|action=edit}} bearbta]</span>.",
        "noarticletext-nopermission": "Diese Seite enthält momentan noo kenn Text.\nDu koast diesen Tittel uff dann andern Seita [[Special:Search/{{PAGENAME}}|sucha]]\noder de zugehieriga <span class=\"plainlinks\">[{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbichern betrachta].</span>",
        "userpage-userdoesnotexist": "Doas Nutzerkonto „<nowiki>$1</nowiki>“ ies nee vurhanda. Bitte priefe, ob du diese Seite wirklich erstella/beoarbta wielst.",
index e9782f0..f14b99f 100644 (file)
        "noname": "Ma jiro magaca isticmaalaha aad qortay",
        "loginsuccesstitle": "Gudagalida waa la dhamaystiray",
        "loginsuccess": "'''Waxaa hadda gudaha ugu soo gashay {{SITENAME}} sida \"$1\".'''",
-       "nosuchuser": "Ma jiro isticmaalo leh magacaan \"$1\".\nMagacyada way kala waaweyn yihiin.\nSax hingaada, ama  [[Special:UserLogin/signup|samayso magac gudagale ah]].",
+       "nosuchuser": "Ma jiro isticmaalo leh magacaan \"$1\".\nMagacyada way kala waaweyn yihiin.\nSax hingaada, ama  [[Special:CreateAccount|samayso magac gudagale ah]].",
        "nosuchusershort": "Majiro isticmaale leh magacaan \"$1\". Sax qoraalkaaga.",
        "nouserspecified": "Waa in aad magac gudagale heshaa.",
        "login-userblocked": "Isticmaalahaan waa la mamnuucay. Lama ogolo in oo gudaha u galo",
        "accmailtext": "Ereysir loogu tala galay [[User talk:$1|$1]] waxaa loogu diray $2.\n\nAkoonkaan cusub eraysirkiisa waxaa ku badali kartaa  bogga ''[[Special:ChangePassword|bedel eraysirka]]'' si aad u soo gashid.",
        "newarticle": "(Cusub)",
        "newarticletext": "Waxaa soo raacday link kula soo xiriiriyay bog oo hadda wali jirin.\nHadii aad rabto in aad sameyso bogga, hoos ka bilaaw qoraalkaada (fiiri [$1 bogga caawinaada] wax war ah oo kale).\nhadii aad meeshaan ku soo qaldantay, riix batoonka barowsahaaga  '''gadaal uuga noqo''' .",
-       "anontalkpagetext": "----''Meeshaan waa bogga wadahadalka isticmaalayaasha la aqoon oo aanan weli sameysanin akoon, ama  wali isticmaalin. \nSidaas darteed, waa in aan isticmaalnaa lambar cinwaaneedka IP:ga si aan u ogaano asiga/ayada. Cinwaanka IP:ga waxaa suurto gal ah in ay qeybsadaan isticmaaleyaal badan.\nHadii aad tahay isticmaale aanan la'aqoonsanin oo aad dareemaysid in laguu gafay, fadlan  [[Special:UserLogin/signup|sameyso akoon]]  ama [[Special:UserLogin|gudaha gal]] si aad u dhowrsatid in hadhowdi laguugu qaldo isticmaalada kale oo aann la'aqoonsanin.''",
+       "anontalkpagetext": "----''Meeshaan waa bogga wadahadalka isticmaalayaasha la aqoon oo aanan weli sameysanin akoon, ama  wali isticmaalin. \nSidaas darteed, waa in aan isticmaalnaa lambar cinwaaneedka IP:ga si aan u ogaano asiga/ayada. Cinwaanka IP:ga waxaa suurto gal ah in ay qeybsadaan isticmaaleyaal badan.\nHadii aad tahay isticmaale aanan la'aqoonsanin oo aad dareemaysid in laguu gafay, fadlan  [[Special:CreateAccount|sameyso akoon]]  ama [[Special:UserLogin|gudaha gal]] si aad u dhowrsatid in hadhowdi laguugu qaldo isticmaalada kale oo aann la'aqoonsanin.''",
        "noarticletext": "Boggaan hadda wax qoraal ah kuma qorno.\nWaxaa  [[Special:Search/{{PAGENAME}}|magaca boggaan]] ka raadin kartaa bogyaasha kale,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} raadi kuwo la mid ah],\nama [{{fullurl:{{FULLPAGENAME}}|action=edit}} wax ka bedel boggaan]</span>.",
        "noarticletext-nopermission": "Hadda boggaan wax qoraal ah kuma qorno.\nWaxaa  [[Special:Search/{{PAGENAME}}|magaca boggaan ka raadin kartaa]] boggaga kale ama <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ka raadi gudagalayaasha kale]</span>.",
        "userpage-userdoesnotexist": "Isticmaalahan  \"<nowiki>$1</nowiki>\" ma diiwaangashno.\nFadlan fiiri hadii aad rabto in aad sameeyso/wax ka bedesho boggaan.",
        "allpagesprefix": "Soo saar boggaga leh horgalaha:",
        "allpagesbadtitle": "Cinwaanka bogga xaq ma'aha ama waa ereyo u gaar ah isdhaafka-luqadaha ama isdhaafka-wiki. Waxaa ku jirikara xuruufo aanan loo isticmaalikarin cinwaan ahaan.",
        "categories": "Qeybaha",
-       "special-categories-sort-count": "xisaabi marka aad tirisid",
        "linksearch-ns": "Xarun magaceedka:",
        "linksearch-ok": "Raadi",
        "linksearch-line": "$1 wuxuu ka socdaa $2",
        "watchlisttools-raw": "Badal liiska waardiyeenta ceeriinka ah",
        "version-poweredby-others": "kuwa kale",
        "redirect-file": "Magaca faylka",
-       "fileduplicatesearch-legend": "Raadi mid tusaale ah",
        "fileduplicatesearch-submit": "Raadi",
        "specialpages": "bogagga khaaska ah",
        "specialpages-note-top": "Furaha",
index d17130e..b804acd 100644 (file)
        "noname": "Nuk keni dhënë një emër përdoruesi të pranueshëm.",
        "loginsuccesstitle": "Identifikim i suksesshëm",
        "loginsuccess": "'''Ju tani jeni identifikuar tek {{SITENAME}} si \"$1\".'''",
-       "nosuchuser": "Nuk ka ndonjë përdorues me emrin \"$1\".\nKontrolloni shkrimin ose [[Special:UserLogin/signup|hapni një llogari të re]].",
+       "nosuchuser": "Nuk ka ndonjë përdorues me emrin \"$1\".\nKontrolloni shkrimin ose [[Special:CreateAccount|hapni një llogari të re]].",
        "nosuchusershort": "Nuk ka asnjë përdorues me emrin \"$1\".",
        "nouserspecified": "Ju duhet të jepni një nofkë",
        "login-userblocked": "Ky përdorues është bllokuar. Identifikimi nuk lejohet.",
        "accmailtext": "Një fjalëkalim i krijuar në mënyrë të rastësishme për [[User talk:$1|$1]] u dërgua në $2.\n\nFjalëkalimi për këtë llogari mund të ndryshohet në faqen ''[[Special:ChangePassword|ndrysho fjalëkalimin]]'' pasi të jeni identifikuar.",
        "newarticle": "(I ri)",
        "newarticletext": "Ti ke ndjekur nje lidhje drejt një faqeje që nuk ekziston.\nPër ta krijuar këtë faqe, fillo të shkruash në kutinë e mëposhtme (shih [$1 faqen e ndihmës] për më shumë informacion).\nNëse ti ke mbërritur këtu gabimisht, atëherë kliko butonin '''pas''' të shfletuesit tënd.",
-       "anontalkpagetext": "----'' Kjo është një faqe diskutimi për një përdorues anonim i cili nuk ka krijuar akoma një llogari, ose qe nuk e përdor atë. \n Prandaj, ne duhet të përdorim adresën IP numerike për identifikimin e tij. \nKjo adresë IP mund të përdoret nga disa përdorues.\n Në qoftë se jeni një përdorues anonim dhe mendoni se ndaj jush janë bërë komente të parëndësishme, ju lutem [[Special:UserLogin/signup|krijoni një llogari]] ose [[Special:UserLogin|identifikohuni]] për të shmangur konfuzionin në të ardhmen me përdorues të tjerë anonim .''",
+       "anontalkpagetext": "----'' Kjo është një faqe diskutimi për një përdorues anonim i cili nuk ka krijuar akoma një llogari, ose qe nuk e përdor atë. \n Prandaj, ne duhet të përdorim adresën IP numerike për identifikimin e tij. \nKjo adresë IP mund të përdoret nga disa përdorues.\n Në qoftë se jeni një përdorues anonim dhe mendoni se ndaj jush janë bërë komente të parëndësishme, ju lutem [[Special:CreateAccount|krijoni një llogari]] ose [[Special:UserLogin|identifikohuni]] për të shmangur konfuzionin në të ardhmen me përdorues të tjerë anonim .''",
        "noarticletext": "Momentalisht nuk ka tekst në këtë faqe.\nJu mund [[Special:Search/{{PAGENAME}}|ta kërkoni këtë titull]] në faqe tjera,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} të kërkoni ngjarjet e ngjashme në regjistër],\nose [{{fullurl:{{FULLPAGENAME}}|action=edit}} të redaktoni këtë faqe]</span>.",
        "noarticletext-nopermission": "Për momentin faqja e kërkuar është bosh.\nJu mund të [[Special:Search/{{PAGENAME}}|kërkoni këtë titiull]] në faqet e tjera, ose të <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} këtkoni regjistrat e ngjashëm]</span>, por ju nuk mundeni ta krijoni këtë faqe.",
        "missing-revision": "Inspektimi #$1 i faqes me emrin \"{{FULLPAGENAME}}\" nuk ekziston.\n\nKjo zakonisht shkaktuar duke ndjekur një lidhje të vjetër tek një faqe që është fshirë. Hollësitë mund të gjenden në [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} regjistrin e fshirjeve].",
index 3d08fa1..59e50e2 100644 (file)
        "noname": "Унели сте неисправно корисничко име.",
        "loginsuccesstitle": "Успешно пријављивање",
        "loginsuccess": "'''Пријављени сте као „$1“.'''",
-       "nosuchuser": "Не постоји корисник с именом „$1“.\nКорисничка имена су осетљива на мала и велика слова.\nПроверите да ли сте га добро унели или [[Special:UserLogin/signup|отворите нови налог]].",
+       "nosuchuser": "Не постоји корисник с именом „$1“.\nКорисничка имена су осетљива на мала и велика слова.\nПроверите да ли сте га добро унели или [[Special:CreateAccount|отворите нови налог]].",
        "nosuchusershort": "Корисник с именом „$1“ не постоји.\nПроверите да ли сте правилно написали.",
        "nouserspecified": "Морате навести корисничко име.",
        "login-userblocked": "{{GENDER:$1|Овај корисник је блокиран|Ова корисница је блокирана|Овај корисник је блокиран}}. Пријава није дозвољена.",
        "accmailtext": "Лозинка за {{GENDER:$1|корисника|корисницу}} [[User talk:$1|$1]] је послата на $2. Након пријаве, лозинка се може променити [[Special:ChangePassword|овде]].",
        "newarticle": "(нови)",
        "newarticletext": "Дошли сте на страницу која још не постоји.\nДа бисте је направили, почните да куцате у прозор испод овог текста (погледајте [$1 страницу за помоћ]).\nАко сте овде дошли грешком, вратите се на претходну страницу.",
-       "anontalkpagetext": "---- Ово је страница за разговор с анонимним корисником који још нема налог или га не користи.\nЗбог тога морамо да користимо бројчану ИП адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:UserLogin/signup|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.",
+       "anontalkpagetext": "---- Ово је страница за разговор с анонимним корисником који још нема налог или га не користи.\nЗбог тога морамо да користимо бројчану ИП адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:CreateAccount|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.",
        "noarticletext": "На овој страници тренутно нема садржаја.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
        "noarticletext-nopermission": "На овој страници тренутно нема садржаја.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне дневнике]</span>, али немате дозволу да направите ову страницу.",
        "missing-revision": "Не могу да пронађем измену бр. $1 на страници под називом „{{FULLPAGENAME}}“.\n\nОво се обично дешава када пратите застарелу везу до странице која је обрисана.\nВише информација можете пронаћи у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневнику брисања].",
        "upload-form-label-infoform-name": "Назив",
        "upload-form-label-infoform-description": "Опис",
        "upload-form-label-usage-filename": "Назив датотеке",
-       "foreign-structured-upload-form-label-own-work": "Ово је моје сопствено дело",
-       "foreign-structured-upload-form-label-infoform-categories": "Категорије",
-       "foreign-structured-upload-form-label-infoform-date": "Датум",
+       "upload-form-label-own-work": "Ово је моје сопствено дело",
+       "upload-form-label-infoform-categories": "Категорије",
+       "upload-form-label-infoform-date": "Датум",
        "backend-fail-stream": "Не могу да емитујем датотеку $1.",
        "backend-fail-backup": "Не могу да направим резерву датотеке $1.",
        "backend-fail-notexists": "Датотека $1 не постоји.",
        "whatlinkshere-prev": "{{PLURAL:$1|претходни|претходних $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|следећи|следећих $1}}",
        "whatlinkshere-links": "← везе",
-       "whatlinkshere-hideredirs": "$1 преусмерења",
-       "whatlinkshere-hidetrans": "$1 укључивања",
-       "whatlinkshere-hidelinks": "$1 везе",
+       "whatlinkshere-hideredirs": "Сакриј преусмерења",
+       "whatlinkshere-hidetrans": "Сакриј укључивања",
+       "whatlinkshere-hidelinks": "Сакриј везе",
        "whatlinkshere-hideimages": "$1 везе до датотеке",
        "whatlinkshere-filters": "Филтери",
        "whatlinkshere-submit": "Иди",
        "size-megabytes": "$1 MB",
        "size-gigabytes": "$1 GB",
        "lag-warn-normal": "Измене новије од $1 {{PLURAL:$1|секунде|секунде|секунди}} неће бити приказане.",
-       "lag-warn-high": "Због преоптерећења базе података, измене новије од $1 {{PLURAL:$1|секунда|секунде}} неће бити приказане.",
+       "lag-warn-high": "Због преоптерећења базе података, измене новије од $1 {{PLURAL:$1|1=секунде|секунде|секунди}} неће бити приказане.",
        "watchlistedit-normal-title": "Уређивање списка надгледања",
        "watchlistedit-normal-legend": "Уклањање наслова са списка надгледања",
        "watchlistedit-normal-explain": "Наслови на вашем списку надгледања су приказани испод.\nДа бисте уклонили наслов, означите квадратић до њега и кликните на „{{int:Watchlistedit-normal-submit}}“.\nМожете и да [[Special:EditWatchlist/raw|уредите сиров списак]].",
index 696e8f4..e786f12 100644 (file)
        "noname": "Uneli ste neispravno korisničko ime.",
        "loginsuccesstitle": "Uspešno prijavljivanje",
        "loginsuccess": "'''Prijavljeni ste kao „$1“.'''",
-       "nosuchuser": "Ne postoji korisnik s imenom „$1“.\nKorisnička imena su osetljiva na mala i velika slova.\nProverite da li ste ga dobro uneli ili [[Special:UserLogin/signup|otvorite novi nalog]].",
+       "nosuchuser": "Ne postoji korisnik s imenom „$1“.\nKorisnička imena su osetljiva na mala i velika slova.\nProverite da li ste ga dobro uneli ili [[Special:CreateAccount|otvorite novi nalog]].",
        "nosuchusershort": "Korisnik s imenom „$1“ ne postoji.\nProverite da li ste pravilno napisali.",
        "nouserspecified": "Morate navesti korisničko ime.",
        "login-userblocked": "{{GENDER:$1|Ovaj korisnik je blokiran|Ova korisnica je blokirana|Ovaj korisnik je blokiran}}. Prijava nije dozvoljena.",
        "accmailtext": "Lozika za {{GENDER:$1|korisnika|korisnicu}} [[User talk:$1|$1]] je poslata na $2. Nakon prijave, lozinka se može promeniti [[Special:ChangePassword|ovde]].",
        "newarticle": "(novi)",
        "newarticletext": "Došli ste na stranicu koja još ne postoji.\nDa biste je napravili, počnite da kucate u prozor ispod ovog teksta (pogledajte [$1 stranicu za pomoć]).\nAko ste ovde došli greškom, vratite se na prethodnu stranicu.",
-       "anontalkpagetext": "---- Ovo je stranica za razgovor s anonimnim korisnikom koji još nema nalog ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo ga prepoznali.\nTakvu adresu može deliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene primedbe, [[Special:UserLogin/signup|otvorite nalog]] ili se [[Special:UserLogin|prijavite]] da biste izbegli buduću zabunu s ostalim anonimnim korisnicima.",
+       "anontalkpagetext": "---- Ovo je stranica za razgovor s anonimnim korisnikom koji još nema nalog ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo ga prepoznali.\nTakvu adresu može deliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene primedbe, [[Special:CreateAccount|otvorite nalog]] ili se [[Special:UserLogin|prijavite]] da biste izbegli buduću zabunu s ostalim anonimnim korisnicima.",
        "noarticletext": "Na ovoj stranici trenutno nema sadržaja.\nMožete [[Special:Search/{{PAGENAME}}|potražiti ovaj naslov]] na drugim stranicama,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti srodne izveštaje] ili [{{fullurl:{{FULLPAGENAME}}|action=edit}} napraviti ovu stranicu]</span>.",
        "noarticletext-nopermission": "Na ovoj stranici trenutno nema sadržaja.\nMožete [[Special:Search/{{PAGENAME}}|potražiti ovaj naslov]] na drugim stranicama ili <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti srodne dnevnike]</span>, ali nemate dozvolu da napravite ovu stranicu.",
        "missing-revision": "Ne mogu da pronađem izmenu br. $1 na stranici pod nazivom „{{FULLPAGENAME}}“.\n\nOvo se obično dešava kada pratite zastarelu vezu do stranice koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} dnevniku brisanja].",
        "upload-form-label-infoform-name": "Ime",
        "upload-form-label-infoform-description": "Opis",
        "upload-form-label-usage-filename": "Naziv datoteke",
-       "foreign-structured-upload-form-label-own-work": "Ovo je moje sopstveno delo",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorije",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work": "Ovo je moje sopstveno delo",
+       "upload-form-label-infoform-categories": "Kategorije",
+       "upload-form-label-infoform-date": "Datum",
        "backend-fail-stream": "Ne mogu da emitujem datoteku $1.",
        "backend-fail-backup": "Ne mogu da napravim rezervu datoteke $1.",
        "backend-fail-notexists": "Datoteka $1 ne postoji.",
        "whatlinkshere-prev": "{{PLURAL:$1|prethodni|prethodnih $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|sledeći|sledećih $1}}",
        "whatlinkshere-links": "← veze",
-       "whatlinkshere-hideredirs": "$1 preusmerenja",
-       "whatlinkshere-hidetrans": "$1 uključivanja",
-       "whatlinkshere-hidelinks": "$1 veze",
+       "whatlinkshere-hideredirs": "Sakrij preusmerenja",
+       "whatlinkshere-hidetrans": "Sakrij uključivanja",
+       "whatlinkshere-hidelinks": "Sakrij veze",
        "whatlinkshere-hideimages": "$1 veze do datoteke",
        "whatlinkshere-filters": "Filteri",
        "whatlinkshere-submit": "Idi",
        "size-megabytes": "$1 MB",
        "size-gigabytes": "$1 GB",
        "lag-warn-normal": "Izmene novije od $1 {{PLURAL:$1|sekunde|sekunde|sekundi}} neće biti prikazane.",
-       "lag-warn-high": "Zbog preopterećenja baze podataka, izmene novije od $1 {{PLURAL:$1|sekunde|sekundi}} neće biti prikazane.",
+       "lag-warn-high": "Zbog preopterećenja baze podataka, izmene novije od $1 {{PLURAL:$1|1=sekunde|sekunde|sekundi}} neće biti prikazane.",
        "watchlistedit-normal-title": "Uređivanje spiska nadgledanja",
        "watchlistedit-normal-legend": "Uklanjanje naslova sa spiska nadgledanja",
        "watchlistedit-normal-explain": "Naslovi na vašem spisku nadgledanja su prikazani ispod.\nDa biste uklonili naslov, označite kvadratić do njega i kliknite na „{{int:Watchlistedit-normal-submit}}“.\nMožete i da [[Special:EditWatchlist/raw|uredite sirov spisak]].",
index 0df794d..3c3d811 100644 (file)
        "noname": "Du moast n Benutsernoome anreeke.",
        "loginsuccesstitle": "Anmäldenge mäd Ärfoulch",
        "loginsuccess": "'''Du bäst nu as \"$1\" bie {{SITENAME}} anmälded.'''",
-       "nosuchuser": "Die Benutsernoome \"$1\" bestoant nit.\nUurpröif ju Skrieuwwiese (Groot-/Littekskrieuwenge beoachtje) of [[Special:UserLogin/signup|mäld die as näien Benutser an]].",
+       "nosuchuser": "Die Benutsernoome \"$1\" bestoant nit.\nUurpröif ju Skrieuwwiese (Groot-/Littekskrieuwenge beoachtje) of [[Special:CreateAccount|mäld die as näien Benutser an]].",
        "nosuchusershort": "Die Benutsernooome \"$1\" bestoant nit. Uurpröif ju Skrieuwwiese.",
        "nouserspecified": "Reek jädden n Benutsernoome an.",
        "login-userblocked": "Dissen Benutser is speerd. Anmäldenge nit ferlööwed.",
        "accmailtext": "N toufällich generierd Paaswoud foar [[User talk:$1|$1]] wuud an $2 fersoand.\n\nDät Paaswoud foar dit näie Benutserkonto kon ap ju Spezioalsiede\n„[[Special:ChangePassword|Paaswoud annerje]]“ annerd wäide.",
        "newarticle": "(Näi)",
        "newarticletext": "Du hääst n Link foulged ätter ne Siede, ju dät noch nit rakt.\nUum ju Siede tou moakjen, dien Text fon dän näie Artikkel iendreege in ju unnerstoundene Box (sjuch ju [$1 Hälpesiede] foar moor Informatione).\nBäst du hier bie Fersjoon, klik ju '''Tourääch'''-Skaltfläche fon din Browser.",
-       "anontalkpagetext": "----''Dit is ju Diskussionssiede fon n uunbekoanden Benutser, die sik nit anmälded häd.\nWail naan Noome deer is, wäd ju nuumeriske IP-Adrässe tou Identifizierenge ferwoand.\nMan oafte wäd sunne Adrässe fon moorere Benutsere ferwoand.\nWan du n uunbekoanden Benutser bääst un du toankst dät du Kommentare krichst do nit foar die meend sunt, dan koast du ap bääste n [[Special:UserLogin/signup|Benutserkonto iengjuchte]] of die [[Special:UserLogin|anmäldje]], uum sukke Fertuusengen mäd uur anomyme Benutsere tou fermieden.''",
+       "anontalkpagetext": "----''Dit is ju Diskussionssiede fon n uunbekoanden Benutser, die sik nit anmälded häd.\nWail naan Noome deer is, wäd ju nuumeriske IP-Adrässe tou Identifizierenge ferwoand.\nMan oafte wäd sunne Adrässe fon moorere Benutsere ferwoand.\nWan du n uunbekoanden Benutser bääst un du toankst dät du Kommentare krichst do nit foar die meend sunt, dan koast du ap bääste n [[Special:CreateAccount|Benutserkonto iengjuchte]] of die [[Special:UserLogin|anmäldje]], uum sukke Fertuusengen mäd uur anomyme Benutsere tou fermieden.''",
        "noarticletext": "Deer is apstuuns naan Text ap disse Siede.\nDu koast dissen Tittel ap do uur Sieden [[Special:Search/{{PAGENAME}}|säike]],\n<span class=\"plainlinks\"> in do touheerige [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Logbouke säike] of disse Siede [{{fullurl:{{FULLPAGENAME}}|action=edit}} beoarbaidje]</span>.",
        "noarticletext-nopermission": "Der is apstuuns noch naan Text ap disse Siede.\nDu koast dissen Tittel ap do uur Sieden [[Special:Search/{{PAGENAME}}|säike]]\nof <span class=\"plainlinks\">in do touheerige [{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbouke säike].</span>",
        "userpage-userdoesnotexist": "Dät Benutserkonto „<nowiki>$1</nowiki>“ is nit deer. Pröif, of du disse Siede wuddelk moakje/beoarbaidje wolt.",
index ce6ca9d..cf4c8c4 100644 (file)
        "noname": "Anjeun teu nuliskeun ngaran pamaké nu sah.",
        "loginsuccesstitle": "Asup log geus hasil",
        "loginsuccess": "Anjeun ayeuna geus asup log ka {{SITENAME}} salaku \"$1\".",
-       "nosuchuser": "Euweuh pamaké nu ngaranna \"$1\".\nNgaran pamaké ngabedakeun hurup kapital.\nPariksa éjahanana, atawa paké formulir di handap pikeun [[Special:UserLogin/signup|nyieun rekening anyar]].",
+       "nosuchuser": "Euweuh pamaké nu ngaranna \"$1\".\nNgaran pamaké ngabedakeun hurup kapital.\nPariksa éjahanana, atawa paké formulir di handap pikeun [[Special:CreateAccount|nyieun rekening anyar]].",
        "nosuchusershort": "Taya pamaké nu ngaranna \"$1\", pariksa éjahanana!",
        "nouserspecified": "Anjeun kudu ngeusian ngaran landihan.",
        "login-userblocked": "Ieu pamaké keur dipeungpeuk, teu diwenangkeun asup log.",
        "accmailtext": "Sandi acak pikeun [[User talk:$1|$1]] geus dikirim ka $2. Éta sandi bisa diganti dina kaca ''[[Special:ChangePassword|ganti sandi]]'' sanggeus asup log.",
        "newarticle": "(anyar)",
        "newarticletext": "Anjeun geus nuturkeun tutumbu ka kaca nu can aya.\nPikeun nyieun kaca, mimitian ku ngetik jeroeun kotak di handap\n(tempo [$1 kaca pitulung] pikeun leuwih écés).\nMun anjeun ka dieu teu ngahaja, klik baé tombol '''back''' na panyungsi anjeun.",
-       "anontalkpagetext": "----\n<em>Ieu mangrupa kaca sawala pikeun pamaké anonim anu can nyieun akun, atawa anu henteu maké.</em>\nKu kituna kapaksa make alamat IP pikeun nyirikeun anjeunna. Alamat IP ieu bisa dipaké ku sababaraha jalma. Lamun anjeun salasahiji pamaké anonim sarta ngarasa aya koméntar nu teu pakait geus ditujukeun ka anjeun, mangga [[Special:UserLogin/signup|nyieun akun]] atawa [[Special:UserLogin|asup log]] sangkan teu pacorok jeung pamaké anonim lianna.",
+       "anontalkpagetext": "----\n<em>Ieu mangrupa kaca sawala pikeun pamaké anonim anu can nyieun akun, atawa anu henteu maké.</em>\nKu kituna kapaksa make alamat IP pikeun nyirikeun anjeunna. Alamat IP ieu bisa dipaké ku sababaraha jalma. Lamun anjeun salasahiji pamaké anonim sarta ngarasa aya koméntar nu teu pakait geus ditujukeun ka anjeun, mangga [[Special:CreateAccount|nyieun akun]] atawa [[Special:UserLogin|asup log]] sangkan teu pacorok jeung pamaké anonim lianna.",
        "noarticletext": "Kiwari can aya téks dina ieu kaca.\nAnjeun bisa [[Special:Search/{{PAGENAME}}|nyusud judul ieu kaca]] dina kaca séjén,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} nyusud log nu tumali],\natawa [{{fullurl:{{FULLPAGENAME}}|action=edit}} nyieun ieu kaca]</span>.",
        "noarticletext-nopermission": "Kiwari can aya téks dina ieu kaca.\nAnjeun bisa [[Special:Search/{{PAGENAME}}|nyusud judul ieu kaca]] dina kaca séjén,atawa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} nyusud log nu tumali]</span>, tapi teu wenang pikeun nyieun ieu kaca.",
        "missing-revision": "Révisi #$1 kaca \"{{FULLPAGENAME}}\" teu aya.\n\nKajadian ieu biasana kusabab nuturkeun tutumbu jujutan kaca anu geus dihapus.\nWincikanana bisa ditempo di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log hapusan].",
index 2527ab7..50a8700 100644 (file)
        "noname": "Du har angett ett ogiltigt användarnamn.",
        "loginsuccesstitle": "Inloggad",
        "loginsuccess": "'''Du är nu inloggad på {{SITENAME}} som \"$1\".'''",
-       "nosuchuser": "Det finns ingen användare med namnet \"$1\".\nAnvändarnamn är skiftlägeskänsliga.\nKontrollera din stavning, eller [[Special:UserLogin/signup|skapa ett nytt konto]].",
+       "nosuchuser": "Det finns ingen användare med namnet \"$1\".\nAnvändarnamn är skiftlägeskänsliga.\nKontrollera din stavning, eller [[Special:CreateAccount|skapa ett nytt konto]].",
        "nosuchusershort": "Det finns ingen användare som heter \"$1\". Kontrollera att du stavat rätt.",
        "nouserspecified": "Du måste ange ett användarnamn.",
        "login-userblocked": "Denna användare är blockerad. Inloggning är inte tillåtet.",
        "accmailtext": "Ett slumpgenererat lösenord för [[User talk:$1|$1]] har skickats till $2. Det kan ändras på sidan ''[[Special:ChangePassword|ändra lösenord]]'' när du loggar in.",
        "newarticle": "(Ny)",
        "newarticletext": "Du har klickat på en länk till en sida som inte finns ännu. För att skapa sidan, börja att skriva i fältet nedan (du kan läsa mer på [$1 hjälpsidan]). Om du kom hit av misstag kan du bara trycka på <strong>tillbaka</strong>-knappen i din webbläsare.",
-       "anontalkpagetext": "----''Detta är diskussionssidan för en anonym användare som inte ännu skapat ett konto, eller som inte använder det.\nDärför måste vi använda den numeriska IP-adressen för att identifiera honom/henne.\nEn sådan IP-adress kan delas av flera användare.\nOm du är en anonym användare och känner att irrelevanta kommentarer har riktats mot dig, vänligen [[Special:UserLogin/signup|skapa ett konto]] eller [[Special:UserLogin|logga in]] för att undvika framtida förväxlingar med andra anonyma användare.''",
+       "anontalkpagetext": "----''Detta är diskussionssidan för en anonym användare som inte ännu skapat ett konto, eller som inte använder det.\nDärför måste vi använda den numeriska IP-adressen för att identifiera honom/henne.\nEn sådan IP-adress kan delas av flera användare.\nOm du är en anonym användare och känner att irrelevanta kommentarer har riktats mot dig, vänligen [[Special:CreateAccount|skapa ett konto]] eller [[Special:UserLogin|logga in]] för att undvika framtida förväxlingar med andra anonyma användare.''",
        "noarticletext": "Det finns just nu ingen text på denna sida.\nDu kan [[Special:Search/{{PAGENAME}}|söka efter denna sidtitel]] på andra sidor, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söka i relaterade loggar], eller [{{fullurl:{{FULLPAGENAME}}|action=edit}} skapa denna sida]</span>.",
        "noarticletext-nopermission": "Det finns för tillfället ingen text på denna sida.\nDu kan [[Special:Search/{{PAGENAME}}|söka efter denna sidas titel]] på andra sidor,\neller <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söka i relaterade loggar]</span> men du har inte behörighet att skapa sidan.",
        "missing-revision": "Revisionen #$1 av sidan med namnet \"{{FULLPAGENAME}}\" finns inte.\n\nDetta orsakas vanligen av efter en gammal historiklänk till en sida som har raderats.\nDetaljer kan hittas i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} raderingsloggen].",
        "history-feed-description": "Versionshistorik för denna sida på wikin",
        "history-feed-item-nocomment": "$1 den $2",
        "history-feed-empty": "Den begärda sidan finns inte.\nDen kan ha tagits bort från wikin eller bytt namn.\nProva att [[Special:Search|söka på wikin]] för relevanta nya sidor.",
-       "history-edit-tags": "Redigera taggar för valda sidversioner",
+       "history-edit-tags": "Redigera märken för valda sidversioner",
        "rev-deleted-comment": "(redigeringssammanfattning togs bort)",
        "rev-deleted-user": "(användarnamn borttaget)",
        "rev-deleted-event": "(loggdetaljer borttagna)",
        "right-override-export-depth": "Exportera sidor inklusive länkade sidor till ett djup på 5",
        "right-sendemail": "Skicka e-post till andra användare",
        "right-passwordreset": "Visa e-postmeddelanden med lösenordsåterställning",
-       "right-managechangetags": "Skapa och radera [[Special:Tags|taggar]] från databasen",
-       "right-applychangetags": "Tillämpa [[Special:Tags|taggar]] tillsammans med ens ändringar",
+       "right-managechangetags": "Skapa och (in)aktivera [[Special:Tags|märken]]",
+       "right-applychangetags": "Tillämpa [[Special:Tags|märken]] tillsammans med ens ändringar",
        "right-changetags": "Lägg till och ta bort godtyckliga [[Special:Tags|märken]] på individuella sidversioner och loggposter.",
+       "right-deletechangetags": "Radera [[Special:Tags|märken]] från databasen",
        "grant-generic": "Rättighetsgrupp \"$1\"",
        "grant-group-page-interaction": "Interagera med sidor",
        "grant-group-file-interaction": "Interagera med media",
        "action-viewmyprivateinfo": "visa din privata information",
        "action-editmyprivateinfo": "redigera din privata information",
        "action-editcontentmodel": "ändra innehållsmodellen för en sida",
-       "action-managechangetags": "skapa och radera taggar från databasen",
-       "action-applychangetags": "tillämpa taggar tillsammans med dina ändringar",
+       "action-managechangetags": "skapa och (in)aktivera märken",
+       "action-applychangetags": "tillämpa märken tillsammans med dina ändringar",
        "action-changetags": "lägg till och ta bort godtyckliga märken på individuella sidversioner och loggposter",
+       "action-deletechangetags": "radera märken från databasen",
        "nchanges": "$1 {{PLURAL:$1|ändring|ändringar}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|sedan senaste besöket}}",
        "enhancedrc-history": "historik",
        "upload-form-label-infoform-description-tooltip": "Beskriv kortfattat allt anmärkningsvärt om verket.\nFör ett foto, nämn huvudmotiv, tillfälle eller plats.",
        "upload-form-label-usage-title": "Användning",
        "upload-form-label-usage-filename": "Filnamn",
-       "foreign-structured-upload-form-label-own-work": "Detta är mitt eget verk",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategorier",
-       "foreign-structured-upload-form-label-infoform-date": "Datum",
-       "foreign-structured-upload-form-label-own-work-message-local": "Jag bekräftar att jag laddar upp denna fil enligt {{SITENAME}}s användarvillkor och licenspolicys.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Stäng denna dialogruta och prova ett annat sätt om du inte kan ladda upp denna fil under {{SITENAME}}s policys.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Du kanske också skulle vilja prova [[Special:Upload|standarduppladdningssidan]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Jag förstår att jag laddar upp denna fil till ett delat centralförvar. Jag bekräftar att jag gör det enligt de användarvillkor och licenspolicys som finns där.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Stäng denna dialogruta och prova en annan metod om du inte kan ladda upp denna fil under de policys som gäller för det delade centralförvaret.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Du kanske också skulle vilja prova att använda [[Special:Upload|uppladdningssidan på {{SITENAME}}]] om denna fil kan laddas upp där under deras policys.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Jag intygar att jag äger upphovsrätten för denna fil och samtycker till att oåterkalleligen släppa filen på Wikimedia Commons under licensen \n[https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] och jag accepterar [https://wikimediafoundation.org/wiki/Terms_of_Use villkoren för användning].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Om du inte äger upphovsrätten för denna fil eller om du önskar att släppa den under en annan licens bör du överväga att använda [https://commons.wikimedia.org/wiki/Special:UploadWizard uppladdningsguiden på Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Du kanske skulle vilja prova att använda [[Special:Upload|uppladdningssidan på {{SITENAME}}]] om webbplatsens policys tillåter att denna fil laddas upp.",
+       "upload-form-label-own-work": "Detta är mitt eget verk",
+       "upload-form-label-infoform-categories": "Kategorier",
+       "upload-form-label-infoform-date": "Datum",
+       "upload-form-label-own-work-message-generic-local": "Jag bekräftar att jag laddar upp denna fil enligt {{SITENAME}}s användarvillkor och licenspolicys.",
+       "upload-form-label-not-own-work-message-generic-local": "Stäng denna dialogruta och prova ett annat sätt om du inte kan ladda upp denna fil under {{SITENAME}}s policys.",
+       "upload-form-label-not-own-work-local-generic-local": "Du kanske också skulle vilja prova [[Special:Upload|standarduppladdningssidan]].",
+       "upload-form-label-own-work-message-generic-foreign": "Jag förstår att jag laddar upp denna fil till ett delat centralförvar. Jag bekräftar att jag gör det enligt de användarvillkor och licenspolicys som finns där.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Stäng denna dialogruta och prova en annan metod om du inte kan ladda upp denna fil under de policys som gäller för det delade centralförvaret.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Du kanske också skulle vilja prova att använda [[Special:Upload|uppladdningssidan på {{SITENAME}}]] om denna fil kan laddas upp där under deras policys.",
        "backend-fail-stream": "Kunde inte strömma filen $1.",
        "backend-fail-backup": "Kunde inte säkerhetskopiera filen ''$1''.",
        "backend-fail-notexists": "Filen $1 finns inte.",
        "logempty": "Inga matchande träffar i loggen.",
        "log-title-wildcard": "Sök efter sidtitlar som börjar med texten",
        "showhideselectedlogentries": "Visa/Dölj markerade loggposter",
-       "log-edit-tags": "Redigera taggar i valda loggposter",
+       "log-edit-tags": "Redigera märken i valda loggposter",
        "checkbox-select": "Välj: $1",
        "checkbox-all": "Alla",
        "checkbox-none": "Ingen",
        "changecontentmodel-success-text": "Innehållstypen för [[:$1]] har ändrats.",
        "changecontentmodel-cannot-convert": "Innehållet på [[:$1]] kan inte konverteras till typen $2.",
        "changecontentmodel-nodirectediting": "Innehållsmodellen $1 stöder inte direkt redigering",
+       "changecontentmodel-emptymodels-title": "Inget innehållsmodeller finns tillgängliga",
+       "changecontentmodel-emptymodels-text": "Innehållet på [[:$1]] kan inte konverteras till någon typ.",
        "log-name-contentmodel": "Ändringslogg för innehållsmodellen",
        "log-description-contentmodel": "Händelser som är relaterade till en sidas innehållsmodeller",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|skapade}} sidan $3 med den icke-standardiserade innehållsmodellen \"$5\"",
        "whatlinkshere-prev": "{{PLURAL:$1|förra|förra $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|nästa|nästa $1}}",
        "whatlinkshere-links": "← länkar",
-       "whatlinkshere-hideredirs": "$1 omdirigeringar",
-       "whatlinkshere-hidetrans": "$1 inkluderingar",
-       "whatlinkshere-hidelinks": "$1 länkar",
-       "whatlinkshere-hideimages": "$1 fillänkar",
+       "whatlinkshere-hideredirs": "Dölj omdirigeringar",
+       "whatlinkshere-hidetrans": "Dölj inkluderingar",
+       "whatlinkshere-hidelinks": "Dölj länkar",
+       "whatlinkshere-hideimages": "Dölj fillänkar",
        "whatlinkshere-filters": "Filter",
        "whatlinkshere-submit": "Gå",
        "autoblockid": "Autoblockera #$1",
        "lockdbsuccesstext": "Databasen är nu låst.\n<br />Kom ihåg att [[Special:UnlockDB|ta bort låsningen]] när du är färdig med ditt underhåll.",
        "unlockdbsuccesstext": "Databasen är upplåst.",
        "lockfilenotwritable": "Det går inte att skriva till databasens låsfil. För att låsa eller låsa upp databasen, så måste webbservern kunna skriva till den filen.",
+       "databaselocked": "Databasen är redan låst.",
        "databasenotlocked": "Databasen är inte låst.",
        "lockedbyandtime": "(av $1 den $2 kl. $3)",
        "move-page": "Flytta $1",
        "autoredircomment": "Omdirigerar till [[$1]]",
        "autosumm-new": "Skapade sidan med '$1'",
        "autosumm-newblank": "Skapade tom sida",
-       "size-bytes": "$1 byte",
+       "size-bytes": "$1 {{PLURAL:$1|byte}}",
        "size-kilobytes": "$1 kbyte",
        "size-megabytes": "$1 Mbyte",
        "size-gigabytes": "$1 Gbyte",
+       "size-pixel": "$1 {{PLURAL:$1|bildpunkt|bildpunkter}}",
        "lag-warn-normal": "Ändringar under {{PLURAL:$1|den senaste sekunden|de $1 senaste sekunderna}} kanske inte visas i den här listan.",
        "lag-warn-high": "På grund av omfattande fördröjning i databasen visas kanske inte ändringar nyare än $1 {{PLURAL:$1|sekund|sekunder}} i den här listan.",
        "watchlistedit-normal-title": "Redigera bevakningslista",
        "timezone-local": "Lokal",
        "duplicate-defaultsort": "'''Varning:''' Standardsorteringsnyckeln \"$2\" tar över från den tidigare standardsorteringsnyckeln \"$1\".",
        "duplicate-displaytitle": "<strong>Varning:</strong> Visningstiteln \"$2\" skriver över den tidigare visningstiteln \"$1\".",
+       "restricted-displaytitle": "<strong>Varning:</strong> Visningstiteln \"$1\" ignorerades eftersom den inte motsvarar sidans riktiga titel.",
        "invalid-indicator-name": "<p>Fel:</strong> Sidstatus-indikatorernas <code>namn</code>-attributet får inte vara tomt.",
        "version": "Version",
        "version-extensions": "Installerade programtillägg",
        "tags-hitcount": "$1 {{PLURAL:$1|ändring|ändringar}}",
        "tags-manage-no-permission": "Du har inte behörighet att hantera förändringstaggar.",
        "tags-manage-blocked": "Du kan inte hantera ändringsmärken när du är blockerad.",
-       "tags-create-heading": "Skapa en ny tag",
+       "tags-create-heading": "Skapa ett nytt märke",
        "tags-create-explanation": "Som standard, kommer nyskapade taggar att bli tillgängliga för användning av användare och botar.",
-       "tags-create-tag-name": "Taggnamn:",
+       "tags-create-tag-name": "Märkesnamn:",
        "tags-create-reason": "Anledning:",
        "tags-create-submit": "Skapa",
-       "tags-create-no-name": "Du måste ange ett taggnamn.",
-       "tags-create-invalid-chars": "Taggnamn får inte innehålla kommatecken (<code>,</code>) eller snedstreck (<code>/</code>).",
-       "tags-create-invalid-title-chars": "Taggnamn får inte innehålla tecken som inte kan användas i sidtitlar.",
-       "tags-create-already-exists": "Taggen \"$1\" finns redan.",
-       "tags-create-warnings-above": "Följande {{PLURAL:$2|varning |varningar}} stöttes på när du försöker skapa etiketten \" $1 \":",
-       "tags-create-warnings-below": "Vill du fortsätta att skapa taggen?",
-       "tags-delete-title": "Radera tagg",
-       "tags-delete-explanation-initial": "Du är på väg att ta bort taggen \"$1\" från databasen.",
+       "tags-create-no-name": "Du måste ange ett märkesnamn.",
+       "tags-create-invalid-chars": "Märkesnamn får inte innehålla kommatecken (<code>,</code>) eller snedstreck (<code>/</code>).",
+       "tags-create-invalid-title-chars": "Märkesnamn får inte innehålla tecken som inte kan användas i sidtitlar.",
+       "tags-create-already-exists": "Märket \"$1\" finns redan.",
+       "tags-create-warnings-above": "Följande {{PLURAL:$2|varning|varningar}} uppstod när du försökte skapa märket \"$1\":",
+       "tags-create-warnings-below": "Vill du fortsätta att skapa märket?",
+       "tags-delete-title": "Radera märke",
+       "tags-delete-explanation-initial": "Du är på väg att ta bort märket \"$1\" från databasen.",
        "tags-delete-explanation-in-use": "Den kommer att tas bort från {{PLURAL:$2|$2 sidversioner eller loggposter|alla $2 sidversioner och/eller loggposter}} som den för närvarande används på.",
-       "tags-delete-explanation-warning": "Denna åtgärd är <strong>oåterkallelig</strong> och <strong>kan inte ångras</strong>, inte ens av databasadministratörer. Var säker på att detta är den tagg du vill radera.",
-       "tags-delete-explanation-active": "<strong>Taggen\" $1 \" är fortfarande aktiv, och kommer att fortsätta att appliceras i framtiden.</strong> För att hindra detta, gå till den eller de platser där taggen är inställd att användas, och inaktivera den där.",
+       "tags-delete-explanation-warning": "Denna åtgärd är <strong>oåterkallelig</strong> och <strong>kan inte ångras</strong>, inte ens av databasadministratörer. Var säker på att detta är det märke du vill radera.",
+       "tags-delete-explanation-active": "<strong>Märket \"$1\" är fortfarande aktivt, och kommer att fortsätta att appliceras i framtiden.</strong> För att hindra detta, gå till den eller de platser där märket är inställd att användas, och inaktivera den där.",
        "tags-delete-reason": "Anledning:",
-       "tags-delete-submit": "Radera denna tagg oåterkalleligen",
-       "tags-delete-not-allowed": "Tagg definierade med ett tillägg kan inte raderas utan att tillägget specifikt tillåter det.",
-       "tags-delete-not-found": "Taggen \"$1\" finns inte.",
-       "tags-delete-too-many-uses": "Taggen \"$1\" appliceras på mer än $2 {{PLURAL:$2|version|versioner}}, vilket innebär att den inte kan raderas.",
-       "tags-delete-warnings-after-delete": "Taggen \"$1\" raderades, men följande {{PLURAL:$2|varning|varningar}} inträffade:",
-       "tags-activate-title": "Aktivera tagg",
-       "tags-activate-question": "Du är på väg att aktivera taggen \"$1\".",
+       "tags-delete-submit": "Radera denna märke oåterkalleligen",
+       "tags-delete-not-allowed": "Märken definierade med ett tillägg kan inte raderas utan att tillägget specifikt tillåter det.",
+       "tags-delete-not-found": "Märket \"$1\" finns inte.",
+       "tags-delete-too-many-uses": "Märket \"$1\" appliceras på fler än $2 {{PLURAL:$2|version|versioner}}, vilket innebär att det inte kan raderas.",
+       "tags-delete-warnings-after-delete": "Märket \"$1\" raderades, men följande {{PLURAL:$2|varning|varningar}} uppstod:",
+       "tags-delete-no-permission": "Du har inte behörighet att radera ändringsmärken.",
+       "tags-activate-title": "Aktivera märke",
+       "tags-activate-question": "Du är på väg att aktivera märket \"$1\".",
        "tags-activate-reason": "Anledning:",
-       "tags-activate-not-allowed": "Det är inte möjligt att aktivera taggen \"$1\".",
-       "tags-activate-not-found": "Taggen \"$1\" finns inte.",
+       "tags-activate-not-allowed": "Det är inte möjligt att aktivera märket \"$1\".",
+       "tags-activate-not-found": "Märket \"$1\" finns inte.",
        "tags-activate-submit": "Aktivera",
-       "tags-deactivate-title": "Inaktivera tagg",
-       "tags-deactivate-question": "Du är på väg att inaktivera taggen \"$1\".",
+       "tags-deactivate-title": "Inaktivera märke",
+       "tags-deactivate-question": "Du är på väg att inaktivera märket \"$1\".",
        "tags-deactivate-reason": "Anledning:",
-       "tags-deactivate-not-allowed": "Det är inte möjligt att inaktivera taggen \"$1\".",
+       "tags-deactivate-not-allowed": "Det är inte möjligt att inaktivera märket \"$1\".",
        "tags-deactivate-submit": "Inaktivera",
-       "tags-apply-no-permission": "Du har inte behörighet att tillämpa taggar på dina ändringar",
+       "tags-apply-no-permission": "Du har inte behörighet att tillämpa märken på dina ändringar",
        "tags-apply-blocked": "Du kan inte ange ändringsmärken med dina ändringar medans du är blockerad.",
        "tags-apply-not-allowed-one": "Märket \"$1\" kan inte läggas till manuellt.",
        "tags-apply-not-allowed-multi": "Följande {{PLURAL:$2|märke|märken}} kan inte läggas till manuellt: $1",
-       "tags-update-no-permission": "Du har inte behörighet att lägga till eller ta bort taggar från individuella sidversioner eller loggposter.",
+       "tags-update-no-permission": "Du har inte behörighet att lägga till eller ta bort märken från individuella sidversioner eller loggposter.",
        "tags-update-blocked": "Du kan inte lägga till eller ta bort ändringsmärken när du är blockerad.",
        "tags-update-add-not-allowed-one": "Märket \"$1\" kan inte läggas till manuellt.",
        "tags-update-add-not-allowed-multi": "Följande {{PLURAL:$2|märke|märken}} kan inte läggas till manuellt: $1",
        "logentry-upload-revert": "$1 {{GENDER:$2|laddade upp}} $3",
        "log-name-managetags": "Märkeshanteringslogg",
        "log-description-managetags": "Denna sida innehåller administrativa [[Special:Tags|märke]]srelaterade uppgifter. Loggen innehåller bara åtgärder som utförts manuellt av en administratör; märken kan skapas eller raderas av wikins mjukvara utan att en post registreras i loggen.",
-       "logentry-managetags-create": "$1 {{GENDER:$2|skapade}} taggen \"$4\"",
-       "logentry-managetags-delete": "$1 {{GENDER:$2|raderade}} taggen \"$4\" (borttagen från $5 {{PLURAL:$5|version eller loggpost|versioner och/eller loggposter}})",
-       "logentry-managetags-activate": "$1 {{GENDER:$2|aktiverade}} taggen \"$4\" för användning av användare och botar.",
-       "logentry-managetags-deactivate": "$1 {{GENDER:$2|inaktiverade}} taggen \"$4\" för användning av användare och botar.",
+       "logentry-managetags-create": "$1 {{GENDER:$2|skapade}} märket \"$4\"",
+       "logentry-managetags-delete": "$1 {{GENDER:$2|raderade}} märket \"$4\" (borttagen från $5 {{PLURAL:$5|version eller loggpost|versioner och/eller loggposter}})",
+       "logentry-managetags-activate": "$1 {{GENDER:$2|aktiverade}} märket \"$4\" för användning av användare och botar.",
+       "logentry-managetags-deactivate": "$1 {{GENDER:$2|inaktiverade}} märket \"$4\" för användning av användare och botar.",
        "log-name-tag": "Märkeslogg",
-       "log-description-tag": "Denna sida visar när användare har lagt till eller tagit bort [[Special:Tags|taggar]] från individuella sidversioner eller loggposter. Loggen registrerar inte handlingar där märken hanteras i redigeringar, raderingar eller liknande handlingar.",
+       "log-description-tag": "Denna sida visar när användare har lagt till eller tagit bort [[Special:Tags|märken]] från individuella sidversioner eller loggposter. Loggen registrerar inte handlingar där märken hanteras i redigeringar, raderingar eller liknande handlingar.",
        "logentry-tag-update-add-revision": "$1 {{GENDER:$2|lade till}} {{PLURAL:$7|märket|märkena}} $6 för sidversionen $4 av sidan $3",
        "logentry-tag-update-add-logentry": "$1 {{GENDER:$2|lade till}} {{PLURAL:$7|märket|märkena}} $6 till loggposten $5 för siden $3",
        "logentry-tag-update-remove-revision": "$1 {{GENDER:$2|tog bort}} {{PLURAL:$9|märket|märkena}} $8 från sidversionen $4 av sidan $3",
        "feedback-useragent": "Användaragent:",
        "searchsuggest-search": "Sök",
        "searchsuggest-containing": "innehåller...",
+       "api-error-autoblocked": "Din IP-adress har blockerats automatiskt eftersom den har använts av en blockerad användare.",
        "api-error-badaccess-groups": "Du får inte ladda upp filer till denna wiki.",
        "api-error-badtoken": "Internt fel: felaktig nyckel.",
+       "api-error-blocked": "Du har blockerats från att redigera.",
        "api-error-copyuploaddisabled": "Uppladdning via URL är inaktiverad på den här servern.",
        "api-error-duplicate": "Det finns redan {{PLURAL:$1|en annan fil|andra filer}} på webbplatsen med samma innehåll.",
        "api-error-duplicate-archive": "Det fanns redan {{PLURAL:$1|en annan fil|några andra filer}} på webbplatsen med samma innehåll, men {{PLURAL:$1|den har|de har}} raderats.",
        "api-error-nomodule": "Internt fel: ingen uppladdningsmodul uppsatt.",
        "api-error-ok-but-empty": "Internt fel: Inget svar från servern.",
        "api-error-overwrite": "Det är inte tillåtet att skriva över en befintlig fil.",
+       "api-error-ratelimited": "Du försöker ladda upp fler filer inom en kort tidsrymd än denna wiki tillåter.\nFörsök igen om några minuter.",
        "api-error-stashfailed": "Internt fel: servern kunde inte lagra temporär fil.",
        "api-error-publishfailed": "Internt fel: Servern kunde inte publicera temporär fil.",
        "api-error-stasherror": "Ett fel uppstod under uppladdningen av filen till mellanlagringsfilen.",
        "sessionprovider-nocookies": "Cookies kan vara inaktiverade. Se till att du har cookies aktiverat och försök igen.",
        "randomrootpage": "Slumprotsida",
        "log-action-filter-block": "Typ av blockering:",
+       "log-action-filter-contentmodel": "Typ av innehållsmodellsändring:",
        "log-action-filter-delete": "Typ av radering:",
        "log-action-filter-import": "Importeringstyp:",
+       "log-action-filter-managetags": "Typ av märkeshanteringsåtgärd:",
        "log-action-filter-move": "Flyttningstyp:",
        "log-action-filter-newusers": "Typ av kontoskapande:",
        "log-action-filter-patrol": "Typ av patrullering:",
        "log-action-filter-protect": "Typ av skydd:",
+       "log-action-filter-rights": "Typ av rättighetsändring",
        "log-action-filter-suppress": "Censurtyp",
        "log-action-filter-upload": "Typ av uppladdning:",
        "log-action-filter-all": "Alla",
        "log-action-filter-block-reblock": "Blockeringsändring",
        "log-action-filter-block-unblock": "Tog bort blockering",
        "log-action-filter-contentmodel-change": "Ändring av innehållsmodell",
+       "log-action-filter-contentmodel-new": "Skapande av sida med icke-standardiserad innehållsmodell",
        "log-action-filter-delete-delete": "Radering av sida",
        "log-action-filter-delete-restore": "Återställde sida",
        "log-action-filter-delete-event": "Radering av logg",
        "log-action-filter-delete-revision": "Radering av sidversion",
+       "log-action-filter-import-interwiki": "Interwikiimport",
        "log-action-filter-import-upload": "Importera med XML-uppladdning",
        "log-action-filter-managetags-create": "Märke skapad",
        "log-action-filter-managetags-delete": "Märke raderad",
        "log-action-filter-newusers-create": "Skapade av anonyma användare",
        "log-action-filter-newusers-create2": "Skapade av registrerade användare",
        "log-action-filter-newusers-autocreate": "Skapades automatiskt",
+       "log-action-filter-newusers-byemail": "Skapades med lösenord via e-post",
        "log-action-filter-patrol-patrol": "Manuell patrullering",
        "log-action-filter-patrol-autopatrol": "Automatisk patrullering",
        "log-action-filter-protect-protect": "Skydd",
        "log-action-filter-protect-move_prot": "Flyttade skydd",
        "log-action-filter-rights-rights": "Manuell ändring",
        "log-action-filter-rights-autopromote": "Automatisk ändring",
+       "log-action-filter-suppress-event": "Loggcensur",
+       "log-action-filter-suppress-revision": "Sidversionscensur",
+       "log-action-filter-suppress-delete": "Sidcensur",
+       "log-action-filter-suppress-block": "Användarcensur efter blockering",
+       "log-action-filter-suppress-reblock": "Användarcensur efter återblockering",
        "log-action-filter-upload-upload": "Ny uppladdning",
        "log-action-filter-upload-overwrite": "Återuppladdning"
 }
index c39fa4e..d47434e 100644 (file)
        "noname": "Hauja dhihilisha jina la mtumiaji.",
        "loginsuccesstitle": "Umefaulu kuingia",
        "loginsuccess": "'''Umeingia {{SITENAME}} kama \"$1\".'''",
-       "nosuchuser": "Hakuna mtumiaji mwenye jina \"$1\".\nKumbuka kwamba programu inatofautishana kati ya herufi kubwa na ndogo.\nLabda umeandika vibaya, au [[Special:UserLogin/signup|sajili akaunti mpya]].",
+       "nosuchuser": "Hakuna mtumiaji mwenye jina \"$1\".\nKumbuka kwamba programu inatofautishana kati ya herufi kubwa na ndogo.\nLabda umeandika vibaya, au [[Special:CreateAccount|sajili akaunti mpya]].",
        "nosuchusershort": "Hakuna mtumiaji mwenye jina \"$1\". Labda umeandika vibaya.",
        "nouserspecified": "Lazima uandike jina la mtumiaji.",
        "login-userblocked": "Mtumiaji huyu amezuiwa. Hawezi kuingia.",
        "accmailtext": "Neno la siri limetolewa na programu kwa ajili ya [[User talk:$1|$1]] na limetumwa kwa $2.\n\nUnaweza kubadilisha neno la siri hilo kwenye ukurasa wa ''[[Special:ChangePassword|kubadilisha neno la siri]]'' baada ya kuingia.",
        "newarticle": "(Mpya)",
        "newarticletext": "Ukurasa unaotaka haujaandikwa bado. Ukipenda unaweza kuuandika wewe mwenyewe kwa kutumia sanduku la hapa chini (tazama [$1 Mwongozo] kwa maelezo zaidi). Ukifika hapa kwa makosa, bofya kibonyezi '''back''' (nyuma) cha programu yako.",
-       "anontalkpagetext": "----''Huu ni ukurasa wa majadiliano wa mtumiaji ambaye hana jina na bado hajaumba akaunti bado, au hajawahi kutumia kabisa.\nKwa hiyo tunatumia namba za anwani ya IP yake kumtambulisha.\nAnwani ya IP kama hiyo inaweza kutumika na watumiaji kadhaa.\nLabda itakusumbua kwamba kuna maoni mengine yanawekwa hapa na unaamini kwamba haya maoni hayakulengi. Ikiwa hivyo, tafadhali [[Special:UserLogin/signup|fungua akaunti]] au  [[Special:UserLogin|ingia]] ili kuepuka kuchanganywa na watumiaji wengine ambao hawana jina.''",
+       "anontalkpagetext": "----''Huu ni ukurasa wa majadiliano wa mtumiaji ambaye hana jina na bado hajaumba akaunti bado, au hajawahi kutumia kabisa.\nKwa hiyo tunatumia namba za anwani ya IP yake kumtambulisha.\nAnwani ya IP kama hiyo inaweza kutumika na watumiaji kadhaa.\nLabda itakusumbua kwamba kuna maoni mengine yanawekwa hapa na unaamini kwamba haya maoni hayakulengi. Ikiwa hivyo, tafadhali [[Special:CreateAccount|fungua akaunti]] au  [[Special:UserLogin|ingia]] ili kuepuka kuchanganywa na watumiaji wengine ambao hawana jina.''",
        "noarticletext": "Ukurasa huu haujaandikwa bado. [[Special:Search/{{PAGENAME}}|tafutia jina hili]] katika kurasa nyingine, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tafuta kumbukumbu zinazohusika], au [{{fullurl:{{FULLPAGENAME}}|action=edit}} hariri ukurasa huu]</span>.",
        "noarticletext-nopermission": "Kwa sasa hakuna maandishi katika ukurasa huu.\nUnaweza [[Special:Search/{{PAGENAME}}|kutafuta jina la ukurasa huu]] katika kurasa nyingine,\nau <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tafuta kumbukumbu zinazohusika]</span>, lakini huruhusiwi kuanzisha ukurasa huu.",
        "userpage-userdoesnotexist": "Akaunti ya mtumiaji \"<nowiki>$1</nowiki>\" haijasajilishwa.\nUkitaka kuanzisha au kuhariri ukurasa huu tafadhali ucheki jina la akaunti.",
index 589adc0..23b272e 100644 (file)
        "noname": "To ńy je poprowne mjano użytkowńika.",
        "loginsuccesstitle": "Logowańy udane",
        "loginsuccess": "'''Terozki jeżeś zalogowany do {{SITENAME}} kej \"$1\".'''",
-       "nosuchuser": "Ńy ma sam użytkowńika uo mjańe \"$1\".\nSprowdź szrajbůng, abo [[Special:UserLogin/signup|utwůrz nowe kůnto]].",
+       "nosuchuser": "Ńy ma sam użytkowńika uo mjańe \"$1\".\nSprowdź szrajbůng, abo [[Special:CreateAccount|utwůrz nowe kůnto]].",
        "nosuchusershort": "Ńy mo sam użytkowńika uo mjańe \"$1\".",
        "nouserspecified": "Podej mjano użytkowńika.",
        "login-userblocked": "Tyn sprowjorz ma zawrzite sprowjyńa. Ńy możno sie zalogować.",
        "accmailtext": "Cufalńe hasło lo [[User talk:$1|$1]] uostoło posłane do $2. Hasło lo tygo nowygo kůnta po zalogowańu je mogebność pomjyńić na zajće ''[[Special:ChangePassword|pomjyńańe hasła]]''.",
        "newarticle": "(Nowy)",
        "newarticletext": "Ńy mo sam jeszcze artikla uo takijj titli. Eli chcesz go sprowjać, naszkryflej niżyj jego tekst (wjyncy informacyj nojdźesz [$1 na zajće půmocy]). Eli żeś chćoł zrobić cośik inksze, naćiś ino knefel \"Nazod\".",
-       "anontalkpagetext": "---- ''To je zajta godki lo anůnimowych używoczy  - takich, kerzi ńy majům jeszcze swojigo kůnta abo ńy chcům go terozki używać.\nBy jejich idyntyfikować, używomy numerůw IP.\nEli jeżeś anůnimowym używoczym a wydowo Ći śe, aże zamjyszczůne sam kůmyntorze ńy sům skjyrowane do Ćebje, [[Special:UserLogin/signup|utwůrz kůnto]] abo [[Special:UserLogin|zaloguj śe]] - beztůż uńikńesz potym podobnych ńyporozumjyń.''",
+       "anontalkpagetext": "---- ''To je zajta godki lo anůnimowych używoczy  - takich, kerzi ńy majům jeszcze swojigo kůnta abo ńy chcům go terozki używać.\nBy jejich idyntyfikować, używomy numerůw IP.\nEli jeżeś anůnimowym używoczym a wydowo Ći śe, aże zamjyszczůne sam kůmyntorze ńy sům skjyrowane do Ćebje, [[Special:CreateAccount|utwůrz kůnto]] abo [[Special:UserLogin|zaloguj śe]] - beztůż uńikńesz potym podobnych ńyporozumjyń.''",
        "noarticletext": "Ńy můmy zajta uo takij titli. Mogesz [{{fullurl:{{FULLPAGENAME}}|action=edit}} wćepać artikel {{FULLPAGENAME}}] abo [[Special:Search/{{PAGENAME}}|sznupać {{PAGENAME}} we inkszych]].",
        "noarticletext-nopermission": "Ta zajta terozki je pusto.\nMogesz [[Special:Search/{{PAGENAME}}|wysznupać ta titla]] we treśćach inkszych zajtůw, abo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przesznupać powjůnzane rejery]</span>, nale ńy mosz uprowńyń coby ta zajta wćepać",
        "userpage-userdoesnotexist": "Użytkowńik \"<nowiki>$1</nowiki>\" ńy je zarejesztrowany. Sprowdź eli na pewno chćołżeś stworzyć/pomjynić gynał ta zajta.",
index 01370ca..1308537 100644 (file)
        "noname": "நீங்கள் கொடுத்த பயனர் பெயர் செல்லுபடியற்றது.",
        "loginsuccesstitle": "புகுபதிகையில் உள்ளீர்கள்.",
        "loginsuccess": "நீங்கள் தற்பொழுது {{SITENAME}} தளத்தில் \"$1\" கணக்கினூடாக புகுபதிகை செய்துள்ளீர்கள்.",
-       "nosuchuser": "\"$1\" என்ற பெயரில் பயனர் எவருமில்லை.\n\nபயனர் பெயர், பெரிய எழுத்து,  சிறிய எழுத்து என்ற வித்தியாசத்திற்குட்பட்டது.\n\nஎழுத்துப் பிழைகளைச் சரி பார்க்கவும், அல்லது [[Special:UserLogin/signup|புதிய பயனர் கணக்கொன்றை உருவாக்கவும்]].",
+       "nosuchuser": "\"$1\" என்ற பெயரில் பயனர் எவருமில்லை.\n\nபயனர் பெயர், பெரிய எழுத்து,  சிறிய எழுத்து என்ற வித்தியாசத்திற்குட்பட்டது.\n\nஎழுத்துப் பிழைகளைச் சரி பார்க்கவும், அல்லது [[Special:CreateAccount|புதிய பயனர் கணக்கொன்றை உருவாக்கவும்]].",
        "nosuchusershort": "\"$1\" என்ற பெயரில் பயனர் யாரும் இல்லை. நீங்கள் உள்ளிட்ட பெயரைச் சரி பார்க்கவும்.",
        "nouserspecified": "நீங்கள் பயனர் பெயரொன்றைக் குறிப்பிட வேண்டும்.",
        "login-userblocked": "இப்பயனர் கணக்கு தடைசெய்யப்பட்டுள்ளது. புகுபதிகை அனுமதிக்கப்படவில்லை.",
        "accmailtext": "தானியக்கமாக [[User talk:$1|$1]]-க்கு ஒரு கடவுச்சொலை உறுவாக்கி $2-க்கு அனுப்பி வைக்கப்பட்டுள்ளது. இதனை புகுபதிகை செய்தவுடன் ''[[Special:ChangePassword|கடவுச்சொல்லை மாற்று]]'' பக்கத்தில் மாற்றிக்கொள்ளளாம்.",
        "newarticle": "(புதிது)",
        "newarticletext": "ஒரு இணைப்பினூடாக நீங்கள் வந்துள்ள இப்பக்கம் இன்னும் உருவாக்கப்படவில்லை. பக்கத்தை உருவாக்குவதற்குக் கீழேயுள்ள கட்டத்துள் தட்டச்சிடத் தொடங்குங்கள். (மேலதிக விபரங்களுக்கு [$1 உதவிப் பக்கத்தைப்] பார்க்கவும்). நீங்கள் தவறுதலாக இங்கே வந்திருந்தால், உங்கள் உலாவியின் பின் செல்வதற்கான பொத்தானைச் சொடுக்கவும்.",
-       "anontalkpagetext": "----''இது இன்னும் கணக்கொன்று ஏற்படுத்தாத அல்லது வழமையாக பயனர் கணக்கை பயன்படுத்தாத பயனர்களுக்குரிய கலந்துரையாடல் பக்கமாகும். அதனால் நாங்கள் இவரை அடையாளம் காண்பதற்கு எண்சார்ந்த ஐபி முகவரியைப் பயன்படுத்த வேண்டியதாய் இருக்கின்றது. இவ்வாறான ஐபி முகவரிகள் பல பயனர்களினால் பகிர்ந்துகொள்ளப்படலாம்.\nநீங்கள் ஒரு முகவரியற்ற பயனராயிருந்து, தொடர்பற்ற கருத்துக்கள் உங்களைக் குறித்துச் சொல்லப்பட்டிருப்பதாக நீங்கள் உணர்ந்தால், முகவரியற்ற ஏனைய பயனர்களுடனான குழப்பங்களை எதிர்காலத்தில் தவிர்ப்பதற்கு, தயவுசெய்து [[Special:UserLogin/signup|புதிய கணக்கொன்றை ஏற்படுத்துங்கள்]] அல்லது [[Special:UserLogin|புகுபதிகை]] செய்யுங்கள்.''",
+       "anontalkpagetext": "----''இது இன்னும் கணக்கொன்று ஏற்படுத்தாத அல்லது வழமையாக பயனர் கணக்கை பயன்படுத்தாத பயனர்களுக்குரிய கலந்துரையாடல் பக்கமாகும். அதனால் நாங்கள் இவரை அடையாளம் காண்பதற்கு எண்சார்ந்த ஐபி முகவரியைப் பயன்படுத்த வேண்டியதாய் இருக்கின்றது. இவ்வாறான ஐபி முகவரிகள் பல பயனர்களினால் பகிர்ந்துகொள்ளப்படலாம்.\nநீங்கள் ஒரு முகவரியற்ற பயனராயிருந்து, தொடர்பற்ற கருத்துக்கள் உங்களைக் குறித்துச் சொல்லப்பட்டிருப்பதாக நீங்கள் உணர்ந்தால், முகவரியற்ற ஏனைய பயனர்களுடனான குழப்பங்களை எதிர்காலத்தில் தவிர்ப்பதற்கு, தயவுசெய்து [[Special:CreateAccount|புதிய கணக்கொன்றை ஏற்படுத்துங்கள்]] அல்லது [[Special:UserLogin|புகுபதிகை]] செய்யுங்கள்.''",
        "noarticletext": "இப் பக்கத்தில் தற்பொழுது உள்ளடக்கம் எதுவுமில்லை. நீங்கள் இப்பக்க [[Special:Search/{{PAGENAME}}|தலைப்பை வேறு பக்கங்களில் தேடவோ]] அல்லது [{{fullurl:{{FULLPAGENAME}}|action=edit}} இப்பக்கத்தை தொகுக்கவோ] முடியும்.",
        "noarticletext-nopermission": "தற்பொழுது இப்பக்கத்தில் உரை எதுவும் இல்லை.\nநீங்கள் [[Special:Search/{{PAGENAME}}|பக்கத் தலைப்பை வைத்து]] அல்லது மற்ற பக்கங்களில்,\nஅல்லது <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} அல்லது தேடுதல் தொடர்பான பதிவுகளில் தேடலாம்.]</span>, ஆனால் இந்தப் பக்கத்தை உருவாக்க அனுமதியில்லை.",
        "missing-revision": "இந்த பரிசீலனை # $1  '' {{FULLPAGENAME}}' பெயருள்ள பக்கத்தின் இல்லை.!N வேடிக்கையானN!இது தான் வழக்கமாக ஏற்பட்டிருக்கலாம் நீக்கப்பட்டுள்ளது பக்கத்திற்கு outdated வரலாறு இணைப்பை தொடர்ந்து.\nவிவரங்கள் முடியும் கண்டறிய, [{{fullurl: {{# சிறப்பு: குறிப்பேடு}} / delete|page = {{FULLPAGENAMEE}}}} நீக்குதல் குறிப்பேடு].",
        "upload-form-label-infoform-description": "விளக்கம்",
        "upload-form-label-usage-title": "பயன்பாடு",
        "upload-form-label-usage-filename": "கோப்பின் பெயர்",
-       "foreign-structured-upload-form-label-own-work": "இது என் சொந்த வேலை",
-       "foreign-structured-upload-form-label-infoform-categories": "பகுப்புகள்",
-       "foreign-structured-upload-form-label-infoform-date": "தேதி",
+       "upload-form-label-own-work": "இது என் சொந்த வேலை",
+       "upload-form-label-infoform-categories": "பகுப்புகள்",
+       "upload-form-label-infoform-date": "தேதி",
        "backend-fail-stream": " $1 கோப்பை stream செய்ய இயலவில்லை  .",
        "backend-fail-backup": " $1 கோப்பை பின்சேமிப்பு (backup) செய்ய இயலவில்லை  .",
        "backend-fail-notexists": " \"$1\"  என்னும் கோப்பு எதுவும் இல்லை",
index 60e4246..0a71f3f 100644 (file)
        "noname": "ಈರ್ ಸರಿಯಾಯಿನ ಬಳಕೆದಾರ ಪುದರ್ ಕೊರ್ತಿಜ್ಜರ್.",
        "loginsuccesstitle": "ಲಾಗ್ ಇನ್ ಯಶಸ್ವಿಯಾತ್ಂಡ್",
        "loginsuccess": "ಲಾಗ್ ಇನ್ ಯಶಸ್ವಿಯಾತ್‘ಂಡ್\". {{SITENAME}}  \"$1\".'''",
-       "nosuchuser": "!!\"$1\"ಪುದರ್‘ದ ವಾ ಸದಸ್ಯೆರ್‘ಲಾ ಇಜ್ಜೆರ್, ಅಕ್ಷರ ಸರಿಯಾದ ತೂಲೆ ಅಥವಾ  [[Special:UserLogin/signup|ಪೊಸ ಸದಸ್ಯತ್ವ  ಖಾತೆನ್ ಸೃಷ್ಟಿ ಮಲ್ಪುಲೆ]].",
+       "nosuchuser": "!!\"$1\"ಪುದರ್‘ದ ವಾ ಸದಸ್ಯೆರ್‘ಲಾ ಇಜ್ಜೆರ್, ಅಕ್ಷರ ಸರಿಯಾದ ತೂಲೆ ಅಥವಾ  [[Special:CreateAccount|ಪೊಸ ಸದಸ್ಯತ್ವ  ಖಾತೆನ್ ಸೃಷ್ಟಿ ಮಲ್ಪುಲೆ]].",
        "nosuchusershort": "!!\"$1\"ಪುದರ್‘ದ ವಾ ಸದಸ್ಯೆರ್‘ಲಾ ಇಜ್ಜೆರ್, ಅಕ್ಷರ ಸರಿಯಾದ ತೂಲೆ.",
        "nouserspecified": "ಈರ್ ಒಂಜಿ ಸದಸ್ಯತ್ವದ ಪುದರ್ ಸೂಚನೆ ಮಲ್ಪೊಡು.",
        "login-userblocked": "ಈ ಸದಸ್ಯರೆನ ಖಾತೆನ್ ತಡೆ ಪತ್ತ್‘ದುಂಡು. ಲಾಗ್ ಇನ್ ಮಲ್ಪರೆ ಆಪುಜ್ಜಿ.",
        "minoredit": "ಉಂದು ಎಲ್ಯ ಬದಲಾವಣೆ",
        "watchthis": "ಈ ಪುಟೊನು ತೂಲೆ",
        "savearticle": "ಪುಟೊನು ಒರಿಪಾಲೆ",
+       "publishpage": "ಪುಟೋನು ಪ್ರಕಟಿಸಲೇ",
        "preview": "ಮುನ್ನೋಟ",
        "showpreview": "ಮುನ್ನೋಟೊ ತೋಜಾವು",
        "showdiff": "ಬದಲಾವಣೆಲೆನ್ ತೋಜಾವ್",
        "tooltip-ca-nstab-category": "ವರ್ಗೊದ ಪುಟೊನು ತೂಲೆ",
        "tooltip-minoredit": "ಇಂದೆನ್ ಎಲ್ಯ ಬದಲಾವಣೆ ಪಂಡ್ದ್ ಗುರ್ತ ಮಲ್ಪುಲೆ",
        "tooltip-save": "ಈರ್ ಮಲ್ತ್‌ನ ಬದಲಾವಣೆಲೆನ್ ಒರಿಪ್ಪಾಲೆ",
+       "tooltip-publish": "ಇರೇನಾ ಬದಲಾವನೇನ್ ತೊಜಲೇ",
        "tooltip-preview": "ಈರ್ ಮಲ್ತ‍್‌ನ ಬದಲಾವಣೆತ ಮುನ್ನೋಟ - ಈ ಪುಟನ್ ಒರಿಪಾವುನ ದು೦ಬು ಉಂದೆನ್ ತೂಲೆ",
        "tooltip-diff": "ಈ ಲೇಕನೊಗ್ ಮಲ್ತಿನ ಬದಲಾವಣೆಲೆನ್ ತೋಜಾವ್",
        "tooltip-compareselectedversions": "ಈ ಪುಟತ ಆಯ್ಕೆ ಮಲ್ತಿನ ರಡ್ಡ್ ಆವೃತ್ತಿದ ವ್ಯತ್ಯಾಸನ್ ತೂಲೆ",
index 4f5de01..ad0cd23 100644 (file)
        "noname": "మీరు సరైన వాడుకరి పేరు ఇవ్వలేదు.",
        "loginsuccesstitle": "ప్రవేశం విజయవంతమైంది",
        "loginsuccess": "<strong>మీరు ఇప్పుడు {{SITENAME}}లోనికి \"$1\"గా ప్రవేశించారు.</strong>",
-       "nosuchuser": "\"$1\" అనే పేరుతో వాడుకరులు లేరు.\nవాడుకరి పేర్లు కేస్ సెన్సిటివ్.\nఅక్షరక్రమం సరిచూసుకోండి, లేదా [[Special:UserLogin/signup|కొత్త ఖాతా సృష్టించుకోండి]].",
+       "nosuchuser": "\"$1\" అనే పేరుతో వాడుకరులు లేరు.\nవాడుకరి పేర్లు కేస్ సెన్సిటివ్.\nఅక్షరక్రమం సరిచూసుకోండి, లేదా [[Special:CreateAccount|కొత్త ఖాతా సృష్టించుకోండి]].",
        "nosuchusershort": "\"$1\" పేరుతో వాడుకరి ఎవరూ లేరు. పేరు సరి చూసుకోండి.",
        "nouserspecified": "వాడుకరి పేరును తప్పనిసరిగా ఇవ్వాలి.",
        "login-userblocked": "ఈ వాడుకరిని నిరోధించారు. ప్రవేశానికి అనుమతి లేదు.",
        "accmailtext": "[[User talk:$1|$1]] కొరకు ఒక యాదృచ్ఛిక సంకేతపదాన్ని $2కి పంపించాం. లాగినయ్యాక, ''[[Special:ChangePassword|సంకేతపదాన్ని మార్చుకోండి]]'' అనే పేజీలో ఈ సంకేతపదాన్ని మార్చుకోవచ్చు.",
        "newarticle": "(కొత్తది)",
        "newarticletext": "ఈ లింకుకు సంబంధించిన పేజీ లేనే లేదు.\nకింది పెట్టెలో మీ రచనను టైపు చేసి ఆ పేజీని సృష్టించండి (దీనిపై సమాచారం కొరకు [$1 సహాయం పేజీ] చూడండి). మీరిక్కడికి పొరపాటున వచ్చి ఉంటే, మీ బ్రౌజరు <strong>back</strong> మీట నొక్కండి.",
-       "anontalkpagetext": "----\n<em>ఇది ఒక అజ్ఞాత వాడుకరి చర్చా పేజీ. ఆ వాడుకరి ఇంకా తనకై ఖాతాను సృష్టించుకోలేదు, లేదా ఖాతా ఉన్నా దానిని ఉపయోగించడం లేదు.</em>\nఅంచేత, అతణ్ణి/ఆమెను గుర్తించడానికి ఐ.పీ. చిరునామాను వాడాల్సి వచ్చింది. \nఒకే ఐ.పీ. చిరునామాని చాలా మంది వాడుకరులు ఉపయోగించే అవకాశం ఉంది. \nమీరూ అజ్ఞాత వాడుకరి అయితే, మీకు సంబంధంలేని వ్యాఖ్యలు మిమ్మల్ని ఉద్దేశించినట్టుగా అనిపిస్తే, భవిష్యత్తులో ఇతర అజ్ఞాత వాడుకరులతో అయోమయం లేకుండా ఉండటానికి, [[Special:UserLogin/signup|ఖాతాను సృష్టించుకోండి]] లేదా [[Special:UserLogin|లాగినవండి]].''",
+       "anontalkpagetext": "----\n<em>ఇది ఒక అజ్ఞాత వాడుకరి చర్చా పేజీ. ఆ వాడుకరి ఇంకా తనకై ఖాతాను సృష్టించుకోలేదు, లేదా ఖాతా ఉన్నా దానిని ఉపయోగించడం లేదు.</em>\nఅంచేత, అతణ్ణి/ఆమెను గుర్తించడానికి ఐ.పీ. చిరునామాను వాడాల్సి వచ్చింది. \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}}|ఈ పేజీ శీర్షిక కోసం వెతకవచ్చు]], లేదా <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} సంబంధిత చిట్టాలలో వెతకవచ్చు]</span>, కానీ ఈ పేజీని సృష్టించడానికి మీకు అనుమతి లేదు.",
        "missing-revision": "\"{{FULLPAGENAME}}\" అనే పేజీ యొక్క కూర్పు #$1 ఉనికిలో లేదు. సాధారణంగా ఏదైనా తొలగించబడిన పేజీ యొక్క కాలం చెల్లిన చరితం లింకును నొక్కినపుడు ఇది జరుగుతుంది. వివరాలు [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} తొలగింపు లాగ్] లో దొరుకుతాయి.",
        "upload-form-label-infoform-description": "వివరణ",
        "upload-form-label-usage-title": "వాడుక",
        "upload-form-label-usage-filename": "దస్త్రపు పేరు",
-       "foreign-structured-upload-form-label-own-work": "ఇది నా స్వంత కృతి",
-       "foreign-structured-upload-form-label-infoform-categories": "వర్గాలు",
-       "foreign-structured-upload-form-label-infoform-date": "తేదీ",
+       "upload-form-label-own-work": "ఇది నా స్వంత కృతి",
+       "upload-form-label-infoform-categories": "వర్గాలు",
+       "upload-form-label-infoform-date": "తేదీ",
        "backend-fail-stream": "\"$1\" ఫైలును స్ట్రీమింగు చెయ్యలేకపోయాం.",
        "backend-fail-backup": "\"$1\" ఫైలును బ్యాకప్పు చెయ్యలేకపోయాం.",
        "backend-fail-notexists": "$1 ఫైలు అసలు లేనేలేదు.",
index 70b9b62..6450ba1 100644 (file)
        "noname": "Номи корбари дурустеро шумо пешниҳод накардед.",
        "loginsuccesstitle": "Вуруд бо муваффақият",
        "loginsuccess": "'''Шумо акнун ба Википедиа ҳамчун \"$1\". вуруд кардед'''",
-       "nosuchuser": "Корбаре бо номи \"$1\" вуҷуд надорад.\nАмали номро барраси кунед, ё [[Special:UserLogin/signup|ҳисоби ҷадидеро эҷод кунед]].",
+       "nosuchuser": "Корбаре бо номи \"$1\" вуҷуд надорад.\nАмали номро барраси кунед, ё [[Special:CreateAccount|ҳисоби ҷадидеро эҷод кунед]].",
        "nosuchusershort": "Ягон корбаре бо номи \"$1\" вуҷуд надорад. Тарзи навишти номро санҷед.",
        "nouserspecified": "Шумо бояд як номи корбарӣ мушаххас кунед.",
        "login-userblocked": "Ин корбар баста шудааст. Вуруд манъ аст.",
        "accmailtext": "Як гузарвожаи тасодуфан эҷодшуда барои [[User talk:$1|$1]] ба $2 фиристода шуд. Онро метавон дар саҳифаи <em>[[Special:ChangePassword|тағийри гузарвожа]]</em> пас аз вуруд тағйир дод.",
        "newarticle": "(Нав)",
        "newarticletext": "Шумо пайвандеро интихоб кардед, ки саҳифа дар он арзи вуҷуд надорад.\nБарои сохтани саҳифа, ба қуттии зерин нависед ([$1 саҳифаи роҳнаморо] барои маълумоти бештар нигаред).\nАгар аз сабаби хатогӣ ва ё иштибоҳ омадед, тугмаи '''Ба оқиб'''-ро дар браузери худ пахш кунед.",
-       "anontalkpagetext": "----\n<em>Ин саҳифаи баҳс барои як корбари гумном (аноним) аст, ки ҳанӯз ҳисоби ҷадид эҷод накардааст ва ё аз он истифода намекунад.</em> \nБинобар ин барои шиносоиаш маҷбурем аз нишонаи IP рақамии вай истифода баред.\nЧунин нишонаи IP шояд аз сӯи чандин корбарон ба шакли муштарак истифода шавад. \nАгар шумо корбари гумном ҳастед ва ҳис мекунед, ки изҳори назари номарбуте ба шумо сурат гирифтааст, лутфан [[Special:UserLogin/signup|ҳисоберо эҷод кунед]] ё [[Special:UserLogin|вуруд шавед]] барои пешгирӣ аз нофаҳмиҳои оянда бо корбарони гумном.",
+       "anontalkpagetext": "----\n<em>Ин саҳифаи баҳс барои як корбари гумном (аноним) аст, ки ҳанӯз ҳисоби ҷадид эҷод накардааст ва ё аз он истифода намекунад.</em> \nБинобар ин барои шиносоиаш маҷбурем аз нишонаи IP рақамии вай истифода баред.\nЧунин нишонаи IP шояд аз сӯи чандин корбарон ба шакли муштарак истифода шавад. \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": "Дар ин саҳифа то кунун матне вуҷуд надорад. Шумо метавонед дар дигар саҳифаҳо [[Special:Search/{{PAGENAME}}|унвони ин саҳифаро ҷустуҷӯ кунед]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} гузоришҳои алоқамандро ҷустуҷӯ намоед], ё [{{fullurl:{{FULLPAGENAME}}|action=edit}} ин саҳифаро вироиш кунед]</span>.",
        "missing-revision": "Нусхаи #$1 саҳифа бо номи \"{{FULLPAGENAME}}\" вуҷуд надорад.\n\nОдатан ин амал дар ҳоли пайгирии пайванди барӯзнашудаи таърих ба саҳифаи ҳазвшуда рух медиҳад.\nИттилооти муфассалро [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дар гузориши ҳазв] дарёфт кардан мумкин аст.",
index 981c7ca..d5c41c3 100644 (file)
        "noname": "Nomi korbari durustero şumo peşnihod nakarded.",
        "loginsuccesstitle": "Vurud bo muvaffaqijat",
        "loginsuccess": "'''Şumo aknun ba Vikipedia hamcun \"$1\". vurud karded'''",
-       "nosuchuser": "Korbare bo nomi \"$1\" vuçud nadorad.\nAmali nomro barrasi kuned, jo [[Special:UserLogin/signup|hisobi çadidero eçod kuned]].",
+       "nosuchuser": "Korbare bo nomi \"$1\" vuçud nadorad.\nAmali nomro barrasi kuned, jo [[Special:CreateAccount|hisobi çadidero eçod kuned]].",
        "nosuchusershort": "Jagon korbare bo nomi \"$1\" vuçud nadorad. Tarzi navişti nomro sançed.",
        "nouserspecified": "Şumo bojad jak nomi korbarī muşaxxas kuned.",
        "login-userblocked": "In korbar basta şudaast. Vurud man' ast.",
index c81200a..2070932 100644 (file)
@@ -22,7 +22,8 @@
                        "วรากร อึ้งวิเชียร (Varakorn Ungvichian)",
                        "아라",
                        "Pphongpan355",
-                       "Macofe"
+                       "Macofe",
+                       "Pilarbini"
                ]
        },
        "tog-underline": "การขีดเส้นใต้ลิงก์:",
@@ -40,6 +41,7 @@
        "tog-watchdefault": "เพิ่มหน้าและไฟล์ที่ฉันแก้ไขเข้ารายการเฝ้าดู",
        "tog-watchmoves": "เพิ่มและไฟล์ที่ฉันย้ายเข้ารายการเฝ้าดู",
        "tog-watchdeletion": "เพิ่มหน้าและไฟล์ที่ฉันลบเข้ารายการเฝ้าดู",
+       "tog-watchuploads": "เพิ่มไฟล์ใหม่ที่ฉันอัพโหลดไปยังรายการเฝ้าดูของฉัน",
        "tog-watchrollback": "เพิ่มหน้าที่ฉันย้อนกลับฉุกเฉินเข้ารายการเฝ้าดู",
        "tog-minordefault": "กำหนดการแก้ไขทุกครั้งเป็นการแก้ไขเล็กน้อยโดยปริยาย",
        "tog-previewontop": "แสดงตัวอย่างก่อนกล่องแก้ไข",
        "noname": "คุณไม่ได้ใส่ชื่อผู้ใช้ที่ถูกต้อง",
        "loginsuccesstitle": "ล็อกอินสำเร็จ",
        "loginsuccess": "<strong>ขณะนี้คุณล็อกอินสู่ {{SITENAME}} ในชื่อ \"$1\"</strong>",
-       "nosuchuser": "ไม่มีผู้ใช้ชื่อ \"$1\"\nชื่อผู้ใช้นั้นไวต่ออักษรใหญ่เล็ก\nกรุณาตรวจการสะกดอีกครั้ง หรือ[[Special:UserLogin/signup|สร้างบัญชีใหม่]]",
+       "nosuchuser": "ไม่มีผู้ใช้ชื่อ \"$1\"\nชื่อผู้ใช้นั้นไวต่ออักษรใหญ่เล็ก\nกรุณาตรวจการสะกดอีกครั้ง หรือ[[Special:CreateAccount|สร้างบัญชีใหม่]]",
        "nosuchusershort": "ไม่มีผู้ใช้ชื่อ \"$1\" \nกรุณาตรวจสอบการสะกด",
        "nouserspecified": "คุณต้องระบุชื่อผู้ใช้",
        "login-userblocked": "ผู้ใช้นี้ถูกบล็อก ไม่อนุญาตให้ล็อกอิน",
        "changepassword-success": "เปลี่ยนรหัสผ่านของคุณสำเร็จ!",
        "changepassword-throttled": "ล่าสุดคุณพยายามล็อกอินมากครั้งเกินไป\nกรุณารอ $1 ก่อนลองอีกครั้ง",
        "botpasswords": "รหัสผ่านบอต",
+       "botpasswords-label-appid": "ชื่อบอต:",
+       "botpasswords-label-create": "สร้าง",
+       "botpasswords-label-update": "อัปเดต",
+       "botpasswords-label-cancel": "ยกเลิก",
+       "botpasswords-label-delete": "ลบ",
+       "botpasswords-label-resetpassword": "ตั้งรหัสผ่านใหม่",
        "resetpass_forbidden": "ไม่สามารถเปลี่ยนรหัสผ่านได้",
        "resetpass-no-info": "คุณต้องล็อกอินเพื่อเข้าถึงหน้านี้โดยตรง",
        "resetpass-submit-loggedin": "เปลี่ยนรหัสผ่าน",
        "minoredit": "เป็นการแก้ไขเล็กน้อย",
        "watchthis": "เฝ้าดูหน้านี้",
        "savearticle": "บันทึกหน้า",
+       "publishpage": "เผยแพร่หน้า",
        "preview": "ตัวอย่าง",
        "showpreview": "แสดงตัวอย่าง",
        "showdiff": "แสดงการเปลี่ยนแปลง",
        "accmailtext": "ส่งรหัสผ่านแบบสุ่มของ [[User talk:$1|$1]] ไป $2 แล้ว สามารถเปลี่ยนรหัสผ่านในหน้า<em>[[Special:ChangePassword|เปลี่ยนรหัสผ่าน]]</em> หลังล็อกอิน",
        "newarticle": "(ใหม่)",
        "newarticletext": "คุณตามลิงก์ไปยังหน้าที่ยังไม่มีในขณะนี้\nในการสร้างหน้า เริ่มพิมพ์ในกล่องด้านล่าง (ดูข้อมูลเพิ่มเติมใน[$1 หน้าคำอธิบาย])\nถ้าคุณเข้ามาหน้านี้โดยผิดพลาด ให้กดปุ่ม<strong>ถอยหลัง</strong> (back) ของเบราว์เซอร์",
-       "anontalkpagetext": "----\n<em>หน้านี้เป็นหน้าคุยกับผู้ใช้สำหรับผู้ใช้นิรนามซึ่งยังไม่ได้สร้างหรือใช้บัญชี</em>\nดังนั้นเราจึงระบุตัวตนโดยใช้เลขที่อยู่ไอพีแทน\nเลขที่อยู่ไอพีนี้อาจมีผู้ใช้ร่วมกันหลายคน\nถ้าคุณเป็นผู้ใช้นิรนาม และรู้สึกว่าคุณได้รับความเห็นที่ไม่เกี่ยวข้องส่งหาคุณ กรุณา[[Special:UserLogin/signup|สร้างบัญชี]]หรือ[[Special:UserLogin|ล็อกอิน]] เพื่อป้องกันการสับสนกับผู้ใช้นิรนามรายอื่นในอนาคต",
+       "anontalkpagetext": "----\n<em>หน้านี้เป็นหน้าคุยกับผู้ใช้สำหรับผู้ใช้นิรนามซึ่งยังไม่ได้สร้างหรือใช้บัญชี</em>\nดังนั้นเราจึงระบุตัวตนโดยใช้เลขที่อยู่ไอพีแทน\nเลขที่อยู่ไอพีนี้อาจมีผู้ใช้ร่วมกันหลายคน\nถ้าคุณเป็นผู้ใช้นิรนาม และรู้สึกว่าคุณได้รับความเห็นที่ไม่เกี่ยวข้องส่งหาคุณ กรุณา[[Special:CreateAccount|สร้างบัญชี]]หรือ[[Special:UserLogin|ล็อกอิน]] เพื่อป้องกันการสับสนกับผู้ใช้นิรนามรายอื่นในอนาคต",
        "noarticletext": "ปัจจุบันไม่มีข้อความในหน้านี้\nคุณสามารถ[[Special:Search/{{PAGENAME}}|ค้นหาชื่อเรื่องหน้านี้]]ในหน้าอื่น <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ค้นหาปูมที่เกี่ยวข้อง] หรือ[{{fullurl:{{FULLPAGENAME}}|action=edit}} แก้ไขหน้านี้]</span>",
        "noarticletext-nopermission": "ปัจจุบันไม่มีข้อความในหน้านี้\nคุณสามารถ[[Special:Search/{{PAGENAME}}|ค้นหาชื่อหน้านี้]]ในหน้าอื่น หรือ<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ค้นหาปูมที่เกี่ยวข้อง]</span> แต่คุณไม่มีสิทธิสร้างหน้านี้",
        "missing-revision": "ไม่มีรุ่นปรับปรุง #$1 ของหน้าชื่อ \"{{FULLPAGENAME}}\" \n\nปกติเกิดจากการตามการโยงประวัติเก่าไปยังหน้าที่ถูกลบแล้ว\nดูรายละเอียดได้ที่[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ปูมการลบ]",
        "yourdiff": "ความแตกต่าง",
        "copyrightwarning": "โปรดระลึกว่างานเขียนทั้งหมดใน {{SITENAME}} ถือว่าเผยแพร่ภายใต้ $2 (ดูรายละเอียดทาง $1)\nหากคุณไม่ต้องการให้งานของคุณถูกแก้ไขและกระจายได้ตามใจ ก็อย่าส่งเข้ามา<br />\nนอกจากนี้ คุณยังสัญญาเราว่าคุณเขียนงานด้วยตนเอง หรือคัดลอกจากสาธารณสมบัติหรือทรัพยากรเสรีที่คล้ายกัน\n<strong>อย่าส่งงานมีลิขสิทธิ์โดยไม่ได้รับอนุญาต!</strong>",
        "copyrightwarning2": "โปรดระลึกว่างานเขียนทั้งหมดใน {{SITENAME}} อาจถูกผู้เขียนอื่นแก้ไข เปลี่ยนแปลงหรือนำออก\nหากคุณไม่ต้องการให้งานของคุณถูกแก้ไข ก็อย่าส่งเข้ามา<br />\nนอกจากนี้ คุณยังสัญญาเราว่าคุณเขียนงานด้วยตนเอง หรือคัดลอกจากสาธารณสมบัติหรือทรัพยากรเสรีที่คล้ายกัน (ดูรายละเอียดที่ $1)\n<strong>อย่าส่งงานมีลิขสิทธิ์โดยไม่ได้รับอนุญาต!</strong>",
+       "editpage-cannot-use-custom-model": "รูปแบบเนื้อหาหน้านี้ไม่สามารถเปลี่ยนได้",
        "longpageerror": "<strong>ข้อผิดพลาด: ข้อความที่คุณส่งมีขนาด $1 กิโลไบต์\nซึ่งเกินสูงสุด $2 กิโลไบต์</strong>\nไม่สามารถบันทึกได้",
        "readonlywarning": "<strong>คำเตือน: ฐานข้อมูลถูกล็อกเพื่อบำรุงรักษา คุณจึงไม่สามารถบันทึกการแก้ไขของคุณได้ในขณะนี้</strong>\nคุณอาจต้องการคัดลอกและวางข้อความของคุณในไฟล์ข้อความ และบันทึกไว้ภายหลัง\n\nผู้ดูแลระบบที่ล็อกฐานข้อมูลให้คำอธิบายดังนี้: $1",
        "protectedpagewarning": "<strong>คำเตือน: หน้านี้ถูกล็อก เพื่อให้เฉพาะผู้ใช้ที่มีสิทธิผู้ดูแลระบบแก้ไขได้เท่านั้น</strong>\nรายการปูมล่าสุดจัดไว้ด้านล่างเพื่อการอ้างอิง:",
        "userrights": "การบริหารสิทธิผู้ใช้",
        "userrights-lookup-user": "บริหารกลุ่มผู้ใช้",
        "userrights-user-editname": "ใส่ชื่อผู้ใช้:",
-       "editusergroup": "แก้ไขกลุ่มผู้ใช้",
+       "editusergroup": "แก้ไขกลุ่ม{{GENDER:$1|ผู้ใช้}}",
        "editinguser": "กำลังเปลี่ยนสิทธิผู้ใช้ของผู้ใช้ <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "แก้ไขกลุ่มผู้ใช้",
-       "saveusergroups": "บันทึกกลุ่มผู้ใช้",
+       "saveusergroups": "บันทึกกลุ่ม{{GENDER:$1|ผู้ใช้}}",
        "userrights-groupsmember": "สมาชิกของ:",
        "userrights-groupsmember-auto": "สมาชิกโดยปริยายของ:",
        "userrights-groups-help": "คุณสามารถเปลี่ยนแปลงกลุ่มที่ผู้ใช้รายนี้อยู่:\n* กล่องที่มีเครื่องหมายถูก หมายความว่า ผู้ใช้อยู่ในกลุ่มนั้น\n* กล่องที่ไม่มีเครื่องหมายถูก หมายความว่า ผู้ใช้ไม่ได้อยู่ในกลุ่มนั้น\n* เครื่องหมาย * ชี้ว่าคุณไม่สามารถนำกลุ่มนั้นออกได้เมื่อคุณเพิ่มกลุ่มนั้นไปแล้ว หรือกลับกัน",
        "right-override-export-depth": "ส่งออกหน้า รวมหน้าที่เชื่อมโยงกับหน้านี้สูงสุด 5 ลำดับชั้น",
        "right-sendemail": "ส่งอีเมลหาผู้ใช้อื่น",
        "right-passwordreset": "ดูอีเมลตั้งรหัสผ่านใหม่",
+       "grant-group-email": "ส่งอีเมล",
+       "grant-createaccount": "สร้างบัญชี",
+       "grant-createeditmovepage": "สร้าง แก้ไข และย้ายหน้า",
+       "grant-editmywatchlist": "แก้ไขรายการเฝ้าดูของคุณ",
+       "grant-editpage": "แก้ไขหน้านี้",
+       "grant-uploadeditmovefile": "อัปโหลด แทนที่ และย้ายไฟล์",
+       "grant-uploadfile": "อัปโหลดไฟล์ใหม่",
+       "grant-viewmywatchlist": "ดูรายการเฝ้าดูของคุณ",
        "newuserlogpage": "ปูมการสร้างผู้ใช้",
        "newuserlogpagetext": "นี่คือปูมการสร้างผู้ใช้",
        "rightslog": "ปูมสิทธิผู้ใช้",
        "uploaderror": "การอัปโหลดผิดพลาด",
        "upload-recreate-warning": "<strong>คำเตือน: ไฟล์ชื่อนั้นถูกลบหรือเปลี่ยนชื่อแล้ว</strong>\n\nปูมการลบและปูมการย้ายของหน้านี้จัดไว้ด้านล่างเพื่อความสะดวก:",
        "uploadtext": "กรุณาใช้แบบด้านล่างในการอัปโหลดไฟล์\nสำหรับการดูหรือการค้นหาไฟล์ที่เคยอัปโหลดก่อนหน้านี้ ให้ไปที่[[Special:FileList|รายการไฟล์ที่ถูกอัปโหลด]] การอัปโหลดและการอัปโหลดซ้ำดูได้ที่[[Special:Log/upload|ปูมการอัปโหลด]] และการลบไฟล์ดูได้ที่[[Special:Log/delete|ปูมการลบ]]\n\nถ้าต้องการแทรกไฟล์ลงในหน้าหนึ่ง ๆ ให้ใช้คำสั่งหนึ่งในรูปแบบต่อไปนี้\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' เพื่อใช้รูปขนาดเต็ม\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|ข้อความอธิบาย]]</nowiki></code>''' เพื่อใช้รูปย่อขนาดกว้าง 200 พิกเซลในกล่องที่จัดชิดซ้าย โดยมี \"ข้อความอธิบาย\" เป็นคำบรรยายใต้ภาพ\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' สำหรับการเชื่อมโยงไฟล์โดยตรง โดยไม่ปรากฏไฟล์นั้นออกมา",
-       "upload-permitted": "ชนิดไฟล์ที่อนุญาต: $1",
-       "upload-preferred": "ชนิดไฟล์ที่ควรใช้: $1",
-       "upload-prohibited": "ชนิดไฟล์ที่ไม่อนุญาต: $1",
+       "upload-permitted": "{{PLURAL:$2|type|ชนิด}}ไฟล์ที่อนุญาต: $1",
+       "upload-preferred": "{{PLURAL:$2|type|ชนิด}}ไฟล์ที่ควรใช้: $1",
+       "upload-prohibited": "{{PLURAL:$2|type|ชนิด}}:ไฟล์ที่ไม่อนุญาต: $1",
        "uploadlogpage": "ปูมการอัปโหลด",
        "uploadlogpagetext": "ด้านล่างเป็นรายการการอัปโหลดไฟล์ล่าสุด\nดูภาพรวมที่ [[Special:NewFiles|แกลอรีไฟล์ใหม่]]",
        "filename": "ชื่อไฟล์",
        "upload-form-label-infoform-description": "คำอธิบาย",
        "upload-form-label-usage-title": "การใช้",
        "upload-form-label-usage-filename": "ชื่อไฟล์",
+       "upload-form-label-infoform-categories": "หมวดหมู่",
+       "upload-form-label-infoform-date": "วันที่",
        "backend-fail-backup": "ไม่สามารถสำรองไฟล์ \"$1\"",
        "backend-fail-notexists": "ไม่มีไฟล์ $1",
        "backend-fail-delete": "ไม่สามารถลบไฟล์ \"$1\"",
        "suppress": "ผู้ดูแลประวัติ",
        "querypage-disabled": "หน้าพิเศษนี้ถูกปิดใช้งานด้วยเหตุผลด้านสมรรถภาพ",
        "apihelp-no-such-module": "ไม่พบมอดูล \"$1\"",
+       "apisandbox-unfullscreen": "แสดงหน้า",
+       "apisandbox-reset": "ล้าง",
+       "apisandbox-retry": "ลองใหม่",
+       "apisandbox-examples": "ตัวอย่าง",
+       "apisandbox-results": "ผลลัพธ์",
+       "apisandbox-request-url-label": "ขอ URL:",
        "booksources": "แหล่งหนังสือ",
        "booksources-search-legend": "ค้นหาแหล่งหนังสือ",
        "booksources-search": "ค้นหา",
        "listgrouprights-namespaceprotection-header": "การจำกัดเนมสเปซ",
        "listgrouprights-namespaceprotection-namespace": "เนมสเปซ",
        "listgrouprights-namespaceprotection-restrictedto": "สิทธิอนุญาตให้ผู้ใช้แก้ไข",
+       "listgrants-rights": "สิทธิ",
        "trackingcategories": "หมวดหมู่ค้นหาและติดตาม",
        "trackingcategories-summary": "หน้านี้แสดงรายการหมวดหมู่ค้นหาและติดตามซึ่งซอฟต์แวร์มีเดียวิกิจัดการอัตโนมัติ สามารถเปลี่ยนชื่อเหล่านี้ได้โดยการเปลี่ยนสารระบบที่เกี่ยวข้องในเนมสเปซ {{ns:8}}",
        "trackingcategories-msg": "หมวดหมู่ค้นหาและติดตาม",
        "rollback-success": "ย้อนการแก้ไขโดย $1; \nเปลี่ยนกลับไปรุ่นล่าสุดโดย $2",
        "sessionfailure-title": "ช่วงเวลาสื่อสารล้มเหลว",
        "sessionfailure": "ดูเหมือนมีปัญหากับช่วงเวลาสื่อสารล็อกอินของคุณ\nการกระทำนี้ถูกยกเลิกเป็นการป้องกันการลักลอบช่วงเวลาสื่อสารไว้ก่อน \nกลับไปหน้าที่แล้ว โหลดหน้าใหม่ แล้วลองอีกครั้ง",
+       "changecontentmodel-title-label": "ชื่อหน้า:",
+       "changecontentmodel-reason-label": "เหตุผล:",
+       "changecontentmodel-submit": "ความเปลี่ยนแปลง",
+       "logentry-contentmodel-change-revertlink": "ย้อน",
+       "logentry-contentmodel-change-revert": "ย้อน",
        "protectlogpage": "ปูมการล็อก",
        "protectlogtext": "ด้านล่างเป็นรายการการเปลี่ยนแปลงการล็อกหน้า\nดู[[Special:ProtectedPages|รายการหน้าที่ถูกล็อก]]สำหรับการล็อกหน้าที่มีผลอยู่ในปัจจุบัน",
        "protectedarticle": "ล็อก \"[[$1]]\"",
        "whatlinkshere-prev": "{{PLURAL:$1|ก่อนหน้า|ก่อนหน้า $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|ถัดไป|ถัดไป $1}}",
        "whatlinkshere-links": "← ลิงก์",
-       "whatlinkshere-hideredirs": "$1การเปลี่ยนทาง",
-       "whatlinkshere-hidetrans": "$1 ถูกรวมอยู่",
-       "whatlinkshere-hidelinks": "$1 ลิงก์",
-       "whatlinkshere-hideimages": "$1ลิงก์ไฟล์",
+       "whatlinkshere-hideredirs": "ซ่อนการเปลี่ยนทาง",
+       "whatlinkshere-hidetrans": "Hide transclusions",
+       "whatlinkshere-hidelinks": "ซ่อนลิงก์",
+       "whatlinkshere-hideimages": "ซ่อนลิงก์ไฟล์",
        "whatlinkshere-filters": "ตัวกรอง",
        "whatlinkshere-submit": "ไป",
        "autoblockid": "บล็อกอัตโนมัติ #$1",
        "lockdbsuccesstext": "ล็อกฐานข้อมูลเรียบร้อย\n<br />อย่าลืม[[Special:UnlockDB|ปลดล็อก]]หลังการบำรุงรักษาเสร็จสิ้น",
        "unlockdbsuccesstext": "ปลดล็อกฐานข้อมูลเรียบร้อย",
        "lockfilenotwritable": "ไม่สามารถล็อกฐานข้อมูลได้ เนื่องจากการเขียนลงฐานข้อมูล การล็อกและการปลดล็อกจำเป็นต้องทำที่เว็บเซิร์ฟเวอร์",
+       "databaselocked": "ฐานข้อมูลถูกล็อก",
        "databasenotlocked": "ฐานข้อมูลไม่ได้ล็อก",
        "lockedbyandtime": "(โดย {{GENDER:$1|$1}} เมื่อวันที่ $2 เวลา $3)",
        "move-page": "ย้าย $1",
        "thumbnail_gd-library": "การตั้งค่าไลบรารี GD ไม่สมบูรณ์: ไม่พบฟังก์ชัน $1",
        "thumbnail_image-missing": "ไฟล์ที่เหมือนหายไป: $1",
        "import": "หน้านำเข้า",
-       "importinterwiki": "à¸\99ำà¹\80à¸\82à¹\89าà¸\82à¹\89ามวิà¸\81ิ",
+       "importinterwiki": "à¸\99ำà¹\80à¸\82à¹\89าหà¸\99à¹\89าà¸\88าà¸\81วิà¸\81ิอืà¹\88à¸\99",
        "import-interwiki-text": "เลือกวิกิและชื่อหัวข้อที่ต้องการนำเข้า วันที่และชื่อผู้เขียนทั้งหมดจะถูกเก็บไว้ โดยการนำเข้าทุกส่วนจะถูกเก็บไว้ใน [[Special:Log/import|ปูมการนำเข้า]]",
        "import-interwiki-sourcewiki": "วิกิต้นทาง:",
        "import-interwiki-sourcepage": "หน้าต้นทาง:",
        "import-logentry-interwiki-detail": "นำเข้า $1 {{PLURAL:$1|รุ่นการแก้ไข|รุ่นการแก้ไข}}จาก $2",
        "javascripttest": "การทดสอบจาวาสคริปต์",
        "javascripttest-qunit-intro": "ดู[$1 เอกสารกำกับการทดสอบ]บน mediawiki.org",
-       "tooltip-pt-userpage": "หน้าผู้ใช้ของคุณ",
+       "tooltip-pt-userpage": "{{GENDER:|หน้าผู้ใช้}}ของคุณ",
        "tooltip-pt-anonuserpage": "หน้าผู้ใช้ของเลขที่อยู่ไอพีที่คุณกำลังใช้แก้ไข",
-       "tooltip-pt-mytalk": "หน้าพูดคุยของคุณ",
+       "tooltip-pt-mytalk": "หน้าพูดคุย{{GENDER:|ของคุณ}}",
        "tooltip-pt-anontalk": "อภิปรายเกี่ยวกับการแก้ไขจากเลขที่อยู่ไอพีนี้",
-       "tooltip-pt-preferences": "การตั้งค่าของคุณ",
+       "tooltip-pt-preferences": "การตั้งค่า{{GENDER:|ของคุณ}}",
        "tooltip-pt-watchlist": "รายการหน้าที่คุณกำลังเฝ้าดูการเปลี่ยนแปลง",
-       "tooltip-pt-mycontris": "รายการหน้าที่คุณเขียน",
+       "tooltip-pt-mycontris": "รายการหน้าที่{{GENDER:|คุณ}}เขียน",
        "tooltip-pt-anoncontribs": "รายการการแก้ไขจากเลขที่อยู่ไอพีนี้",
        "tooltip-pt-login": "สนับสนุนให้คุณล็อกอิน แต่ไม่บังคับ",
        "tooltip-pt-logout": "ล็อกเอาต์",
        "tooltip-t-recentchangeslinked": "รายการปรับปรุงล่าสุดในหน้าที่ลิงก์จากหน้านี้",
        "tooltip-feed-rss": "ฟีดชนิดอาร์เอสเอส (RSS) ของหน้านี้",
        "tooltip-feed-atom": "ฟีดอะตอม (Atom) ของหน้านี้",
-       "tooltip-t-contributions": "รายการเรื่องที่ผู้ใช้นี้เขียน",
+       "tooltip-t-contributions": "รายการเรื่องที่{{GENDER:$1|ผู้ใช้นี้}}เขียน",
        "tooltip-t-emailuser": "ส่งอีเมลถึงผู้ใช้นี้",
        "tooltip-t-info": "สารสนเทศเพิ่มเติมเกี่ยวกับหน้านี้",
        "tooltip-t-upload": "อัปโหลดไฟล์",
index 590dbe2..7219a74 100644 (file)
        "noname": "Dogry bir ulanyjy adyny görkezmediňiz.",
        "loginsuccesstitle": "Hasaba girdiňiz",
        "loginsuccess": "'''{{SITENAME}} saýtynda \"$1\" ulanyjy ady bilen hasaba girdiňiz.'''",
-       "nosuchuser": "\"$1\" diýen at bilen ulanyjy ýok.\nUlanyjy atlary baş hem-de setir harplara duýgurdyr.\nÝazylyşyny barlaň ýa-da [[Special:UserLogin/signup|täze hasap açyň]].",
+       "nosuchuser": "\"$1\" diýen at bilen ulanyjy ýok.\nUlanyjy atlary baş hem-de setir harplara duýgurdyr.\nÝazylyşyny barlaň ýa-da [[Special:CreateAccount|täze hasap açyň]].",
        "nosuchusershort": "\"$1\" dýen at bilen ulanyjy ýok. Ýazylyşyny barlaň.",
        "nouserspecified": "Ulanyjy adyny görkezmegiňiz hökmanydyr.",
        "login-userblocked": "Bu ulanyjy blokirlenipdir. Sessiýa açmaga rugsat berilmeýär.",
        "accmailtext": "[[User talk:$1|$1]] üçin ugralla döredilen parol $2 adresine iberildi.\n\nBu paroly sessiýa açanyňyzdan soňra ''[[Special:ChangePassword|paroly üýtget]]'' sahypasynda üýtgedip bilersiňiz.",
        "newarticle": "(Täze)",
        "newarticletext": "Häzirlikçe ýazylmadyk bir sahypa goýlan çykgyda tykladyňyz. Bu sahypany döretmek üçin aşakdaky tekst gutusyndan peýdalanyň. Maglumat üçin [$1 ýardam sahypasyna] serediň. Bu ýere ýalňyşlyk bilen gelen bolsaňyz, programmanyň '''Yza''' düwmesine tyklaň.",
-       "anontalkpagetext": "----''Bu sahypa heniz ulanyjy hasaby edinmedik ýa-da hasabyny ulanmaýan bir anonim ulanyjynyň pikir alyşma sahypasydyr.\nŞonuň üçinem biz ony görkezmek üçin sanlaýyn IP adresini ulanmaly bolýarys.\nŞunuň ýaly IP adresinden ençeme ulanyjy peýdalanýan bolmagy ahmal.\nEger-de sizem anonim ulanyjy bolsaňyz we size siziň bilen dahyly ýok habarlaşyklar gelýän bolsa, onda mundan beýläk başga anonim ulanyjylar bilen garjaşmazlygyňyz üçin [[Special:UserLogin/signup|özüňize hasap ediniň]] ýa-da [[Special:UserLogin|sessiýa açyň]].''",
+       "anontalkpagetext": "----''Bu sahypa heniz ulanyjy hasaby edinmedik ýa-da hasabyny ulanmaýan bir anonim ulanyjynyň pikir alyşma sahypasydyr.\nŞonuň üçinem biz ony görkezmek üçin sanlaýyn IP adresini ulanmaly bolýarys.\nŞunuň ýaly IP adresinden ençeme ulanyjy peýdalanýan bolmagy ahmal.\nEger-de sizem anonim ulanyjy bolsaňyz we size siziň bilen dahyly ýok habarlaşyklar gelýän bolsa, onda mundan beýläk başga anonim ulanyjylar bilen garjaşmazlygyňyz üçin [[Special:CreateAccount|özüňize hasap ediniň]] ýa-da [[Special:UserLogin|sessiýa açyň]].''",
        "noarticletext": "Bu sahypa şu wagt boş dur.\nBu ady başga sahypalarda [[Special:Search/{{PAGENAME}}|gözläp bilersiňiz]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} degişli gündeliklerde gözleg geçirip bilersiňiz],\nýa-da bu sahypany [{{fullurl:{{FULLPAGENAME}}|action=edit}} üýtgedip bilersiňiz]</span>.",
        "noarticletext-nopermission": "Häzirki wagtda bu sahypada tekst ýok.\nBu sahypa adyny [[Special:Search/{{PAGENAME}}|başga sahypalarda gözläp]]\nýa-da <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} degişli gündeliklerde gözleg geçirip bilersiňiz]</span>, ýöne bu sahypany döretmäge rugsadyňyz ýok.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" ulanyjy hasaby hasaba alynmandyr.\nBu sahypany döretmek/redaktirlemek isleýän bolsaňyz, onda esewan boluň.",
index ca3e47b..670b022 100644 (file)
        "noname": "Hindi mo tinukoy ang isang tanggap na pangalan ng tagagamit.",
        "loginsuccesstitle": "Matagumpay ang paglagda",
        "loginsuccess": "'''Nakalagda ka na sa {{SITENAME}} bilang si \"$1\".'''",
-       "nosuchuser": "Walang tagagamit na may pangalang \"$1\".\nMaselan sa pagtitipa ang mga pangalan ng tagagamit.\nSuriin ang iyong pagbabaybay, o [[Special:UserLogin/signup|lumikha ng bagong account]].",
+       "nosuchuser": "Walang tagagamit na may pangalang \"$1\".\nMaselan sa pagtitipa ang mga pangalan ng tagagamit.\nSuriin ang iyong pagbabaybay, o [[Special:CreateAccount|lumikha ng bagong account]].",
        "nosuchusershort": "Walang tagagamit na may pangalang \"$1\".\nPakitingnan ang iyong pagbabaybay.",
        "nouserspecified": "Kailangang tukuyin mo ang isang pangalang pantagagamit.",
        "login-userblocked": "Hinarang ang tagagamit na ito.  Hindi pinahihintulutan ang paglalagda.",
        "accmailtext": "Ipinadala na sa $2 ang isang password na nilikha ng pagkakataon para kay [[User talk:$1|$1]].  Maaari itong baguhin sa pahinang ''[[Special:ChangePassword|palitan ang password]]'' kapag nag-login.",
        "newarticle": "(Bago)",
        "newarticletext": "Sinundan mo ang isang kawing para sa isang pahinang hindi pa umiiral.\nPara likhain ang pahina, magsimulang magmakinilya sa loob ng kahong nasa ibaba (tingnan ang [$1 pahina ng tulong] para sa mas maraming kabatiran).\nKung napunta ka rito dahil sa pagkakamali, pakipindot ang pinduntang '''balik''' ('''''back''''') ng iyong pantingin-tingin (''browser'').",
-       "anontalkpagetext": "Ito ang pahinang usapan para sa isang hindi nakikilalang tagagamit na hindi pa lumilikha ng account, o kaya hindi ito ginagamit.\nKaya't kinailangan naming gamitin ang may bilang na IP address para makilala siya.\nMaaaring pagsaluhan ng ilang mga tagagamit ang ganiyang  IP address.\nKung isa kang hindi nagpapakilalang tagagamit at nakadaramang may mga walang saysay na komentong patungkol sa iyo, [[Special:UserLogin/signup|pakilikha ng isang account]] o [[Special:UserLogin|lumagda]] para maiwasan ang kalituhan o mapagkamalan ka bilang ibang hindi nakikilalang mga tagagamit sa hinaharap.",
+       "anontalkpagetext": "Ito ang pahinang usapan para sa isang hindi nakikilalang tagagamit na hindi pa lumilikha ng account, o kaya hindi ito ginagamit.\nKaya't kinailangan naming gamitin ang may bilang na IP address para makilala siya.\nMaaaring pagsaluhan ng ilang mga tagagamit ang ganiyang  IP address.\nKung isa kang hindi nagpapakilalang tagagamit at nakadaramang may mga walang saysay na komentong patungkol sa iyo, [[Special:CreateAccount|pakilikha ng isang account]] o [[Special:UserLogin|lumagda]] para maiwasan ang kalituhan o mapagkamalan ka bilang ibang hindi nakikilalang mga tagagamit sa hinaharap.",
        "noarticletext": "Kasalukuyang walang teksto sa loob ng pahinang ito.\nMaaari mong [[Special:Search/{{PAGENAME}}|hanapin ang pamagat ng pahinang ito]] sa loob iba pang mga pahina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa kaugnay na mga talaan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} baguhin ang pahinang ito]</span>.",
        "noarticletext-nopermission": "Kasalukuyang walang teksto sa pahinang ito.\nMaaari mong [[Special:Search/{{PAGENAME}}|hanapin ang pamagat ng pahinang ito]] sa ibang mga pahina,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa kaugnay na mga talaan]</span>.",
        "missing-revision": "Hindi umiiral ang rebisyong #$1 ng pahinang napangalanang \"{{FULLPAGENAME}}\".\n\nKaraniwang itong dulot ng pagsunod sa isang wala na sa panahong kawing ng kasaysayan na papunta sa isang pahinang nabura na.\nMatatagpuan ang mga detalye sa loob ng [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} talaan ng pagbura].",
index 36f0429..8ee0000 100644 (file)
        "noname": "Naʻe ʻikai te ke ʻoatu hao hingoa ʻetita ʻoku totonu.",
        "loginsuccesstitle": "Kuo ola ʻa e kau-ki-ai",
        "loginsuccess": "'''ʻOku ke kau-ki-ai he taimí ni ki he {{SITENAME}} hangē \"$1\".'''",
-       "nosuchuser": "ʻOku ʻikai ʻi ai ha ʻetita mo hono hingoa \"$1\". Sivi hoʻo sipela pe [[Special:UserLogin/signup|fakatupu haʻo kau-ki-ai foʻou]].",
+       "nosuchuser": "ʻOku ʻikai ʻi ai ha ʻetita mo hono hingoa \"$1\". Sivi hoʻo sipela pe [[Special:CreateAccount|fakatupu haʻo kau-ki-ai foʻou]].",
        "nosuchusershort": "ʻOku ʻikai ʻi ai ha ʻetita mo hono hingoa \"$1\". Sivi hoʻo sipela.",
        "nouserspecified": "ʻOku pau te ke ʻoatu ha hingoa ʻo e ʻetita.",
        "wrongpassword": "ʻOku ʻikai totonu ʻa e leatapu, kātaki ʻe toki feinga.",
index e29e5e5..16f13b9 100644 (file)
@@ -84,7 +84,8 @@
                        "HakanIST",
                        "Imabadplayer",
                        "İnternion",
-                       "Hbseren"
+                       "Hbseren",
+                       "Kumkumuk"
                ]
        },
        "tog-underline": "Bağlantıların altını çiz:",
        "noname": "Geçerli bir kullanıcı adı girmediniz.",
        "loginsuccesstitle": "Oturum açıldı",
        "loginsuccess": "'''{{SITENAME}} üzerinde \"$1\" kullanıcı adıyla oturum açtınız.'''",
-       "nosuchuser": "\"$1\" adında bir kullanıcı bulunmamaktadır.\nKullanıcı adları büyük-küçük harf duyarlıdır.\nYazılışı kontrol edin veya [[Special:UserLogin/signup|yeni bir hesap açın]].",
+       "nosuchuser": "\"$1\" adında bir kullanıcı bulunmamaktadır.\nKullanıcı adları büyük-küçük harf duyarlıdır.\nYazılışı kontrol edin veya [[Special:CreateAccount|yeni bir hesap açın]].",
        "nosuchusershort": "\"$1\" adında bir kullanıcı bulunmamaktadır. Yazılışı kontrol edin.",
        "nouserspecified": "Bir kullanıcı adı belirtmek zorundasınız.",
        "login-userblocked": "Bu kullanıcı engellenmiş. Giriş yapmaya izin verilmiyor.",
        "accmailtext": "[[User talk:$1|$1]] için rastgele oluşturulan parola $2 adresine gönderildi.\n\nBu yeni hesap için parola, giriş yapıldıktan sonra ''[[Special:ChangePassword|parolayı değiştir]]'' bölümünde değiştirilebilir.",
        "newarticle": "(Yeni)",
        "newarticletext": "Henüz varolmayan bir sayfaya konulmuş bir bağlantıya tıkladınız.\nSayfayı oluşturmak için aşağıdaki metin kutusunu kullanın. ([$1 yardım sayfasına] bakınız).\nBuraya yanlışlıkla geldiyseniz tarayıcınızın  <strong>geri </strong> tuşuna tıklayın.",
-       "anontalkpagetext": "----''Bu sayfa henüz bir kullanıcı hesabı oluşturmamış veya hesabını kullanmayan bir anonim kullanıcının mesaj sayfasıdır. Bu nedenle bu kişiyi belirtmek için rakamsal IP adresini kullanmak zorundayız. Bu gibi IP adresleri birçok kullanıcı tarafından paylaşılabilir. Eğer siz de bir anonim kullanıcıysanız ve size sizin ilginiz olmayan iletiler geliyorsa, lütfen diğer anonim kullanıcılarla olabilecek olan karmaşayı önlemek için [[Special:UserLogin/signup|bir hesap edinin]] veya [[Special:UserLogin|oturum açın]].''",
+       "anontalkpagetext": "----''Bu sayfa henüz bir kullanıcı hesabı oluşturmamış veya hesabını kullanmayan bir anonim kullanıcının mesaj sayfasıdır. Bu nedenle bu kişiyi belirtmek için rakamsal IP adresini kullanmak zorundayız. Bu gibi IP adresleri birçok kullanıcı tarafından paylaşılabilir. Eğer siz de bir anonim kullanıcıysanız ve size sizin ilginiz olmayan iletiler geliyorsa, lütfen diğer anonim kullanıcılarla olabilecek olan karmaşayı önlemek için [[Special:CreateAccount|bir hesap edinin]] veya [[Special:UserLogin|oturum açın]].''",
        "noarticletext": "Bu sayfa şu anda boştur.\nBu başlığı [[Special:Search/{{PAGENAME}}|diğer sayfalarda arayabilir]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ilgili kayıtları arayabilir],\nya da bu sayfayı [{{fullurl:{{FULLPAGENAME}}|action=edit}} oluşturabilirsiniz]</span>.",
        "noarticletext-nopermission": "Bu sayfa şu anda boştur. \nBu başlığı [[Special:Search/{{PAGENAME}}|diğer sayfalarda arayabilir]] ya da <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ilgili kayıtları tarayabilirsiniz]</span>, fakat sayfayı oluşturma yetkiniz bulunmamaktadır.",
        "missing-revision": "\"{{FULLPAGENAME}}\" sayfasının #$1 sürümü yok.\n\nBu duruma genellikle silinmiş bir sayfaya eski tarihli bir bağlantının takip edilmesi neden olur.\n\nDaha fazla detaylı bilgi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sayfasında bulunabilir].",
        "rcshowhidemine-show": "göster",
        "rcshowhidemine-hide": "gizle",
        "rcshowhidecategorization": "sayfa kategorizasyonunu $1",
-       "rcshowhidecategorization-show": "Göster",
-       "rcshowhidecategorization-hide": "Gizle",
+       "rcshowhidecategorization-show": "göster",
+       "rcshowhidecategorization-hide": "gizle",
        "rclinks": "Son $2 günde yapılan son $1 değişikliği göster;<br /> $3",
        "diff": "fark",
        "hist": "geçmiş",
        "upload-form-label-infoform-description": "Açıklama",
        "upload-form-label-usage-title": "Kullanımı",
        "upload-form-label-usage-filename": "Dosya adı",
-       "foreign-structured-upload-form-label-own-work": "Bu benim kendi çalışmam",
-       "foreign-structured-upload-form-label-infoform-categories": "Kategoriler",
-       "foreign-structured-upload-form-label-infoform-date": "Tarih",
+       "upload-form-label-own-work": "Bu benim kendi çalışmam",
+       "upload-form-label-infoform-categories": "Kategoriler",
+       "upload-form-label-infoform-date": "Tarih",
        "backend-fail-stream": "$1 dosyası okunamadı.",
        "backend-fail-backup": "\"$1\" dosyası yedeklenemedi.",
        "backend-fail-notexists": "$1 dosyası mevcut değil.",
        "whatlinkshere-prev": "{{PLURAL:$1|önceki|önceki $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|sonraki|sonraki $1}}",
        "whatlinkshere-links": "← bağlantılar",
-       "whatlinkshere-hideredirs": "Yönlendirmeleri $1",
-       "whatlinkshere-hidetrans": "Dönüştürmeleri $1",
-       "whatlinkshere-hidelinks": "Bağlantıları $1",
+       "whatlinkshere-hideredirs": "Yönlendirmeleri gizle",
+       "whatlinkshere-hidetrans": "Dönüştürmeleri gizle",
+       "whatlinkshere-hidelinks": "Bağlantıları gizle",
        "whatlinkshere-hideimages": "Dosya bağlantılarını $1",
        "whatlinkshere-filters": "Süzgeçler",
        "autoblockid": "Otomatik engelleme #$1",
index 96f7c0b..cee7a0d 100644 (file)
@@ -43,6 +43,7 @@
        "tog-watchdefault": "Мин үзгәрткән битләр һәм файллар күзәтү исемлегемә өстәлсен",
        "tog-watchmoves": "Мин күчергән битләр һәм файллар күзәтү исемлегемә өстәлсен",
        "tog-watchdeletion": "Мин бетергән битләр һәм файлларны күзәтү исемлегемгә өстәлсен",
+       "tog-watchuploads": "Минем тарафтан йөкләнелгән файлларны күзәтү исемлегемә кертергә",
        "tog-minordefault": "Барлык үзгәртүләрне килешү буенча кече дип билгеләнсен",
        "tog-previewontop": "Үзгәртү тәрәзәсеннән өстәрәк битне алдан карау өлкәсен күрсәтелсен",
        "tog-previewonfirst": "Үзгәртү битенә күчкәндә башта алдан карау бите күрсәтелсен",
@@ -59,6 +60,7 @@
        "tog-watchlisthidebots": "Бот үзгәртүләре күзәтү исемлегеннән яшерелсен",
        "tog-watchlisthideminor": "Кече үзгәртүләр күзәтү исемлегеннән яшерелсен",
        "tog-watchlisthideliu": "Авторизацияне узган кулланучыларның үзгәртүләре күзәтү исемлегеннән яшерелсен",
+       "tog-watchlistreloadautomatically": "Фильтр алмашкан очракта күзәтү исемлеген автоматик рәвештә яңартырга (JavaScript кирәк)",
        "tog-watchlisthideanons": "Аноним кулланучыларның үзгәртүләре күзәтү исемлегеннән яшерелсен",
        "tog-watchlisthidepatrolled": "Тикшерелгән үзгәртүләр күзәтү исемлегеннән яшерелсен",
        "tog-watchlisthidecategorization": "Битләрне төркемләшүне ябу",
        "noname": "Сез кулланучы исемегезне күрсәтергә тиешсез.",
        "loginsuccesstitle": "Керү уңышлы үтте",
        "loginsuccess": "'''Сез {{SITENAME}} проектына $1 исеме белән кердегез.'''",
-       "nosuchuser": "$1 исемле кулланучы юк.\nКулланучы исеменең дөреслеге регистрга бәйле.\nЯзылышыгызны тикшерегез яки [[Special:UserLogin/signup|яңа хисап язмасы төзегез]].",
+       "nosuchuser": "$1 исемле кулланучы юк.\nКулланучы исеменең дөреслеге регистрга бәйле.\nЯзылышыгызны тикшерегез яки [[Special:CreateAccount|яңа хисап язмасы төзегез]].",
        "nosuchusershort": "$1 исемле кулланучы юк. Язылышыгызны тикшерегез.",
        "nouserspecified": "Сез теркәү исмегезне күрсәтергә тиешсез.",
        "login-userblocked": "Бу кулланучы тыелды. Керү тыелган.",
        "accmailtext": "[[User talk:$1|$1]] кулланучысы өчен төзелгән серсүз $2 адресына җибәрелде.\n\nАвторизация узгач, үз хисап язмагызда сез ''[[Special:ChangePassword|серсүзегезне үзгәртә аласыз]]''.",
        "newarticle": "(Яңа)",
        "newarticletext": "Сез әлегә язылмаган биткә кердегез.\nЯңа бит ясау өчен астагы тәрәзәдә мәкалә текстын җыегыз ([$1 ярдәм битен] карый аласыз).\nӘгәр сез бу биткә ялгышлык белән эләккән булсагыз, браузерыгызның '''артка''' төймәсенә басыгыз.",
-       "anontalkpagetext": "----''Бу бәхәс бите системада теркәлмәгән яисә үз исеме белән кермәгән кулланучыныкы.\nАны тану өчен IP адресы файдаланыла.\nӘгәр сез аноним кулланучы һәм сезгә юлланмаган хәбәрләр алдым дип саныйсыз икән (бер IP адресы күп кулланучы өчен булырга мөмкин), башка мондый аңлашылмаучанлыклар килеп чыкмасын өчен [[Special:UserLogin|системага керегез]] яисә [[Special:UserLogin/signup|теркәлегез]].''",
+       "anontalkpagetext": "----''Бу бәхәс бите системада теркәлмәгән яисә үз исеме белән кермәгән кулланучыныкы.\nАны тану өчен IP адресы файдаланыла.\nӘгәр сез аноним кулланучы һәм сезгә юлланмаган хәбәрләр алдым дип саныйсыз икән (бер IP адресы күп кулланучы өчен булырга мөмкин), башка мондый аңлашылмаучанлыклар килеп чыкмасын өчен [[Special:UserLogin|системага керегез]] яисә [[Special:CreateAccount|теркәлегез]].''",
        "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}}}} көндәлекләрдәге язмаларны] таба аласыз.</span> Сезнең бу битне ясарга хакыгыз юк.",
        "userpage-userdoesnotexist": "«<nowiki>$1</nowiki>» исемле хисап язмасы юк. Сез чынлап та бу битне ясарга яисә үзгәртергә телисезме?",
        "userpage-userdoesnotexist-view": "\"$1\" исемле хисап язмасы юк.",
        "blocked-notice-logextract": "Бу кулланучы хәзергә тыелды.\nТүбәндә тыю көндәлегенең соңгы язу бирелгән:",
-       "clearyourcache": "<strong>Искәрмә:</strong> Сез саклаган үзгәртүләр кулланышка керсен өчен браузерыгызның кешын чистартырга туры киләчәк. \n* <strong>Firefox/Safari:</strong> Shift төймшсенә баскан килеш җиһазлар тасмасында <em>Яңарту (Обновить)</em> язуына басыгыз, яисә <em>Ctrl-F5</em> яки  ''Ctrl-R</em> (Mac өчен <em>⌘-R</em>) төймәләренә басыгыз\n* <strong>Google Chrome:</strong>  <em>Ctrl-Shift-R</em> (Mac өчен <em>⌘-Shift-R</em> ) төймәләренә басыгыз\n* <strong>Internet Explorer:</strong> <em>Ctrl</em>  төймәсенә баскан килеш  <em>Яңарту (Обновить)</em> язуына, яисә <em>Ctrl-F5</em> басыгыз\n* <strong>Opera:</strong> Менюдан кеш чистартуны сайлагыз: <em>Кораллар (Инструменты) → Көйләнмәләр (Настройки)</em>",
+       "clearyourcache": "<strong>Искәрмә:</strong> Сез саклаган үзгәртүләр кулланышка керсен өчен браузерыгызның кешын чистартырга туры киләчәк. \n* <strong>Firefox/Safari:</strong> Shift төймшсенә баскан килеш җиһазлар тасмасында <em>Яңарту (Обновить)</em> язуына басыгыз, яисә <em>Ctrl-F5</em> яки  ''Ctrl-R</em> (Mac өчен <em>⌘-R</em>) төймәләренә басыгыз\n* <strong>Google Chrome:</strong>  <em>Ctrl-Shift-R</em> (Mac өчен <em>⌘-Shift-R</em> ) төймәләренә басыгыз\n* <strong>Internet Explorer:</strong> <em>Ctrl</em>  төймәсенә баскан килеш  <em>Яңарту (Обновить)</em> язуына, яисә <em>Ctrl-F5</em> басыгыз\n* <strong>Opera:</strong> <em>Menu → Көйләнмәләр</em> (<em>Opera → Көйләнмәләр</em> на Mac) бүлегенә күчегез,  аннан <em>Куркынычсызлык → Керүләр тарыхын чистарту → Рәсемнәр кэшлау</em>",
        "usercssyoucanpreview": "'''Ярдәм:''' \"{{int:showpreview}} төймәсенә басып, яңа CSS-файлны тикшереп була.",
        "userjsyoucanpreview": "'''Ярдәм:''' \"{{int:showpreview}}\" төймәсенә басып, яңа JS-файлны тикшереп була.",
        "usercsspreview": "'''Бу бары тик CSS-файлны алдан карау гына, ул әле сакланмаган!'''",
        "recentchangesdays-max": "(иң күбе $1 {{PLURAL:$1|көн}})",
        "recentchangescount": "Төп буларак кулланучы үзгәртүләр саны:",
        "prefs-help-recentchangescount": "Үз өченә үзгәртүләрне, битләрнең тарихын һәм язлу көндәлеген дә кертә.",
+       "prefs-help-watchlist-token2": "Бу сезнең кузәтү исемлеге өчен ясалган веб-агымының серле ачкычы.\nАны белгән һәркем сезнең күзәтү исемлегегезне карый ала, шуңа да башкаларга аны күрсәтмәгез. [[Special:ResetTokens|Ачкычны ташларга теләсәгез, әлеге юрамага басыгыз]].",
        "savedprefs": "Көйләнмәләрегез сакланды.",
        "timezonelegend": "Сәгать поясы:",
        "localtime": "Җирле вакыт",
        "upload-form-label-infoform-description": "Тасвир",
        "upload-form-label-usage-title": "Куллану",
        "upload-form-label-usage-filename": "Файл исеме",
-       "foreign-structured-upload-form-label-own-work": "Бу минем үз эшем",
-       "foreign-structured-upload-form-label-infoform-categories": "Төркемнәр",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work": "Бу минем үз эшем",
+       "upload-form-label-infoform-categories": "Төркемнәр",
+       "upload-form-label-infoform-date": "Дата",
        "uploadstash": "Яшерен йөкләү",
        "uploadstash-summary": "Әлеге бит йөкләнгән (яисә йукләү барышында булган), әмма викида әлегә күрсәтелмәгән файлларны карау мөмкинлеген бирә. Бу файлларны йөкләгән кулланучыдан башка беркемдә күрә алмый.",
        "uploadstash-clear": "Яшерен файлларны бетерү",
        "whatlinkshere-prev": "{{PLURAL:$1|1=алдагы}} $1",
        "whatlinkshere-next": "{{PLURAL:$1|1=киләсе}} $1",
        "whatlinkshere-links": "← сылтамалар",
-       "whatlinkshere-hideredirs": "Юнәлтүләрне $1",
-       "whatlinkshere-hidetrans": "Кертүләрне $1",
-       "whatlinkshere-hidelinks": "Сылтамаларны $1",
-       "whatlinkshere-hideimages": "$1 файл сылтамалары",
+       "whatlinkshere-hideredirs": "Юнәлтүләрне яшер",
+       "whatlinkshere-hidetrans": "Кертүләрне яшер",
+       "whatlinkshere-hidelinks": "Сылтамаларны яшер",
+       "whatlinkshere-hideimages": "Файл сылтамаларын яшер",
        "whatlinkshere-filters": "Фильтрлар",
        "whatlinkshere-submit": "Башкару",
        "autoblockid": "Автотыю #$1",
index 2461405..8007f58 100644 (file)
        "noname": "Sez qullanuçı isemegezne kürsätergä tieşsez.",
        "loginsuccesstitle": "Kerü uñışlı ütte",
        "loginsuccess": "'''Sez {{SITENAME}} proyektına $1 iseme belän kerdegez.'''",
-       "nosuchuser": "$1 isemle qullanuçı yuq.\nQullanuçı isemeneñ döreslege registrğa bäyle.\nYazılışığıznı tikşeregez yäki [[Special:UserLogin/signup|yaña xisap yazması tözegez]].",
+       "nosuchuser": "$1 isemle qullanuçı yuq.\nQullanuçı isemeneñ döreslege registrğa bäyle.\nYazılışığıznı tikşeregez yäki [[Special:CreateAccount|yaña xisap yazması tözegez]].",
        "nosuchusershort": "$1 isemle qullanuçı yuq. Yazılışığıznı tikşeregez.",
        "nouserspecified": "Sez terkäw ismegezne kürsätergä tieşsez.",
        "login-userblocked": "Bu qullanuçı tıyıldı. Kerü tıyılğan.",
        "accmailtext": "[[User talk:$1|$1]] qullanuçısı öçen tözelgän sersüz $2 adresına cibärelde.\n\nSaytqa kergäç sez ''[[Special:ChangePassword|sersüzegezne üzgärtä alasız]]''.",
        "newarticle": "(Yaña)",
        "newarticletext": "Sez älegä yazılmağan bitkä kerdegez.\nYaña bit yasaw öçen astağı täräzädä mäqälä tekstın cıyığız ([$1 yärdäm biten] qarıy alasız).\nÄgär sez bu bitkä yalğışlıq belän eläkkän bulsağız, brauzerığıznıñ '''artqa''' töymäsenä basığız.",
-       "anontalkpagetext": "----''Bu bäxäs bite sistemada terkälmägän yäisä üz iseme belän kermägän qullanuçınıqı.\nAnı tanu öçen IP adresı faydalanıla.\nÄgär sez anonim qullanuçı häm sezgä yullanmağan xäbärlär aldım dip sanıysız ikän (ber IP adresı küp qullanuçı öçen bulırğa mömkin), başqa mondıy añlaşılmawçanlıqlar kilep çıqmasın öçen [[Special:UserLogin|sistemağa keregez]] yäisä [[Special:UserLogin/signup|terkälegez]].''",
+       "anontalkpagetext": "----''Bu bäxäs bite sistemada terkälmägän yäisä üz iseme belän kermägän qullanuçınıqı.\nAnı tanu öçen IP adresı faydalanıla.\nÄgär sez anonim qullanuçı häm sezgä yullanmağan xäbärlär aldım dip sanıysız ikän (ber IP adresı küp qullanuçı öçen bulırğa mömkin), başqa mondıy añlaşılmawçanlıqlar kilep çıqmasın öçen [[Special:UserLogin|sistemağa keregez]] yäisä [[Special:CreateAccount|terkälegez]].''",
        "noarticletext": "Xäzerge waqıtta bu bittä tekst yuq.\nSez [[Special:Search/{{PAGENAME}}|bu isem kergän başqa mäqälälärne]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} köndäleklärdäge yazmalarnı] taba\nyäki '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} şuşındıy isemle yaña bit tözi]'''</span> alasız.",
        "noarticletext-nopermission": "Xäzerge waqıtta bu bittä tekst yuq.\n[[Special:Search/{{PAGENAME}}|Bu başlam qullanılğan başqa bitlärne]] yäki\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} köndäleklärdäge yazmalarnı] ezläp ala alasız</span>, läkin bu bitne tözü röxsätegez yuq.",
        "userpage-userdoesnotexist": "«<nowiki>$1</nowiki>» isemle xisap yazması yuq. Sez çınlap ta bu bitne yasarğa yäisä üzgärtergä telisezme?",
        "filename-bad-prefix": "Faylnıñ iseme '''«$1»''' dip başlana. Zinhar, faylnı taswirlawçı isem biregez.",
        "filename-prefix-blacklist": " #<!-- niçek bar şulay qaldırığız --> <pre>\n# Sintaksis töbändägeçä:\n#   *  «#» dip başlanğan barlıq närsä dä qömmentariy dip atalaçaq\n#   * Härber buş rät — faylnıñ isemeneñ prefiksı, sifrlı kamera birüçe isem\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # qaysıber käräzle telefonnar\nIMG # barlıq\nJD # Jenoptik\nMGP # Pentax\nPICT # törle\n #</pre> <!-- niçek bar şulay qaldırığız -->",
        "upload-form-label-usage-title": "Qullanılış",
-       "foreign-structured-upload-form-label-infoform-categories": "Törkemnär",
-       "foreign-structured-upload-form-label-infoform-date": "Data",
+       "upload-form-label-infoform-categories": "Törkemnär",
+       "upload-form-label-infoform-date": "Data",
        "license": "Litsenziäse:",
        "license-header": "Litsenziäse",
        "nolicense": "Yuq",
index ae20f1d..3eeda7f 100644 (file)
@@ -11,7 +11,8 @@
                        "بىلگە",
                        "아라",
                        "Macofe",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "Amire80"
                ]
        },
        "tog-underline": "ئۇلانما ئاستى سىزىقى:",
        "noname": "سىز تېخى ئىناۋەتلىك ئىشلەتكۈچى نامىنى بەلگىلىمىدىڭىز.",
        "loginsuccesstitle": "تىزىمغا كىرىش مۇۋەپپەقىيەتلىك",
        "loginsuccess": "'''سىز {{SITENAME}} غا \"$1\" سالاھىيىتىدە كىردىڭىز.'''",
-       "nosuchuser": "\"$1\" ناملىق ئىشلەتكۈچىنى تاپالمىدى.\nئىشلەتكۈچى نامىنى تەكشۈرۈڭ.\nياكى [[Special:UserLogin/signup|يېڭى ھېسابات قۇرۇڭ]].",
+       "nosuchuser": "\"$1\" ناملىق ئىشلەتكۈچىنى تاپالمىدى.\nئىشلەتكۈچى نامىنى تەكشۈرۈڭ.\nياكى [[Special:CreateAccount|يېڭى ھېسابات قۇرۇڭ]].",
        "nosuchusershort": "\"$1\" ناملىق ئىشلەتكۈچى يوق.\nكىرگۈزگىنىڭىزنى تەكشۈرۈڭ.",
        "nouserspecified": "ئىشلەتكۈچى نامىدىن بىرنى بەلگىلەڭ.",
        "login-userblocked": "بۇ ئىشلەتكۈچى چەكلەنگەن. تىزىمغا كىرىشكە يول قويۇلمايدۇ.",
        "accmailtext": "[[User talk:$1|$1]] ئىختىيارىي قۇرۇلغان ئىم  $2 غا يوللاندى.\n\nيېڭى ھېساباتقا قۇرغان ئىمنى تىزىمغا كىرىپ''[[Special:ChangePassword|ئىم ئۆزگەرت]]'' بېتىدىن ئۆزگەرتەلەيسىز.",
        "newarticle": "(يېڭى)",
        "newarticletext": "سىز تېخى قۇرۇلمىغان بەتكە كىردىڭىز.\n بۇ بەتنى قۇرسىڭىز، تۆۋەندىكى تەھرىرلەش رامكىسىغا مەزمۇن كىرگۈزۈڭ(تەپسىلاتىنى  [$1 ياردەم بېتى]دىن كۆرۈڭ)",
-       "anontalkpagetext": "----''بۇ تېخى ھېسابات قۇرمىغان ئاتسىز ئىشلەتكۈچىنىڭ مۇنازىرە بېتى ياكى ئۇنى ئىشلەتمەڭ..\nبىز ئۇنىڭ بىلەن پەقەت IP ئادرېسى بىلەنلا ئالاقە قىلالايمىز..\nبۇ خىل IP ئادرېسنى بىر قانچە ئىشلەتكۈچى ئىشلىتىشى ئورتاق ئىشلىتىشى مۇمكىن.\nئەگەر سىز ئاتسىز ئىشلەتكۈچى بولسىڭىز ھەمدە بۇ بەتتىكى مۇنازىرە سىز بىلەن مۇناسىۋەتلىك بولسا،  [[Special:UserLogin/signup|ھېسابات قۇر]] ياكى [[Special:UserLogin|تىزىمغا كىر]]  ئارقىلىق كەلگۈسىدىكى باشقا ئاتسىز ئىشلەتكۈچى بىلەن ئارىلىشىپ كېتىشنىڭ ئالدىنى ئېلىڭ.''",
+       "anontalkpagetext": "----''بۇ تېخى ھېسابات قۇرمىغان ئاتسىز ئىشلەتكۈچىنىڭ مۇنازىرە بېتى ياكى ئۇنى ئىشلەتمەڭ..\nبىز ئۇنىڭ بىلەن پەقەت IP ئادرېسى بىلەنلا ئالاقە قىلالايمىز..\nبۇ خىل IP ئادرېسنى بىر قانچە ئىشلەتكۈچى ئىشلىتىشى ئورتاق ئىشلىتىشى مۇمكىن.\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}}|بۇ بەتنىڭ ماۋزۇسىنى ئىزدىيەلەيسىز]] ياكى <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}] مۇناسىۋەتلىك خاتىرىسىنى ئىزدىيەلەيسىز،</span>لىكىن سزنىڭ بەت قۇرۇش ھوقوقىڭز يوق.",
        "missing-revision": "\"{{FULLPAGENAME}}\" ئاتلىق بەتنىڭ تۈزىتىلگەن نەشرى #$1 مەۋجۇت ئەمەس.\n\nئادەتتە بۇ ئۆچۈرۈلگەن بىر بەتنىڭ ئۇلانمىسىغا كىرگەنلىك سەۋەبىدىن بولىدۇ.\nتەپسىلىي ئۇچۇرنى [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ئۆچۈرۈش خاتىرىسى] دىن تاپقىلى بولىدۇ.",
        "listgrouprights-rights": "ھوقۇق",
        "listgrouprights-helppage": "Help: گۇرۇپپا ھوقۇقى",
        "listgrouprights-members": "(ئەزالار تىزىملىكى)",
-       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code dir=\"ltr\">($2)</code></span>",
        "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": " {{PLURAL:$2|بىر|بىر قانچە}} گۇرۇپپىغا قوشالايدۇ: $1",
        "listgrouprights-removegroup": " {{PLURAL:$2|بىر|بىر قانچە}} گۇرۇپپىدىن چىقىرىۋېتەلەيدۇ: $1",
index a0c8837..16ddb75 100644 (file)
        "noname": "Ви зазначили неправильне ім'я користувача.",
        "loginsuccesstitle": "Вхід виконано",
        "loginsuccess": "'''Тепер ви працюєте в {{grammar:locative|{{SITENAME}}}} під іменем $1.'''",
-       "nosuchuser": "Користувача з іменем «$1» не існує.\nВ іменах користувачів розрізняються великі й малі символи.\nПеревірте правильність написання або скористайтеся формою нижче, щоб [[Special:UserLogin/signup|створити новий обліковий запис]].",
+       "nosuchuser": "Користувача з іменем «$1» не існує.\nВ іменах користувачів розрізняються великі й малі символи.\nПеревірте правильність написання або скористайтеся формою нижче, щоб [[Special:CreateAccount|створити новий обліковий запис]].",
        "nosuchusershort": "Користувача з іменем «$1» не існує.\nПеревірте правильність написання імені.",
        "nouserspecified": "Ви повинні зазначити ім'я користувача.",
        "login-userblocked": "Цей користувач заблокований. Вхід в систему не дозволений.",
        "minoredit": "Незначна зміна",
        "watchthis": "Спостерігати за цією сторінкою",
        "savearticle": "Зберегти сторінку",
+       "publishpage": "Опублікувати сторінку",
        "preview": "Попередній перегляд",
        "showpreview": "Попередній перегляд",
        "showdiff": "Показати зміни",
        "accmailtext": "Пароль для користувача [[User talk:$1|$1]], згенерований випадковим чином, надісланий на адресу $2.\nПісля реєстрації в системі ви зможете ''[[Special:ChangePassword|змінити пароль]]''.",
        "newarticle": "(Нова)",
        "newarticletext": "Ви перейшли на сторінку, яка поки що не існує.\n\nЩоб створити нову сторінку, наберіть текст у вікні нижче (див. [$1 довідкову статтю], щоб отримати більше інформації).\nЯкщо Ви опинились тут помилково, просто натисніть кнопку браузера '''назад'''.",
-       "anontalkpagetext": "----\n<em>Це сторінка обговорення анонімного користувача, який ще не зареєструвався або не скористався зареєстрованим обліковим записом.</em>\nТому ми вимушені використовувати IP-адресу для його ідентифікації.\nОдна IP-адреса може використовуватись кількома користувачами.\nЯкщо Ви — анонімний користувач і вважаєте, що отримали коментарі, адресовані не Вам, будь ласка [[Special:UserLogin/signup|зареєструйтесь]] або [[Special:UserLogin|увійдіть до системи]], щоб у майбутньому уникнути можливої плутанини з іншими анонімними користувачами.",
+       "anontalkpagetext": "----\n<em>Це сторінка обговорення анонімного користувача, який ще не зареєструвався або не скористався зареєстрованим обліковим записом.</em>\nТому ми вимушені використовувати IP-адресу для його ідентифікації.\nОдна IP-адреса може використовуватись кількома користувачами.\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}}}} пошукати пов'язані записи в журналах]</span>, але ви не маєте дозволу на створення такої сторінки.",
        "missing-revision": "Версія #$1 сторінки «{{FULLPAGENAME}}» не існує.\n\nІмовірно, Ви перейшли за застарілим посиланням на вилучену сторінку.\nПодробиці можна дізнатися з [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журналу вилучень].",
        "userpage-userdoesnotexist": "Користувач під назвою \"<nowiki>$1</nowiki>\" не зареєстрований. Переконайтеся, що ви хочете створити/редагувати цю сторінку.",
        "userpage-userdoesnotexist-view": "Обліковий запис користувача „$1“ не зареєстровано.",
        "blocked-notice-logextract": "Цей користувач наразі заблокований.\nОстанній запис у журналі блокувань такий:",
-       "clearyourcache": "'''Увага:''' Після збереження слід очистити кеш оглядача, щоб побачити зміни.\n* '''Firefox / Safari:''' тримайте ''Shift'', коли натискаєте ''Оновити'', або натисніть ''Ctrl-F5'' чи ''Ctrl-Shift-R'' (''⌘-R'' на Apple Mac)\n* '''Google Chrome:''' натисніть ''Ctrl-Shift-R'' (''⌘-Shift-R'' на Apple Mac)\n* '''Internet Explorer:''' тримайте ''Ctrl'', коли натискаєте ''Оновити'', або натисніть ''Ctrl-F5''\n* '''Opera:''' очистіть кеш за допомогою ''Інструменти → Налаштування''",
+       "clearyourcache": "<strong>Увага:</strong> Після збереження слід очистити кеш оглядача, щоб побачити зміни.\n* <strong>Firefox / Safari:</strong> тримайте <em>Shift</em>, коли натискаєте <em>Оновити</em>, або натисніть <em>Ctrl-F5</em> чи <em>Ctrl-Shift-R</em> (<em>⌘-R</em> на Apple Mac)\n* <strong>Google Chrome:</strong> натисніть <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> на Apple Mac)\n* <strong>Internet Explorer:</strong> тримайте <em>Ctrl</em>, коли натискаєте <em>Оновити</em>, або натисніть <em>Ctrl-F5</em>\n* <strong>Opera:</strong> очистіть кеш за допомогою <em>Інструменти → Налаштування</em> (<em>Opera → Побажання</em> на Apple Mac) та перейдіть на <em>Привітність & безпека → очистити дані браузера → кеш</em>",
        "usercssyoucanpreview": "'''Підказка:''' використовуйте кнопку «{{int:showpreview}}», щоб протестувати ваш новий css-файл перед збереженням.",
        "userjsyoucanpreview": "'''Підказка:''' використовуйте кнопку «{{int:showpreview}}», щоб протестувати ваш новий код JavaScript перед збереженням.",
        "usercsspreview": "'''Пам'ятайте, що це лише попередній перегляд вашого css-файлу.'''\n'''Його ще не збережено!'''",
        "upload-form-label-infoform-description-tooltip": "Коротко напишіть усе основне та цікаве про цю роботу.\nНаприклад, для фото опишіть, що сфотографовано, місце та нагоду знімка.",
        "upload-form-label-usage-title": "Використання",
        "upload-form-label-usage-filename": "Назва файлу",
-       "foreign-structured-upload-form-label-own-work": "Це моя власна робота",
-       "foreign-structured-upload-form-label-infoform-categories": "Категорії",
-       "foreign-structured-upload-form-label-infoform-date": "Дата",
-       "foreign-structured-upload-form-label-own-work-message-local": "Я підтверджую, що вивантажую цей файл згідно з умовами користування та політики ліцензування {{GRAMMAR:locative|{{SITENAME}}}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Якщо Ви не можете завантажити цей файл згідно з правилами {{GRAMMAR:genitive|{{SITENAME}}}}, будь ласка, закрийте цей вікно та оберіть інший спосіб.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Ви також можете спробувати [[Special:Upload|сторінку завантаження за замовчуванням]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Я розумію, що я завантажую цей файл до спільного сховища. Я підтверджую, що я роблю це у відповідності до Умов надання послуг та правил ліцензування.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Якщо ви не можете завантажити цей файл згідно з правилами спільного сховища, будь ласка, закрийте цей вікно та оберіть інший спосіб.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Ви також можете спробувати використати [[Special:Upload|сторінку завантаження на {{GRAMMAR:locative|{{SITENAME}}}}]], якщо цей файл може бути завантажений згідно з їх правилами.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Я підтверджую, що я є власником авторських прав на цей файл та погоджуюся беззастережно поширити його у Вікісховищі на умовах ліцензії [https://creativecommons.org/licenses/by-sa/4.0/deed.uk Creative CommonsAttribution-ShareAlike 4.0], і я згоден з [https://wikimediafoundation.org/wiki/Terms_of_Use умовами використання].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Якщо ви не є власником авторських прав на цей файл або ви хочете розповсюджувати його на умовах іншої ліцензії, рекомендуємо використати [https://commons.wikimedia.org/wiki/Special:UploadWizard Майстер завантажень на Вікісховищі].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Ви також можете спробувати використати [[Special:Upload|сторінку завантаження на {{GRAMMAR:locative|{{SITENAME}}}}]], якщо цей файл може бути завантажений на цей сайт згідно з його правилами.",
+       "upload-form-label-own-work": "Це моя власна робота",
+       "upload-form-label-infoform-categories": "Категорії",
+       "upload-form-label-infoform-date": "Дата",
+       "upload-form-label-own-work-message-generic-local": "Я підтверджую, що вивантажую цей файл згідно з умовами користування та політики ліцензування {{GRAMMAR:locative|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Якщо Ви не можете завантажити цей файл згідно з правилами {{GRAMMAR:genitive|{{SITENAME}}}}, будь ласка, закрийте цей вікно та оберіть інший спосіб.",
+       "upload-form-label-not-own-work-local-generic-local": "Ви також можете спробувати [[Special:Upload|сторінку завантаження за замовчуванням]].",
+       "upload-form-label-own-work-message-generic-foreign": "Я розумію, що я завантажую цей файл до спільного сховища. Я підтверджую, що я роблю це у відповідності до Умов надання послуг та правил ліцензування.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Якщо ви не можете завантажити цей файл згідно з правилами спільного сховища, будь ласка, закрийте цей вікно та оберіть інший спосіб.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Ви також можете спробувати використати [[Special:Upload|сторінку завантаження на {{GRAMMAR:locative|{{SITENAME}}}}]], якщо цей файл може бути завантажений згідно з їх правилами.",
        "backend-fail-stream": "Не вдалося транслювати файл $1.",
        "backend-fail-backup": "Не вдалося створити резервну копію файлу $1.",
        "backend-fail-notexists": "Файл $1 не існує.",
        "changecontentmodel-success-text": "Тип вмісту сторінки [[:$1]] було змінено.",
        "changecontentmodel-cannot-convert": "Вміст сторінки [[:$1]] не можна перетворити на вміст типу $2.",
        "changecontentmodel-nodirectediting": "Модель вмісту $1 не підтримує пряме редагування",
+       "changecontentmodel-emptymodels-title": "Немає доступних моделей коментарів",
+       "changecontentmodel-emptymodels-text": "Вміст сторінки [[:$1]] не може бути перетворений до будь якого типу.",
        "log-name-contentmodel": "Журнал змін моделі вмісту",
        "log-description-contentmodel": "Події, пов'язані з моделями вмісту сторінки",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|створив|створила}} сторінку $3, використовуючи нестандартну модель вмісту «$5»",
        "unblockip": "Розблокувати IP-адресу",
        "unblockiptext": "Використовуйте подану нижче форму, щоб відновити можливість збереження з раніше заблокованої IP-адреси.",
        "ipusubmit": "Зняти це блокування",
-       "unblocked": "[[User:$1|$1]] {{GENDER:$1|Ñ\80азблокований|Ñ\80азблокована}}",
+       "unblocked": "[[User:$1|$1]] {{GENDER:$1|Ñ\80озблокований|Ñ\80озблокована}}",
        "unblocked-range": "$1 розблоковано",
        "unblocked-id": "Блокування $1 було зняте",
        "unblocked-ip": "[[Special:Contributions/$1|$1]] вже розлоковано.",
        "lockdbsuccesstext": "Базу даних проекту заблоковано.<br />\nНе забудьте її [[Special:UnlockDB|розблокувати]] після завершення обслуговування.",
        "unlockdbsuccesstext": "Базу даних проекту розблоковано.",
        "lockfilenotwritable": "Немає права на запис в файл блокування бази даних. Щоб заблокувати чи розблокувати БД, веб-сервер повинен мати дозвіл на запис в цей файл.",
+       "databaselocked": "База даних вже заблокована.",
        "databasenotlocked": "База даних не заблокована.",
        "lockedbyandtime": "($1 $2 $3)",
        "move-page": "Перейменування сторінки «$1»",
        "movesubpagetext": "Ця сторінка має $1 {{PLURAL:$1|підсторінку|підсторінки|підсторінок}}.",
        "movenosubpage": "Ця сторінка не має підсторінок.",
        "movereason": "Причина:",
-       "revertmove": "відкинути",
+       "revertmove": "скасувати перейменування",
        "delete_and_move_text": "Сторінка з назвою [[:$1|«$1»]] вже існує.\nБажаєте вилучити її для можливості перейменування?",
        "delete_and_move_confirm": "Так, вилучити для перейменування",
        "delete_and_move_reason": "Вилучена для можливості перейменування сторінки «[[$1]]»",
        "tooltip-ca-nstab-category": "Сторінка категорії",
        "tooltip-minoredit": "Позначити це редагування як незначне",
        "tooltip-save": "Зберегти ваші зміни",
+       "tooltip-publish": "Опублікувати ваші зміни",
        "tooltip-preview": "Попередній перегляд сторінки, будь ласка, використовуйте перед збереженням!",
        "tooltip-diff": "Показати зміни, що зроблені відносно початкового тексту.",
        "tooltip-compareselectedversions": "Переглянути різницю між двома вказаними версіями цієї сторінки.",
        "feedback-useragent": "User Agent:",
        "searchsuggest-search": "Пошук",
        "searchsuggest-containing": "що містять...",
+       "api-error-autoblocked": "Вашу IP-адресу було заблоковано автоматично, тому що її використовував заблокований користувач.",
        "api-error-badaccess-groups": "Вам не дозволено завантажувати файли до цього вікіпроекту.",
        "api-error-badtoken": "Внутрішня помилка: некоректний токен.",
+       "api-error-blocked": "Можливість редагування для вас заблоковано.",
        "api-error-copyuploaddisabled": "На цьому сервері вимкнене завантаження за URL-адресою.",
        "api-error-duplicate": "Уже {{PLURAL:$1|1=існує інший файл|існують інші файли}} з таким самим вмістом.",
        "api-error-duplicate-archive": "Раніше на сайті вже {{PLURAL:$1|1=був файл|були файли}} з ідентичним вмістом, але {{PLURAL:$1|1=його|їх}} вилучили.",
        "api-error-nomodule": "Внутрішня помилка: Відсутній модуль завантажень.",
        "api-error-ok-but-empty": "Внутрішня помилка: сервер не відповідає.",
        "api-error-overwrite": "Заміну існуючого файлу не дозволено.",
+       "api-error-ratelimited": "Ви намагаєтесь завантажити більше файлів за короткий проміжок часу, ніж дозволено у цій вікі. Будь ласка, спробуйте за декілька хвилин.",
        "api-error-stashfailed": "Внутрішня помилка: сервер не зміг зберегти тимчасовий файл.",
        "api-error-publishfailed": "Внутрішня помилка: сервер не зміг опублікувати тимчасовий файл.",
        "api-error-stasherror": "Сталася помилка при завантаженні файлу у сховище.",
index aae60f7..73e3d6d 100644 (file)
        "noname": "آپ نے صحیح اسم صارف نہیں چنا.",
        "loginsuccesstitle": "داخلہ کامیاب",
        "loginsuccess": "'''اب آپ {{SITENAME}} میں بنام \"$1\" داخل ہوچکے ہیں۔'''",
-       "nosuchuser": "\"$1\" کے نام سے کوئی صارف موجود نہیں ہے.\nبرائے مہربانی! ہجوں کے درست اندراج کی تصدیق کرلیجئے.\nاگر آپ چاہیں تو [[Special:UserLogin/signup|نیا کھاتہ بھی بناسکتے ہیں]].",
+       "nosuchuser": "\"$1\" کے نام سے کوئی صارف موجود نہیں ہے.\nبرائے مہربانی! ہجوں کے درست اندراج کی تصدیق کرلیجئے.\nاگر آپ چاہیں تو [[Special:CreateAccount|نیا کھاتہ بھی بناسکتے ہیں]].",
        "nosuchusershort": "\"$1\" کے نام سے کوئی صارف موجود نہیں.\nاپنا ہجہ جانچئے.",
        "nouserspecified": "آپ کو ایک اسمِ صارف مخصوص کرنا ہے.",
        "login-userblocked": "اِس صارف پر پابندی ہے. داخلِ نوشتہ ہونے کی اجازت نہیں.",
        "accmailtext": "[[User talk:$1|$1]] کے لیے خودکار طریقے سے تخلیق کیا گیا پاسورڈ $2 کو بھیج دیا گیا ہے.\n\nلاگ ان ہونے کے بعد <em>[[Special:ChangePassword|اسے تبدیل]]</em> کیا جا سکتا ہے۔",
        "newarticle": "(نیا)",
        "newarticletext": "آپ نے ایک ایسے صفحے کے ربط کی پیروی کی ہے جو کہ ابھی موجود نہیں ہے.\nیہ صفحہ تخلیق کرنے کیلئے درج ذیل خانہ میں متن درج کیجئے (مزید معلومات کیلئے [$1 صفحۂ معاونت] ملاحظہ فرمائیے).\nاگر آپ یہاں غلطی سے پہنچے ہیں تو پچھلے صفحے پر واپس جانے کیلئے اپنے متصفح پر '''back''' کا بٹن ٹک کیجئے.",
-       "anontalkpagetext": "----''یہ صفحہ ایک ایسے صارف کا ہے جنہوں نے یا تو اب تک اپنا کھاتا نہیں بنایا یا پھر وہ اسے استعمال نہیں کر رہے/ رہی ہیں۔ لہٰذا ہمیں انکی شناخت کے لئے ایک عددی آئی پی پتہ استعمال کرنا پڑرہا ہے۔ اس قسم کا آئی پی پتہ ایک سے زائد صارفین کے لئے مشترک بھی ہوسکتا ہے۔ اگر آپکی موجودہ حیثیت ایک گمنام صارف کی ہے اور آپ محسوس کریں کہ اس صفحہ پر آپکی جانب منسوب یہ بیان غیرضروری ہے تو براہ کرم [[Special:UserLogin/signup|کھاتہ بنائیں]] یا [[Special:UserLogin|داخلِ نوشتہ]] ہوجائیے تاکہ مستقبل میں آپکو گمنام صارفین میں شمار کرنے سے پرہیز کیا جاسکے۔\"",
+       "anontalkpagetext": "----''یہ صفحہ ایک ایسے صارف کا ہے جنہوں نے یا تو اب تک اپنا کھاتا نہیں بنایا یا پھر وہ اسے استعمال نہیں کر رہے/ رہی ہیں۔ لہٰذا ہمیں انکی شناخت کے لئے ایک عددی آئی پی پتہ استعمال کرنا پڑرہا ہے۔ اس قسم کا آئی پی پتہ ایک سے زائد صارفین کے لئے مشترک بھی ہوسکتا ہے۔ اگر آپکی موجودہ حیثیت ایک گمنام صارف کی ہے اور آپ محسوس کریں کہ اس صفحہ پر آپکی جانب منسوب یہ بیان غیرضروری ہے تو براہ کرم [[Special:CreateAccount|کھاتہ بنائیں]] یا [[Special:UserLogin|داخلِ نوشتہ]] ہوجائیے تاکہ مستقبل میں آپکو گمنام صارفین میں شمار کرنے سے پرہیز کیا جاسکے۔\"",
        "noarticletext": "اِس صفحہ میں فی الحال کوئی متن موجود نہیں ہے.\nآپ دیگں صفحات میں [[Special:Search/{{PAGENAME}}|اِس صفحہ کے عنوان کیلئے تلاش کرسکتے ہیں]]، <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\" مندرج نہیں ہے۔",
        "upload-form-label-infoform-description": "تفصیل",
        "upload-form-label-usage-title": "استعمال",
        "upload-form-label-usage-filename": "ملف نام",
-       "foreign-structured-upload-form-label-own-work": "یہ میرا ذاتی کام ہے",
-       "foreign-structured-upload-form-label-infoform-categories": "زمرہ جات",
-       "foreign-structured-upload-form-label-infoform-date": "تاریخ",
+       "upload-form-label-own-work": "یہ میرا ذاتی کام ہے",
+       "upload-form-label-infoform-categories": "زمرہ جات",
+       "upload-form-label-infoform-date": "تاریخ",
        "license": "اجازہ:",
        "license-header": "اجازہ کاری",
        "listfiles-delete": "حذف",
        "allmessagesdefault": "طے شدہ متن",
        "allmessagescurrent": "موجودہ متن",
        "allmessagestext": "یہ میڈیاویکی: جاۓ نام میں دستیاب نظامی پیغامات کی فہرست ہے۔",
+       "allmessages-filter": "تلاش بلحاظ:",
        "allmessages-filter-all": "تمام",
        "allmessages-filter-modified": "تبدیل شدہ",
+       "allmessages-prefix": "تلاش بلحاظ سابقہ:",
        "allmessages-language": "زبان:",
        "allmessages-filter-submit": "ٹھیک",
        "allmessages-filter-translate": "ترجمہ",
        "logentry-delete-delete": "$1 {{GENDER:$2|حذف کیا گیا}} صفحہ $3",
        "logentry-move-move": "$1 نے صفحہ $3 کو بجانب $4 منتقل کیا",
        "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بنایا گیا}}",
+       "logentry-protect-modify": "$1 نے $3 کا درجۂ حفاظت {{GENDER:$2|تبدیل کیا}} $4",
        "logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
        "rightsnone": "(کچھ نہیں)",
        "revdelete-summary": "خلاصۂ تدوین",
index f890815..608b867 100644 (file)
        "accmailtitle": "Maxfiy soʻz joʻnatildi",
        "newarticle": "(Yangi)",
        "newarticletext": "Bu sahifa hali mavjud emas.\nSahifani yaratish uchun quyida matn kiritishingiz mumkin (qoʻshimcha axborot uchun [$1 yordam sahifasini] koʻring).\nAgar bu sahifaga xatolik sabab kelib qolgan boʻlsangiz brauzeringizning '''orqaga''' tugmasini bosing.",
-       "anontalkpagetext": "----\n<em>Ushbu munozara sahifasi hisob yozuvi yaratmagan (yoki yaratishni xohlamaydigan) anonim foydalanuvchiga tegishli.</em>\n\nShu sababli, uni aniqlash uchun raqamli IP-manzildan foydalaniladi.\nUshbu IP-manzil bir nechta foydalanuvchilarga tegishli boʻlishi mumkin.\nAgar siz anonim foydalanuvchi boʻlsangiz va qoldirilgan xabarlar sizga yoʻnaltirilmagan deb hisoblasangiz, iltimos, boshqa anonim foydalanuvchilar bilan adashtirib yubormasliklari uchun [[Special:UserLogin/signup|hisob yozuvi yarating]] yoki [[Special:UserLogin|tizimga kiring]].",
+       "anontalkpagetext": "----\n<em>Ushbu munozara sahifasi hisob yozuvi yaratmagan (yoki yaratishni xohlamaydigan) anonim foydalanuvchiga tegishli.</em>\n\nShu sababli, uni aniqlash uchun raqamli IP-manzildan foydalaniladi.\nUshbu IP-manzil bir nechta foydalanuvchilarga tegishli boʻlishi mumkin.\nAgar siz anonim foydalanuvchi boʻlsangiz va qoldirilgan xabarlar sizga yoʻnaltirilmagan deb hisoblasangiz, iltimos, boshqa anonim foydalanuvchilar bilan adashtirib yubormasliklari uchun [[Special:CreateAccount|hisob yozuvi yarating]] yoki [[Special:UserLogin|tizimga kiring]].",
        "noarticletext": "Bu sahifada hozircha hech qanday matn yoʻq. Siz bu sarlavhani boshqa sahifalardan [[Special:Search/{{PAGENAME}}|qidirishingiz]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tegishli qaydlarga qarashingiz] yoki bu sahifani [{{fullurl:{{FULLPAGENAME}}|action=edit}} tahrirlashingiz]</span> mumkin.",
        "userpage-userdoesnotexist-view": "\"$1\" foydalanuvchi hisobi roʻyxatga olinmagan.",
        "clearyourcache": "'''Eslatma.''' Saqlaganingizdan so'ng o'zgarishlarni ko'rish uchun siz o'z brauzeringiz keshini tozalashingizga to'gri kelishi mumkin.\n* '''Firefox / Safari:''' ''Shift'' tugmasini bosgan holda, ''Yangilash'' unsurlar darchasini bosing, yoki ''Ctrl-F5'' yoki ''Ctrl-R'' (Macda ''⌘-R'') ni bosing\n* '''Google Chrome:''' ''Ctrl-Shift-R'' (Macda ''⌘-Shift-R'') ni bosing\n* '''Internet Explorer:''' ''Ctrl''ni bosgan holda, ''Yangilash''ni bosing, yoki ''Ctrl-F5''ni bosing\n* '''Opera:''' ''Asboblar → Moslamalar'' menyusidan keshni tozalashni tanlang",
        "categories": "Turkumlar",
        "categoriespagetext": "Quyidagi {{PLURAL:$1|turkumda|turkumlarda}} sahifa yoki media-fayllar mavjud.\n[[Special:UnusedCategories|Ishlatilmayotgan turkumlar]] bu yerda koʻrsatilmaydi.\nShuningdek qarang: [[Special:WantedCategories|talab qilinayotgan turkumlar]].",
        "categoriesfrom": "Quyidagidan boshlanuvchi turkumlarni koʻrsatish:",
-       "special-categories-sort-count": "miqdori bo‘yicha saralash",
-       "special-categories-sort-abc": "alifbo bo‘yicha saralash",
        "deletedcontributions": "Foydalanuvchining o‘chirilgan hissasi",
        "deletedcontributions-title": "O‘chirilgan foydalanuvchilar hissalari",
        "sp-deletedcontributions-contribs": "hissa",
index 5bcc052..bc47ef1 100644 (file)
        "noname": "El nome utente indicà no xè vałido.",
        "loginsuccesstitle": "Aceso efetuà",
        "loginsuccess": "'''Te si sta conesso al server de {{SITENAME}} con el nome utente de \"$1\".'''",
-       "nosuchuser": "Nol xè rejistrà alcun utente de nome \"$1\". I nomi utente i xè sensibiłi a łe majuscołe. Verifegare el nome inserio o [[Special:UserLogin/signup|creare on novo aceso]].",
+       "nosuchuser": "Nol xè rejistrà alcun utente de nome \"$1\". I nomi utente i xè sensibiłi a łe majuscołe. Verifegare el nome inserio o [[Special:CreateAccount|creare on novo aceso]].",
        "nosuchusershort": "No ghe xe nissun utente de nome \"$1\". Sito sicuro che te lo ghè scrito ben?",
        "nouserspecified": "Te ghè da métar un nome utente.",
        "login-userblocked": "Sta utensa xè blocà. No xè posibiłe efetuare el login.",
        "accmailtext": "Na password xenerà casualmente par [[User talk:$1|$1]] la xe stà mandà a $2.\n\nLa password par sta nova utensa la pode vegner canbià, dopo ver fato l'acesso, su la pàxena ''[[Special:ChangePassword|canbiar la password]]''.",
        "newarticle": "(Novo)",
        "newarticletext": "Te ghe sì 'ndà drio a un colegamento a na pagina che no esiste gnancora.\nSe te voli crear sta pagina, taca scrìvar el testo in te la casèla qua soto\n(varda le [$1 pagine de ajuto] par saverghene de pì).\nSe te sì rivà qua par sbajo, basta che te struchi '''Indrìo''' sul to browser.",
-       "anontalkpagetext": "----''Sta qua la xe la pagina de discussion de un utente anonimo che no'l se gà gnancora registrà o che no'l xe entrà col so nome utente.\nDe conseguenza xè necessario identificarlo tramite l'indirizo IP numerico.\nSto indirizo el pode èssar doparà da tanti utenti.\nSe te sì un utente anonimo e te ghè ricevù dei messagi che te secondo ti i gera par qualchedun altro, te podi [[Special:UserLogin/signup|registrarte]] o [[Special:UserLogin|entrar col to nome utente]] par evitar confusion con altri utenti anonimi in futuro.''",
+       "anontalkpagetext": "----''Sta qua la xe la pagina de discussion de un utente anonimo che no'l se gà gnancora registrà o che no'l xe entrà col so nome utente.\nDe conseguenza xè necessario identificarlo tramite l'indirizo IP numerico.\nSto indirizo el pode èssar doparà da tanti utenti.\nSe te sì un utente anonimo e te ghè ricevù dei messagi che te secondo ti i gera par qualchedun altro, te podi [[Special:CreateAccount|registrarte]] o [[Special:UserLogin|entrar col to nome utente]] par evitar confusion con altri utenti anonimi in futuro.''",
        "noarticletext": "In sto momento no ghe xe nissun testo su sta pagina.\nTe pol [[Special:Search/{{PAGENAME}}|sercar el titolo de sta pagina]] in altre pagine,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sercar in tei registri ligà a sta pagina] o se nò [{{fullurl:{{FULLPAGENAME}}|action=edit}} canbiar la pagina]</span>.",
        "noarticletext-nopermission": "In sto momento no ghe xe nissun testo su sta pagina.\nTe pol [[Special:Search/{{PAGENAME}}|sercar sto titolo de pagina]] in altre pagine,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sercar in tei registri ligà a sta pagina]</span>, ma no te ghè el parmesso de crear sta pagina.",
        "missing-revision": "Ła revixion #$1 de ła pàjina \"{{FULLPAGENAME}}\" nó ła existe.",
index 2b12101..3e57c4e 100644 (file)
        "noname": "Tö ei olgoi kirjutanuded lasktud kävutajan nimed.",
        "loginsuccesstitle": "Tulend sistemha om lopnus satusekahas.",
        "loginsuccess": "'''Tö radat nügüd' {{SITENAME}}-saital kut \"$1\".'''",
-       "nosuchuser": "Ei ole kävutajad \"$1\"-nimenke.\nKävutajan nimiden oigedkirjutamine rippub kirjamiden registraspäi.\nKodvgat teiden oigedkirjutamine, vai [[Special:UserLogin/signup|säkat uz' registracii]].",
+       "nosuchuser": "Ei ole kävutajad \"$1\"-nimenke.\nKävutajan nimiden oigedkirjutamine rippub kirjamiden registraspäi.\nKodvgat teiden oigedkirjutamine, vai [[Special:CreateAccount|säkat uz' registracii]].",
        "nosuchusershort": "Ei ole kävutajad \"$1\"-nimenke.\nKodvgat teiden oigedkirjutamine.",
        "nouserspecified": "Pidab kirjutada kävutajan nimi.",
        "login-userblocked": "Nece kävutai om blokiruidud. Tulend sistemha om kel'tud.",
index d788a2f..67005f6 100644 (file)
@@ -52,6 +52,7 @@
        "tog-watchdefault": "Tự động theo dõi các trang và tập tin tôi sửa",
        "tog-watchmoves": "Tự động theo dõi các trang và tập tin tôi di chuyển",
        "tog-watchdeletion": "Tự động theo dõi các trang và tập tin tôi xóa",
+       "tog-watchuploads": "Thêm các tập tin tải lên của tôi vào danh sách theo dõi của tôi",
        "tog-watchrollback": "Tự động theo dõi các trang tôi lùi sửa",
        "tog-minordefault": "Mặc định đánh dấu tất cả sửa đổi của tôi là sửa đổi nhỏ",
        "tog-previewontop": "Hiển thị phần xem trước nằm trên hộp sửa đổi",
        "noname": "Chưa nhập tên.",
        "loginsuccesstitle": "Đã đăng nhập",
        "loginsuccess": "'''Bạn đã đăng nhập vào {{SITENAME}} với tên “$1”.'''",
-       "nosuchuser": "Không có thành viên nào có tên “$1”.\nTên người dùng có phân biệt chữ hoa chữ thường.\nHãy kiểm tra lại chính tả, hoặc [[Special:UserLogin/signup|mở tài khoản mới]].",
+       "nosuchuser": "Không có thành viên nào có tên “$1”.\nTên người dùng có phân biệt chữ hoa chữ thường.\nHãy kiểm tra lại chính tả, hoặc [[Special:CreateAccount|mở tài khoản mới]].",
        "nosuchusershort": "Không có thành viên nào có tên “$1”. Xin hãy kiểm tra lại chính tả.",
        "nouserspecified": "Bạn phải chỉ định một tên người dùng.",
        "login-userblocked": "Thành viên này đã bị cấm. Không cho phép đăng nhập.",
        "minoredit": "Sửa đổi nhỏ",
        "watchthis": "Theo dõi trang này",
        "savearticle": "Lưu trang",
+       "publishpage": "Xuất bản trang",
        "preview": "Xem trước",
        "showpreview": "Xem trước",
        "showdiff": "Xem thay đổi",
        "accmailtext": "Một mật khẩu được tạo ngẫu nhiên cho [[User talk:$1|$1]] đã được gửi đến $2. Có thể đổi mật khẩu tại trang ''[[Special:ChangePassword|đổi mật khẩu]]'' sau khi đã đăng nhập.",
        "newarticle": "(Mới)",
        "newarticletext": "Bạn đi đến đây từ một liên kết đến một trang chưa tồn tại. Để tạo trang, hãy bắt đầu gõ vào ô bên dưới (xem [$1 trang trợ giúp] để có thêm thông tin). Nếu bạn đến đây do nhầm lẫn, chỉ cần nhấn vào nút '''Lùi''' (hoặc Trở lại, Quay lại, Back) trong trình duyệt của bạn.",
-       "anontalkpagetext": "----''Đây là trang thảo luận của một người dùng vô danh chưa tạo tài khoản hoặc có tài khoản nhưng không đăng nhập.\nDo đó chúng ta phải dùng một dãy số gọi là địa chỉ IP để xác định anh/chị ta.\nMột địa chỉ IP như vậy có thể có nhiều người cùng dùng chung.\nNếu bạn là một thành viên vô danh và cảm thấy rằng có những lời bàn luận không thích hợp đang nhắm vào bạn, xin hãy [[Special:UserLogin/signup|tạo tài khoản]] hoặc [[Special:UserLogin|đăng nhập]] để tránh sự nhầm lẫn về sau với những thành viên vô danh khác.''",
+       "anontalkpagetext": "----''Đây là trang thảo luận của một người dùng vô danh chưa tạo tài khoản hoặc có tài khoản nhưng không đăng nhập.\nDo đó chúng ta phải dùng một dãy số gọi là địa chỉ IP để xác định anh/chị ta.\nMột địa chỉ IP như vậy có thể có nhiều người cùng dùng chung.\nNếu bạn là một thành viên vô danh và cảm thấy rằng có những lời bàn luận không thích hợp đang nhắm vào bạn, xin hãy [[Special:CreateAccount|tạo tài khoản]] hoặc [[Special:UserLogin|đăng nhập]] để tránh sự nhầm lẫn về sau với những thành viên vô danh khác.''",
        "noarticletext": "Trang này hiện chưa có nội dung.\nBạn có thể [[Special:Search/{{PAGENAME}}|tìm kiếm tựa trang này]] trong các trang khác, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tìm trong các nhật trình liên quan],\nhoặc [{{fullurl:{{FULLPAGENAME}}|action=edit}} tạo mới trang này]</span>.",
        "noarticletext-nopermission": "Trang này hiện đang trống.\nBạn có thể [[Special:Search/{{PAGENAME}}|tìm kiếm tựa trang này]] tại các trang khác, hoặc <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tìm kiếm các nhật trình liên quan]</span>, nhưng bạn không được phép tạo trang này.",
        "missing-revision": "Phiên bản #$1 của trang có tên “{{FULLPAGENAME}}” không tồn tại.\n\nLỗi này thường xuất hiện đối khi theo dõi liên kết lỗi thời đến phiên bản cũ của một trang đã bị xóa.\nXem chi tiết trong [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} nhật trình xóa].",
        "userpage-userdoesnotexist": "Đây chưa có tài khoản với tên “<nowiki>$1</nowiki>”. Xin hãy kiểm tra lại nếu bạn muốn tạo hay sửa trang này.",
        "userpage-userdoesnotexist-view": "Chưa có tài khoản với tên “$1”.",
        "blocked-notice-logextract": "Người dùng này hiện đang bị cấm sửa đổi. Nhật trình cấm gần nhất được ghi ở dưới để tiện theo dõi:",
-       "clearyourcache": "'''Chú ý:''' Sau khi lưu trang, có thể bạn sẽ phải xóa bộ nhớ đệm của trình duyệt để xem các thay đổi.\n* '''Firefox / Safari:''' Nhấn giữ phím ''Shift'' trong khi nhấn ''Tải lại'' (''Reload''), hoặc nhấn tổ hợp ''Ctrl-F5'' hay ''Ctrl-R'' (⌘R trên Mac)\n* '''Google Chrome:''' Nhấn tổ hợp ''Ctrl-Shift-R'' (⇧⌘R trên Mac)\n* '''Internet Explorer:''' Nhấn giữ phím ''Ctrl'' trong khi nhấn ''Làm tươi'' (''Refresh''), hoặc nhấn tổ hợp ''Ctrl-F5''\n* '''Opera:''' Xóa bộ nhớ đệm trong ''Công cụ → Sở thích'' (''Tools → Preferences'')",
+       "clearyourcache": "<strong>Chú ý:</strong> Sau khi lưu trang, có thể bạn sẽ phải xóa bộ nhớ đệm của trình duyệt để xem các thay đổi.\n* <strong>Firefox / Safari:</strong> Nhấn giữ phím <em>Shift</em> trong khi nhấn <em>Tải lại</em> (<em>Reload</em>), hoặc nhấn tổ hợp <em>Ctrl-F5</em> hay <em>Ctrl-R</em> (⌘R trên Mac)\n* <strong>Google Chrome:</strong> Nhấn tổ hợp <em>Ctrl-Shift-R</em> (⇧⌘R trên Mac)\n* <strong>Internet Explorer:</strong> Nhấn giữ phím <em>Ctrl</em> trong khi nhấn <em>Làm tươi</em>, hoặc nhấn tổ hợp <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Mở <em>Menu → Cài đặt</em> (<em>Opera → Tùy chỉnh</em> trên Mac), mở tab <em>Quyền riêng tư & bảo mật</em>, bấm <em>Xóa dữ liệu duyệt web</em> và đánh hộp kiểm <em>Hình ảnh và tệp trong cache</em>.",
        "usercssyoucanpreview": "'''Mẹo:''' Sử dụng nút “{{int:showpreview}}” để kiểm thử bản CSS của bạn trước khi lưu trang.",
        "userjsyoucanpreview": "'''Mẹo:''' Sử dụng nút “{{int:showpreview}}” để kiểm thử bản JS của bạn trước khi lưu trang.",
        "usercsspreview": "'''Hãy nhớ rằng bạn chỉ đang xem trước trang CSS cá nhân của bạn.\nNó chưa được lưu!'''",
        "recentchangeslinked-page": "Tên trang:",
        "recentchangeslinked-to": "Hiện thay đổi tại những trang có liên kết đến trang này thay thế",
        "recentchanges-page-added-to-category": "[[:$1]] được xếp vào thể loại",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] và [[Special:WhatLinksHere/$1|{{PLURAL:$2|một trang|$2 trang}} nữa]] được xếp vào thể loại",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] được xếp vào thể loại; [[Special:WhatLinksHere/$1|trang này được nhúng vào các trang khác]]",
        "recentchanges-page-removed-from-category": "[[:$1]] được gỡ khỏi thể loại",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] và [[Special:WhatLinksHere/$1|{{PLURAL:$2|một trang|$2 trang}} nữa]] được gỡ khỏi thể loại",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] được xóa gỡ thể loại; [[Special:WhatLinksHere/$1|trang này được nhúng vào các trang khác]]",
        "autochange-username": "MediaWiki thay đổi tự động",
        "upload": "Tải tập tin lên",
        "uploadbtn": "Tải tập tin lên",
        "upload-form-label-infoform-description-tooltip": "Miêu tả một cách ngắn gọn mỗi điều đáng kể về tác phẩm này.\nNếu là hình chụp, hãy nói đến những vật thể chính, cũng như tình hình, sự kiện, hay địa điểm của hình chụp.",
        "upload-form-label-usage-title": "Sử dụng",
        "upload-form-label-usage-filename": "Tên tập tin",
-       "foreign-structured-upload-form-label-own-work": "Đây là tác phẩm của chính tôi",
-       "foreign-structured-upload-form-label-infoform-categories": "Thể loại",
-       "foreign-structured-upload-form-label-infoform-date": "Ngày tháng",
-       "foreign-structured-upload-form-label-own-work-message-local": "Tôi xác nhận rằng tôi tải lên tập tin này tuân theo các điều khoản sử dụng và quy định giấy phép của {{SITENAME}}.",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "Nếu bạn không được phép tải lên tập tin này tuân theo quy định của {{SITENAME}}, xin vui lòng đóng hộp thoại này và thử tải lên bằng một phương pháp khác.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Bạn cũng có thể muốn sử dụng [[Special:Upload|trang tải lên mặc định]].",
-       "foreign-structured-upload-form-label-own-work-message-default": "Tôi hiểu rằng tôi đang tải tập tin này lên một kho dùng chung. Tôi xác nhận rằng tôi làm việc này tuân theo các điều khoản sử dụng và quy định về giấy phép tại đấy.",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "Nếu bạn không có thể tải tập tin này lên mà tuân theo quy định của kho dùng chung, xin vui lòng đóng hộp thoại này và thử một cách khác.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Bạn có thể muốn thử [[Special:Upload|trang tải lên tại {{SITENAME}}]] nếu tập tin này có thể được tải lên đấy theo các quy định của họ.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Tôi khẳng định rằng tôi giữ quyền tác giả của tập tin này và đồng ý phát hành, một cách không thể hủy bỏ, tập tin này cho Wikimedia Commons theo giấy phép [https://creativecommons.org/licenses/by-sa/4.0/deed.vi Creative Commons Ghi công–Chia sẻ tương tự 4.0], và tôi chấp nhận các [https://wikimediafoundation.org/wiki/Terms_of_Use/vi?uselang=vi Điều khoản Sử dụng].",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Nếu bạn không giữ quyền tác giả của tập tin hoặc muốn phát hành nó theo một giấy phép khác, xin nghĩ đến việc sử dụng [https://commons.wikimedia.org/wiki/Special:UploadWizard?uselang=vi Trình thuật sĩ tải lên Commons].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Bạn cũng có thể muốn thử [[Special:Upload|trang tải lên tại {{SITENAME}}]] nếu trang đó cho phép tải lên tập tin này theo các quy định của họ.",
+       "upload-form-label-own-work": "Đây là tác phẩm của chính tôi",
+       "upload-form-label-infoform-categories": "Thể loại",
+       "upload-form-label-infoform-date": "Ngày tháng",
+       "upload-form-label-own-work-message-generic-local": "Tôi xác nhận rằng tôi tải lên tập tin này tuân theo các điều khoản sử dụng và quy định giấy phép của {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Nếu bạn không được phép tải lên tập tin này tuân theo quy định của {{SITENAME}}, xin vui lòng đóng hộp thoại này và thử tải lên bằng một phương pháp khác.",
+       "upload-form-label-not-own-work-local-generic-local": "Bạn cũng có thể muốn sử dụng [[Special:Upload|trang tải lên mặc định]].",
+       "upload-form-label-own-work-message-generic-foreign": "Tôi hiểu rằng tôi đang tải tập tin này lên một kho dùng chung. Tôi xác nhận rằng tôi làm việc này tuân theo các điều khoản sử dụng và quy định về giấy phép tại đấy.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Nếu bạn không có thể tải tập tin này lên mà tuân theo quy định của kho dùng chung, xin vui lòng đóng hộp thoại này và thử một cách khác.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Bạn có thể muốn thử [[Special:Upload|trang tải lên tại {{SITENAME}}]] nếu tập tin này có thể được tải lên đấy theo các quy định của họ.",
        "backend-fail-stream": "Không thể gửi luồng tập tin $1.",
        "backend-fail-backup": "Không thể sao lưu tập tin $1.",
        "backend-fail-notexists": "Tập tin $1 không tồn tại.",
        "changecontentmodel-nodirectediting": "Kiểu nội dung $1 không hỗ trợ sửa đổi trực tiếp",
        "log-name-contentmodel": "Nhật trình thay đổi kiểu nội dung",
        "log-description-contentmodel": "Sự kiện có liên quan đến kiểu nội dung của trang.",
-       "logentry-contentmodel-new": "$1 {{GENDER:$2}}đã tạo trang $3 với mô hình nội dung không mặc định “$5”",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2}}đã tạo trang $3 với kiểu nội dung không mặc định “$5”",
        "logentry-contentmodel-change": "$1 {{GENDER:$2}}đã thay đổi kiểu nội dung của trang $3 từ “$4” thành “$5”",
        "logentry-contentmodel-change-revertlink": "lùi lại",
        "logentry-contentmodel-change-revert": "lùi lại",
        "whatlinkshere-prev": "{{PLURAL:$1|kết quả trước|$1 kết quả trước}}",
        "whatlinkshere-next": "{{PLURAL:$1|kết quả sau|$1 kết quả sau}}",
        "whatlinkshere-links": "← liên kết",
-       "whatlinkshere-hideredirs": "$1 trang đổi hướng",
+       "whatlinkshere-hideredirs": "Ẩn trang đổi hướng",
        "whatlinkshere-hidetrans": "$1 trang nhúng",
-       "whatlinkshere-hidelinks": "$1 liên kết",
-       "whatlinkshere-hideimages": "$1 liên kết tập tin",
+       "whatlinkshere-hidelinks": "Ẩn liên kết",
+       "whatlinkshere-hideimages": "Ẩn liên kết tập tin",
        "whatlinkshere-filters": "Bộ lọc",
        "whatlinkshere-submit": "Xem",
        "autoblockid": "Cấm tự động #$1",
        "ipb-unblock": "Bỏ cấm thành viên hay địa chỉ IP",
        "ipb-blocklist": "Xem danh sách đang bị cấm",
        "ipb-blocklist-contribs": "Đóng góp của $1",
+       "ipb-blocklist-duration-left": "còn $1 nữa",
        "unblockip": "Bỏ cấm thành viên",
        "unblockiptext": "Sử dụng mẫu sau để phục hồi lại quyền sửa đổi đối với một địa chỉ IP hoặc tên thành viên đã bị cấm trước đó.",
        "ipusubmit": "Bỏ cấm",
        "tooltip-ca-nstab-category": "Xem trang thể loại",
        "tooltip-minoredit": "Đánh dấu đây là sửa đổi nhỏ",
        "tooltip-save": "Lưu lại những thay đổi của bạn",
+       "tooltip-publish": "Xuất bản các thay đổi của bạn",
        "tooltip-preview": "Xem trước những thay đổi, hãy dùng nó trước khi lưu!",
        "tooltip-diff": "Xem thay đổi bạn đã thực hiện.",
        "tooltip-compareselectedversions": "Xem khác biệt giữa hai phiên bản đã chọn của trang này.",
        "confirmemail_body_set": "Ai đó, có thể là bạn, từ địa chỉ IP $1, đã đặt địa chỉ này là địa\nchỉ thư điện tử của tài khoản “$2” tại {{SITENAME}}.\n\nĐể xác nhận rằng tài khoản này thực sự là của bạn và để kích hoạt các tính năng\nthư điện tử tại {{SITENAME}}, xin mở liên kết này trong trình duyệt:\n\n$3\n\nNếu tài khoản *không* phải là của bạn, hãy nhấn vào liên kết này để hủy thủ tục\nxác nhận địa chỉ thư điện tử:\n\n$5\n\nMã xác nhận này sẽ hết hạn vào $4.",
        "confirmemail_invalidated": "Đã hủy xác nhận địa chỉ thư điện tử",
        "invalidateemail": "Hủy xác nhận thư điện tử",
+       "notificationemail_subject_changed": "Địa chỉ thư điện tử đăng ký tại {{SITENAME}} đã được thay đổi",
+       "notificationemail_subject_removed": "Địa chỉ thư điện tử đăng ký tại {{SITENAME}} đã được loại bỏ",
+       "notificationemail_body_changed": "Ai đó, có thể là bạn, từ địa chỉ IP $1,\nđã thay đổi địa chỉ thư điện tử của tài khoản “$2” thành “$3” tại {{SITENAME}}.\n\nNếu bạn không phải là người thay đổi địa chỉ này, xin hãy liên lạc với một bảo quản viên của trang Web ngay lập tức.",
+       "notificationemail_body_removed": "Ai đó, có thể là bạn, từ địa chỉ IP $1,\nđã loại bỏ địa chỉ thư điện tử của tài khoản “$2” tại {{SITENAME}}.\n\nNếu bạn không phải là người loại bỏ địa chỉ này, xin hãy liên lạc với một bảo quản viên của trang Web ngay lập tức.",
        "scarytranscludedisabled": "[Nhúng giữa các wiki bị tắt]",
        "scarytranscludefailed": "[Truy xuất bản mẫu $1 bị thất bại]",
        "scarytranscludefailed-httpstatus": "[Truy xuất bản mẫu $1 bị thất bại: HTTP $2]",
        "watchlistedit-raw-done": "Danh sách các trang bạn theo dõi đã được cập nhật.",
        "watchlistedit-raw-added": "$1 tựa đề đã được thêm vào:",
        "watchlistedit-raw-removed": "$1 tựa đề đã được xóa khỏi danh sách:",
-       "watchlistedit-clear-title": "Đã xóa sạch danh sách theo dõi",
+       "watchlistedit-clear-title": "Xóa sạch danh sách theo dõi",
        "watchlistedit-clear-legend": "Xóa sạch danh sách theo dõi",
        "watchlistedit-clear-explain": "Tất cả các tiêu đề sẽ được xóa khỏi danh sách theo dõi của bạn.",
        "watchlistedit-clear-titles": "Các tiêu đề:",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2}}đã khóa $3 $4 [theo tầng]",
        "logentry-protect-modify": "$1 {{GENDER:$2}}đã đổi mức khóa $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2}}đã đổi mức khóa $3 $4 [theo tầng]",
-       "logentry-rights-rights": "$1 {{GENDER:$2}}đã đổi các nhóm bao gồm $3 từ $4 đến $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2}}đã đổi các nhóm bao gồm {{GENDER:$6}}$3 từ $4 đến $5",
        "logentry-rights-rights-legacy": "{{GENDER:$2}}$1 đã đổi các nhóm bao gồm $3",
        "logentry-rights-autopromote": "$1 {{GENDER:$2}}đã được tự động phong cấp từ $4 đến $5",
        "logentry-upload-upload": "$1 {{GENDER:$2}}đã tải lên $3",
        "feedback-useragent": "Tác nhân người dùng:",
        "searchsuggest-search": "Tìm kiếm",
        "searchsuggest-containing": "có chứa…",
+       "api-error-autoblocked": "Địa chỉ IP của bạn bị cấm tự động vì nó đã được sử dụng bởi một người dùng bị cấm.",
        "api-error-badaccess-groups": "Bạn không được phép tải tập tin lên wiki này.",
        "api-error-badtoken": "Lỗi nội bộ: Dấu hiệu bị hỏng.",
+       "api-error-blocked": "Bạn đã bị cấm không được sửa đổi.",
        "api-error-copyuploaddisabled": "Chức năng tải lên từ URL đã bị tắt trên máy chủ này.",
        "api-error-duplicate": "Wiki này đã có {{PLURAL:$1|tập tin|$1 tập tin}} cùng nội dung có tên khác.",
        "api-error-duplicate-archive": "{{PLURAL:$1|Một|Các}} tập tin khác cùng nội dung đã tồn tại trên website, nhưng {{PLURAL:$1|nó|chúng}} đã bị xóa.",
        "api-error-nomodule": "Lỗi nội bộ: Mô đun tải lên không được định rõ.",
        "api-error-ok-but-empty": "Lỗi nội bộ: Máy chủ không phản hồi.",
        "api-error-overwrite": "Không được ghi đè một tập tin đã tồn tại.",
+       "api-error-ratelimited": "Bạn cố tải lên nhiều tập tin trong một thời gian ngắn vượt quá hạn chế của wiki này.",
        "api-error-stashfailed": "Lỗi nội bộ: Máy chủ bị thất bại trong việc lưu giữ tập tin tạm.",
        "api-error-publishfailed": "Lỗi nội bộ: Máy chủ bị thất bại trong việc xuất bản tập tin tạm.",
        "api-error-stasherror": "Đã xuất hiện lỗi khi tải tập tin lên hàng đợi.",
        "api-error-unknownerror": "Lỗi không rõ: “$1”.",
        "api-error-uploaddisabled": "Chức năng tải lên đã bị tắt trên wiki này.",
        "api-error-verification-error": "Tập tin này có thể bị hỏng hoặc có phần mở rộng sai.",
+       "api-error-was-deleted": "Một tập tin cùng tên này đã được tải lên và bị xóa về sau.",
        "duration-seconds": "$1 giây",
        "duration-minutes": "$1 phút",
        "duration-hours": "$1 giờ",
        "special-characters-group-ipa": "Phiên âm quốc tế",
        "special-characters-group-symbols": "Ký hiệu",
        "special-characters-group-greek": "Hy Lạp",
+       "special-characters-group-greekextended": "Hy Lạp mở rộng",
        "special-characters-group-cyrillic": "Kirin",
        "special-characters-group-arabic": "Ả Rập",
        "special-characters-group-arabicextended": "Ả Rập mở rộng",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "phiên dựa trên cookie",
        "sessionprovider-nocookies": "Cookie có thể bị vô hiệu hóa. Đảm bảo bạn đã bật cookie và bắt đầu một lần nữa.",
        "randomrootpage": "Trang gốc ngẫu nhiên",
+       "log-action-filter-block": "Kiểu cấm:",
+       "log-action-filter-contentmodel": "Kiểu thay đổi kiểu nội dung:",
+       "log-action-filter-delete": "Kiểu xóa:",
+       "log-action-filter-import": "Kiểu nhập:",
+       "log-action-filter-managetags": "Kiểu tác vụ quản lý thẻ:",
+       "log-action-filter-move": "Kiểu di chuyển:",
+       "log-action-filter-newusers": "Kiểu tạo tài khoản:",
+       "log-action-filter-patrol": "Kiểu tuần tra:",
        "log-action-filter-protect": "Loại bảo vệ:",
+       "log-action-filter-rights": "Kiểu thay đổi quyền:",
+       "log-action-filter-suppress": "Kiểu ẩn giấu",
        "log-action-filter-upload": "Loại tải lên:",
        "log-action-filter-all": "Tất cả",
        "log-action-filter-block-block": "Khối",
+       "log-action-filter-block-reblock": "Thay đổi tác vụ cấm",
+       "log-action-filter-block-unblock": "Bỏ cấm",
+       "log-action-filter-contentmodel-change": "Thay đổi kiểu nội dung",
+       "log-action-filter-contentmodel-new": "Tạo trang có kiểu nội dung không chuẩn",
+       "log-action-filter-delete-delete": "Xóa trang",
+       "log-action-filter-delete-restore": "Phục hồi trang",
+       "log-action-filter-delete-event": "Xóa nhật trình",
+       "log-action-filter-delete-revision": "Xóa phiên bản",
+       "log-action-filter-import-interwiki": "Nhập liên wiki",
        "log-action-filter-import-upload": "Nhập bằng cách tải lên XML",
+       "log-action-filter-managetags-create": "Tạo thẻ",
+       "log-action-filter-managetags-delete": "Xóa thẻ",
+       "log-action-filter-managetags-activate": "Kích hoạt thẻ",
+       "log-action-filter-managetags-deactivate": "Vô hiệu thẻ",
+       "log-action-filter-move-move": "Di chuyển mà không ghi đè trang đổi hướng",
+       "log-action-filter-move-move_redir": "Di chuyển mà ghi đè trang đổi hướng",
        "log-action-filter-newusers-create": "Tạo bởi người dùng vô danh",
        "log-action-filter-newusers-create2": "Tạo bởi người dùng đã đăng ký",
+       "log-action-filter-newusers-autocreate": "Tạo tự động",
        "log-action-filter-newusers-byemail": "Tạo với mật khẩu được gửi qua thư điện tử",
+       "log-action-filter-patrol-patrol": "Tuần tra thủ công",
+       "log-action-filter-patrol-autopatrol": "Tuần tra tự động",
        "log-action-filter-protect-protect": "Bảo vệ",
+       "log-action-filter-protect-modify": "Thay đổi mức khóa",
+       "log-action-filter-protect-unprotect": "Mở khóa",
+       "log-action-filter-protect-move_prot": "Di chuyển khóa",
+       "log-action-filter-rights-rights": "Thay đổi thủ công",
        "log-action-filter-rights-autopromote": "Tự động thay đổi",
+       "log-action-filter-suppress-event": "Ẩn giấu nhật trình",
+       "log-action-filter-suppress-revision": "Ẩn giấy phiên bản",
+       "log-action-filter-suppress-delete": "Ẩn giấu trang",
+       "log-action-filter-suppress-block": "Ẩn giấu người dùng bằng cách cấm",
+       "log-action-filter-suppress-reblock": "Ẩn giấu người dùng bằng cách cấm lại",
        "log-action-filter-upload-upload": "Tải lên mới",
        "log-action-filter-upload-overwrite": "Tải lên lại"
 }
index 998e2c5..c2aef82 100644 (file)
        "noname": "No egivol gebananemi lonöföl.",
        "loginsuccesstitle": "Enunädol oli benosekiko",
        "loginsuccess": "'''Binol anu in {{SITENAME}} as \"$1\".'''",
-       "nosuchuser": "No dabinon geban labü nem: \"$1\".\nGebananems distidons mayudis i minudis.\nKoräkolös tonatami nema at, u [[Special:UserLogin/signup|jafolös kali nulik]].",
+       "nosuchuser": "No dabinon geban labü nem: \"$1\".\nGebananems distidons mayudis i minudis.\nKoräkolös tonatami nema at, u [[Special:CreateAccount|jafolös kali nulik]].",
        "nosuchusershort": "No dabinon geban labü nem: \"$1\". Koräkolös tonatami nema at.",
        "nouserspecified": "Mutol välön gebananemi.",
        "wrongpassword": "Letavöd neveräton. Steifülolös dönu.",
index 3e1403c..b98a327 100644 (file)
        "noname": "Võlssi kirotõt pruukjanimi.",
        "loginsuccesstitle": "Sisseminek läts' kõrda",
        "loginsuccess": "Olõt nimega sisse lännüq. Suq pruukjanimi om \"$1\".",
-       "nosuchuser": "\"$1\" nimelist pruukjat olõ-i olõman.\nKaeq kiräpilt üle vai [[Special:UserLogin/signup|luuq vahtsõnõ pruukjanimi]].",
+       "nosuchuser": "\"$1\" nimelist pruukjat olõ-i olõman.\nKaeq kiräpilt üle vai [[Special:CreateAccount|luuq vahtsõnõ pruukjanimi]].",
        "nosuchusershort": "\"$1\" nimelist pruukjat olõ-i olõman. Kas kirotit iks nime õigõhe?",
        "nouserspecified": "Olõ-i kirotõt pruukjanimme.",
        "login-userblocked": "Seo pruukja om kinniq peet. Nimega sisseminemine olõ-õi lubat.",
        "accmailtext": "Pruukjalõ '$1' luud johuslinõ salasõna saadõti aadresi pääle $2.\n\nTuud salasõnna saa muutaq ''[[Special:ChangePassword|salasõba muutmisõ lehe pääl]]'' päält vahtsõ nimega sisseminemist.",
        "newarticle": "(Vahtsõnõ)",
        "newarticletext": "Taad lehekülge olõ-i viil luud.\nLeheküle luumisõs nakkaq kirotama alanolõvahe kasti (kaeq [$1 oppust]).\nKu sa johtuq siiäq kogõmaldaq, sis klõpsaq võrgokaeja '''Tagasi'''-nuppi.",
-       "anontalkpagetext": "---- ''Taa om arotusleht nimeldä pruukja kotsilõ, kiä olõ-i loonuq pruukjanimme vai pruugi-i tuud. Tuuperäst tulõ meil pruukja kimmästegemises pruukiq timä puutri võrgoaadrõssit. Taa aadrõs või ollaq mitmõ pruukja pääle ütine. Ku olõt nimeldä pruukja ja lövvät, et taa leheküle pääle kirotõt jutt käü suq kotsilõ, sis olõq hää, [[Special:UserLogin/signup|luuq konto]] vai [[Special:UserLogin|mineq nimega sisse]], et edespiten segähüisi ärq hoitaq.''",
+       "anontalkpagetext": "---- ''Taa om arotusleht nimeldä pruukja kotsilõ, kiä olõ-i loonuq pruukjanimme vai pruugi-i tuud. Tuuperäst tulõ meil pruukja kimmästegemises pruukiq timä puutri võrgoaadrõssit. Taa aadrõs või ollaq mitmõ pruukja pääle ütine. Ku olõt nimeldä pruukja ja lövvät, et taa leheküle pääle kirotõt jutt käü suq kotsilõ, sis olõq hää, [[Special:CreateAccount|luuq konto]] vai [[Special:UserLogin|mineq nimega sisse]], et edespiten segähüisi ärq hoitaq.''",
        "noarticletext": "Seo leht om parlaq tühi.\nVõit [[Special:Search/{{PAGENAME}}|otsiq soe lehe nimme]]  tõisi lehti päält vai\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} uuriq muutmisnimekirjo] vai [{{fullurl:{{FULLPAGENAME}}|action=edit}} puuduolõva leheküle esiq luvvaq]</span>.",
        "noarticletext-nopermission": "Seo lehe pääl olõ-õi parlaq teksti.\nVõit [[Special:Search/{{PAGENAME}}|otsiq soe lehe nimme]] tõisi lehti päält vai\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} uuriq muutmisnimekirjo]</span>, a sul olõ-õi õigust seod lehte alostaq.",
        "userpage-userdoesnotexist": "Pruukjanimme \"<nowiki>$1</nowiki>\" olõ-i kirjä pant. Kaeq perrä, kas olõt iks kimmäs, et tahat taad lehte toimõndaq.",
index 898a3df..eb0577b 100644 (file)
        "noname": "Vos n' avoz nén dné di no d' uzeu valide.",
        "loginsuccesstitle": "Vos estoz elodjî",
        "loginsuccess": "'''L' elodjaedje a stî comifåt, asteure vos estoz elodjî dins {{SITENAME}} dizo l' no d' uzeu «$1».'''",
-       "nosuchuser": "I g na nou uzeu dizo l' no «$1».\nNotez k' les nos d' uzeu fjhèt l' diferince etur pitites et grandès letes.\nVerifyîz çou k' vos avoz tapé, oudonbén [[Special:UserLogin/signup|ahivez on novea conte]].",
+       "nosuchuser": "I g na nou uzeu dizo l' no «$1».\nNotez k' les nos d' uzeu fjhèt l' diferince etur pitites et grandès letes.\nVerifyîz çou k' vos avoz tapé, oudonbén [[Special:CreateAccount|ahivez on novea conte]].",
        "nosuchusershort": "I g na nou uzeu dizo l' no «$1». Verifyîz çou k' vos avoz tapé.",
        "nouserspecified": "Vos dvoz dner on no d' elodjaedje.",
        "login-userblocked": "{{GENDER:$1|Cist uzeu est bloké|Ciste uzeuse est blokêye}}. L' elodjaedje n' est nén possibe.",
        "accmailtext": "On scret costrût a l' astcheyance po [[User talk:$1|$1]] a stî evoyî a $2.\n\nLi scret po ci novea conte ci pout esse candjî sol pådje di ''[[Special:ChangePassword|candjmint di scret]]'' après l' elodjaedje.",
        "newarticle": "(Novea)",
        "newarticletext": "Vos avoz clitchî so on loyén viè ene pådje ki n' egzistêye nén co.\nMins '''vos''' l' poloz askepyî! Po çoula, vos n' avoz k' a cmincî a taper vosse tecse dins l' boesse di tecse cial pa dzo (alez vey li [$1 pådje d' aidance] po pus d' infôrmåcion).\nSi vos estoz droci par accidint, clitchîz simplumint sol boton <strong>En erî</strong> di vosse betchteu waibe po rivni al pådje di dvant.",
-       "anontalkpagetext": "---- ''Çouchal, c' est li pådje di copene po èn uzeu anonime ki n' a nén (co) fwait on conte por lu s' elodjî, ou ki n' l' eploye nén.\nÇa fwait k' on doet eployî si adresse IP limerike po l' idintifyî.\nCome ene sifwaite adresse IP pout esse eployeye pa pus d' èn uzeu, i s' pout ki vos veyoz chal des rmarkes et des messaedjes ki n' sont nén por vos.\nLoukîz s' i vs plait po [[Special:UserLogin/signup|fé on novea conte]] ou [[Special:UserLogin|s' elodjî]] po n' pus aveur d' ecramiaedje avou des ôtes uzeus anonimes.''",
+       "anontalkpagetext": "---- ''Çouchal, c' est li pådje di copene po èn uzeu anonime ki n' a nén (co) fwait on conte por lu s' elodjî, ou ki n' l' eploye nén.\nÇa fwait k' on doet eployî si adresse IP limerike po l' idintifyî.\nCome ene sifwaite adresse IP pout esse eployeye pa pus d' èn uzeu, i s' pout ki vos veyoz chal des rmarkes et des messaedjes ki n' sont nén por vos.\nLoukîz s' i vs plait po [[Special:CreateAccount|fé on novea conte]] ou [[Special:UserLogin|s' elodjî]] po n' pus aveur d' ecramiaedje avou des ôtes uzeus anonimes.''",
        "noarticletext": "I gn a pol moumint nou tecse e cisse pådje chal.\nVos ploz [[Special:Search/{{PAGENAME}}|cweri après l' tite di cisse pådje ci]] dins des ôtès pådjes,\noudonbén <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cweri dins les djournås],\nou co [{{fullurl:{{FULLPAGENAME}}|action=edit}} ahiver l' pådje]</span>.",
        "noarticletext-nopermission": "I gn a pol moumint nou tecse e cisse pådje chal.\nVos ploz [[Special:Search/{{PAGENAME}}|cweri après l' tite di cisse pådje ci]] dins des ôtès pådjes,\noudonbén <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cweri dins les djournås]</span>, mins vos n' avoz nén l' livea d' permission pol poleur ahiver.",
        "blocked-notice-logextract": "{{GENDER:$1|Cist uzeu est bloké|Ciste uzeuse est blokêye}} pol moumint.\nLi dierinne intrêye e djournå des blocaedjes est dnêye chal pa dzo po infôrmåcion:",
        "categories-submit": "Mostrer",
        "categoriespagetext": "{{PLURAL:$1|Li categoreye shuvante est eployeye|Les categoreyes shuvantes sont-st eployeyes}} pa des pådjes ou des fitchîs.\n[[Special:UnusedCategories|Les categoreyes nén eployeyes]] èn sont nén håynêyes chal.\nLoukîz eto [[Special:WantedCategories|les categoreyes dimandêyes]].",
        "categoriesfrom": "Håyner les categoreyes a pårti di:",
-       "special-categories-sort-count": "relére pa nombe di cayets",
-       "special-categories-sort-abc": "relére alfabeticmint",
        "deletedcontributions": "Contribouwaedjes disfacés",
        "deletedcontributions-title": "Contribouwaedjes disfacés",
        "sp-deletedcontributions-contribs": "contribouwaedjes",
index b4e8ba2..f563ad9 100644 (file)
@@ -53,7 +53,7 @@
        "tog-ccmeonemails": "Padad-i ak hin mga kopya hin mga email nga akon ginpapadara ha iba nga mga gumaramit",
        "tog-diffonly": "Ayaw igpakita an sulod han pakli ha ilarom han pagkakaiba",
        "tog-showhiddencats": "Igpakita an mga tinago nga mga kaarangay",
-       "tog-norollbackdiff": "Iglat-ang an kaiban kahuman himoa an libot-pabalik",
+       "tog-norollbackdiff": "Ayaw igpakita an kaiban kahuman himoa an rollback",
        "tog-useeditwarning": "Pasabti ako kun nabaya ako hin ginliwat ng pakli nga waray katipig an mga pagbag-o",
        "tog-prefershttps": "Pirmihi paggamit hin segurado nga koneksyon kun nakalog-in",
        "underline-always": "Pirme",
        "laggedslavemode": "Pahimatngon: It pakli bangin waray mga kabag-ohan nga bag-o.",
        "readonly": "Gintrankahan an database",
        "enterlockreason": "Pagbutang hin rason para han pagtrangka, upod hin banabana kon san-o kukuha-on an pagtrangka",
-       "readonlytext": "An database in nakatrangka yana ha bag-o nga mga entrada ngan iba nga mga modipikasyon, tungod siguro ha routine database maintenance, kahuman ini in mabalik ha normal.\n\nAn magdudumara nga nagtrangka hini in naghatag hini nga kasayoran: $1",
+       "readonlytext": "An database in nakatrangka yana ha bag-o nga mga entrada ngan iba nga mga modipikasyon, tungod siguro ha routine database maintenance, kahuman ini in mabalik ha normal.\n\nAn system administrator nga nagtrangka hini in naghatag hini nga kasayoran: $1",
        "missing-article": "Ini nga database in waray nakaagi han teksto han pakli nga dapat mabilngan, nga ginngaranan nga \"$1\" $2.\n\nIni in agsob hinungdan han pagsunod han kadaan nga kaibhan o sumpay han kaagi ngadto ha pakli nga ginpara.\n\nKun diri ini an kaso, bangin ka nakabiling hin bug ha software.\nAlayon la igsumat ini ha [[Special:ListUsers/sysop|administrator]], igsurat la an URL.",
        "missingarticle-rev": "(pagbag-o#: $1)",
        "missingarticle-diff": "(Kaibhan: $1, $2)",
        "protectedinterface": "Ini nga pakli in nahatag hin teksto hit interface para han software han hin nga wiki, ngan in pinasasaliporan para makalikay hit pag-abuso.\nPara makadugang o makaliwat hin mga paghubad para han tanan nga mga wiki, alayon paggamit han [//translatewiki.net/ translatewiki.net], an kanan MediaWiki proyekto hin lokalisasyon.",
        "editinginterface": "'''Pahimatngon:''' Imo ginliliwat an pakli nga gingagamit paghatag hin interface text para han software.\nAn mga pagbag-o hinin nga pakli in makakaapekto han itsura han user interface han iba nga mga gumaramit hini nga wiki.",
        "translateinterface": "Para han pagdugang o pagbag-o han mga paghubad han ngatanan nga mga wiki, alayon paggamit han [//translatewiki.net/ translatewiki.net], an MediaWiki lokalisasyon nga proyekto.",
-       "cascadeprotected": "Ini nga pakli in pinapasaliporan hin pagliwat tungod ini in nalalakip ha masunod nga {{PLURAL:$1|pakli, kun diin |mga pakli, kun diin}} pinapasaliporan hit \"cascading\" nga pagpili nga pinaandar:\n$2",
+       "cascadeprotected": "Ini nga pakli in pinapasaliporan tikang ha pagliwat tungod ini in naka-transclude ha masunod nga {{PLURAL:$1|pakli, kun diin |mga pakli, kun diin}} pinapasaliporan hit \"cascading\" nga pagpili nga pinaandar:\n$2",
        "namespaceprotected": "Diri ka gintutugutan pagliwat han mga pakli ha ngaran-lat'ang nga '''$1'''.",
        "customcssprotected": "Diri ka gintutugotan pagliwat hini nga CSS nga pakli, tungod nga nagsusulod ini hin kanan iba nga tawo personal nga karuyagon.",
        "customjsprotected": "Diri ka gintutugotan pagliwat hini nga JavaScript nga pakli, tungod nga nagsusulod ini hin kanan iba nga tawo personal nga karuyagon.",
        "mypreferencesprotected": "Waray nim pagtugot hit pagliwat hit imo mga karuyag.",
        "ns-specialprotected": "Diri maliliwat an mga ispisyal nga pakli.",
        "titleprotected": "Ini nga titulo pinasalipod ha paghimo ni [[User:$1|$1]].\nAn katadungan nga ginhatag amo in <em>$2</em>.",
-       "filereadonlyerror": "Diri maliliwat ini nga paypay \"$1\" tungod an ginsusudlan han paypay nga \"$2\" in aada la ha pagbasa-la nga kahimtang.\n\nAn magdudurmara nga nagtrangka hini in naghatag hini nga eksplenasyon: \"$3\".",
+       "filereadonlyerror": "Diri maliliwat ini nga paypay \"$1\" tungod an ginsusudlan han paypay nga \"$2\" in aada la ha pagbasa-la nga kahimtang.\n\nAn system administrator nga nagtrangka hini in naghatag hini nga eksplenasyon: \"$3\".",
        "invalidtitle-knownnamespace": "Titulo nga inbalido nga may pan-ngaran \"$2 ngan teksto nga \"$3\"",
        "invalidtitle-unknownnamespace": "Diri ginkakarawat nga titulo tungod mayda ini hin mga diri nakikilala nga ngaran-lat'ang ihap $1 ngan teksto \"$2\"",
        "exception-nologin": "Diri nakalog-in",
-       "exception-nologin-text": "Alayon [[Special:Userlogin|pagsakob]] basi makakadto hiní nga pakli o buruhatón.",
+       "exception-nologin-text": "Alayon paglog-in basi makakadto hiní nga pakli o buruhatón.",
        "exception-nologin-text-manual": "Alayon $1 basi makakadto hini nga pakli o buruhatón.",
        "virus-badscanner": "Maraot nga configuration: Waray kasabti nga virus scanner: ''$1''",
        "virus-scanfailed": "Pakyas an pag-scan (kodigo $1)",
        "noname": "Waray ka nakahatag hin maupay nga agnay-hit-gumaramit.",
        "loginsuccesstitle": "Nakalog-in",
        "loginsuccess": "'''Ikaw in nakalog-in ha {{SITENAME}} komo \"$1\".'''",
-       "nosuchuser": "Waray gumaramit an may-ada ngaran nga \"$1\".\nIt mga agnay-hi-gumaramit in case sensitive.\nPanginano-a it imo pagbaybay, o [[Special:UserLogin/signup|paghimo hin bag-o nga akawnt]].",
+       "nosuchuser": "Waray gumaramit an may-ada ngaran nga \"$1\".\nIt mga agnay-hi-gumaramit in case sensitive.\nPanginano-a it imo pagbaybay, o [[Special:CreateAccount|paghimo hin bag-o nga akawnt]].",
        "nosuchusershort": "Waray nagamit it may ngaran nga \"$1\".\nKitaa kun amo it im pagbaybay.",
        "nouserspecified": "Dapat nim magbutang hin agnay hit gumaramit.",
        "login-userblocked": "Ini nga gumaramit in ginpugngan.  Diri gintutugutan an pagsakob.",
        "noemail": "Waray e-mail nga adres nga ginrekord para han nágámit \"$1\".",
        "noemailcreate": "Kinahanglan nim maghatag hin may hinungdan nga e-mail address",
        "passwordsent": "Uska bag-o nga password in ginpadangat ha e-mail address nga nakarehistro kan \"$1\".\nAlayon paglog-in utro kahuman mo makarawat ini.",
-       "blocked-mailpassword": "An imo IP address in ginpugong ha pag-edit, ngan tungod hini in diri gintutugotan paggamit han password recovery function para malikyan an abuso.",
+       "blocked-mailpassword": "An imo IP address in ginpugngan pag-edit. Para pugngan an pag-abuso, ini in diri tinutogotan paggamit hin password recovery function tikang hinin nga IP address.",
        "eauthentsent": "Mayda e-mail hin pagkumpirma nga ginpadará hini nga ginhatag nga e-mail adres.\n\nSan-o magatagán pa hin ibá nga e-mail it akwant, kinahanglan nimo sundon an mga tugon nga nahabutáng han email basi makumpirma nga imo gud itón akawnt.",
        "throttled-mailpassword": "Usa nga tigaman-pagnakob reset email in ginpadangat na, ha sakob han urhi nga  {{PLURAL:$1|oras|$1 ka mga oras}}.\nBasi diri ini maabuso, uusa la nga tigaman-panakob in igpapadangat kada {{PLURAL:$1|oras|$1 ka mga oras}}.",
        "mailerror": "Sayop han pagpadangat hin surat: $1",
        "createaccount-title": "Paghimo hin akawnt para han {{SITENAME}}",
        "createaccount-text": "Mayda tawo nga naghimo hin akawnt nga gingamit an imo email address ha {{SITENAME}} ($4) nga ginngaranan nga \"$2\", nga may-ada tigaman-panakob nga \"$3\".\n\nKinahanglan ka maglog-in ngan igbal-iw an imo tigaman-panakob yana dayon.\n\nPuydi nimo pabay-on ini nga mensahe, kun ini nga paghimo hin akwant in nagsayop la.",
        "login-throttled": "Damo na an imo login attempts ha pagkayana.\nAlayon paghulat hin $1 san-o ka umutro.",
-       "login-abort-generic": "An imo paglog-in in diri malinamposon - Naundang",
+       "login-abort-generic": "An imo paglog-in in pakyas - Gin-undang",
        "login-migrated-generic": "An imo account in nabalhin, ngan an imo agnay-gumaramit in waray na hinin nga wiki.",
        "loginlanguagelabel": "Pinulongan: $1",
        "suspicious-userlogout": "Waray ka tugoti pag-logout tungod nga baga ini ginpadangat hin usa nga broken browser o caching proxy.",
        "newpassword": "Bag-o nga tigaman-pagsulod:",
        "retypenew": "Utroha pagbutang an bag-o nga tigaman-pagsulod:",
        "resetpass_submit": "Igbutang an password ngan log in",
-       "changepassword-success": "Malinamposon an pagbal-iw hit imo tigaman-panakob!",
+       "changepassword-success": "Malinamposon an pagliwat hit imo password!",
        "changepassword-throttled": "Damo na nga mga paningkamot hin pagsakob an imo ginhimò.\nAlayon paghulat hin $1 san-o ka umutro.",
        "botpasswords": "Mga bot password",
        "botpasswords-disabled": "Ginparong an mga bot password.",
        "botpasswords-insert-failed": "Pakyas han pagdugang han ngaran han bot nga \"$1\". Naidugang na ini?",
        "botpasswords-update-failed": "Pakyas han pag-update han bot nga ngaran nga \"$1\". Ginpara na ini?",
        "botpasswords-created-title": "Nahimo an bot password",
-       "botpasswords-created-body": "An bot password nga \"$1\" in malinamposon nga nahimo.",
+       "botpasswords-created-body": "An bot password para han bot nga ngaran nga \"$1\" ni gumaramit \"$2\" in  nahimo.",
        "botpasswords-updated-title": "Gin-update an bot password",
-       "botpasswords-updated-body": "An bot password nga \"$1\" in malinamposon nga na-update.",
+       "botpasswords-updated-body": "An bot password para han ngaran han bot nga \"$1\" ni gumaramit \"$2\" in na-update.",
        "botpasswords-deleted-title": "Ginpara an bot password",
-       "botpasswords-deleted-body": "An bot password nga \"$1\" in ginpara.",
+       "botpasswords-deleted-body": "An bot password para han bot nga ngaran nga \"$1\" ni gumaramit \"$2\" in ginpara.",
        "botpasswords-newpassword": "An bag-o nga password para han pag log-in han <strong>$1</strong> in <strong>$2</strong>. <em>Alayon igrecord ini para han future reference.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider in waray dinhi.",
        "botpasswords-restriction-failed": "An mga restriction han bot password in nagpupugong han pag-login hinin.",
        "resetpass-no-info": "Kinahanglan mo paglog-in para direkta ka makasakob dinhi nga pakli.",
        "resetpass-submit-loggedin": "Igbal-iw an tigaman-pagsulod",
        "resetpass-submit-cancel": "Pasagdi",
-       "resetpass-wrong-oldpass": "Diri balido an temporaryo o yana nga tigaman-panakob.\nImo malinamposon nga ginsalyuan an imo tigaman-panakob o umaro ka na hin bag-o nga temporaryo nga tigman-panakob.",
+       "resetpass-wrong-oldpass": "Diri balido an temporaryo o yana nga password.\nImo na ginsalyuan an imo password o umaro ka na hin bag-o nga temporaryo nga password.",
        "resetpass-recycled": "Alayon pagreset han imo tigaman-pansakob hin lain tikang han imo yanâ nga tigaman-pansakob",
        "resetpass-temp-emailed": "Nagsakob ka pinaagi hin temporary nga gin-email nga kodigo.\nBasi matapos an imo pagsakob, kinahanglan ka maghimo hin bag-o nga tigaman-pansakob dinhi:",
        "resetpass-temp-password": "Temporaryo nga tigaman-pagsakob:",
        "passwordreset-emailtitle": "Mga detalye han akawnt ha {{SITENAME}}",
        "passwordreset-emailtext-ip": "Mayda gumaramit (bangin hi ikaw, tikang han IP adres nga $1) nga naghangyo hin reset han imo tigaman-pansulod han {{SITENAME}} ($4). An nasunod nga gumaramit {{PLURAL:$3|nga akawnt|nga mga akawnt}} nahanungod hini nga email nga adres: \n\n$2\n\n{{PLURAL:$3|Iní nga temporaryo nga tigaman-pansulod|Iní nga mga temporaryo nga tigaman-pansulod}} ma-waray bali hin {{PLURAL:$5|usa ka adlaw|$5 nga mga adlaw}}.\nAngay ka sumakob ngan pumílì hin bag-o nga tigaman-pansulod ha yanâ.  Kun mayda lain nga naghatag hini nga hangyo, o kun nahinumdoman mo an imo orihinal nga tigaman-pansulod, ngan nadírì ka na pagbalyo hiní, puyde mo pasagdan ini nga sumat ngan magpadayon hin paggamit han imo daan nga tigaman-pansulod.",
        "passwordreset-emailelement": "Agnay han gumaramit: \n$1\n\nTemporaryo nga tigaman han pagsakob: \n$2",
-       "passwordreset-emailsentemail": "Ginpadangat an password reset email.",
+       "passwordreset-emailsentemail": "Kun inin nga email address in may pagkahisumpay ha imo account, papadangaton ka hin usa nga password reset email.",
        "passwordreset-emailsent-capture": "Ginpadangat an password reset email, nga ginpakita ha ubos.",
        "passwordreset-emailerror-capture": "Ginhimo an password reset email, kun diin nakikita ha ubos, pero pakyas an pagpadara ha  {{GENDER:$2|gumaramit}}: $1",
        "changeemail": "Igliwat o igtanggal an e-mail address",
-       "changeemail-header": "Igliwan an e-mail address akawnt",
+       "changeemail-header": "Kompletoha ini nga form para masalyuan an imo email address. Kun karuyag nimo tanggalun an may pagkahisumpay han bisan ano nga email address tikang ha imo account, blankoha la an bag-o nga email address kun magsusumiti ka han form.",
        "changeemail-passwordrequired": "Kinahanglan nim igbutang an imo password para igkompirma inin nga pagbag-o.",
        "changeemail-no-info": "Kinahanglanon mo mag-log-in para ka direkta makasakob hini nga pakli.",
        "changeemail-oldemail": "Yana nga e-mail address:",
        "minoredit": "Gutiay ini nga pagliwat",
        "watchthis": "Bantayi ini nga pakli",
        "savearticle": "Igtipig an pakli",
+       "publishpage": "Igmantala an pakli",
        "preview": "Pahiuna nga pagawas",
        "showpreview": "Pakit-a an pahiuna nga pagawas",
        "showdiff": "Igpakita an mga ginliwat",
        "anonpreviewwarning": "''Diri ka naka-log in.  Mahisusurat an imo IP address ngada ha kanan pakli kaagi hit pagliwat kun igtipig nimo.''",
        "missingsummary": "<strong>Pahinumdom:</strong> Waray ka humatag hin halipotay nga masisiring hiton pagliwat. Kun pidliton mo an \"{{int:savearticle}}\" utro, an imo ginliwat in matitipig bisan waray hini.",
        "missingcommenttext": "Alayon pagbutang hin komento ha ilarom.",
-       "missingcommentheader": "'''Pahinumdom:''' Waray ka humatag hin subject/headline para hini nga komento.  Kun pinduton mo an \"{{int:savearticle}}\" utro, an imo pagliwat in matitipig bisan waray hini.",
+       "missingcommentheader": "<strong>Pahinumdom:</strong> Waray ka humatag hin subject para hinin nga komento.  Kun pinduton mo an \"{{int:savearticle}}\" utro, an imo pagliwat in matitipig bisan waray hini.",
        "summary-preview": "Pahiuna nga pagawas han dalikyat nga pulong:",
        "subject-preview": "Pahiuna nga pagawas hit himangrawan:",
        "blockedtitle": "Ginpugngan ini nga gumaramit",
        "accmailtext": "Uska hinimo nga random nga tigaman-panakob para kan [[User talk:$1|$1]] in ginpadangat ha $2. Puydi ini mabal-iwan ha ''[[Special:ChangePassword|liwani an tigaman-panakob]]'' nga pakli han paglog-in.",
        "newarticle": "(Bag-o)",
        "newarticletext": "Ginsunod mo an pakli nga waray pa kahihimo.  Para ighimo an pakli, tikanga pagmakinilya ha kahon nga aada ha ubos (kitaa an [$1 nabulig nga pakli] para han kadugangan nga pananabutan).  Kun sayop an imo pagkanhi, igpidlit an imo kanan panngaykay (''browser'') '''balik''' (''back'') nga piridlitan.",
-       "anontalkpagetext": "----\n''Ini in hiruhimangraw-nga-pakli para han waray magpakilala nga gumaramit, nga waray pa hinmimo hin akawnt.''\nMagamit la kami hin IP address para makilal-an hiya.\nSugad hini nga IP address, in puydi sinmaro hiton pipira nga mga gumaramit.\nKun ikaw in waray magpakilala nga gumaramit, ngan pag-abat mo in may mga diri naangay nga komento an ginpapadangat ha imo, alayon nala [[Special:UserLogin/signup|paghimo hin akawnt]] o [[Special:UserLogin|pag-log in]] para malikyan an sumurunod nga mga pagkalipat nga dapat para ha iba nga waray magpakilala nga mga gumaramit.",
+       "anontalkpagetext": "----\n''Ini in hiruhimangraw-nga-pakli para han waray magpakilala nga gumaramit, nga waray pa hinmimo hin akawnt.''\nMagamit la kami hin IP address para makilal-an hiya.\nSugad hini nga IP address, in puydi sinmaro hiton pipira nga mga gumaramit.\nKun ikaw in waray magpakilala nga gumaramit, ngan pag-abat mo in may mga diri naangay nga komento an ginpapadangat ha imo, alayon nala [[Special:CreateAccount|paghimo hin akawnt]] o [[Special:UserLogin|pag-log in]] para malikyan an sumurunod nga mga pagkalipat nga dapat para ha iba nga waray magpakilala nga mga gumaramit.",
        "noarticletext": "Waray yana teksto ha sulod hinin nga pakli.\nPuyde ka [[Special:Search/{{PAGENAME}}|mamiling hin titulo hinin nga pakli]] ha iba pa nga mga pakli,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pamilnga an may mga pagkahisumpay nga mga talaan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} igliwat ini nga pakli]</span>.",
        "noarticletext-nopermission": "Waray yana nahasurat hini nga pakli\nPuyde hi ikaw [[Special:Search/{{PAGENAME}}|magbiling han ngaran hini nga pakli]] ha iba nga mga pakli,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mamiling han mga nanginginlabot nga mga talaan]</span>, kundi diri ka gintutugotan hin paghímò hini nga pakli.",
        "missing-revision": "Waray na an rebisyon #$1 han pakli nga ginngaranan nga  \"{{FULLPAGENAME}}\".\n\nIni in agsob tungod han pagsunod hin daan nga sumpay hin kaagi ha pakli nga ginpara.\nAn mga detalye in mabibilngan ha [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].",
        "rev-delundel": "igpakita/igtago",
        "rev-showdeleted": "igpakita",
        "revisiondelete": "Pagpara/pagtanggal han pagpara nga mga rebisyon",
+       "revdelete-nooldid-title": "Diri ginkakarawat an target revision",
        "revdelete-show-file-confirm": "Sigurado ka nga gusto mo makita an ginpara nga pagliwat han file \"<nowiki>$1</nowiki>\" tikang $2 ha $3?",
        "revdelete-show-file-submit": "Oo",
        "revdelete-hide-text": "Rebisyon nga sinurat",
        "mergehistory-into": "Kakadtoan nga pakli:",
        "mergehistory-submit": "Igtampo an mga rebisyon",
        "mergehistory-empty": "Waray mga rebisyon in puydi matampo.",
+       "mergehistory-fail-bad-timestamp": "Diri puydi an timestamp.",
+       "mergehistory-fail-invalid-source": "Diri puydi an ginkuhaan nga pakli.",
+       "mergehistory-fail-invalid-dest": "Diri puydi an kakadtoan nga pakli.",
+       "mergehistory-fail-self-merge": "Pareho an tinikangan ngan kakadtoan nga mga pakli.",
        "mergehistory-no-source": "Waray pa an tinikangan nga pakli nga $1.",
        "mergehistory-no-destination": "Waray pa an kakadtuan nga pakli nga $1.",
+       "mergehistory-invalid-source": "An tinikangan nga pakli in dapat may-ada valid title.",
+       "mergehistory-invalid-destination": "An kakadtoan nga pakli in kinahanglan may-ada valid title.",
        "mergehistory-autocomment": "Gintampo an [[:$1]] tipakadto ha [[:$2]]",
        "mergehistory-comment": "Gintampo an [[:$1]] ngada ha [[:$2]]: $3",
        "mergehistory-same-destination": "An gintikangan ngan kakadtoan nga mga pakli in diri puydi magkaparo",
        "revertmerge": "Igbulag an gintampo",
        "history-title": "Kaagi han pagbag-o han ''$1''",
        "difference-title": "An pagkakaiba han mga rebisyon han \"$1\"",
+       "difference-title-multipage": "An pagkakaiba ha butnga han mga pakli \"$1\" ngan \"$2\"",
        "difference-multipage": "(Kaibhan ha butnga han mga pakli)",
        "lineno": "Bagis $1:",
        "compareselectedversions": "Igkumpara an mga pinili nga pagbabag-o",
        "prefs-watchlist-token": "Token hin talaan hin barantayon:",
        "prefs-misc": "Dirudilain",
        "prefs-resetpass": "Igliwan an tigaman-pagsulod",
-       "prefs-changeemail": "Igliwan an e-mail address",
+       "prefs-changeemail": "Igliwan o tatanggalon an e-mail address",
        "prefs-setemail": "Igbutang an email address",
        "prefs-email": "Mga pagpipilian han e-mail",
        "prefs-rendering": "Hitsura",
        "yournick": "Bag-o nga pirma:",
        "badsiglength": "Hilaba hin duro it im pirma.\nDapat diri malabaw ha $1 {{PLURAL:$1|agi|mga agi}} nga kahilaba.",
        "yourgender": "Ano an karuyag mo nga pangilal-an?",
-       "gender-unknown": "Karuyag ko diri la magyakan",
+       "gender-unknown": "Kun ikaw in mangangaranan, an software in magamit hin gender neutral words kun mahihimo",
        "gender-male": "Hiya in nag-aayad hin mga wiki nga pakli",
        "gender-female": "Hiya in nag-aayad hin mga wiki nga pakli",
        "email": "E-mail",
-       "prefs-help-realname": "Opsyonal an tinuod nga ngaran.\nKun pilion mo nga ihatag, ini in gagamiton ha paghatag hin atribusyon ha imo mga buhat.",
+       "prefs-help-realname": "Opsyonal an paggamit hin tinuod nga ngaran.\nKun ihatag, ini in puydi magamit paghatag hin pagkilala ha imo mga buhat.",
        "prefs-help-email": "Diri pinipirit it pagbutang hin E-mail address, pero kinahanglan ini para hin pag-utro hin tigaman-hit-pagsulod (''password''), ngan kun mangalimot ka hit imo tigaman-hit-pagsulod.",
        "prefs-help-email-others": "Puydi mo pilion nga it iba in makakontak ha imo gamit an e-mail pinaagi han sumpay ha imo gumaramit o hiruhimangraw nga pakli.\nAn imo e-mail address in diri makikit-an kun an iba nga mga gumaramit in makontak ha imo.",
        "prefs-help-email-required": "Kinahanglanon it e-mail address.",
        "userrights": "Pagdudumara hin mga katungod han gumaramit",
        "userrights-lookup-user": "Pagdumaraa han mga hugpo han gumaramit",
        "userrights-user-editname": "Igbutang an agnay han gumaramit:",
-       "editusergroup": "Igliwat han mga hugpo han gumaramit",
-       "editinguser": "Igliliwat an mga katungod han gumaramit han gumaramit '''[[User:$1|$1]]''' $2",
+       "editusergroup": "Igliwat an mga hugpo han {{GENDER:$1|gumaramit}}",
+       "editinguser": "Ginsasaliwanan an katungod-han-gumaramit ni {{GENDER:$1|gumaramit}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Igliwat an mga hugpo hin gumaramit",
-       "saveusergroups": "Igtipig an mga hugpo han gumaramit",
+       "saveusergroups": "Igtipig an mga hugpo han {{GENDER:$1|gumaramit}}",
        "userrights-groupsmember": "Api han:",
        "userrights-groupsmember-auto": "Api nga daan han:",
        "userrights-reason": "Katadungan:",
        "userrights-notallowed": "Waray nim pagtugot hin pagdugang o pagtanggal hin mga katungod han gumaramit.",
        "userrights-changeable-col": "Mga hugpo nga puydi mo labtan",
        "userrights-unchangeable-col": "Mga hugpo nga diri mo puydi labtan",
-       "userrights-removed-self": "Malinamposon nim gintanggal an imo kalugaringon mga katungod. Tungod hito, diri ka na makaka-access hini nga pakli.",
+       "userrights-removed-self": "Imo gintanggal an imo kalugaringon mga katungod. Tungod hito, diri kana makaka-access hinin nga pakli.",
        "group": "Hugpo:",
        "group-user": "Mga gumaramit",
        "group-autoconfirmed": "Mga gumaramit nga lugaring nakokonpirma",
        "group-suppress": "Mga suppressor",
        "group-all": "(ngatanan)",
        "group-user-member": "{{HENERO:$1|gumaramit}}",
+       "group-autoconfirmed-member": "{{GENDER:$1|autoconfirmed user}}",
        "group-bot-member": "bot",
        "group-sysop-member": "magdudumara",
        "group-bureaucrat-member": "{{GENDER:$1|burokrata}}",
-       "group-suppress-member": "{{GENDER:$1|magmarangno}}",
+       "group-suppress-member": "{{GENDER:$1|suppressor}}",
        "grouppage-user": "{{ns:project}}:Mga gumaramit",
        "grouppage-autoconfirmed": "{{ns:project}}:Mga gumaramit nga naka-awtokompirmado",
        "grouppage-bot": "{{ns:project}}:Mga bot",
        "grouppage-sysop": "{{ns:project}}:Mga magdudumara",
        "grouppage-bureaucrat": "{{ns:project}}:Mga burokrata",
-       "grouppage-suppress": "{{ns:project}}:Nanginginano",
+       "grouppage-suppress": "{{ns:project}}:Suppress",
        "right-read": "Igbasa an mga pakli",
        "right-edit": "Igliwat an mga pakli",
        "right-createpage": "Paghimo hin mga pakli (nga diri an mga hiruhimangraw nga mga pakli)",
        "right-reupload": "Sapawa an mga aada nga mga paypay",
        "right-reupload-own": "Igsapaw an aada yana nga mga paypay nga ginkarga-pasaka nimo mismo",
        "right-upload_by_url": "Igkarga paigbaw an mga paypay tikang ha uska URL",
+       "right-purge": "Igpurge an site cache han pakli bisan waray kompirmasyon",
        "right-autoconfirmed": "Diri malalalbtan hin IP-nga-nahibasi nga mga rate hin paglimit",
        "right-bot": "Igtrato komo uska naglulugaring nga proseso",
        "right-writeapi": "Paggamit han write API",
        "right-import": "Man-aangbit hin mga pakli tikang ha iba nga mga wiki",
        "right-importupload": "Man-aangbit hin mga pakli tikang ha uska paypay nga iginkarga-pasaka",
        "right-patrol": "Igmarka an kanan iba mga pagliwat komo ginpatrolya na",
+       "right-unwatchedpages": "Pakit-a an lista han mga gintanggal an pagbantay nga mga pakli",
        "right-mergehistory": "Igtampo an kaagi han mga pakli",
        "right-userrights": "Igliwat an ngatanan nga mga katungod han gumaramit",
        "right-userrights-interwiki": "Igliwat an mga katungod han gumaramit han mga gumaramit ha iba nga mga wiki",
        "right-siteadmin": "Igtrangka ngan igrangka an database",
        "right-sendemail": "Padad-i hin e-mail ngada ha iba nga mga gumaramit",
+       "right-passwordreset": "Pakit-a an mga password reset email",
+       "right-deletechangetags": "Igpara an [[Special:Tags|tags]] tikang han database",
+       "grant-generic": "mga katungod nga katitirok han \"$1\"",
+       "grant-group-page-interaction": "Pakig-interact han mga pakli",
+       "grant-group-file-interaction": "Pakig-interact hiton media",
+       "grant-group-watchlist-interaction": "Pakig-interact ha imo mga barantayan",
        "grant-group-email": "Padangat hin email",
        "grant-createaccount": "Pahimo hin mga account",
        "grant-createeditmovepage": "Paghimo, pagliwat, ngan pagbalhin hin mga pakli",
        "grant-delete": "Pagpara hin mga pakli, mga rebisyon, ngan mga iginsulod ha log",
+       "grant-editinterface": "Igliwat an MediaWiki namespace ngan kanan gumaramit CSS/JavaScript",
+       "grant-editmycssjs": "Igliwat an imo gumaramit nga CSS/JavaScript",
+       "grant-editmyoptions": "Igliwat an imo gumaramit nga mga karuyagon",
+       "grant-editmywatchlist": "Igliwat an imo barantayon",
+       "grant-editpage": "Igliwat an aada nga mga pakli",
+       "grant-editprotected": "Igliwat an mga pinansaliporan nga mga pakli",
+       "grant-highvolume": "High-volume nga pagliwat",
+       "grant-oversight": "Igtago an mga gumaramit ngan pag-suppress nga mga rebisyon",
+       "grant-patrol": "Igliwat an pagpatrolya ha mga pakli",
+       "grant-protect": "Igpanalipod ngan igtanggal an mga panalipod han mga pakli",
+       "grant-rollback": "Ig-rollback an mga pagliwat ngadto ha mga pakli",
        "grant-sendemail": "Igpadara hin email ngadto ha iba nga mga gumaramit",
        "grant-uploadeditmovefile": "Pagkarga, pagsaliwan, ngan pagbalhin hin mga file",
        "grant-uploadfile": "Pagkarga hin bag-o nga mga file",
        "action-createpage": "pahimo hin mga pakli",
        "action-createtalk": "Paghimo hin hiruhimangraw nga mga pakli",
        "action-createaccount": "Himoa ini nga akawnt hin gumaramit",
+       "action-autocreateaccount": "automatic nga hihimoon ini nga external user account",
+       "action-history": "kitaa an kaagi hinin nga pakli",
        "action-minoredit": "butanga hin tigaman hinin nga pagliwat komo gutiay",
        "action-move": "balhina ini nga pakli",
        "action-move-subpages": "igbalhin ini nga pakli, ngan iya mga bahin-pakli",
        "action-move-rootuserpages": "Igbalhin an gamot nga mga pakli han gumaramit",
+       "action-move-categorypages": "igbalhin an mga kaarangay nga pakli",
        "action-movefile": "igbalhin ini nga paypay",
        "action-upload": "igkarga-pasaka ini nga paypay",
        "action-reupload": "igsapaw ini nga aanhi nga paypay",
+       "action-reupload-shared": "ig-override ini nga file ha pinagsasaroan nga repositoryo",
        "action-upload_by_url": "igkaraga-pasaka ini nga paypay tikang ha uska URL",
+       "action-writeapi": "gamiti an write API",
        "action-delete": "paraa ini nga pakli",
        "action-deleterevision": "igpara ini nga pagbag-o",
        "action-deletedhistory": "kitaa an kanan hini nga pakli kaagi han mga ginpara",
        "action-browsearchive": "Pamiling hin mga ginpara nga mga pakli",
        "action-undelete": "Balika an ginpara hini nga pakli",
+       "action-suppressrevision": "ig-review ngan ig-restore inin nga nakatago nga rebisyon",
        "action-suppressionlog": "kitaa an kanan hini pribado nga talaan",
        "action-block": "Pugnga ini nga gumaramit ha pagliwat",
        "action-protect": "igsaliwan an katupngan han pananalipod para hini nga pakli",
+       "action-rollback": "dagmiti pag-rollback an mga pagliwat an kataposan nga gumaramit nga nagliwat hit usa ka partikular nga pakli",
        "action-import": "ig-angbit hin mga pakli tikang ha iba nga wiki",
        "action-importupload": "ig-angbit hin mga pakli pakli tikang ha uska ginkarga-pasaka nga paypay",
        "action-patrol": "markahi an kanan iba pagliwat komo nakapatrolya",
+       "action-autopatrol": "ig-marka nga ginpatrolyahan na an imo pagliwat",
+       "action-unwatchedpages": "kitaa an mga lista han gintanggal an pagbantay nga mga pakli",
        "action-mergehistory": "Igtampo an kaagi hini nga pakli",
        "action-userrights": "Igliwat an ngatanan nga mga katungod han gumaramit",
+       "action-userrights-interwiki": "igliwat an mga katungod han mga gumaramit ha iba nga mga wiki",
+       "action-siteadmin": "ig-lock o ig-unlock an database",
        "action-sendemail": "Padara hin mga e-mail",
        "action-editmywatchlist": "igliwat an imo watchlist",
        "action-viewmywatchlist": "kitaa an imo watchlist",
        "action-viewmyprivateinfo": "kitaa an imo pribado nga impormasyon",
        "action-editmyprivateinfo": "igliwat an imo pribado nga impormasyon",
        "action-editcontentmodel": "igliwat an content model han uska pakli",
-       "action-managechangetags": "himua ngan igpara na mga tag tikang ha database",
+       "action-managechangetags": "himoa ngan igpaandar/diri-paganahon an mga tag",
+       "action-applychangetags": "ig-apply an mga tag kaupod an imo mga pagliwat",
        "nchanges": "$1 {{PLURAL:$1|pagbag-o|mga pagbabag-o}}",
        "enhancedrc-history": "kasaysayan",
        "recentchanges": "Mga kabag-ohan",
        "recentchanges-legend-heading": "<strong>Leyenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (kitaa gihapon [[Special:NewPages|talaan han mga bag-o nga pakli]])",
        "recentchanges-submit": "Pakit-a",
-       "rcnotefrom": "An ha ubos in mga pagbabag-o tikang han <strong>$2</strong> (kutob ngadto ha <strong>$1</strong> nga ginpakita).",
+       "rcnotefrom": "Didi ha ubos amo {{PLURAL:$5|an pagbag-o|an mga pagbabag-o}} tikang<strong>$3, $4</strong> (tubtob <strong>$1</strong> nga ginpakita).",
        "rclistfrom": "Pakit-a an mga ginbag-ohan tikang han $3 $2",
        "rcshowhideminor": "$1 gudti nga mga pagliwat",
        "rcshowhideminor-show": "Pakit-a",
        "newpageletter": "B",
        "boteditletter": "b",
        "number_of_watching_users_pageview": "[$1 nagbabatay hin {{PLURAL:$1|gumaramit|mga gumaramit}}]",
-       "rc_categories_any": "Bisan ano nga",
+       "rc_categories_any": "Bisan ano nga pinili",
        "rc-change-size-new": "$1 {{PLURAL:$1|nga byte|nga mga byte}} kahuman han pagbag-o",
        "newsectionsummary": "/* $1 */ bag-o nga bahin",
        "rc-enhanced-expand": "Igpakita an detalye",
        "uploadnologintext": "Alayon $1 para han pag-upload han mga file.",
        "uploaderror": "Sayop hit pagkarga-pasaka",
        "upload-recreate-warning": "'''Pahimatngon:  An fayl nga may-ada hiton nga ngaran in ginpara o ginbalhin.'''\n\nAn taramdan han pagpara ngan pagbalhin para hini nga pakli in ginhahatag para han imo kamurayaw:",
-       "upload-permitted": "Gintutugotan nga mga klase han paypay: $1.",
-       "upload-preferred": "Karuyag nga mga tipo hin paypay: $1.",
-       "upload-prohibited": "Gindidire nga mga klase han paypay: $1.",
+       "upload-permitted": "Gintutugotan nga {{PLURAL:$2|klase|mga klase}} nga file: $1.",
+       "upload-preferred": "Mas karuyag nga {{PLURAL:$2|klase|mga klase}} hin file: $1.",
+       "upload-prohibited": "Gindidire nga {{PLURAL:$2|klase|mga klase}} hin file: $1.",
        "uploadlogpage": "Talaan han mga ginkarga-paigbaw",
        "filename": "Ngaran han fayl",
        "filedesc": "Dalikyat nga pulong",
        "filename-toolong": "Iton ngaran hin paypay in diri puyde na mas lapos pa ha 240 ka mga byte.",
        "badfilename": "An ngaran-han-paypay in ginliwat ngada ha \"$1\".",
        "empty-file": "An paypay nga imo ginsumite in waray sulod.",
+       "file-too-large": "An file nga imo ginhatag in sobra kadako.",
        "filename-tooshort": "An ngaran han fayl in halipot hin duro.",
        "filetype-banned": "Ini nga klase nga paypay in gindidire.",
+       "verification-error": "Ini nga pakli in waray nakapasar han file verification.",
+       "hookaborted": "An modipikasyon nga imo gintatalinguha nga himoon in gin-undang hin usa ka extension.",
        "illegal-filename": "An ngaran han fayl in diri gintutugutan.",
        "overwrite": "It pagsapaw han aada nga paypay in diri gintutugotan.",
        "unknown-error": "Nahitabo an waray kasasabti nga sayop.",
        "tmp-write-error": "Sayop ha pagsurat hin temporaryo nga paypay.",
        "large-file": "Ginrerekomenda nga it mga paypay in diri malapos hin $1;\nini nga paypay in $2.",
        "largefileserver": "Ini nga paypay in durudako kaysa ha ginpapakarawat han serbidor.",
+       "emptyfile": "An file nga imo gin-upload in baga waray sulod.\nIni in bangin tungod nagsayop pag-type han filename.\nAlayon kitaa kun imo karuyag gud nimo ig-upload inin nga file.",
        "windows-nonascii-filename": "Ini nga wiki in diri nakasuportado han mga ngaran-han-paypay nga may-ada pinaurog nga mga karakter.",
        "uploadwarning": "Pahimatngon han pagkarga paigbaw",
        "savefile": "Igtipig an paypay",
        "upload-proto-error": "Sayop nga protocol",
        "upload-file-error": "Sayop ha sulod",
        "upload-misc-error": "Waray kasasabti nga sayop hin pagkarga-paigbaw",
+       "upload-too-many-redirects": "An URL in nagsusulod hin damo hin duro nga mga redirect",
        "upload-http-error": "Mayda nahitabo nga sayop hin HTTP: $1",
        "upload-dialog-title": "Ig-upload an file",
        "upload-dialog-button-cancel": "Pasagda",
        "upload-dialog-button-upload": "Upload",
        "upload-form-label-infoform-title": "Mga detalye",
        "upload-form-label-infoform-name": "Ngaran",
+       "upload-form-label-infoform-description": "Deskripsyon",
        "upload-form-label-usage-title": "Paggamit",
        "upload-form-label-usage-filename": "Ngaran han file",
-       "foreign-structured-upload-form-label-own-work": "Buhat ko ini",
-       "foreign-structured-upload-form-label-infoform-categories": "Mga kategorya",
-       "foreign-structured-upload-form-label-infoform-date": "Petsa",
+       "upload-form-label-own-work": "Buhat ko ini",
+       "upload-form-label-infoform-categories": "Mga kategorya",
+       "upload-form-label-infoform-date": "Petsa",
        "backend-fail-notexists": "Waray ngada an paypay nga $1.",
        "backend-fail-delete": "Diri nakakapara han paypay nga \"$1\".",
        "backend-fail-alreadyexists": "May-ada na paypay nga \"$1\".",
        "backend-fail-read": "Diri nababasahan han paypay nga \"$1\".",
        "backend-fail-create": "Diri nasusuratan an paypay nga \"$1\".",
        "backend-fail-maxsize": "Diri nasusuratan an paypay nga \"$1\" tungod nga mas dako ini kaysa hin {{PLURAL:\"$2|usa nga byte|$2 nga mga byte}}.",
-       "backend-fail-readonly": "An panluyo nga tiripigan nga \"$1\" in ha pagkayana in panbasa-la.  An rason nga ginhatag in: \"''$2''\"",
+       "backend-fail-readonly": "An storage backend nga \"$1\" in ha pagkayana read-only.  An rason nga ginhatag in: <em>$2</em>",
        "backend-fail-connect": "Diri nakakasumpay ha storage backend \"$1\".",
        "lockmanager-notlocked": "Waray ka rangka an \"$1\"; diri ini nakatrangka.",
        "lockmanager-fail-closelock": "Diri nakakasera han nakatrangka nga paypay para han \"$1\".",
        "lockmanager-fail-svr-acquire": "Diri nakakakarawat in mga trangka ha serbidor $1.",
        "lockmanager-fail-svr-release": "Diri nakakabul-iw in mga trangka ha serbidor $1.",
        "zip-wrong-format": "An espisipikado nga paypay in diri naka ZIP nga paypay.",
-       "uploadstash-errclear": "An paghawan han mga paypay in diri malinamposon.",
+       "uploadstash-errclear": "Pakyas an paghawan han mga file.",
        "uploadstash-refresh": "Igpalab-as utro an talaan hin mga paypay",
        "img-auth-accessdenied": "Diri gintutugutan makasulod",
        "img-auth-nofile": "Waray ngada an paypay nga \"$1\".",
+       "img-auth-streaming": "Nag ii-stream \"$1\".",
        "http-read-error": "HTTP maysayop ha pagbasa.",
        "http-timed-out": "Naubosan hin oras ha pagpaalayon ha HTTP.",
        "http-curl-error": "May sayop ha pagkuha hin URL: $1",
        "statistics-users": "Mga [[Special:ListUsers|gumaramit]] nga nakarehistro",
        "statistics-users-active": "Mga gumaramit nga nanggigios",
        "statistics-users-active-desc": "Mga gumaramit nga may-ada iginbuhat ha urhi nga {{PLURAL:$1|ka adlaw|$1 ka mga adlaw}}",
+       "pageswithprop-prop": "Ngaran han propyudad:",
        "pageswithprop-submit": "Kadto-a",
        "doubleredirects": "Mga doble nga redirekta",
        "double-redirect-fixer": "Mangangayad hin redirekta",
        "wantedtemplates": "Mga ginkikinahanglan nga batakan",
        "mostlinked": "Pinakadamo nga mga ginsumpayan nga pakli",
        "mostlinkedcategories": "Pinakadamo nga mga ginsumpayan nga kaarangay",
-       "mostlinkedtemplates": "Pinakadamo nga mga ginsumpayan nga batakan",
+       "mostlinkedtemplates": "Pinakana-transclude nga mga pakli",
        "mostcategories": "Mga paypay nga may-ada pinakadamo nga mga kaarangay",
        "mostimages": "Pinakadamo nga nahisumpayan nga mga paypay",
        "mostinterwikis": "Mga pakli nga may-ada pinakadamo nga mga interwiki",
        "longpages": "Haglaba nga mga pakli",
        "deadendpages": "Waray na kakadtoan nga mga pakli",
        "protectedpages": "Pinapasaliporan nga mga pakli",
+       "protectedpages-timestamp": "Timestamp",
+       "protectedpages-page": "Pakli",
+       "protectedpages-expiry": "Mahuhuman",
+       "protectedpages-performer": "Pinapasaliporan an gumaramit",
+       "protectedpages-params": "Mga parametro han pananalipod",
+       "protectedpages-reason": "Rason",
+       "protectedpages-submit": "Pagdisplay hin mga pakli",
+       "protectedpages-unknown-timestamp": "Waray kasabti",
+       "protectedpages-unknown-performer": "Waray magpasabot nga gumaramit",
        "protectedtitles": "Pinapasaliporan nga mga titulo",
+       "protectedtitles-submit": "Igpakita an mga titulo",
        "listusers": "Lista han mga gumaramit",
        "listusers-editsonly": "Igpakita la an mga gumaramit nga may-ada ginliwat",
        "listusers-creationsort": "Ginsusunodsunod pinaagi han paghimo nga petsa",
+       "listusers-desc": "Ig-ayos ha paubos nga orden",
        "usereditcount": "$1 {{PLURAL:$1|ka pagliwat|ka mga pagliwat}}",
        "usercreated": "{{GENDER:$3|Ginhimo}} han $1 ha $2",
        "newpages": "Bag-o nga mga pakli",
        "nopagetitle": "Waray sugad hito nga kakadtoan nga pakli",
        "pager-newer-n": "{{PLURAL:$1|burubag-o 1|burubag-o $1}}",
        "pager-older-n": "{{PLURAL:$1|durudaan 1|durudaan $1}}",
+       "suppress": "Ig-suppress",
+       "apihelp": "Pabulig hit API",
+       "apihelp-no-such-module": "Waray kahiagii an Module \"$1\"",
+       "apisandbox": "sandbox hit API",
+       "apisandbox-jsonly": "Kinahanglan hin JavaSript para ha paggamit han API sandbox.",
+       "apisandbox-fullscreen": "Igpahilawig an panel",
+       "apisandbox-fullscreen-tooltip": "Igpahilawig an sandbox panel para masudlan an browser window.",
        "apisandbox-unfullscreen": "Igpakita an pakli",
+       "apisandbox-unfullscreen-tooltip": "Igpaguti an sandbox panel, para an MediaWiki navigation link in mahikit-an.",
        "apisandbox-submit": "Paghimo hin request",
        "apisandbox-reset": "Hawana",
        "apisandbox-retry": "Utroha",
+       "apisandbox-loading": "Nagloload hin impormasyon para han API module nga \"$1\"...",
+       "apisandbox-load-error": "May sayop nga nahitabo samtang nagloload hin impormasyon para han API module nga \"$1\": $2",
+       "apisandbox-no-parameters": "Waray mga parametro ini nga API module.",
        "apisandbox-helpurls": "Mga sumpay hit pabulig",
        "apisandbox-examples": "Mga pananglitan",
        "apisandbox-dynamic-parameters": "Dugang nga mga parameter",
        "apisandbox-dynamic-parameters-add-label": "Dugngi hin parameter:",
        "apisandbox-dynamic-parameters-add-placeholder": "Ngaran hit parameter",
        "apisandbox-dynamic-error-exists": "May-ada na nakangaran nga \"$1\" nga parameter.",
+       "apisandbox-deprecated-parameters": "Naka-deprecate nga mga parametro",
        "apisandbox-results": "Mga resulta",
+       "apisandbox-request-url-label": "Ginpapaalayon nga URL:",
+       "apisandbox-request-time": "Oras nga naglabay han pagpaalayon : {{PLURAL:$1|$1 ms}}",
        "booksources": "Mga libro nga tinikangan",
        "booksources-search-legend": "Pamilnga an mga libro nga gintikangan",
        "booksources-search": "Bilnga",
        "specialloguserlabel": "Magburuhat:",
-       "speciallogtitlelabel": "iiguon (titulo o gumarami):",
+       "speciallogtitlelabel": "Hinihingyap nga (titulo o {{ns:user}}:ngaran-han-gumaramit para han gumaramit):",
        "log": "Mga talaan",
+       "logeventslist-submit": "Igpakita",
        "all-logs-page": "Ngatanan nga mga talaan panpubliko",
+       "checkbox-all": "Ngatanan",
+       "checkbox-none": "Waray",
+       "checkbox-invert": "Baliskara",
        "allpages": "Ngatanan nga mga pakli",
        "nextpage": "Sunod nga pakli ($1)",
        "prevpage": "Nahiuna nga pakli ($1)",
        "allpages-hide-redirects": "Igtago an mga redirekta",
        "cachedspecial-refresh-now": "Igkita an pinakaurhi.",
        "categories": "Mga kaarangay",
+       "categories-submit": "Igpakita",
        "categoriesfrom": "Igpakita in mga kaarangay nga natikang ha:",
        "deletedcontributions": "Mga ginpara nga mga ámot hin nágámit",
        "deletedcontributions-title": "Ginpara nga mga amot han nagamit",
        "listusers-noresult": "Waray gumaramit nga nahiagian.",
        "listusers-blocked": "(ginpugngan)",
        "activeusers": "Taramdan hin mga gumaramit nga nanggigios",
+       "activeusers-from": "Igpakita an mga gumaramit tikang ha:",
        "activeusers-hidebots": "Igtago an mga bot",
        "activeusers-hidesysops": "Igtago an mga magdudumara",
        "activeusers-noresult": "Waray gumaramit nga nahiagian.",
+       "activeusers-submit": "Igpakita an mga gumaramit nga nangigios",
        "listgrouprights": "Mga katungod han grupo hin gumaramit",
        "listgrouprights-summary": "An masunod nga uska talaan hin mga grupo hin gumaramit sumala hinin nga wiki, ngan an ira nahisusumpay nga paggamit nga katungod.  Bangin may-ada [[{{MediaWiki:Listgrouprights-helppage}}|dugang nga impormasyon]] mahiunong han indibiduwal nga mga katungod.",
        "listgrouprights-key": "Leyenda:\n* <span class=\"listgrouprights-granted\">Gintagan hin katungod</span>\n* <span class=\"listgrouprights-revoked\">Waray ginhatag an katungod</span>",
        "listgrouprights-removegroup-self": "Igtanggal an {{PLURAL:$2|grupo|mga grupo}} tikang ha kalugaringon nga akawnt: $1",
        "listgrouprights-addgroup-self-all": "Igdugang an ngatanan nga mga grupo ha kalugaringon nga akawnt",
        "listgrouprights-removegroup-self-all": "Igtanggal an ngatanan nga mga grupo tikang ha kalugaringon nga akawnt",
+       "listgrouprights-namespaceprotection-namespace": "Ngaran-lat'ang",
+       "listgrouprights-namespaceprotection-restrictedto": "(Mga) katungod han pagtugot ha gumaramit nga pagliwat",
+       "listgrants": "Mga iginhatag",
+       "listgrants-grant": "Iginhatag",
+       "listgrants-rights": "Mga katungod",
+       "trackingcategories": "Ginsusubay an mga kategorya",
+       "trackingcategories-msg": "Ginsusubay an kaarangay",
+       "trackingcategories-name": "Ngaran han mensahe",
        "mailnologin": "Waray kakadtoan nga address",
        "mailnologintext": "Kinahanglan nimo nga [[Special:UserLogin|nakalog-in]] ngan may-ada balido nga email address ha imo[[Special:Preferences|mga preperensya]] para makapadangat hin email ngadto ha iba nga mga gumaramit.",
        "emailuser": "Ig-e-mail ini nga gumaramit",
        "notanarticle": "Diri uska unod nga pakli",
        "notvisiblerev": "An urhi nga pagliwat han iba nga gumaramit in ginpara",
        "watchlist-details": "{{PLURAL:$1|$1 nga pakli|$1 nga mga pakli}} nga aada ha imo talaan nga binabantayan, diri bulag nga paglakip han mga hiruhimangraw-nga-pakli.",
-       "wlshowlast": "Igpakita an katapusan nga $1 nga mga oras $2 nga mga adlaw",
+       "wlshowlast": "Igpakita an katapusan nga $1 ka mga oras $2 ka mga adlaw",
        "watchlist-hide": "Tago-a",
        "watchlist-submit": "Pakit-a",
+       "wlshowtime": "Kaiha han oras ha pagdisplay:",
        "wlshowhideminor": "gudti nga mga pagliwat",
        "wlshowhidebots": "Mga bot",
        "wlshowhideliu": "Mga nakarehistro nga gumaramit",
        "wlshowhideanons": "Mga waray magpakilala nga gumaramit",
        "wlshowhidepatr": "Nakapatrolya na nga mga pagliwat",
        "wlshowhidemine": "ako mga pagliwat",
+       "wlshowhidecategorization": "Kategorisasyon han pakli",
        "watchlist-options": "Mga pirilian han talaan han binabantayan",
        "watching": "Ginbabantay...",
        "unwatching": "Diri na ginbabantay...",
        "deletepage": "Igpara an pakli",
        "confirm": "Kompirma",
        "excontent": "An sulod in: ''$1''",
-       "excontentauthor": "an sulod in: ''$1'' (ngan hi \"[[Special:Contributions/$2|$2]]\" la an nag-amot)",
+       "excontentauthor": "An nasusulod in: ''$1'', ngan hi \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|pakighimangraw]]) la an nag-amot",
        "exbeforeblank": "sulod san-o paghawan in: \"$1\"",
        "delete-confirm": "Igpara \"$1\"",
        "delete-legend": "Igpara",
+       "historyaction-submit": "Igpakita",
        "actioncomplete": "Malinampuson an ginbuhat",
        "actionfailed": "Napakyas an ginbuhat",
        "deletedtext": "Ginpara an \"$1\".\nKitaa an $2 para hin talaan han mga gibag-ohi nga mga ginpamara.",
        "deletereasonotherlist": "Lain nga katadungan",
        "deletereason-dropdown": "*Agsob nga rason hin pagpara\n** Spam\n** Bandalismo\n** Pagtalapas ha katungod hin pagtatag-iya (''copyright'')\n** Tugon han manunurat\n** Utod nga redirek",
        "delete-edit-reasonlist": "Igliwat an mga rason han pagpara",
+       "deleteprotected": "Diri nimo mapapara ini nga pakli tunod ini in ginpasaliporan.",
        "rollback": "Mga libot-pabalik nga pagliwat",
        "rollbacklink": "libot-pabalik",
        "rollbacklinkcount": "rollback $1 {{PLURAL:$1|ka pagliwat|ka mga pagliwat}}",
+       "rollbacklinkcount-morethan": "Igrollback hin labaw han $1 nga {{PLURAL:$1|pagliwat|mga pagliwat}}",
        "rollbackfailed": "Diri malinamposon an paglibot-pabalik",
+       "cantrollback": "Diri makakapabalik han pagliwat;\nan urhi nga nag-amot in amo la an awtor hinin nga pakli.",
+       "editcomment": "An halipotay nga masisiring hiunong han pagliwat in: <em>$1</em>.",
        "revertpage": "Ginpabalik an ginliwat ni [[Special:Contributions/$2|$2]] ([[User talk:$2|hiruhimangraw]]) ngadto ha urhi nga pagliwat ni [[User:$1|$1]]",
        "sessionfailure-title": "Pakyas an sesyon",
+       "changecontentmodel-title-label": "Titulo han pakli",
        "changecontentmodel-reason-label": "Rason:",
+       "changecontentmodel-submit": "Balyo-a",
        "protectlogpage": "Talaan han pinasaliporan",
        "protectedarticle": "pinasaliporan \"[[$1]]\"",
        "prot_1movedto2": "[[$1]] in ginbalhin ngadto ha [[$2]]",
        "protectcomment": "Katadongan:",
        "protect-default": "Togota an ngatanan nga mga gumaramit",
        "protect-level-sysop": "Tuguti la an mga magdudumara",
+       "protect-expiring": "mawawaray gamit pag-$1 (UTC)",
+       "protect-expiring-local": "mawawaray gamit $1",
+       "protect-expiry-indefinite": "waray kataposan",
        "protect-othertime": "Lain nga oras:",
        "protect-othertime-op": "lain nga oras",
        "protect-otherreason": "Lain/dugang nga katadongan:",
        "restriction-move": "Balhina",
        "restriction-create": "Himo-a",
        "restriction-upload": "Igkarga-pasaka",
+       "restriction-level-sysop": "bug-os nga pinasaliporan",
+       "restriction-level-autoconfirmed": "tunga-tunga nga pinasaliporan",
        "restriction-level-all": "bisan ano nga katupngan",
        "undelete": "Igpakita an mga ginpara nga mga pakli",
+       "undeletepage": "Igpakita ngan igbalik-ha-pagkawara an mga pinara nga pakli",
+       "undeletepagetitle": "<strong>An masunod in nag-uupod hin mga pinara nga mga rebisyon han [[:$1|$1]]</strong>.",
+       "viewdeletedpage": "Igpakita an mga pinara nga pakli",
        "undeletelink": "igpakita/igbalik",
        "undeleteviewlink": "kitaa",
        "undeletecomment": "Katadungan:",
        "whatlinkshere-prev": "{{PLURAL:$1|nahiuna|nahiuna $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|masunod|masunod $1}}",
        "whatlinkshere-links": "← mga sumpay",
-       "whatlinkshere-hideredirs": "$1 nga mga redirek",
-       "whatlinkshere-hidetrans": "$1 nga mga transklusyon",
-       "whatlinkshere-hidelinks": "$1 an mga sumpay",
-       "whatlinkshere-hideimages": "$1 an mga sumpay han paypay",
+       "whatlinkshere-hideredirs": "Igtago an mga redirect",
+       "whatlinkshere-hidetrans": "Igtago an mga tranclusion",
+       "whatlinkshere-hidelinks": "Igtago an mga sumpay",
+       "whatlinkshere-hideimages": "Igtago an mga sumpay han file",
        "whatlinkshere-filters": "Mga panara",
        "whatlinkshere-submit": "Kadto-a",
        "block": "Pugngi an gumaramit",
-       "blockip": "Pugngi an gumaramit",
+       "blockip": "Pugngi an{{GENDER:$1|gumaramit}}",
        "blockip-legend": "Pugngi an gumaramit",
        "ipaddressorusername": "IP address o agnay-hit-gumaramit:",
        "ipbexpiry": "Matitima an dulot:",
        "movenotallowedfile": "Waray ka pagtugot para makabalhin hin mga paypay.",
        "cant-move-user-page": "Diri ka gintutugotan pagbalhin hin mga pakli nga gumaramit (labot la tikang ha mga bahin-pakli).",
        "cant-move-to-user-page": "Diri ka gintutugotan pagbalhin hin uska pakli pakada ha uska pakli hin gumaramit (labot la pakadto ha usa nga bahin-pakli han gumaramit).",
-       "newtitle": "Para ha bag-o nga titulo:",
+       "cant-move-category-page": "Waray ka pagtugot hin pagbalhin han mga kaarangay nga pakli.",
+       "cant-move-to-category-page": "Waray ka pagtugot pagbalhin hin pakli ngada ha kaarangay nga pakli.",
+       "newtitle": "Bag-o nga titulo:",
        "move-watch": "Kitaa an tinikangan nga pakli ngan kakadtoan nga pakli",
        "movepagebtn": "Igbalhin an pakli",
        "pagemovedsub": "Malinamposon an pagbalhin",
        "movepage-moved": "'''\"$1\" in ginbalhin ngadto ha \"$2\"'''",
        "movepage-moved-redirect": "Nahimo an uska redirect.",
+       "movepage-moved-noredirect": "An paghimo hin redirect in nai-suppress.",
+       "articleexists": "May-ada na hiton nga ngaran nga pakli, o an ngaran nga imo pinili in diri balido.\nAlayon pagpili hin iba nga ngaran.",
+       "cantmove-titleprotected": "Didi ka makakabalhin hin pakli ngadi nga lokasyon tungod an bag-o nga titulo in pinasaliporan tikang ha paghimo.",
+       "movetalk": "Igbalhin an may pagkahisumpay nga pakighimangraw-nga-pakli",
        "move-subpages": "Balhina an mga bahin-pakli (tubtob ngadto ha $1)",
        "move-talk-subpages": "Balhina an mga bahin-pakli han pakli han hiruhimangraw (tubtob ngadto ha $1)",
        "movepage-page-exists": "An pakli nga $1 in aada na ngan diri ini lugaring nga masasapawan pagsurat.",
        "movepage-page-unmoved": "An pakli nga $1 in diri mababalhin ngadto ha $2.",
        "movelogpage": "Talaan han pagbalhin",
        "movelogpagetext": "Ubos hini in uska talaan han ngatanan nga nabalhin nga pakli",
+       "movesubpage": "{{PLURAL:$1|Ubos-pakli|Mga ubos-pakli}}",
+       "movesubpagetext": "Ini nga pakli in may-ada $1 nga {{PLURAL:$1|ubos-pakli|mga ubos-pakli}} nga ginpapakita ha ubos.",
        "movenosubpage": "Ini nga pakli in waray mga bahin-pakli.",
        "movereason": "Rason:",
        "revertmove": "igbalik",
+       "delete_and_move_text": "An destinasyon nga pakli nga \"[[:$1]]\" in may-ada na.\nKaruyag mo nga igpara ini para maghatag hin paagi para hini nga pagbalhin?",
        "delete_and_move_confirm": "Oo, paraa an pakli",
        "delete_and_move_reason": "Ginpara para makahatag hin aragian para makabalhin tikang ha \"[[$1]]\"",
        "selfmove": "An tinikangan ngan kakadtoan nga mga titulo in parehas;\ndiri makakabalhin ha iya kalugaringon ngahaw.",
        "immobile-target-namespace-iw": "An sumpay interwiki in diri balido nga irig-on para han pagbalhin hin pakli.",
        "immobile-source-page": "Diri mababalhin ini nga pakli.",
        "immobile-target-page": "Diri makakabalhin ha ngada nga kakadtoon nga titulo.",
+       "imagenocrossnamespace": "Diri nakakabalhin hin file ngadto ha non-file namespace.",
+       "nonfile-cannot-move-to-file": "Diri nakakabalhin hin non-file ngadto ha file namespace.",
        "imageinvalidfilename": "An kakadtoon nga ngaran-han-paypay in diri balido",
        "move-leave-redirect": "Pagbilin hin redirect",
        "export": "Mga pakli hit pagexport",
        "importbadinterwiki": "Maraot nga sumpay hit interwiki",
        "importsuccess": "Natapos an pag-aangbit!",
        "import-noarticle": "Waray pakli nga maaangbit!",
-       "import-token-mismatch": "Nawara an datos sesyon.\nAlayon utroha.",
-       "import-error-edit": "An pakli nga \"$1\" in waray naangbit tungod nga diri ka gintutugotan pagliwat hini.",
-       "import-error-create": "An pakli nga \"$1\" in waray naangbit tungod nga diri ka gintutugotan paghimo hini.",
-       "import-error-interwiki": "An pakli nga \"$1\" in waray naangbit tungod nga an ngaran in nakareserba para han pagsusumpay ha gawas (interwiki).",
-       "import-error-special": "An pakli nga \"$1\" in waray naangbit tungod nga nahihilakip ini ha uska pinaurog nga ngaran-lat'ang nga diri natugot hin mga pakli.",
-       "import-error-invalid": "An pakli nga \"$1\" in waray naangbit tungod nga it iya ngaran in diri balido.",
+       "import-token-mismatch": "Nawara an datos sesyon.\n\n\nBangin ka naglog-out na. <strong>Alayon igberipika kun ikaw in nakalog-in pa ngan utro buhata</strong>. Kun diri pa iton nagana, alayon [[Special:UserLogout|pag-log out]] ngan paglog-in balik, ngan kitaa an imo browser kun natugot hin cookies tikang hinin nga site.",
+       "import-error-edit": "An pakli nga \"$1\" in waray napaangbit tungod nga diri ka gintutugotan pagliwat hini.",
+       "import-error-create": "An pakli nga \"$1\" in waray napaangbit tungod nga diri ka gintutugotan paghimo hini.",
+       "import-error-interwiki": "An pakli nga \"$1\" in waray napaangbit tungod nga an ngaran in nakareserba para han external linking (interwiki).",
+       "import-error-special": "An pakli nga \"$1\" in waray napaangbit tungod nga nahihilakip ini ha usa ka special namespace nga diri natugot hin mga pakli.",
+       "import-error-invalid": "An pakli nga \"$1\" in waray napaangbit tungod nga an ngaran kun diin inin ig-aangbit in diri balido para hinin nga wiki.",
        "import-options-wrong": "Sayop {{PLURAL:$2|nga pirilion|nga mga pirilion}}: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "An ginhatag nga gamot-pakli in uska diri balido nga titulo.",
        "import-rootpage-nosubpage": "Ngaran-lat'ang nga \"$1\" han gamot-pakli in diri natugot hin mga bahin-pakli.",
        "tooltip-feed-rss": "RSS nga pangarga para hini nga pakli",
        "tooltip-feed-atom": "Atom nga pangarga para hini nga pakli",
        "tooltip-t-contributions": "Kitaa an listahan hin mga amot {{GENDER:$1|hinin nga gumaramit}}",
-       "tooltip-t-emailuser": "Padad-i hin e-mail ini nga nágámit",
+       "tooltip-t-emailuser": "Padad-i hin e-mail ngadto {{GENDER:$1|hinin nga gumaramit}}",
        "tooltip-t-upload": "Pagkarga hin mga paypay",
        "tooltip-t-specialpages": "Talaan hin mga pinaurog nga pakli",
        "tooltip-t-print": "Maipapatik nga bersyon hini nga pakli",
        "tooltip-compareselectedversions": "Kitaa an mga kaibhan ha butnga han duha nga pinili nga mga pagliwat hini nga pakli",
        "tooltip-watch": "Dugnga ini nga pakli ngadto han imo talaan hin ginbibinantayan",
        "tooltip-watchlistedit-normal-submit": "Igtanggal an mga titulo",
+       "tooltip-watchlistedit-raw-submit": "Ig-update an barantayon",
        "tooltip-recreate": "Utroha paghimo an pakli bisan ini in ginpara na",
        "tooltip-upload": "Tikanga an pagkarga-pasaka",
        "tooltip-rollback": "An \"libot-pabalik\" in nabalik han (mga) pagliwat hini nga pakli ngadto han kataposan nga nag-amot hin usa ka pidlit",
        "tooltip-preferences-save": "Tipiga an mga karuyag",
        "tooltip-summary": "Pagbutang hin halipotay nga masisiring hiton pagliwat",
        "interlanguage-link-title": "$1 – $2",
+       "anonymous": "Waray magpakilala nga {{PLURAL:$1|gumaramit|mga gumaramit}} han {{SITENAME}}",
        "siteuser": "{{SITENAME}} gumaramit $1",
        "anonuser": "{{SITENAME}} waray nagpakilala nga gumaramit $1",
+       "lastmodifiedatby": "Ini nga pakli in urhi ginliwat $2, $1 ni $3.",
        "othercontribs": "Ginbasihan ha binuhat ni $1.",
        "others": "mga iba",
-       "siteusers": "{{SITENAME}} {{PLURAL:$2|gumaramit|mga gumaramit}} $1",
+       "siteusers": "{{SITENAME}} {{PLURAL:$2|{{GENDER:$1|gumaramit}}|mga gumaramit}} $1",
+       "anonusers": "{{SITENAME}} waray magpakilala nga {{PLURAL:$2|gumaramit|mga gumaramit}} $1",
+       "creditspage": "Pagkilala ha pakli",
+       "nocredits": "Waray pagkilala nga impormasyon para hinin nga pakli",
+       "spamprotectiontitle": "Filter para pag-iwas hit spam",
+       "spamprotectiontext": "An teksto nga karuyag nimo igtipig in ginpugong han spam filter.\nIni posible nahitabo tungod han sumpay ngadto ha usa nga blacklisted nga external site.",
+       "spamprotectionmatch": "An masunod nga teksto amo an nagpagana han amon spam filter: $1",
+       "spambot_username": "Paglimpyo han MediaWiki spam",
+       "spam_reverting": "Ginbabalik ha urhi nga rebisyon nga waray lakip nga sumpay ngadto ha $1",
+       "spam_blanking": "Ngatan nga mga rebisyon nga may-ada sumpay ngadto ha $1, ginhahawan",
+       "spam_deleting": "Ngatanan nga mga rebisyon nga naglalakip hin sumpay ngadto ha $1, ginpapara",
        "simpleantispam-label": "Anti-spam check.\n<strong>Ayaw</strong> pagbinutangi dinhi!",
        "pageinfo-title": "Impormasyon para \"$1\"",
        "pageinfo-not-current": "Pasaylo-a, imposible makahatag hin impormasyon hiunong han mga daan nga rebisyon.",
        "pageinfo-header-basic": "Panguna nga pananabotan",
        "pageinfo-header-edits": "Kaagi han pagliwat",
        "pageinfo-header-restrictions": "Panalipod han pakli",
+       "pageinfo-header-properties": "Mga propyudad han pakli",
        "pageinfo-display-title": "Iglatag an titulo",
+       "pageinfo-default-sort": "Default sort key",
        "pageinfo-length": "Kahilaba han pakli (ha mga byte)",
        "pageinfo-article-id": "ID han pakli",
+       "pageinfo-language": "An ginagamit nga pinulongan han pakli",
+       "pageinfo-content-model": "An gingagamit nga modelo han pakli",
        "pageinfo-robot-policy": "Pag-index hin mga robot",
        "pageinfo-robot-index": "Gintutugot",
        "pageinfo-robot-noindex": "Dírì gintutugot",
        "pageinfo-watchers": "Ihap han nangingita hin pakli",
+       "pageinfo-visiting-watchers": "An kadamo han mga nagbabantay han pakli nga nagduaw ha mga bag-o nga pagliwat",
+       "pageinfo-few-watchers": "Guruguti han $1 nga {{PLURAL:$1|nagbabantay|mga nagbabantay}}",
+       "pageinfo-few-visiting-watchers": "Bangin may-ada o waray nagbabantay nga gumaramit nga nagduduaw han mga bag-o nga pagliwat",
        "pageinfo-redirects-name": "Ihap hin mga redirek ngani nga pakli",
        "pageinfo-subpages-name": "Mga bahinpakli hin nga pakli",
        "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|redirekta|mga redirekta}}; $3 {{PLURAL:$3|diri redirekta|mga diri redirekta}})",
        "pageinfo-lastuser": "Giurhii nga nagliwat",
        "pageinfo-lasttime": "Petsa han kataposan nga pagliwat",
        "pageinfo-edits": "Ngatanan nga ihap han mga pakli",
+       "pageinfo-authors": "An kadamo han mga awtor",
+       "pageinfo-recent-edits": "An kadamo han mga bag-o nga mga pakli (ha sulod han naglabay nga $1)",
+       "pageinfo-recent-authors": "An kadamo han awtor nga nagliwat pala",
+       "pageinfo-magic-words": "Magic {{PLURAL:$1|word|words}} ($1)",
+       "pageinfo-hidden-categories": "Nakatago nga {{PLURAL:$1|kaarangay|mga kaarangay}} ($1)",
+       "pageinfo-templates": "Naka-transclude nga {{PLURAL:$1|batakan|mga batakan}} ($1)",
+       "pageinfo-transclusions": "{{PLURAL:$1|Pakli|Mga pakli}} nga naka-transclude ha ($1)",
        "pageinfo-toolboxlink": "Impormasyon han pakli",
        "pageinfo-redirectsto": "Igredirect ngadto ha",
        "pageinfo-redirectsto-info": "info",
        "show-big-image-preview": "Kadako hin nga pahiuna nga pagawas: $1.",
        "show-big-image-other": "Iba {{PLURAL:$2|nga resolusyon|nga mga resolusyon}}: $1.",
        "show-big-image-size": "$1 × $2 nga mga pixel",
+       "file-info-gif-looped": "naka-loop",
+       "file-info-png-looped": "naka-loop",
+       "file-info-png-frames": "$1 {{PLURAL:$1|frame|mga frame}}",
+       "file-no-thumb-animation": "<strong>Pasabot: Tungod hin limitasyon pan-teknikal\n, an mga thumbnail hinin nga file in diri naka-animate.</strong>",
+       "file-no-thumb-animation-gif": "<strong>Pasabot: Tungod hin limitasyon pan-teknikal\n, an mga thumbnail nga may-ada hitaas nga resolusyon nga GIF images sugad hini in diri naka-animate.</strong>",
        "newimages": "Galeryia hin mga paypay nga bag-o",
+       "imagelisttext": "Ngadi ubos in lista hin  <strong>$1</strong> ka {{PLURAL:$1|file|mga file}} nga naka-sort $2.",
+       "newimages-summary": "Ini nga ispisyal nga pakli in nagpapakita hin kataposan nga gin-upload nga mga file.",
        "newimages-legend": "Panara",
        "newimages-label": "Ngaran han paypay (o uska bahin hini):",
+       "newimages-showbots": "Igpakita an upload han mga bot",
+       "newimages-hidepatrolled": "Igtago an nakapatrolya nga mga upload",
        "noimages": "Waray makikit-an.",
        "ilsubmit": "Bilnga",
        "bydate": "pinaagi han petsa",
+       "sp-newimages-showfrom": "Igpakita an mga bag-o nga file tikang $2, $1",
+       "seconds": "{{PLURAL:$1|$1 ka segundo|$1 ka mga segundo}}",
+       "minutes": "{{PLURAL:$1|$1 ka minuto|$1 ka mga minuto}}",
+       "hours": "{{PLURAL:$1|$1 ka oras|$1 ka mga oras}}",
+       "days": "{{PLURAL:$1|$1 ka adlaw|$1 ka mga adlaw}}",
+       "weeks": "{{PLURAL:$1|$1 ka semana|$1 ka mga semana}}",
+       "months": "{{PLURAL:$1|$1 ka bulan|$1 ka mga bulan}}",
+       "years": "{{PLURAL:$1|$1 ka tuig|$1 ka mga tuig}}",
        "ago": "$1 an nakalabay",
        "just-now": "yana pala",
+       "hours-ago": "{{PLURAL:$1|$1 ka oras|$1 ka mga oras}} nga naglabay",
+       "minutes-ago": "{{PLURAL:$1|$1 ka minuto|$1 ka mga minuto}} nga naglabay",
+       "seconds-ago": "{{PLURAL:$1|$1 ka segundo|$1 ka mga segundo}} nga naglabay",
        "monday-at": "Lunes ha $1",
        "tuesday-at": "Martes ha $1",
        "wednesday-at": "Miyerkules ha $1",
        "bad_image_list": "An kabutangan in masunod:\n\nAn nakatalala la nga mga butang (mga bagis nga nagtitikang hin *) in mahiuupod paglabot.\nAn syahan nga sumpay ha uska bagis in dapat may-ada sumpay ngadto ha maraot nga fayl.\nAn bisan ano nga masunod nga mga sumpay ha kapareho nga bagis in igtratrato nga eksepsyon, sugad hin, mga pakli kun diin an mga fayl in puydi mabubutang ha sulod han bagis.",
        "metadata": "Metadata",
        "metadata-help": "Iní nga paypay mayda dugang nga pagpasabot, nga bangin gindugáng tikang han digital nga camera o iskaner nga gin-gamit paghimo o pag-digitar hini.\nKon an paypay ginliwat tikang han orihinal nga kamutangan, mayda mga detalye nga bangin diri magpakita han ginliwat nga paypay",
+       "metadata-expand": "Igpakita an mga pinahilawig nga detalye",
+       "metadata-collapse": "Igtago an mga pinahilawig nga detalye",
        "metadata-fields": "An mga rumbay han hulagway han metadato nga nakatala dinhi nga mensahe in iglalakip ha padayag hin hulagway nga pakli kun an taramdan metadato in nakalukot.\nAn iba in daan nakatago.\n* make\n* modelo\n* pitsaorasorihinal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpsngalatitud\n* gpsngalongitud\n* gpsngaaltitud",
        "exif-imagewidth": "Kahaluag",
        "exif-imagelength": "Kahitaas",
+       "exif-bitspersample": "Mga bit kada komponente",
        "exif-orientation": "Orientation",
        "exif-ycbcrpositioning": "Pagpoposisyon han Y ngan C",
        "exif-xresolution": "Resolusyon horizontal",
        "exif-copyrighted": "Kahimtang han copyright",
        "exif-copyrightowner": "Tag-iya han copyright",
        "exif-usageterms": "Mga termino hit paggamit",
+       "exif-copyrighted-true": "Naka-copyright",
        "exif-copyrighted-false": "Status hin katungod-hin-panag-iya waray mahabutang",
        "exif-unknowndate": "Waray kasabti an petsa",
        "exif-orientation-1": "Normal",
+       "exif-orientation-3": "Igpalibot hin 180°",
+       "exif-orientation-4": "Igpalibot patukdaw",
+       "exif-orientation-5": "Igpalibot hin 90° CCW ngan igpalibot patukdaw",
+       "exif-orientation-6": "Igpalibot hin 90° CCW",
+       "exif-orientation-7": "Igpalibot hin 90° CW ngan igpalibot patukdaw",
+       "exif-orientation-8": "Igpalibot hin 90° CW",
        "exif-exposureprogram-1": "Mano-mano",
+       "exif-exposureprogram-2": "Normal nga progama",
        "exif-subjectdistance-value": "$1 ka mga metro",
        "exif-meteringmode-0": "Waray kasabti",
        "exif-meteringmode-255": "iba",
        "confirm-watch-top": "Dudugngon ini nga pakli ngadto han imo talaan hin ginbibinantayan?",
        "confirm-unwatch-button": "OK",
        "confirm-unwatch-top": "Tatanggalon ini nga pakli tikang han imo tala hin binabantayan?",
+       "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← naha-una nga pakli",
        "imgmultipagenext": "sunod nga pakli →",
        "imgmultigo": "Pakadto!",
        "imgmultigoto": "Pakadto ha pakli $1",
+       "img-lang-go": "Kadto",
        "ascending_abbrev": "pasaka",
        "descending_abbrev": "paubos",
        "table_pager_next": "Sunod nga pakli",
        "autosumm-replace": "Ginsaliwanan an sulod hin \"$1\"",
        "autoredircomment": "Ginredirecta an pakli ngada ha [[$1]]",
        "autosumm-new": "Nahimo an pakli nga may \"$1\"",
+       "autosumm-newblank": "Paghimo hin blangko nga pakli",
        "size-bytes": "$1 nga B",
        "size-kilobytes": "$1 nga KB",
        "size-megabytes": "$1 nga MB",
        "size-gigabytes": "$1 nga GB",
+       "lag-warn-normal": "An mga pagbabag-o nga burubag-o han $1 ka {{PLURAL:$1|segundo|mga segundo}} in diri maipapakita dinhi nga lista.",
        "watchlistedit-normal-title": "Igliwat an talaan han binabantayan",
        "watchlistedit-normal-legend": "Igtanggal an mga titulo tikang ha talaan hit binabantayan",
        "watchlistedit-normal-submit": "Igtanggal an mga titulo",
        "watchlistedit-raw-done": "Ginpayana an imo talaan han barantayon.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 nga titulo|$1 nga mga titulo}} in gindugang:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 nga titulo|$1 nga mga titulo}} in gintanggal:",
+       "watchlistedit-clear-titles": "Mga titulo:",
+       "watchlistedit-clear-submit": "Hawana an barantayon nga listahan (Permamente ini!)",
        "watchlisttools-view": "Kitaa an mga nanginginlabot nga mga pagbabag-o",
        "watchlisttools-edit": "Kitaa ngan igliwat an talaan han binabantayan",
        "watchlisttools-raw": "Igliwat an hilaw nga talaan han binabantayan",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|hiruhimangraw]])",
+       "timezone-local": "Lokal",
        "duplicate-defaultsort": "'''Pahimatngon:''' An daan-aada nga paglainlain nga piridlitan nga \"$2\" in igsasapaw an durudaan nga daan-aada nga paglainlain nga piridlitan nga \"$1\".",
        "version": "Bersyon",
-       "version-skins": "Mga panit",
+       "version-skins": "Mga panit nga naka-install",
        "version-specialpages": "Mga pinaurog nga pakli",
        "version-parserhooks": "Mga kawil parser",
        "version-variables": "Mga variable",
        "version-hook-name": "Ngaran han kawil",
        "version-version": "($1)",
        "version-license": "MediaWiki nga Lisensya",
+       "version-ext-license": "Lisensya",
+       "version-skin-colheader-name": "Panit",
+       "version-ext-colheader-version": "Bersyon",
+       "version-ext-colheader-license": "Lisensya",
+       "version-ext-colheader-description": "Deskripsyon",
+       "version-ext-colheader-credits": "Mga awtor",
+       "version-license-title": "Lisensya para han $1",
+       "version-license-not-found": "Waray detalye han impormasyon han lisensya nga mahihiagian para hinin nga ekstensyon.",
+       "version-credits-title": "Pagkilala para kan $1",
+       "version-credits-not-found": "Waray detalye nga mga pagkilala nga impormasyon an mahihiagian para hinin nga ekstensyon.",
        "version-poweredby-credits": "Ini nga wiki in pinapaandar han '''[https://www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
        "version-poweredby-others": "mga iba",
+       "version-poweredby-translators": "mga maghuhubad han translatewiki.net",
+       "version-credits-summary": "Gusto namon ngaranon an masunod nga mga tawo tungod han ira mga amot ha [[Special:Version|MediaWiki]].",
        "version-software-product": "Produkto",
        "version-software-version": "Bersyon",
        "version-entrypoints": "Surudlan nga mga URL",
        "version-entrypoints-header-entrypoint": "Surudlan",
        "version-entrypoints-header-url": "URL",
+       "version-libraries-version": "Bersyon",
+       "version-libraries-license": "Lisensya",
+       "version-libraries-description": "Deskripsyon",
+       "version-libraries-authors": "Mga awtor",
+       "redirect-submit": "Kadtoa",
+       "redirect-file": "Ngaran han file",
        "fileduplicatesearch": "Pamiling hin nadoble nga mga paypay",
        "fileduplicatesearch-filename": "Ngaran han paypay:",
        "fileduplicatesearch-submit": "Pamilnga",
        "fileduplicatesearch-noresults": "Waray nabilngan nga paypay nga an ngaran in \"$1\".",
        "specialpages": "Mga pinaurog nga pakli",
+       "specialpages-note-top": "Leyenda",
        "specialpages-group-maintenance": "Mga sumat han pagmintinar",
        "specialpages-group-other": "Mga iba nga pinaurog nga pakli",
        "specialpages-group-login": "Magpalista nga masakob / paghimo hin bag-o nga akawnt",
        "tag-filter": "[[Special:Tags|Tag]] panara:",
        "tag-filter-submit": "Panara",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ka tag|ka mga tag}}]]: $2)",
+       "tags-title": "Mga tag",
+       "tags-tag": "Ngaran han tag",
+       "tags-source-header": "Tinikangan",
+       "tags-active-header": "Nagios?",
+       "tags-actions-header": "Mga buhat",
+       "tags-active-yes": "Oo",
+       "tags-active-no": "Diri",
        "tags-edit": "igliwat",
+       "tags-delete": "paraa",
        "tags-hitcount": "$1 {{PLURAL:$1|nga pagbag-o|nga mga pagbag-o}}",
+       "tags-create-reason": "Rason:",
+       "tags-create-submit": "Himoa",
+       "tags-delete-title": "Igpara an tag",
+       "tags-delete-reason": "Rason:",
+       "tags-activate-reason": "Rason:",
+       "tags-deactivate-reason": "Rason:",
+       "tags-edit-new-tags": "Mga bag-o nga tag:",
+       "tags-edit-add": "Dugngi ini nga mga tag:",
+       "tags-edit-remove": "Tanggala ini nga mga tag:",
+       "tags-edit-remove-all-tags": "(tanggala an ngatanan nga mga tag)",
+       "tags-edit-chosen-placeholder": "Pilia an pipira nga mga tag",
+       "tags-edit-chosen-no-results": "Waray tag nga kaparehas",
+       "tags-edit-reason": "Rason:",
        "comparepages": "Igkumpara an mga pakli",
        "compare-page1": "Pakli 1",
        "compare-page2": "Pakli 2",
        "logentry-newusers-autocreate": "An gumaramit nga akawnt nga $1 in lugaring nga {{GENDER:$2|ginhimo}}",
        "logentry-upload-upload": "Hi $1 {{GENDER:$2|gin-upload}} an $3",
        "rightsnone": "(waray)",
+       "feedback-back": "Balik",
        "feedback-cancel": "Pasagdi",
        "feedback-close": "Human na.",
+       "feedback-error-title": "Sayop",
        "feedback-error2": "Sayop: Pakyas an pagliwat",
        "feedback-message": "Mensahe:",
        "feedback-subject": "Himangrawon:",
+       "feedback-thanks-title": "Salamat!",
        "searchsuggest-search": "Pamilnga",
        "searchsuggest-containing": "nagsusulod. . .",
        "api-error-badaccess-groups": "Diri ka gintutugotan pagkarga paigbaw ha dinhi nga wiki.",
        "duration-years": "$1 {{PLURAL:$1|tuig|mga tuig}}",
        "duration-decades": "$1 {{PLURAL:$1|dekada|mga dekada}}",
        "duration-centuries": "$1 {{PLURAL:$1|gatostuig|mga gatostuig}}",
-       "duration-millennia": "$1 {{PLURAL:$1|yukottuig|mga yukottuig}}"
+       "duration-millennia": "$1 {{PLURAL:$1|yukottuig|mga yukottuig}}",
+       "mediastatistics-header-unknown": "Waray kasabti",
+       "mediastatistics-header-audio": "Audio",
+       "mediastatistics-header-video": "Mga video",
+       "mediastatistics-header-office": "Buhatan",
+       "mediastatistics-header-total": "Ngatanan nga pakli"
 }
index f8a8300..3064aa8 100644 (file)
        "noname": "用户名无效。",
        "loginsuccesstitle": "登录哉",
        "loginsuccess": "<strong>侬现在以“$1”个身份登录到{{SITENAME}}。</strong>",
-       "nosuchuser": "寻弗着用户“$1”。用户名是大小写敏感外加区分繁简体个。请检查拼写,或者[[Special:UserLogin/signup|开只新账户]]。",
+       "nosuchuser": "寻弗着用户“$1”。用户名是大小写敏感外加区分繁简体个。请检查拼写,或者[[Special:CreateAccount|开只新账户]]。",
        "nosuchusershort": "无没叫“$1”个用户。请检查侬个输入。",
        "nouserspecified": "侬必须选个用户名。",
        "login-userblocked": "箇个用户拨封锁拉许。弗允许登录。",
        "accmailtext": "已经为[[User talk:$1|$1]]产生只随机密码,并且已经发送到$2。登录之后,侬可以垃拉<em>[[Special:ChangePassword|箇只页面]]</em>更改密码。",
        "newarticle": "(新)",
        "newarticletext": "倷跟著链接来着一个还弗勒里个页面。要创建该页面呢,就勒下底个框里向开始写([$1 帮助页面]浪有更加多个信息)。要是倷是弗用心到该𡍲个说话,请点击浏览器个<strong>返回</strong>揿钮。",
-       "anontalkpagetext": "---- ''箇是一个还弗曾建立账户个匿名用户个讨论页, 箇咾我伲只好用IP地址来搭渠联络。该IP地址可能由几名用户共享。如果侬是一名匿名用户并认为箇只页面高头个评语搭侬弗搭界,请 [[Special:UserLogin/signup|创建新账户]]或[[Special:UserLogin|登录]]来避免垃拉将来搭其他匿名用户混淆。''",
+       "anontalkpagetext": "---- ''箇是一个还弗曾建立账户个匿名用户个讨论页, 箇咾我伲只好用IP地址来搭渠联络。该IP地址可能由几名用户共享。如果侬是一名匿名用户并认为箇只页面高头个评语搭侬弗搭界,请 [[Special:CreateAccount|创建新账户]]或[[Special:UserLogin|登录]]来避免垃拉将来搭其他匿名用户混淆。''",
        "noarticletext": "箇只页面目前呒没文本。侬可以垃拉其他页面高头[[Special:Search/{{PAGENAME}}|寻该只标题]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 寻相关日志]或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 建立此页]</span>。",
        "noarticletext-nopermission": "箇只页面目前呒不文本。侬可以垃拉其他页面高头[[Special:Search/{{PAGENAME}}|寻箇页标题]],或者<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 寻相关日志]</span>,但必过侬呒不权限建立箇只页面。",
        "userpage-userdoesnotexist": "用户账户“<nowiki>$1</nowiki>”弗曾创建。请垃拉创建/编辑迭个页面前头先检查一记。",
        "destfilename": "目标文件名:",
        "watchthisupload": "關注箇文件",
        "upload-misc-error": "弗識個傳錯誤",
-       "foreign-structured-upload-form-label-infoform-date": "日脚",
+       "upload-form-label-infoform-date": "日脚",
        "backend-fail-delete": "文件“$1”刪弗爻。",
        "backend-fail-move": "嘸處畀“$1”移到“$2”。",
        "backend-fail-opentemp": "臨時文件開弗爻。",
index bae1ce5..7dae096 100644 (file)
        "noname": "Та зөвшәсн демнәчнә нер заасн уга.",
        "loginsuccesstitle": "Йовудта орлһн",
        "loginsuccess": "<strong>Та ода «$1» нертә {{SITENAME}} төсвд бәәнәт.</strong>",
-       "nosuchuser": "«$1» нертә демнәч уга.\nДемнәчнә нернд баһ болн том үзгүд әдл биш.\nЧик бичсн шүүтн аль [[Special:UserLogin/signup|бигчдлһн бүтәтн]].",
+       "nosuchuser": "«$1» нертә демнәч уга.\nДемнәчнә нернд баһ болн том үзгүд әдл биш.\nЧик бичсн шүүтн аль [[Special:CreateAccount|бигчдлһн бүтәтн]].",
        "nosuchusershort": "«$1» нертә демнәч уга.\nЧик бичсн шүүтн.",
        "nouserspecified": "Та демнәчнә нер заах йоста.",
        "login-userblocked": "Эн демнәч бүслсн, орҗ болшго.",
index 19b3bec..6ea0b36 100644 (file)
        "noname": "თქვენს მიერ მითითებული მომხმარებლის სახელი ქმედითი არ არის.",
        "loginsuccesstitle": "სისტემაში შესვლა განხორციელდა.",
        "loginsuccess": "'''ასე მიშულირ რეთ {{SITENAME}}-ს მუჭოთ \"$1\".'''",
-       "nosuchuser": "მომხმარებელი სახელად $1 არ არსებობს.\nმომხმარებელთა სახელები გრძნობადია ასოების რეგისტრამდე..\nშეამოწმეთ სახელის დაწერა ან[[Special:UserLogin/signup|შექმენით ახალი ანგარიში]].",
+       "nosuchuser": "მომხმარებელი სახელად $1 არ არსებობს.\nმომხმარებელთა სახელები გრძნობადია ასოების რეგისტრამდე..\nშეამოწმეთ სახელის დაწერა ან[[Special:CreateAccount|შექმენით ახალი ანგარიში]].",
        "nosuchusershort": "მომხმარებელი სახელით „$1“ არ არსებობს. შეამოწმეთ მართლწერა.",
        "nouserspecified": "საჭირო რე მახვარებუშ ჯოხოშ მიშაჭარუა.",
        "login-userblocked": "ეს მომხმარებელი დაბლოკილია. სისტემაში შესვლა არაა ნებადართული.",
        "accmailtext": "შემთხვევითი მეთოდით შექმნილი პაროლი მომხმარებლისათვის [[User talk:$1|$1]] გაგზავნილია მისამართზე $2.\n\nავტორიზაციის გავლის შემდეგ შესაძლებელი იქნება ამ ანგარიშის  <em>[[Special:ChangePassword|პაროლის შეცვლა]]</em> ანგარიშში შესვლის გვერდზე.",
        "newarticle": "(ახალ)",
        "newarticletext": "თქვა გეჸუნელჷ რეთ ხასჷლაშ რცხის, ნამუთ დიო ვა რე დორცხუაფილი.\nხასჷლაშ დარცხუაფალო გემშეჸონით ტექსტი თუდონ ოჭკორიეშა. (ქოძირით[$1 მოხვარაშ ხასჷლა] უმოს ინფორმაციაშო).\nთე ხასჷლას ჩილათირო მოხვადით–და, ქიგუნჭირით თქვან ბრაუზერიშ კონჭის '''უკახალე'''.\"",
-       "anontalkpagetext": "----\n<em>ეს არის ანონიმური მომხმარებლის განხილვის გვერდი, რომელსაც ანგარიში ჯერ არ შეუქმნია ან არ იყენებს მას.</em>\n\nშესაბამისად, ჩვენ მისი ციფრული IP მისამართი უნდა გამოვიყენოთ მისი იდენტიფიცირებისთვის.\n\nამგვარი მისამართი შეიძლება რამდენიმე მომხმარებელმა გამოიყენოს.\n\nთუ თქვენ ანონიმური მომხმარებელი ხართ და თვლით, რომ სხვისთვის გამიზნული მითითება მიიღეთ, გთხოვთ [[Special:UserLogin/signup|შექმენით ანგარიში ან დარეგისტრირდით]] მომავალში გაუგებრობის თავიდან ასაცილებლად.",
+       "anontalkpagetext": "----\n<em>ეს არის ანონიმური მომხმარებლის განხილვის გვერდი, რომელსაც ანგარიში ჯერ არ შეუქმნია ან არ იყენებს მას.</em>\n\nშესაბამისად, ჩვენ მისი ციფრული IP მისამართი უნდა გამოვიყენოთ მისი იდენტიფიცირებისთვის.\n\nამგვარი მისამართი შეიძლება რამდენიმე მომხმარებელმა გამოიყენოს.\n\nთუ თქვენ ანონიმური მომხმარებელი ხართ და თვლით, რომ სხვისთვის გამიზნული მითითება მიიღეთ, გთხოვთ [[Special:CreateAccount|შექმენით ანგარიში ან დარეგისტრირდით]] მომავალში გაუგებრობის თავიდან ასაცილებლად.",
        "noarticletext": "ასე თე ხასჷლას ტექსტი ვა რე. \nთქვა შეილებუნა [[Special:Search/{{PAGENAME}}|გორათ ათე ხასჷლაშ ჯოხო]] შხვა ხასჷლეფს,\n<span class=\\\"plainlinks\\\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} დოგორათ მეხუჯაფილ ჟურნალეფი],\nვარა [{{fullurl:{{FULLPAGENAME}}|action=edit}} დიჭყათ ათე ხასჷლაშ რედაქტირაფა]</span>.",
        "noarticletext-nopermission": "ათე ხასჷლას ასე ტექსტი ვა რე. თქვა შეილებუნა [[Special:Search/{{PAGENAME}}|დოგორათ თე ხასჷლაშ დუდჯოხო]] შხვა ხასჷლეფს,\nვარდა <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} დოგორათ გინორცხილ ჟურნალეფი]</span>.",
        "missing-revision": "ვერსია $1 გვერდისათვის „{{FULLPAGENAME}}“ არ არსებობს.\n\nეს ჩვეულებრივ ხდება მაშინ, თუ მოძველებული ბმულით გადადიხართ გვერდზე, რომელიც წაიშალა.\nდეტალური ინფორმაცია შესაძლებელია იყოს [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} წაშლების ჟურნალში].",
        "allpagessubmit": "გინულა",
        "categories": "კატეგორიეფი",
        "categoriesfrom": "გეგმარჩქინ ხასილეფ დოჭყაფილ:",
-       "special-categories-sort-count": "დაალაგეთ რაოდენობის მიხედვით",
-       "special-categories-sort-abc": "ანბანზე დალაგება",
        "deletedcontributions": "მომხმარებლის წაშლილი წვლილი",
        "deletedcontributions-title": "წაშლილი წვლილი",
        "sp-deletedcontributions-contribs": "წვლილი",
        "version-libraries-library": "ბიბლიოთეკა",
        "version-libraries-version": "ვერსია",
        "redirect": "გადამისამართება ფაილიდან, მომხმარებლიდან, გვერდიდან ან ვერსიის იდენტიფიკატორიდან",
-       "redirect-legend": "გადამისამართება ფაილზე ან გვერდზე",
        "redirect-summary": "ეს დამხმარე გვერდი ამისამართებს ფაილის (ფაილის სახელიდან) გვერდზე, (გვერდის ან ვერსიის იდენტიფიკატორიდან) ან მომხმარებლის გვერდზე (მომხმარებლის რაოდენობრივი იდენტიფიკატორიდან). გამოყენება: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] ან [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "გინულა",
        "redirect-lookup": "ძიება:",
        "redirect-not-exists": "მნიშვნელობა ვერ მოიძებნა",
        "fileduplicatesearch": "ერთნაირი ფაილების ძიება",
        "fileduplicatesearch-summary": "ერთნაირი ფაილების ძიება ჰეშ-კოდის მიხედვით.",
-       "fileduplicatesearch-legend": "დუბლიკატების ძიება",
        "fileduplicatesearch-filename": "ფაილის სახელი:",
        "fileduplicatesearch-submit": "გორუა",
        "specialpages": "გჷშაკერძაფილი ხასჷლეფი",
index 97278ac..0e3b9fd 100644 (file)
@@ -17,7 +17,8 @@
                        "පසිඳු කාවින්ද",
                        "Matma Rex",
                        "Macofe",
-                       "Nemo bis"
+                       "Nemo bis",
+                       "Alefbeis"
                ]
        },
        "tog-underline": "שטרייכט אונטער לינקען",
@@ -35,6 +36,7 @@
        "tog-watchdefault": "צולייגן בלעטער וואס איך רעדאקטיר צו מיין אכטונג ליסטע",
        "tog-watchmoves": "צולייגן בלעטער וואס איך באוועג און טעקעס וואס איך לאד ארויף צו מיין אכטונג ליסטע",
        "tog-watchdeletion": "צולייגן בלעטער וואס איך מעק אויס צו מיין אויפפאסונג ליסטע",
+       "tog-watchuploads": "לייגט צו נייע פיילס וואס איך טוה ארויפלאדירן צו מיין וואטש ליסטע",
        "tog-watchrollback": "צולייגן צו מיין אויפפאסן ליסטע בלעטער אויף וואס איך האב אדורכגעפירט א צוריקשטעלן.",
        "tog-minordefault": "באגרענעצן אלע רעדאַקטירונגען גרונטלעך אלס מינערדיק",
        "tog-previewontop": "צײַג די \"פֿאָרויסיגע װײַזונג\" גלײַך בײַ דער ערשטער באַאַרבעטונג",
@@ -58,7 +60,7 @@
        "tog-ccmeonemails": "שיק מיר קאפיעס פון בליצבריוו וואס איך שיק צו אנדערע באַניצער",
        "tog-diffonly": "ווייז נישט אינהאלט אונטער די דיפערענץ",
        "tog-showhiddencats": "ווײַזן באהאלטענע קאטעגאריעס",
-       "tog-norollbackdiff": "×\94×\99פ×\98 ×\90×\99×\91ער ווײַזן אונטערשייד נאכן אויספֿירן א צוריקדריי",
+       "tog-norollbackdiff": "× ×\99ש×\98 ווײַזן אונטערשייד נאכן אויספֿירן א צוריקדריי",
        "tog-useeditwarning": "שטעלן א ווארענונג ווען איך לאז איבער א רעדאקטירונג בלאט מיט נישט אויפגעהיטענע ענדערונגען",
        "tog-prefershttps": "ניצט שטענדיק א זיכערע פארבינדונג ווען ארײַנגלאגירט",
        "underline-always": "אייביג",
        "february-gen": "פעברואר",
        "march-gen": "מערץ",
        "april-gen": "אַפּריל",
-       "may-gen": "×\9e×\99י",
+       "may-gen": "×\9e×\90Ö·י",
        "june-gen": "יוני",
        "july-gen": "יולי",
        "august-gen": "אויגוסט",
        "returnto": "צוריקקערן צו $1.",
        "tagline": "פֿון {{SITENAME}}",
        "help": "הילף",
-       "search": "×\96×\95×\9b×\9f",
-       "searchbutton": "×\96×\95×\9b×\9f",
+       "search": "×\96×\95×\9a",
+       "searchbutton": "×\96×\95×\9a",
        "go": "גיין",
        "searcharticle": "גיין",
        "history": "בלאט היסטאריע",
        "protectedpage": "באשיצטער בלאט",
        "jumpto": "שפּרינג צו:",
        "jumptonavigation": "נאַוויגאַציע",
-       "jumptosearch": "×\96×\95×\9b×\9f",
+       "jumptosearch": "×\96×\95×\9a",
        "view-pool-error": "אנטשולדיגט, די סערווערס זענען איבערגעפילט איצט.\nצופיל באניצער פרובירן צו ליינען דעם בלאט.\nביטע ווארטן א ביסל צייט בעפאר איר פרובירט ווידער אריינגיין אינעם בלאט.\n\n$1",
        "generic-pool-error": "אנטשולדיגט, די סערווערס זענען איבערגעפילט איצט.\nצופיל באניצער פרובירן צו באקוקן דעם רעסורס.\nביטע ווארטן א ביסל צייט בעפאר איר פרובירט ווידער אריינטרעטן אין דעם רעסורס.",
        "pool-timeout": "אַריבער דער צײַט וואַרטן פֿאר דער שליסונג",
        "newmessageslinkplural": "{{PLURAL:$1|א נייע מעלדונג|999=נייע מעלדונגען}}",
        "newmessagesdifflinkplural": "לעצטע {{PLURAL:$1|ענדערונג|999=ענדערונגען}}",
        "youhavenewmessagesmulti": "איר האט נייע מעלדונגען אין $1",
-       "editsection": "×\91×\90Ö·×\90ַר×\91×¢×\98ן",
+       "editsection": "רע×\93×\90ַק×\98×\99רן",
        "editold": "רעדאַקטירן",
        "viewsourceold": "ווײַזן מקור",
        "editlink": "רעדאַקטירן",
        "red-link-title": "$1 (בלאט טוט נאָך נישט עקזיסטירן)",
        "sort-descending": "סארטירן אַראָפ",
        "sort-ascending": "סארטירן אַרויף",
-       "nstab-main": "×\90ַר×\98×\99ק×\9c",
+       "nstab-main": "×\91×\9c×\90Ö·×\98",
        "nstab-user": "באַניצער בלאט",
        "nstab-media": "מעדיע בלאט",
        "nstab-special": "ספעציעלער בלאט",
        "virus-unknownscanner": "אומבאוואוסטער אנטי־ווירוס:",
        "logouttext": "'''איר האָט זיך ארויסלאָגירט.'''\n\nבאמערקט אז געוויסע בלעטער קענען זיך ווייטער ארויסשטעלן אזוי ווי ווען איר זענט אריינלאגירט, ביז איר וועט אויסליידיגן דעם בלעטערער זאפאס.",
        "cannotlogoutnow-title": "קען נישט ארויסלאגירן אצינד",
+       "cannotlogoutnow-text": "ארויסלאגירן נישט מעגלעך ווען מען ניצט $1.",
        "welcomeuser": "ברוך הבא, $1!",
        "welcomecreation-msg": "מ'האט געשאפן אייער קאנטע.\nפארגעסט נישט צו ענדערן אייערע [[Special:Preferences|{{SITENAME}} פרעפערענצן]].",
        "yourname": "באַניצער נאָמען:",
        "userlogin-remembermypassword": "לאז מיך בלײַבן ארײַנלאגירט",
        "userlogin-signwithsecure": "ניצן זיכערן סארווער",
        "cannotloginnow-title": "קען נישט אריינלאגירן אצינד",
+       "cannotloginnow-text": "אריינלאגירן נישט מעגלעך ווען מען ניצט $1.",
        "yourdomainname": "אײַער געביט:",
        "password-change-forbidden": "איר קען נישט ענדערן פאסווערטער אויף דער וויקי.",
        "externaldberror": "עס איז אדער פארגעקומען אן אויטענטיקאציע דאטנבאזע פֿעלער אדער איר זענט נישט ערמעגליכט צו דערהיינטיגן אייער דרויסנדיגע קאנטע.",
        "nocookieslogin": "{{SITENAME}} נוצט קיכלעך כדי אַרײַנלאגירן באַניצער.\nבײַ אײַך זענען קיכלעך אומדערמעגלעכט.\nביטע אַקטיווירט זיי און פרובירט נאכאַמאָל.",
        "nocookiesfornew": "מען האט נישט געשאַפֿן די באַניצער קאנטע, ווײַל מען האט נישט געקענט באַשטעטיקן איר מקור.\nטוט פֿעסטשטעלן אָז קיכלעך זענען אַקטיווירט, לאָדט אָן נאכאַמאל דעם בלאַט און פרואווט ווידער.",
        "noname": "איר האט נישט אַרײַנגעשטעלט קײַן גילטיקן באַניצער נאָמען.",
-       "loginsuccesstitle": "אריינלאגירט מיט הצלחה",
+       "loginsuccesstitle": "אריינלאגירט",
        "loginsuccess": "'''איר זענט אַצינד אַרײַנלאָגירט אין {{SITENAME}} ווי \"$1\".'''",
-       "nosuchuser": "נישטא קיין באניצער מיטן נאמען  \"$1\".\n\nקוקט איבער אײַער אויסלייג, אדער [[Special:UserLogin/signup|שאַפֿט א נײַע קאנטע]].",
+       "nosuchuser": "נישטא קיין באניצער מיטן נאמען  \"$1\".\n\nקוקט איבער אײַער אויסלייג, אדער [[Special:CreateAccount|שאַפֿט א נײַע קאנטע]].",
        "nosuchusershort": "נישטאָ קיין באַניצער מיטן נאָמען \"$1\".\nביטע באַשטעטיקט דעם אויסלייג.",
        "nouserspecified": "איר ברויכט ספעציפֿיצירן א באַניצער-נאָמען.",
        "login-userblocked": "דער באַניצער איז בלאקירט. ארײַנלאגירן נישט ערלויבט.",
        "createaccount-title": "קאנטע באשאפֿן אין {{SITENAME}}",
        "createaccount-text": "עמעצער האט באשאפֿן א קאנטע פֿאר אייער ע-פאסט אדרעס אין {{SITENAME}} ($4) מיטן נאמען \"$2\" און  פאסווארט \"$3\". איר דארפט אצינד איינלאגירן און ענדערן דאס פאסווארט.\n\nאיר קענט איגנארירן די מעלדונג, ווען די קאנטע איז באשאפֿן בטעות.",
        "login-throttled": "איר האט געפרוווט צופֿיל מאל אריינלאגירן.\nזייט אזוי גוט און וואַרט $1 איידער איר פרוווט נאכאמאל.",
-       "login-abort-generic": "×\90ײַער ×\90רײַנ×\9c×\90×\92×\99ר×\95× ×\92 ×\90×\99×\96 × ×\99ש×\98 ×\92×¢×\95×\95×¢×\9f ×\93ערפֿ×\90×\9c×\92ר×\99×\99×\9a—אָפגעשטעלט",
+       "login-abort-generic": "×\90ײַער ×\90רײַנ×\9c×\90×\92×\99ר×\95× ×\92 ×\90×\99×\96 ×\93×\95ר×\9b×\92עפ×\90×\9c×\9f—אָפגעשטעלט",
        "login-migrated-generic": "אײַער קאנטע איז געווארן מיגרירט, און אײַער באניצער־נאמען איז מער נישט פאראן אויף דער וויקי.",
        "loginlanguagelabel": "שפראך: $1",
        "suspicious-userlogout": " אײַער בקשה אַרויסלאָגירן זיך איז אפגעלייגט געווארן ווייַל אייגנטלעך איז זי געשיקט דורך אַ צעבראכענעם בלעטערער אָדער א פראקסי מיט א זאפאס.",
        "createacct-another-realname-tip": "עכטער נאמען איז אפציאנאל.\nאויב איר וויילט אויס צוצושטעלן אים, וועט דאס גענוצט ווערן צו געבן אטריבוציע פאר זייער ארבעט.",
-       "pt-login": "אַרײַנלאגירן",
+       "pt-login": "אריינלאגירן",
        "pt-login-button": "אַרײַנלאָגירן",
        "pt-createaccount": "שאַפֿן אַ קאנטע",
        "pt-userlogout": "אַרויסלאָגירן",
        "newpassword": "ניי פּאסוואָרט:",
        "retypenew": "ווידער שרײַבן פאַסווארט:",
        "resetpass_submit": "שטעלן פאסווארט און אריינלאגירן",
-       "changepassword-success": "אייער פאַסווארט איז געטוישט געווארן מיט דערפֿאלג!",
+       "changepassword-success": "אייער פאַסווארט איז געטוישט געווארן!",
        "changepassword-throttled": "איר האט געפרוווט צופֿיל מאל אריינלאגירן.\nזייט אזוי גוט און וואַרט $1 איידער איר פרוווט נאכאמאל.",
+       "botpasswords": "באט פאסווערטער",
+       "botpasswords-label-appid": "באט נאמען:",
        "botpasswords-label-create": "שאַפֿן",
        "botpasswords-label-update": "דערהײַנטיקן",
        "botpasswords-label-cancel": "אַנולירן",
        "botpasswords-label-delete": "אויסמעקן",
        "botpasswords-label-resetpassword": "ווידערשטעלן פאַסווארט",
+       "botpasswords-label-grants-column": "נאכגעגעבן",
+       "botpasswords-created-title": "באט פאסווארט געשאפן",
+       "botpasswords-created-body": "דאס באט פאסווארט פאר באט־נאמען \"$1\" פון באניצער \"$2\" איז געשאפן געווארן.",
+       "botpasswords-updated-title": "באט פאסווארט דערהיינטיקט",
+       "botpasswords-updated-body": "דאס באט פאסווארט פאר באט־נאמען \"$1\" פון באניצער \"$2\" איז דערהיינטיקט געווארן.",
+       "botpasswords-deleted-title": "באט פאסווארט אויסגעמעקט",
        "resetpass_forbidden": "פאסווערטער קענען נישט ווערן געטוישט",
        "resetpass-no-info": "איר דארפֿט זיין אריינלאגירט צוצוקומען גלייך צו דעם דאזיגן בלאט.",
        "resetpass-submit-loggedin": "טוישן פאסווארט",
        "resetpass-submit-cancel": "אַנולירן",
-       "resetpass-wrong-oldpass": "×\90×\95×\9e×\92×\99×\9c×\98×\99×\92 ×¦×²Ö·×\98×\95×\95ײַ×\9c×\99ק ×\90×\93ער ×\9c×\95×\99פֿ×\99ק ×¤×\90ַס×\95×\95×\90ר×\98.\n×\90×\99ר ×\94×\90×\98 ×\9e×¢×\92×\9c×¢×\9a ×©×\95×\99×\9f ×\92×¢×\98×\95×\99ש×\98 ×\90×\99×\99ער ×¤×\90ַס×\95×\95×\90ר×\98 ×\9e×\99×\98 ×\94צ×\9c×\97×\94 ×\90×\93ער ×\92×¢×\91×¢×\98×\9f ×\90 × ×²Ö·  ×¦×²Ö·×\98×\95×\95ײַ×\9c×\99ק ×¤×\90ַס×\95×\95×\90ר×\98.",
+       "resetpass-wrong-oldpass": "אומגילטיג צײַטווײַליק אדער לויפֿיק פאַסווארט.\nאיר האט מעגלעך שוין געטוישט אייער פאַסווארט אדער געבעטן א נײַ  צײַטווײַליק פאַסווארט.",
        "resetpass-recycled": "זײַט אזוי גוט שטעטל אירע פאסווארט צו עפעס אנדערש פונעם לויפיקן פאסווארט.",
        "resetpass-temp-emailed": "איר האט זיך ארי לאגירת מיט א פראוויזארישן קאד געשיקט דורכן ע־פאסט. כדי שליסן דאס ארײַנלאגירן, דארט איר שטעלן א נײַ פאסווארט דא.",
        "resetpass-temp-password": "צײַטווייליק פאַסווארט:",
        "passwordreset-emailtext-user": "באניצער $1 אויף  {{SITENAME}} האט געבעטן צוריקצושטעלן אייער פאסווארט פאר {{SITENAME}} ($4).\nדי פאלגנדע באניצער {{PLURAL:$3|קאנטע איז|קאנטעס זענען}} פארבונדן מיט דעם ע־פאסט אדרעס:\n\n$2\n\n{{PLURAL:$3|דאס פראוויזארישע פאסווארט|די פראוויזארישע פאסווערטער}} וועלן אויסגיין נאך {{PLURAL:$5|איין טאג|$5 טעג}}.\nאיר זאלט אריינלאגירן און קלויבן א נייע פאסווארט אצינד. טאמער א צווייטער האט געשיקט די בקשה,\nאדער ווען איר געדענקט יא אייער פריעריקע פאסווארט, און וויל עס נישט ענדערן,\n קענט איר איגנארירן דעם אנזאג און ניצן ווייטער דאס אלטע פאסווארט.",
        "passwordreset-emailelement": "באַניצער נאָמען: \n$1\n\nפראוויזארישער פּאַראָל: \n$2",
        "passwordreset-emailsentemail": "טאמער איז דער ע־פאסט אדרעס פארקניפט מיט אייער קאנטע, וועט מען שיקן א פאסווארט צוריקשטעלן ע-פּאָסט.",
+       "passwordreset-emailsentusername": "טאמער איז פאראן אן ע־פאסט אדרעס פארקניפט מיט דעם באניצער־נאמען, וועט מען שיקן א פאסווארט צוריקשטעלן ע-פּאָסט.",
        "passwordreset-emailsent-capture": "מען האט געשיקט א פאסווארט צוריקשטעלן בליצבריוו, וואס ווערט געוויזן אונטן.",
        "passwordreset-emailerror-capture": "מען האט געשאפן א פאסווארט צוריקשטעלן בליצבריוו, וואס ווערט געוויזן אונטן, אבער שיקן צום {{GENDER:$2|באניצער}}איז דורכגעפאלן: $1",
        "changeemail": "ענדערן אדער אראפנעמען ע-פּאָסט אַדרעס",
        "minoredit": "דאס איז א מינערדיגע ענדערונג",
        "watchthis": "טוט אױפֿפּאַסן דעם בלאט",
        "savearticle": "אויפהיטן בלאַט",
+       "publishpage": "פובליצירן בלאט",
        "preview": "פֿאראויסקוק",
        "showpreview": "ווייזן פאָרױסקוק",
        "showdiff": "ווײַז די ענדערונגען",
        "accmailtext": "א צופֿעליק פאַסווארט פֿאַר [[User talk:$1|$1]] איז געשיקט געוואָרן צו $2.\n\nמען קען עס טוישן אויפֿן [[Special:ChangePassword|טוישן פאַסווארט]] בלאַט נאָכן ארײַנלאגירן.",
        "newarticle": "(ניי)",
        "newarticletext": "איר זענט געקומען צו אַ בלאַט וואָס עקזיסטירט נאָך נישט!\nכדי שאַפֿן דעם בלאַט, קלאַפט אַרײַן טעקסט אין דעם קעסטל אונטן (זעט דעם [$1 הילף בלאַט] פֿאַר מער אינפֿארמאַציע).\nאויב איר זענט אַהערגעקומען בטעות, דרוקט דאָס '''Back''' קנעפל אין אײַער בלעטערער.",
-       "anontalkpagetext": "----'''דאָס איז א רעדן בלאַט פון א אַן אַנאנימען באַניצער וואָס האט נאך נישט געשאַפֿן קיין קאנטע, אדער באניצט זיך נישט דערמיט. דערוועגן, מוזן מיר זיך באניצן מיט זיין IP אדרעס כדי אים צו אידענטיפיצירן. עס קען זיין אז עטלעכע אנדערע ניצן אויך דעם  IP אדרעס. אויב זענט איר אן אנאנימער באַניצער וואס שפירט אז איר האט באקומען מעלדונגען וואס זענען נישט שייך צו אייך, ביטע [[Special:UserLogin/signup|שאַפֿט א קאנטע]] אדער [[Special:UserLogin|טוט זיך אריינלאגירן]] כדי צו פארמיידן דאס אין די עתיד זיך פארמישן מיט אנדערע אַנאנימע באַניצערס.'''",
+       "anontalkpagetext": "----'''דאָס איז א רעדן בלאַט פון א אַן אַנאנימען באַניצער וואָס האט נאך נישט געשאַפֿן קיין קאנטע, אדער באניצט זיך נישט דערמיט. דערוועגן, מוזן מיר זיך באניצן מיט זיין IP אדרעס כדי אים צו אידענטיפיצירן. עס קען זיין אז עטלעכע אנדערע ניצן אויך דעם  IP אדרעס. אויב זענט איר אן אנאנימער באַניצער וואס שפירט אז איר האט באקומען מעלדונגען וואס זענען נישט שייך צו אייך, ביטע [[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}}}} נאָכזוכן די רעלעוואנטע לאגביכער]</span>., אבער איר זענט נישט ערלויבט צו שאפֿן דעם בלאט.",
        "missing-revision": "די רעוויזיע #$1 פונעם בלאט \"{{FULLPAGENAME}}\" עקזיסטירט נישט.\n\nדאס געשעט געוויינלעך פון פאלגן א פארעלטערטן היסטאריע לינק צו א בלאט וואס איז געווארן אויסגעמעקט.\nפרטים קען מען געפינען אינעם [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} אויסמעקונג לאגבוך].",
        "userpage-userdoesnotexist": "באניצער קאנטע \"$1\" איז נישט אײַנגעשריבן.\nקוקט איבער צי איר ווילט שאפֿן/רעדאקטירן דעם בלאט.",
        "userpage-userdoesnotexist-view": "באניצער קאנטע \"$1\" איז נישט איינגעשריבן.",
        "blocked-notice-logextract": "דער באַניצער איז דערווייַל פֿאַרשפאַרט.\nדי לעצטע בלאָקירן לאג אַקציע איז צוגעשטעלט אונטן:",
-       "clearyourcache": "'''אַכטונג:''' נאכן אויפֿהיטן, ברויכט איר אפשר נאך אַריבערגיין דעם בלעטערערס זאַפאַס צו זען די ענדערונגען.\n\n* '''פֿייערפוקס/סאפֿארי:''' האלט אראפ ''שיפֿט'' בשעתן דרוקן ''Reload'', אדער דרוקט ''Ctrl-F5'' אדער ''Ctrl-R'' (אויף א מאקינטאש ''⌘-R'')\n\n* '''גוגל כראם:''' דרוקט ''Ctrl-Shift-R'' (אויף א מאקינטאש ''⌘-Shift-R'')\n\n* '''אינטערנעט עקספלארער:''' האלט אראפ ''Ctrl'' בשעתן קליקן ''Refresh'', אדער  דרוקט ''Ctrl-F5''\n\n* '''אפערע:''' ליידיגט אויס דעם זאַפאַס אין ''Tools → Preferences'' (''העדפות'' > ''כלים'')",
+       "clearyourcache": "'''אַכטונג:''' נאכן אויפֿהיטן, ברויכט איר אפשר נאך אַריבערגיין דעם בלעטערערס זאַפאַס צו זען די ענדערונגען.\n\n* '''פֿייערפוקס/סאפֿארי:''' האלט אראפ ''שיפֿט'' בשעתן דרוקן ''Reload'', אדער דרוקט ''Ctrl-F5'' אדער ''Ctrl-R'' (אויף א מאקינטאש ''⌘-R'')\n\n* '''גוגל כראם:''' דרוקט ''Ctrl-Shift-R'' (אויף א מאקינטאש ''⌘-Shift-R'')\n\n* '''אינטערנעט עקספלארער:''' האלט אראפ ''Ctrl'' בשעתן קליקן ''Refresh'', אדער  דרוקט ''Ctrl-F5''\n\n* <strong>אפערע:</strong> גייט צו <em>מעניו → שטעלונגען</em> (<em> אפערע → פרעפערנצן</em> אויף א מעק) און דערנאך צו <em>פריוואטקייט & און זיכערהייט → אראפראמען בלעטערן דאטן → בילדער און טעקעס אין זאפאס</em>",
        "usercssyoucanpreview": "'''טיפ:''' נוצט דאס {{int:showpreview}} קנעפל אויספרובירן אייער CSS בעפארן אויפהיטן.",
        "userjsyoucanpreview": "'''טיפ:''' נוצט דאס {{int:showpreview}} קנעפל אויספרובירן אייער  JavaScript בעפארן אויפהיטן.",
        "usercsspreview": "'''געדענקט אז איר טוט בלויז פאראויס זען אייער באניצער CSS.'''\n'''ער איז דערווייל נאכנישט אויפֿגעהיטן!'''",
        "revdelete-unsuppress": "טוה אפ באגרענעצונגן אין גענדערטע רעוויזיעס",
        "revdelete-log": "אורזאַך:",
        "revdelete-submit": "צושטעלן צו {{PLURAL:$1|סעלעקטירטער רעוויזיע| סעלעקטירטע רעוויזיעס}}",
-       "revdelete-success": "'''רע×\95×\95×\99×\96×\99×¢ ×\96×¢×\91×\90ַרק×\99×\99×\98 ×\93ערפֿ×\90×\9c×\92ר×\99×\99×\9a ×\93ער×\94ײַנ×\98×\99ק×\98.'''",
+       "revdelete-success": "'''רעוויזיע זעבאַרקייט דערהײַנטיקט.'''",
        "revdelete-failure": "'''נישט מעגלעך צו דערהײַנטיקן רעוויזיע זעבאַרקייט:'''\n$1",
-       "logdelete-success": "'''לאג באהאלטן איז סוקסעספול איינגעשטעלט.'''",
+       "logdelete-success": "'''לאגבוך זעבארקייט איינגעשטעלט.'''",
        "logdelete-failure": "'''נישט מעגלעך צו שטעלן לאג זעבאַרקייט:'''\n$1",
        "revdel-restore": "טויש די זעבארקייט",
        "pagehist": "בלאט היסטאריע",
        "userrights-changeable-col": "גרופעס איר קענט ענדערן",
        "userrights-unchangeable-col": "גרופעס איר קענט נישט ענדערן",
        "userrights-conflict": "קאנפֿליקט פון באניצער־רעכטן ענדערונגען! זייט אזוי גוט רעצענזירן און באשטעטיקן אײַערע ענדערונגען.",
-       "userrights-removed-self": "×\90×\99ר ×\94×\90×\98 ×\93ערפ×\90×\9c×\92ר×\99×\99×\9a ×\90ר×\90פ×\92×¢× ×\95×\9e×¢×\9f ×\90×\99×\99ערע ×\90×\99×\99×\92×¢× ×¢ ×¨×¢×\9b×\98×¢. אזוי קענט איר מער נישט דערגרייכן דעם בלאט.",
+       "userrights-removed-self": "×\90×\99ר ×\94×\90×\98 ×\90ר×\90פ×\92×¢× ×\95×\9e×¢×\9f ×\90×\99×\99ערע ×\90×\99×\99×\92×¢× ×¢ ×¨×¢×\9b×\98×\9f. אזוי קענט איר מער נישט דערגרייכן דעם בלאט.",
        "group": "גרופע:",
        "group-user": "באניצערס",
        "group-autoconfirmed": "באַשטעטיקטע באַניצער",
        "grant-group-file-interaction": "אינטעראגירן מיט מעדיע",
        "grant-group-email": "שיקן ע־פאסט",
        "grant-createaccount": "שאַפֿן קאנטעס",
+       "grant-uploadfile": "אַרויפֿלאָדן נייע טעקעס",
+       "grant-basic": "בעיסיק רעכטן",
+       "grant-viewmywatchlist": "קוקט אייער אויפפאסונג ליסטע",
        "newuserlogpage": "נייע באַניצערס לאָג-בוך",
        "newuserlogpagetext": "דאס איז א לאג פון באַניצערס אײַנשרײַבונגען.",
        "rightslog": "באַניצער רעכטן לאג",
        "upload-form-label-infoform-description": "באַשרײבונג",
        "upload-form-label-usage-title": "באניץ",
        "upload-form-label-usage-filename": "טעקע נאמען",
-       "foreign-structured-upload-form-label-own-work": "דאס איז מיין אייגענע ארבעט.",
-       "foreign-structured-upload-form-label-infoform-categories": "קאַטעגאריעס",
-       "foreign-structured-upload-form-label-infoform-date": "דאַטע",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "אפשר ווילט איר פרובירן [[Special:Upload|דעם גרונטלעכן ארויפלאד־בלאט]].",
+       "upload-form-label-own-work": "דאס איז מיין אייגענע ארבעט.",
+       "upload-form-label-infoform-categories": "קאַטעגאריעס",
+       "upload-form-label-infoform-date": "דאַטע",
+       "upload-form-label-not-own-work-local-generic-local": "אפשר ווילט איר פרובירן [[Special:Upload|דעם גרונטלעכן ארויפלאד־בלאט]].",
        "backend-fail-stream": "קען נישט מאכן שטראמען טעקע $1.",
        "backend-fail-notexists": "נישט פֿאראן די טעקע $1.",
        "backend-fail-notsame": "א נישט־אידענטישע טעקע עקזיסטירט שוין ביי \"$1\".",
        "imagelinks": "טעקע באַניץ",
        "linkstoimage": "{{PLURAL:$1|דער פאלגנדער בלאט ניצט|די פאלגנדע בלעטער ניצן}} דאס דאזיגע בילד:",
        "linkstoimage-more": "מער ווי $1 {{PLURAL:$1|בלאַט פֿאַרבינדט|בלעטער פֿאַרבינדן}} צו דער דאזיגער טעקע.\nדי פֿאלגנדע ליסטע ווײַזט  {{PLURAL:$1|דעם ערשטן בלאַט לינק|די ערשטע $1 בלאַט לינקען}} צו דער טעקע.\nס'איז פֿאַראַן[[Special:WhatLinksHere/$2|פֿולע רשימה]].",
-       "nolinkstoimage": "× ×\99ש×\98×\90 ×§×\99×\99×\9f ×\91×\9c×¢×\98ער ×\95×\95×\90ס × ×\99צ×\9f ×\93×\90ס ×\93×\90×\96×\99×\92×¢ ×\91×\99×\9c×\93.",
+       "nolinkstoimage": "× ×\99ש×\98×\90 ×§×\99×\99×\9f ×\91×\9c×¢×\98ער ×\95×\95×\90ס ×¤×\90ר×\91×\99× ×\93×\9f ×¦×\95 ×\93×\99 ×\98עקע.",
        "morelinkstoimage": "באַקוקן  [[Special:WhatLinksHere/$1|מער לינקען]] צו דער טעקע.",
        "linkstoimage-redirect": "$1 (טעקע ווײַטערפֿירונג) $2",
        "duplicatesoffile": "די פֿאלגנדע {{PLURAL:$1|טעקע דופליקירט|$1 טעקעס דופליקירן}} די דאזיגע טעקע ([[Special:FileDuplicateSearch/$2|נאך פרטים]]):",
        "unusedtemplates": "נישט באניצטע מוסטערן",
        "unusedtemplatestext": "דער בלאט ווײַזט אלע בלעטער אינעם {{ns:template}} נאמענטייל וואס זענען נישט אײַנגעשלאסן אין אן אנדער בלאט. געדענקט צו באקוקן אנדערע בלעטער פאר לינקען צו די מוסטערן איידער איר מעקט זיי אויס.",
        "unusedtemplateswlh": "אנדערע פֿאַרבינדונגען",
-       "randompage": "צ×\95פֿע×\9c×\99×\92ער ×\90ַר×\98×\99ק×\9c",
+       "randompage": "צ×\95פֿע×\9c×\99×\92ער ×\91×\9c×\90×\98",
        "randompage-nopages": "נישטא קיין בלעטער אין {{PLURAL:$2|דעם פאלגנדן נאמענטייל |די פאלגנדע נאמענטיילן}} \"$1\".",
        "randomincategory": "צופעליקער בלאט אין קאטעגאריע",
        "randomincategory-invalidcategory": "\"$1\" איז נישט קיין גילטיקער קאטעגאריע נאמען.",
        "querypage-disabled": "דער באַזונדער־בלאַט איז אומאַקטיווירט צוליב אויספֿירונג סיבות.",
        "apihelp": "API־הילף",
        "apihelp-no-such-module": "מאָדול \"$1\" נישט געפונען.",
+       "apisandbox-unfullscreen": "ווייזן בלאט",
        "apisandbox-reset": "רייניקן",
+       "apisandbox-retry": "פרובירן נאכאמאל",
+       "apisandbox-helpurls": "הילף לינקען",
+       "apisandbox-examples": "ביישפילן",
        "apisandbox-results": "רעזולטאטן",
        "booksources": "דרויסנדיגע ליטעראַטור ISBN",
        "booksources-search-legend": "זוכן פאר דרויסנדע ביכער מקורות",
        "logempty": "נישטא קיין פאַסנדיקע זאכן אין לאג.",
        "log-title-wildcard": "זוכן טיטלען וואס הייבן אָן מיט דעם טעקסט",
        "showhideselectedlogentries": "ווײַזן/באַהאַלטן געקליבענע לאגבוך אקציעס",
+       "checkbox-all": "אַלע",
+       "checkbox-none": "קיינע",
+       "checkbox-invert": "אומקערן",
        "allpages": "אַלע בלעטער",
        "nextpage": "קומענדיקער בלאַט ($1)",
        "prevpage": "פֿריִערדיקער בלאַט ($1)",
        "listgrouprights-namespaceprotection-header": "נאמענטייל באשרענקונגען",
        "listgrouprights-namespaceprotection-namespace": "נאָמענטייל",
        "listgrouprights-namespaceprotection-restrictedto": "רעכט(ן) וואס דערלויבט באניצער צו רעדאקטירן",
+       "listgrants-grant": "צולאזן",
+       "listgrants-rights": "רעכטן",
        "trackingcategories": "אויפפאסן־קאטעגאריעס",
        "trackingcategories-msg": "אויפפאסן־קאטעגאריע",
        "trackingcategories-name": "מעלדונג נאמען",
        "wlnote": "אונטן {{PLURAL:$1|איז די לעצטע ענדערונג|זענען די לעצטע <strong>$1</strong> ענדערונגען}} אין {{PLURAL:$2|דער לעצטער שעה|די לעצטע <strong>$2</strong> שעה'ן}} ביז $3, $4.",
        "wlshowlast": "ווײַזן די לעצטע $1 שעה'ן  $2 טעג",
        "watchlist-hide": "באַהאַלטן",
+       "watchlist-submit": "ווײַזן",
        "wlshowtime": "צייט־פעריאד צו ווייזן:",
        "wlshowhideminor": "מינערדיקער רעדאקטירונגען",
        "wlshowhidebots": "באטן",
        "wlshowhideanons": "אַנאָנימע באַניצער",
        "wlshowhidepatr": "פאטראלירטע רעדאקטירונגען",
        "wlshowhidemine": "מיינע רעקאקטירונגען",
+       "wlshowhidecategorization": "בלאט קאטעגאריזירונג",
        "watchlist-options": "אויפֿפאַסן ליסטע ברירות",
        "watching": "אויפפאסענדונג…",
        "unwatching": "נעמט אראפ פון אויפפאסונג ליסטע…",
        "delete-toobig": "דער בלאַט האט א גרויסע רעדאקטירונג היסטאריע, מער ווי $1 {{PLURAL:$1|רעוויזיע|רעוויזיעס}}. אויסמעקן אזעלכע בלעטער איז באַגרענעצט געווארן בכדי צו פֿאַרמײַדן א צופֿעליגע פֿאַרשטערונג פֿון  {{SITENAME}}.",
        "delete-warning-toobig": "דער בלאַט האט א גרויסע רעדאקטירונג היסטאריע, מער ווי $1 {{PLURAL:$1|רעוויזיע|רעוויזיעס}}. אויסמעקן אים קען פֿאַרשטערן דאַטנבאַזע אפעראַציעס פֿון {{SITENAME}}; זײַט פֿארזיכטיג איידער איר מעקט אויס.",
        "deleteprotected": "איר קענט נישט אויסמעקן דעם בלאט ווײַל ער איז געשיצט.",
-       "deleting-backlinks-warning": "'''ווארענוג:'''\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|אנדערע בלעטער]]  פארבינדן צום בלאט אדער אריבערשליסן דעם בלאט איר האלט ביי אויסמעקן.",
+       "deleting-backlinks-warning": "</strong>ווארענוג:<strong>\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|אנדערע בלעטער]]  פארבינדן צום בלאט אדער אריבערשליסן דעם בלאט איר האלט ביי אויסמעקן.",
        "rollback": "צוריקדרייען רעדאַקטירונגען",
        "rollbacklink": "צוריקדרייען",
        "rollbacklinkcount": "צוריקדרייען $1 {{PLURAL:$1|רעדאקטירונג|רעדאקטירונגען}}",
        "rollbackfailed": "צוריקדרייען דורכגעפֿאַלן",
        "cantrollback": "מען קען נישט צוריקדרייען די ענדערונג – דער לעצטער בײַשטייערער איז דער איינציגסטער שרײַבער אין דעם בלאַט.",
        "alreadyrolled": "מען קען נישט צוריקדרייען די לעצטע ענדערונג פון בלאט [[:$1]] פֿון\n[[User:$2|$2]] ([[User talk:$2|רעדן]]{{int:pipe-separator}} [[Special:Contributions/$2|{{int:contribslink}}]]);\nאן אנדערער האט שוין געענדערט אדער צוריקגעדרייט דעם בלאט.\n\nדי לעצטע ענדערונג צום בלאַט איז געווען פון [[User:$3|$3]] ([[User talk:$3|רעדן]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
-       "editcomment": "קורץ ווארט איז געווען: \"'''$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",
        "changecontentmodel-title-label": "בלאט־טיטל",
        "changecontentmodel-model-label": "נייער אינהאלט מאדעל",
        "changecontentmodel-reason-label": "אורזאַך:",
+       "changecontentmodel-submit": "טוישן",
        "logentry-contentmodel-change-revertlink": "צוריקשטעלן",
        "logentry-contentmodel-change-revert": "צוריקשטעלן",
        "protectlogpage": "באשיצונג לאָג-בוך",
        "ipb-unblock": "אויפֿבלאקירן א באַניצער נאמען אדער IP אדרעס",
        "ipb-blocklist": "זעט עקזיסטירנדע בלאקירונגען",
        "ipb-blocklist-contribs": "בײַשטײַערונגען פֿון {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "נאך $1",
        "unblockip": "אויפֿבלאקירן באניצער",
        "unblockiptext": "מיט דעם פארמולאר קענט איר צוריקשטעלן שרייבן ערלויבניש צו אן IP אדרעס אדער באניצער נאמען וואס איז געווען בלאקירט.",
        "ipusubmit": "אוועקנעמען דעם בלאק",
        "blocklink": "ארויסטרייבן",
        "unblocklink": "באַפֿרײַען",
        "change-blocklink": "ענדערן בלאק",
-       "contribslink": "×\91×\90Ö·× ×\99צערס ×\91ײַש×\98ײַער×\95× ×\92×¢×\9f",
+       "contribslink": "בײַשטײַערונגען",
        "emaillink": "שיקן ע־פאסט",
        "autoblocker": "אויטאמאטיש בלאקירט ווייל אײַער IP אדרעס איז לעצטנס געניצט געווארן דורך [[User:$1|$1]]. \nדער סיבה וואס איז אנגעבען געווארן איז: \"$2\".",
        "blocklogpage": "בלאקירן לאג",
        "import-nonewrevisions": "קיין רעוויזיעס נישט אימפארטירט (אדער אלע שוין דא, אדער איבערגעהיפט צוליב גרײַזן).",
        "xml-error-string": "$1 בײַ שורה $2, זייל $3 (בייט $4): $5",
        "import-upload": "אַרויפֿלאָדן XML דאַטן",
-       "import-token-mismatch": "×\90ָנ×\95×\95ער ×¤×\95×\9f ×¡×¢×¡×\99×¢ ×\93×\90Ö·×\98×\9f.\n ×\91×\99×\98×¢ ×¤×¨×\95×\91×\99ר×\98 × ×\90×\9b×\90×\9e×\90×\9c.",
+       "import-token-mismatch": "פ×\90ר×\9c×\95ס×\98 ×¤×\95×\9f ×¡×¢×¡×\99×¢ ×\93×\90×\98×\9f. \n\nקע×\9f ×\96×\99×\99×\9f ×\90×\96 ×\90×\99ר ×\96×¢× ×¢×\9f ×\92×¢×\95×\95×\90ר×\9f ×\90ר×\95×\99ס×\9c×\90×\92×\99ר×\98\n<strong>×\91×\99×\98×¢ ×¤×¨×\95×\91×\99ר×\98 × ×\90×\9b×\90×\9e×\90×\9c</strong>. \n\n×\90×\95×\99×\91 ×¡'×\90ר×\91×¢×\98 × ×\90×\9a ×\90×\9cס × ×\99×\98, ×¤×¨×\95×\91×\99ר×\98 [[Special:UserLogout|×\90ר×\95×\99ס×\9c×\90×\92×\99ר×\9f]] ×\90×\95×\9f ×\96×\99×\9a ×¦×\95ר×\99ק ×\90ר×\99×\99× ×\9c×\90×\92×\99ר×\9f.",
        "import-invalid-interwiki": "נישט מעגלעך צו אימפארטירן פון ספעציפֿירטער וויקי.",
        "import-error-edit": "דעם בלאט \"$1\" קען מען נישט אימפארטירן ווייל איר האט נישט די רעכט אים צו רעדאקטירן.",
        "import-error-create": "דעם  בלאט \"$1\" האט מען נישט אימפארטירט ווייל איר האט נישט די רעכט צו שאפן אים.",
        "tooltip-pt-mycontris": "ליסטע פון {{GENDER:|אייערע}} ביישטייערונגען",
        "tooltip-pt-login": "עס איז רעקאָמענדירט זיך אײַנשרײַבן; ס'איז אבער נישט קיין פֿליכט",
        "tooltip-pt-logout": "ארויסלאגירן",
-       "tooltip-pt-createaccount": "איר ווערט דערמוטיגט צו שאפן א קאנטע און אריינלאגירן; ס'איז אביר נישט אבליגאטאריש",
+       "tooltip-pt-createaccount": "איר ווערט דערמוטיגט צו שאַפֿן א קאנטע און אריינלאגירן; ס׳איז אבער נישט פארפליכטעט",
        "tooltip-ca-talk": "שמועס וועגן דעם אינהאַלט בלאַט",
-       "tooltip-ca-edit": "רעדאקטירן דעם בלאַט",
+       "tooltip-ca-edit": "רעדאַקטירן דעם בלאַט",
        "tooltip-ca-addsection": "אָנהייבן א נײַע אָפטיילונג",
        "tooltip-ca-viewsource": "דאס איז א פֿארשלאסענער בלאט, איר קענט נאר באַקוקן זיין מקור",
        "tooltip-ca-history": "פריערדיגע ווערסיעס פון דעם בלאט.",
        "tooltip-ca-move": "באַוועגן דעם בלאַט",
        "tooltip-ca-watch": "לייגט צו דעם בלאט אויפצופאסן",
        "tooltip-ca-unwatch": "נעמט אַראָפּ דעם בלאַט פון נאָכפאָלג־ליסטע",
-       "tooltip-search": "×\96×\95×\9b×\98 ×\90×\99× ×¢×\9d ×¡×\99×\99×\98",
+       "tooltip-search": "×\96×\95×\9a {{SITENAME}}",
        "tooltip-search-go": "גייט צו א בלאט מיט אט דעם נאמען, אויב ער עקסיסטירט",
        "tooltip-search-fulltext": "זוכט דעם טעקסט אין די בלעטער",
-       "tooltip-p-logo": "×\94×\95×\99פ×\98 ×\96×\99×\99ט",
+       "tooltip-p-logo": "×\91×\90Ö·×\96×\95×\9b×\9f ×\93×¢×\9d ×\94×\95×\99פ×\98 ×\91×\9c×\90Ö·ט",
        "tooltip-n-mainpage": "באַזוכט דעם הויפּט־זײַט",
        "tooltip-n-mainpage-description": "באַזוכן דעם הויפט בלאַט",
        "tooltip-n-portal": "גייט אריין אין די געמיינדע צו שמועסן",
        "tooltip-n-currentevents": "מער אינפארמאציע איבער אקטועלע געשענישען",
-       "tooltip-n-recentchanges": "ליסטע פון לעצטע ענדערונגען",
+       "tooltip-n-recentchanges": "ליסטע פון לעצטע ענדערונגען אין דעם וויקי",
        "tooltip-n-randompage": "וועלט אויס א צופעליגער בלאט",
-       "tooltip-n-help": "×\94×\99×\9c×£",
-       "tooltip-t-whatlinkshere": "×\90×\9c×¢ ×\91×\9c×¢×\98ער ×\95×\95×\90ס ×¤×\90ר×\91×\99× ×\93×¢×\9f ×¦×\95 ×\93×¢×\9d ×\91×\9c×\90×\98",
+       "tooltip-n-help": "×\93ער ×¤×\9c×\90×¥ ×\90×\95×\99סצ×\95×\92עפ×\99× ×¢×\9f",
+       "tooltip-t-whatlinkshere": "×\90×\9c×¢ ×\9c×\99ס×\98×¢ ×¤×\95×\9f ×\90×\9c×¢ ×\91×\9c×¢×\98ער ×\95×\95×\90ס ×¤×\90ר×\91×\99× ×\93×\98 ×\90×\94ער",
        "tooltip-t-recentchangeslinked": "אלע ענדערונגען פון בלעטער וואס זענען אהער פארבינדען",
        "tooltip-feed-rss": "דערהײַנטיגט אויטאמאטיש פון אר.עס.עס. RSS",
        "tooltip-feed-atom": "לייג צו אן אטאמאטישער אפדעיט דורך אטאם Atom",
        "lastmodifiedatby": "די לעצטע ענדערונג פֿון דעם בלאַט איז געווען $2, $1 דורך $3.",
        "othercontribs": "באזירט אויף ארבעט פון $1.",
        "others": "אנדערע",
-       "siteusers": "{{PLURAL:$2|באַניצער| באַניצערס}} {{SITENAME}} $1",
+       "siteusers": "{{SITENAME}} {{PLURAL:$2|{{GENDER:$1| באַניצער}}| באַניצערס}} $1",
        "anonusers": "{{SITENAME}} {{PLURAL:$2| אַנאנימער באַניצער|אַנאנימע באַניצער}} $1",
        "creditspage": "בלאט קרעדיטס",
        "nocredits": "נישט פאראן קיין אינפארמאציע פאר דעם בלאט.",
        "scarytranscludedisabled": "[אינטערוויקי אריבערשליסן איז אַנולירט]",
        "scarytranscludetoolong": "[URL צו לאנג]",
        "deletedwhileediting": "ווארענונג: דער בלאט איז געווארן אויסגעמעקט נאכדעם וואס איר האט אנגעהויבן רעדאקטירן!",
-       "confirmrecreate": "באַניצער [[User:$1|$1]] ([[User talk:$1|רעדן]]) האט אויסגעמעקט דעם בלאט נאכדעם וואס איר האט אנגעהויבן דאס צו ענדערן, אלס אָנגעבליכער סיבה:\n:'''$2'''\nביטע באשטעטיגט אז איר ווילט טאקע צוריקשטעלן דעם בלאט.",
+       "confirmrecreate": "באַניצער [[User:$1|$1]] ([[User talk:$1|רעדן]]) {{GENDER:$1|האט אויסגעמעקט}} דעם בלאט נאכדעם וואס איר האט אנגעהויבן דאס צו ענדערן, אלס אָנגעבליכער סיבה:\n: <em>$2</em>\nביטע באשטעטיגט אז איר ווילט טאקע צוריקשטעלן דעם בלאט.",
        "recreate": "שאַפֿן פֿונדאסניי",
        "confirm_purge_button": "אויספֿירן",
        "confirm-purge-top": "אויסקלארן די קאשעי פון דעם בלאט?",
        "fileduplicatesearch-submit": "זוכן",
        "fileduplicatesearch-info": "$1 × $2 פיקסעל<br />טעקע גרייס: $3<br /> טיפ MIME: $4",
        "fileduplicatesearch-noresults": "קיין טעקע מיטן נאמען \"$1\" נישט געטראפֿן.",
-       "specialpages": "ספּעציעלע זײַטן",
+       "specialpages": "ספעציעלע בלעטער",
        "specialpages-note-top": "לעגענדע",
        "specialpages-note": "* נארמאַלע באַזונדערע בלעטער.\n* <span class=\"mw-specialpagerestricted\">באַגרענעצטע באַזונדערע בלעטער.</span>",
        "specialpages-group-maintenance": "אויפֿהאַלטונג באַריכטן",
        "logentry-newusers-create2": "באניצער קאנטע $1 איז {{GENDER:$2|געשאפן געווארן}} דורך $3",
        "logentry-newusers-byemail": "באניצער קאנטע $3 איז {{GENDER:$2|געשאפן געווארן}} דורך $1 און דאס פאסווארט איז געשיקט געווארט דורך ע־פאסט",
        "logentry-newusers-autocreate": "באַניצער קאנטע $1 {{GENDER:$2|געשאפן}} אויטאמאטיש",
-       "logentry-rights-rights": "$1 האָט {{GENDER:$2|געביטן}} גרופּע מיטגלידערשאַפט פאַר $3 פון $4 אויף $5",
+       "logentry-rights-rights": "$1 האָט {{GENDER:$2|געביטן}} גרופּע מיטגלידערשאַפט פאַר {{GENDER:$6|$3}} פון $4 אויף $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|האט געביטן}} גרופע מיטגלידערשאפט פאר $3",
        "logentry-rights-autopromote": "$1 אויטאמאטיש  {{GENDER:$2|פראמאווירט}} פון $4 צו $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|האט ארויפגעלאדן}} $3",
        "feedback-subject": "טעמע:",
        "feedback-submit": "אײַנגעבן",
        "feedback-thanks": "ייש\"כ! אײַער פֿידבעק איז געווארן ארויפגעלעגט צום בלאט \"[$2 $1]\".",
-       "searchsuggest-search": "×\96×\95×\9b×\9f",
+       "searchsuggest-search": "×\96×\95×\9a",
        "searchsuggest-containing": "כולל…",
        "api-error-badaccess-groups": "איר האט נישט קיין רעכטן אַרויפֿלאָדן טעקעס אויף דער וויקי.",
        "api-error-badtoken": "אינערלעכער גרײַז: סימן טויג נישט.",
index 3b75367..b12aac5 100644 (file)
        "noname": "Ẹ kò tọ́kasí orúkọ oníṣe tó ní ìbámu.",
        "loginsuccesstitle": "Ìwọlé ti yọrí sí rere",
        "loginsuccess": "'''Ẹ ti wọlé sínú {{SITENAME}} gẹ́gẹ́ bi \"$1\".'''",
-       "nosuchuser": "Kò sí oníṣe kankan pẹ̀lú orúkọ \"$1\".\nÀwọn lẹ́tà àwọn orúkọ oníṣe gbọ́dọ̀ jẹ́ irúkanna.\nẸ yẹ lẹ́tà yín wò, tàbí [[Special:UserLogin/signup|kí ẹ dá àkópamọ́ tuntun]].",
+       "nosuchuser": "Kò sí oníṣe kankan pẹ̀lú orúkọ \"$1\".\nÀwọn lẹ́tà àwọn orúkọ oníṣe gbọ́dọ̀ jẹ́ irúkanna.\nẸ yẹ lẹ́tà yín wò, tàbí [[Special:CreateAccount|kí ẹ dá àkópamọ́ tuntun]].",
        "nosuchusershort": "Kò sí oníṣe t'ón jẹ́ $1.\nẸ yẹ lẹ́tà ọ̀rọ̀ yín wò.",
        "nouserspecified": "Ẹ gbọ́dọ̀ tọ́kasí orúkọ oníṣe kan.",
        "login-userblocked": "Oníṣe yìí jẹ́ dídínà. Ìwọlé kò jẹ́ gbígbà láyè.",
        "accmailtext": "A ti fi ọ̀rọ̀ìpamọ́ àrìnàkò tí a pèsè fún [[User talk:$1|$1]] ránṣẹ́ sí $2. Ẹ le ṣe àyípadà ọ̀rọ̀ìpamọ́ fún àpamọ́ tuntun yìí ní ibi ''[[Special:ChangePassword|àyípadà ọ̀rọ̀ìpamọ́]]'' lẹ́yìn tí ẹ bá ti jáwọlé.",
        "newarticle": "(Tuntun)",
        "newarticletext": "Ẹ ti tẹ̀lé ìjápọ̀ mọ́ ojúewé tí kò sí.\nLáti dá ojúewé yí ẹ bẹ̀rẹ̀ síní tẹ́kọ sí inú àpótí ìsàlẹ̀ yí (ẹ wo [$1 ojúewé ìrànlọ́wọ́ ] fun ẹ̀kúnrẹ́rẹ́ ).\nT'óbá sepé àsìse ló gbé yin dé bi, ẹ kọn bọ́tìnì ìpadàsẹ́yìn.",
-       "anontalkpagetext": "''Ojúewé ìfọ̀rọ̀wérọ̀ yìí wà fún oníṣe aláílórúkọ tí kò tíì dá àkópamọ́, tàbí tí kò lò ó rárá.\nBí bẹ́ẹ̀ laṣe únlo àdírẹ́ẹ̀sì IP oníyenọ́mbà láti dáamọ̀.\nIrú àdírẹ́ẹ̀sì IP báun ṣeéṣe kó jẹ́ pínpínlọ̀ pẹ̀lú àwọn oníṣe míràn.\nTó bá jẹ́ pé oníṣe aláìlórúkọ ni yín, tí ẹ sì ri pé wọ́n ùnsọ̀rọ̀ tí kò kàn yín sí i yín, ẹ jọ̀wọ́ [[Special:UserLogin/signup|ẹ dá àkópamọ́ kan]] tàbí [[Special:UserLogin|kí ẹ wọlẹ́]] kó mọ́ baà sí ìdàrúpọ̀ lọ́jọ́ọwájú mọ́ àwọn oníṣe aláìlórúkọ mírán.''",
+       "anontalkpagetext": "''Ojúewé ìfọ̀rọ̀wérọ̀ yìí wà fún oníṣe aláílórúkọ tí kò tíì dá àkópamọ́, tàbí tí kò lò ó rárá.\nBí bẹ́ẹ̀ laṣe únlo àdírẹ́ẹ̀sì IP oníyenọ́mbà láti dáamọ̀.\nIrú àdírẹ́ẹ̀sì IP báun ṣeéṣe kó jẹ́ pínpínlọ̀ pẹ̀lú àwọn oníṣe míràn.\nTó bá jẹ́ pé oníṣe aláìlórúkọ ni yín, tí ẹ sì ri pé wọ́n ùnsọ̀rọ̀ tí kò kàn yín sí i yín, ẹ jọ̀wọ́ [[Special:CreateAccount|ẹ dá àkópamọ́ kan]] tàbí [[Special:UserLogin|kí ẹ wọlẹ́]] kó mọ́ baà sí ìdàrúpọ̀ lọ́jọ́ọwájú mọ́ àwọn oníṣe aláìlórúkọ mírán.''",
        "noarticletext": "Lọ́wọ́lọ́wọ́ kò sí ìkọ̀ nínú ojúewé yìí.\nẸ le [[Special:Search/{{PAGENAME}}|wá àkọlé ojúewé yìí]] nínú àwọn ojúewé mìíràn,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} wá àkọọ́lẹ̀ rẹ̀], tàbí [{{fullurl:{{FULLPAGENAME}}|action=edit}} kí ẹ ṣ'àtúnṣe ojúewé òún]</span>.",
        "noarticletext-nopermission": "Lọ́wọ́lọ́wọ́ kò sí ìkọ̀ nínú ojúewé yìí.\nẸ le [[Special:Search/{{PAGENAME}}|wá àkọlé ojúewé yìí]] nínú àwọn ojúewé mìíràn, tàbí\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} wá àwọn àkọọ́lẹ̀ tó bámu]</span>, sùgbọ́n ẹ kò ní àṣẹ láti ṣ'ẹ̀dá ojúewé yìí.",
        "missing-revision": "Àtúnyẹ̀wò #$1 ojúewé tó únjẹ́ \"{{FULLPAGENAME}}\" kò sí.\n\nÈyí únsábà ṣẹlẹ̀ nítorípé ẹ tẹ̀lé ìtàn àjápọ̀ tí kò ṣiṣẹ́ mọ́ wá sí orí ojúewé tó ti jẹ́ píparẹ́.\nẸ̀kúnrẹ́rẹ́ wà nínú [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} àkọọ́lẹ̀ ìparẹ́].",
index 1e1cd61..925e37f 100644 (file)
        "noname": "你未指定一個有效嘅用戶名。",
        "loginsuccesstitle": "登入成功",
        "loginsuccess": "'''「$1」登入咗{{SITENAME}}。'''",
-       "nosuchuser": "呢度冇叫做 \"$1\"嘅用戶。\n用戶名係有分大細楷嘅。\n請檢查你個名嘅輸入方法,或者[[Special:UserLogin/signup|建立一個新嘅戶口]]。",
+       "nosuchuser": "呢度冇叫做 \"$1\"嘅用戶。\n用戶名係有分大細楷嘅。\n請檢查你個名嘅輸入方法,或者[[Special:CreateAccount|建立一個新嘅戶口]]。",
        "nosuchusershort": "呢度冇叫做 \"$1\"嘅用戶。 請檢查你個名嘅輸入方法。",
        "nouserspecified": "你需要指定一個用戶名。",
        "login-userblocked": "呢位用戶封鎖咗。唔容許登入。",
        "accmailtext": "「[[User talk:$1|$1]]」嘅隨機產生密碼已經寄咗去 $2。\n\n呢個密碼可以響簽到咗之後嘅<em>[[Special:ChangePassword|改密碼]]</em> 版度改佢。",
        "newarticle": "(新)",
        "newarticletext": "你連連過嚟嘅頁面重未存在。\n要起版新嘅,請你喺下面嗰格度輸入。(睇睇[$1 自助版]拎多啲資料。)\n如果你係唔覺意嚟到呢度,撳一次你個瀏覽器'''返轉頭'''個掣。",
-       "anontalkpagetext": "----''呢度係匿名用戶嘅討論頁,佢可能係重未開戶口,或者佢重唔識開戶口。我哋會用數字表示嘅IP地址嚟代表佢。一個IP地址係可以由幾個用戶夾來用。如果你係匿名用戶,同覺得呢啲留言係同你冇關係嘅話,唔該去[[Special:UserLogin/signup|開一個新戶口]]或[[Special:UserLogin|登入]],避免喺以後嘅留言會同埋其它用戶混淆。''",
+       "anontalkpagetext": "----''呢度係匿名用戶嘅討論頁,佢可能係重未開戶口,或者佢重唔識開戶口。我哋會用數字表示嘅IP地址嚟代表佢。一個IP地址係可以由幾個用戶夾來用。如果你係匿名用戶,同覺得呢啲留言係同你冇關係嘅話,唔該去[[Special:CreateAccount|開一個新戶口]]或[[Special:UserLogin|登入]],避免喺以後嘅留言會同埋其它用戶混淆。''",
        "noarticletext": "喺呢一頁而家並冇任何嘅文字,你可以喺其它嘅頁面中[[Special:Search/{{PAGENAME}}|搵呢一頁嘅標題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搵有關嘅日誌],\n或者[{{fullurl:{{FULLPAGENAME}}|action=edit}} 編輯呢一版]</span>。",
        "noarticletext-nopermission": "呢一頁而家冇任何文字,你可以喺其它嘅頁面中[[Special:Search/{{PAGENAME}}|搵呢一頁嘅標題]],或者<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搵有關嘅日誌]</span>。",
        "missing-revision": "The revision #$1 of the page named \"{{FULLPAGENAME}}\" does not exist.\n\nThis is usually caused by following an outdated history link to a page that has been deleted.\nDetails can be found in the [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].\n\n《{{FULLPAGENAME}}》嘅編輯#$1唔存在。\n\n恁通常係因為一條過徂時嘅鏈接帶徂閣下去一個已經刪除徂嘅版。\n詳情請查閱[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪文紀錄]。",
        "upload-form-label-infoform-description": "描述",
        "upload-form-label-usage-title": "用法",
        "upload-form-label-usage-filename": "文件名",
-       "foreign-structured-upload-form-label-own-work": "呢個係我自己嘅作品",
-       "foreign-structured-upload-form-label-infoform-categories": "分類",
-       "foreign-structured-upload-form-label-infoform-date": "日子",
+       "upload-form-label-own-work": "呢個係我自己嘅作品",
+       "upload-form-label-infoform-categories": "分類",
+       "upload-form-label-infoform-date": "日子",
        "backend-fail-stream": "傳送唔到檔案「$1」。",
        "backend-fail-backup": "檔案 \"$1\" 唔備份得。",
        "backend-fail-notexists": "檔案$1唔存在。",
index ac41f0c..f29afc7 100644 (file)
        "noname": "Je ei hin heldihe gebrukersnaem opeheven.",
        "loginsuccesstitle": "Anmelden geslaegd",
        "loginsuccess": "'''Je bin noe anemeld bie {{SITENAME}} as \"$1\".'''",
-       "nosuchuser": "De gebruker \"$1\" besti nie.\nControleer de schriefwieze of [[Special:UserLogin/signup|mik een nieuwe gebruker an]].",
+       "nosuchuser": "De gebruker \"$1\" besti nie.\nControleer de schriefwieze of [[Special:CreateAccount|mik een nieuwe gebruker an]].",
        "nosuchusershort": "De gebruker \"$1\" besti nie. Controleer de schriefwieze.",
        "nouserspecified": "Je dien een gebrukersnaem op te heven.",
        "wrongpassword": "Wachtwoôrd onjuust. Probeer 't opnieuw.",
        "allpagesbadtitle": "D'n ingegeven bladzie-titel was ongeldeg of ao 'n interwiki-vòvoegsel. Meschien stae d'r eên of meer teêkens in die-an nie in titels gebruukt ore kunne.",
        "categories": "Categorieën",
        "categoriespagetext": "De wiki eit de volgende categorieën.\n[[Special:UnusedCategories|Unused categories]] are not shown here.\nAlso see [[Special:WantedCategories|wanted categories]].",
-       "special-categories-sort-count": "op antal sorteern",
-       "special-categories-sort-abc": "alfabetisch sorteern",
        "linksearch-line": "$1 ei 'n verwiezienge in $2",
        "listgrouprights-members": "(ledenlieste)",
        "emailuser": "E-mail deêze gebruker",
index bde32ba..7b995ee 100644 (file)
        "password-change-forbidden": "您不能在本wiki上更改密码。",
        "externaldberror": "验证数据库出错或您被禁止更新您的外部账号。",
        "login": "登录",
+       "login-security": "证明您的身份",
        "nav-login-createaccount": "登录/创建账户",
        "userlogin": "登录/创建账户",
        "userloginnocreate": "登录",
        "userlogin-resetpassword-link": "忘记密码?",
        "userlogin-helplink2": "登录帮助",
        "userlogin-loggedin": "您已经以{{GENDER:$1|$1}}的身份登录。使用下面的表格以其他用户的身份登录。",
+       "userlogin-reauth": "您必须再次登录以证明您是{{GENDER:$1|$1}}。",
        "userlogin-createanother": "创建另一个账户",
        "createacct-emailrequired": "电子邮件地址",
        "createacct-emailoptional": "电子邮件地址(可选)",
        "createacct-email-ph": "请输入你的电子邮件地址",
        "createacct-another-email-ph": "输入电子邮件地址",
        "createaccountmail": "使用一个临时的随机密码并将其发送到指定的电子邮件地址中",
+       "createaccountmail-help": "可被用于为另一个人创建账户而不需要得知密码。",
        "createacct-realname": "真实姓名(可选)",
        "createaccountreason": "原因:",
        "createacct-reason": "原因",
        "createacct-reason-ph": "您为什么要创建另一个账户",
+       "createacct-reason-help": "在账户创建日志中显示的消息",
        "createacct-submit": "创建您的账户",
        "createacct-another-submit": "创建账户",
+       "createacct-continue-submit": "继续账户创建",
+       "createacct-another-continue-submit": "继续账户创建",
        "createacct-benefit-heading": "{{SITENAME}}是由同你一样的人们构筑的。",
        "createacct-benefit-body1": "{{PLURAL:$1|编辑}}",
        "createacct-benefit-body2": "{{PLURAL:$1|页面}}",
        "nocookiesnew": "该用户帐户已被创建,但登录失败。{{SITENAME}}使用Cookie实现用户登录。您已禁用Cookie,请启用Cookie,然后使用您的新用户名与密码登录。",
        "nocookieslogin": "{{SITENAME}}使用Cookie实现用户登录。您已停用Cookie。请启用Cookie后再试。",
        "nocookiesfornew": "该用户账户未被创建,我们不能确认它的来源。请确保你已启用Cookie,刷新本页后再试。",
+       "createacct-loginerror": "账户已成功创建,但您不能自动登录。请继续[[Special:UserLogin|手动登录]]。",
        "noname": "未指定有效的用户名。",
        "loginsuccesstitle": "已登录",
        "loginsuccess": "<strong>您现在已经以\"$1\"的身份登录了{{SITENAME}}。</strong>",
-       "nosuchuser": "没有名为“$1”的用户。用户名区分大小写。请检查你的拼写或[[Special:UserLogin/signup|创建新账户]]。",
+       "nosuchuser": "没有名为“$1”的用户。用户名区分大小写。请检查您的拼写或[[Special:CreateAccount|创建新账户]]。",
        "nosuchusershort": "没有名为“$1”的用户。请检查您的拼写。",
        "nouserspecified": "您必须指定一个用户名。",
        "login-userblocked": "该用户已被封禁,禁止登录。",
        "createacct-another-realname-tip": "真实姓名是选填项目。\n如果你选择提供它,它将会用于贡献署名。",
        "pt-login": "登录",
        "pt-login-button": "登录",
+       "pt-login-continue-button": "继续登录",
        "pt-createaccount": "创建账户",
        "pt-userlogout": "退出",
        "php-mail-error-unknown": "在 PHP 的 mail() 函数中的未知错误",
        "changepassword-success": "您已经修改了您的密码!",
        "changepassword-throttled": "您最近尝试了多次登录。请等待$1后再试。",
        "botpasswords": "机器人密码",
-       "botpasswords-summary": "<em>机器人密码</em>允许通过API访问用户账户而不使用账户的主要登录凭据。通过机器人密码登录时,用户权限可能会受限制。\n\n如果您不知道为什么您想这样做,您就不应该这样做。没有人会要求您生成这些密码之一,并向其提供。",
+       "botpasswords-summary": "<em>机器人密码</em>允许在不使用账户的主要登录凭据的情况下,通过API访问用户账户。通过机器人密码登录时可用的用户权限可以被限制。\n\n在不知道为什么要这样做的情况下,您不应该使用此功能。任何人都不应该让您生成这些密码并向其提供。",
        "botpasswords-disabled": "机器人密码已禁用。",
        "botpasswords-no-central-id": "要使用机器人密码,您必须登录至已集中的账户。",
        "botpasswords-existing": "现有机器人密码",
        "botpasswords-label-delete": "删除",
        "botpasswords-label-resetpassword": "重置密码",
        "botpasswords-label-grants": "应用授权:",
-       "botpasswords-help-grants": "每个授权提供列举的,对用户账户已拥有的用户权限的访问权。参见[[Special:ListGrants|授权表]]以获取更多信息。",
+       "botpasswords-help-grants": "每个授权将会赋予被列出、且用户账户已拥有权限的访问权。参见[[Special:ListGrants|授权表]]以获取更多信息。",
        "botpasswords-label-restrictions": "使用限制:",
        "botpasswords-label-grants-column": "已授权",
        "botpasswords-bad-appid": "机器人名“$1”无效。",
        "botpasswords-invalid-name": "指定的用户名不包含机器人密码分隔符(“$1”)。",
        "botpasswords-not-exist": "用户“$1”没有名叫“$2”的机器人密码。",
        "resetpass_forbidden": "无法更改密码",
+       "resetpass_forbidden-reason": "密码不能更改:$1",
        "resetpass-no-info": "您必须登录后直接进入这个页面。",
        "resetpass-submit-loggedin": "更改密码",
        "resetpass-submit-cancel": "取消",
        "passwordreset-emailsentusername": "如果有邮件地址与此用户名相关联的话,将发送一封密码重置邮件。",
        "passwordreset-emailsent-capture": "密码重设电子邮件已发送,并在下面显示。",
        "passwordreset-emailerror-capture": "重置密码邮件已生成,但是无法向{{GENDER:$2|下列用户}} 发送:$1",
+       "passwordreset-emailsent-capture2": "密码重置{{PLURAL:$1|邮件}}已发送。{{PLURAL:$1|用户名和密码|用户名和密码列表}}在下方显示。",
+       "passwordreset-emailerror-capture2": "向{{GENDER:$2|用户}}发送电子邮件失败:$1 {{PLURAL:$3|用户名和密码|用户名和密码列表}}在下方显示。",
+       "passwordreset-nocaller": "必须提供一个调用方",
+       "passwordreset-nosuchcaller": "调用方不存在:$1",
+       "passwordreset-ignored": "密码重置没有处理。也许没有配置提供者?",
+       "passwordreset-invalideamil": "无效的电子邮件地址",
+       "passwordreset-nodata": "用户名和电子邮件地址均未提供",
        "changeemail": "更改或移除电子邮件地址",
        "changeemail-header": "完成此表格以更改您的电子邮件地址。如果您希望从您的账户中移除任何关联的电子邮件地址,请在提交表格时将新电子邮件地址留空。",
        "changeemail-passwordrequired": "您需要输入您的密码以确认此次更改。",
        "accmailtext": "为[[User talk:$1|$1]]随机生成的密码已送至$2。登录后可以在<em>[[Special:ChangePassword|更改密码]]</em>页面中修改。",
        "newarticle": "(新页面)",
        "newarticletext": "您点击了一个尚不存在的页面的链接。要创建该页面,请在下面的编辑框中输入内容(更多信息请见[$1 帮助页面])。如果您是错误地进入了此页面,请点击您的浏览器的<strong>返回</strong>按钮。",
-       "anontalkpagetext": "----\n<em>这是一个还未建立账户的匿名用户的讨论页, 因此我们只能用IP地址来与他或她联络。</em>该IP地址可能由几名用户共享。如果您是一名匿名用户并认为此页上的评语与您无关,请[[Special:UserLogin/signup|创建新账户]]或[[Special:UserLogin|登录]]以避免在未来与其他匿名用户混淆。",
+       "anontalkpagetext": "----\n<em>这是一个还未建立账户的匿名用户的讨论页, 因此我们只能用IP地址来与他或她联络。</em>该IP地址可能由几名用户共享。如果您是一名匿名用户并认为此页上的评语与您无关,请[[Special:CreateAccount|创建新账户]]或[[Special:UserLogin|登录]]以避免在未来与其他匿名用户混淆。",
        "noarticletext": "本页面目前没有内容。您可以在其他页面中[[Special:Search/{{PAGENAME}}|搜索本页标题]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜索相关日志]或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 创建本页面]</span>。",
        "noarticletext-nopermission": "本页面目前没有内容。您可以在其他页面中[[Special:Search/{{PAGENAME}}|搜索本页标题]]或<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜索相关日志]</span>,但您没有权限创建本页面。",
        "missing-revision": "“{{FULLPAGENAME}}”的版本#$1不存在。\n\n这通常是因为进入了一个已被删除的页面的历史链接。\n详细信息可以在[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 删除日志]中找到。",
        "right-override-export-depth": "导出页面,包括最多5层链接",
        "right-sendemail": "发送电子邮件给其他用户",
        "right-passwordreset": "查看密码重置电子邮件",
-       "right-managechangetags": "从数据库创建和删除[[Special:Tags|标签]]",
+       "right-managechangetags": "创建和(取消)激活[[Special:Tags|标签]]",
        "right-applychangetags": "连同某人的更改一起应用[[Special:Tags|标签]]",
        "right-changetags": "在个别修订和日志记录中添加和移除任意[[Special:Tags|标签]]",
+       "right-deletechangetags": "从数据库删除[[Special:Tags|标签]]",
        "grant-generic": "“$1”权限束",
        "grant-group-page-interaction": "与页面交互",
        "grant-group-file-interaction": "与媒体交互",
        "grant-group-email": "发送电子邮件",
        "grant-group-high-volume": "执行大量活动",
        "grant-group-customization": "自定义与设置",
-       "grant-group-administration": "执行行政操作",
+       "grant-group-administration": "执行管理操作",
        "grant-group-other": "杂项活动",
        "grant-blockusers": "封禁与解封用户",
        "grant-createaccount": "创建账户",
        "action-viewmyprivateinfo": "查看您的私人信息",
        "action-editmyprivateinfo": "编辑你的私人信息",
        "action-editcontentmodel": "编辑页面的内容模型",
-       "action-managechangetags": "创建和从数据库中删除标签",
+       "action-managechangetags": "创建和(取消)激活标签",
        "action-applychangetags": "连同您的更改应用标签",
        "action-changetags": "在个别修订和日志记录中添加和移除任意标签",
+       "action-deletechangetags": "从数据库删除标签",
        "nchanges": "$1次更改",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|上次访问后}}$1个",
        "enhancedrc-history": "历史",
        "upload-form-label-infoform-description-tooltip": "简单描述有关作品的任何显著信息。\n对于照片,可提及描述的主要事物、场景或地点。",
        "upload-form-label-usage-title": "用法",
        "upload-form-label-usage-filename": "文件名",
-       "foreign-structured-upload-form-label-own-work": "这是我的作品",
-       "foreign-structured-upload-form-label-infoform-categories": "分类",
-       "foreign-structured-upload-form-label-infoform-date": "日期",
-       "foreign-structured-upload-form-label-own-work-message-local": "我确认我正在依据{{SITENAME}}上的服务条款和许可协议方针上传文件。",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "如果您无法依据{{SITENAME}}的方针上传此文件,请关闭此对话框并尝试其他方法。",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "您也可以尝试[[Special:Upload|默认上传页面]]。",
-       "foreign-structured-upload-form-label-own-work-message-default": "我知道我正在上传此文件至一个共享的存储库。我确认我依据这里的服务条款和许可方针做此事。",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "如果您无法依据分享存储库的方针上传此文件,请关闭此对话框并尝试其他方法。",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "如果此文件可以依据他们的方针上传的话,您也可以尝试使用[[Special:Upload|{{SITENAME}}上的上传页面]]。",
-       "foreign-structured-upload-form-label-own-work-message-shared": "我证明我拥有此文件的版权,并不可撤销地同意采用[https://creativecommons.org/licenses/by-sa/4.0/ 知识共享 署名-相同方式共享 4.0]许可协议将此文件发布至维基共享资源,并且我同意[https://wikimediafoundation.org/wiki/Terms_of_Use 使用条款]。",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "如果您并不拥有此文件的版权,或者您希望将其以其他许可协议发布,请考虑使用[https://commons.wikimedia.org/wiki/Special:UploadWizard 共享资源的上传向导]。",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "如果网站允许依据他们的方针上传此文件的话,您也可以尝试使用[[Special:Upload|{{SITENAME}}上的上传页面]]。",
+       "upload-form-label-own-work": "这是我的作品",
+       "upload-form-label-infoform-categories": "分类",
+       "upload-form-label-infoform-date": "日期",
+       "upload-form-label-own-work-message-generic-local": "我确认我正在依据{{SITENAME}}上的服务条款和许可协议方针上传文件。",
+       "upload-form-label-not-own-work-message-generic-local": "如果您无法依据{{SITENAME}}的方针上传此文件,请关闭此对话框并尝试其他方法。",
+       "upload-form-label-not-own-work-local-generic-local": "您也可以尝试[[Special:Upload|默认上传页面]]。",
+       "upload-form-label-own-work-message-generic-foreign": "我知道我正在上传此文件至一个共享的存储库。我确认我依据这里的服务条款和许可方针做此事。",
+       "upload-form-label-not-own-work-message-generic-foreign": "如果您无法依据分享存储库的方针上传此文件,请关闭此对话框并尝试其他方法。",
+       "upload-form-label-not-own-work-local-generic-foreign": "如果此文件可以依据他们的方针上传的话,您也可以尝试使用[[Special:Upload|{{SITENAME}}上的上传页面]]。",
        "backend-fail-stream": "无法流传送文件$1。",
        "backend-fail-backup": "无法备份文件$1。",
        "backend-fail-notexists": "条目$1不存在。",
        "listgrouprights-namespaceprotection-header": "名字空间限制",
        "listgrouprights-namespaceprotection-namespace": "名字空间",
        "listgrouprights-namespaceprotection-restrictedto": "允许用户编辑的权限",
-       "listgrants": "æ\9d\83é\99\90",
+       "listgrants": "æ\8e\88æ\9d\83",
        "listgrants-summary": "以下是与用户权限访问相关的授权列表。用户可以授予应用程序访问它们的账户,但限定于用户授予应用程序的功能权限。代表一位用户的应用程序不能使用用户没有的权限。\n这里可能有关于个别权限的[[{{MediaWiki:Listgrouprights-helppage}}|额外信息]]。",
        "listgrants-grant": "授权",
        "listgrants-rights": "权限",
        "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已使用非默认的内容模型“$5”{{GENDER:$2|创建}}页面$3",
        "whatlinkshere-prev": "{{PLURAL:$1|前|前$1个}}",
        "whatlinkshere-next": "{{PLURAL:$1|后|后$1个}}",
        "whatlinkshere-links": "←链接",
-       "whatlinkshere-hideredirs": "$1重定向",
-       "whatlinkshere-hidetrans": "$1嵌入",
-       "whatlinkshere-hidelinks": "$1链接",
-       "whatlinkshere-hideimages": "$1文件链接",
+       "whatlinkshere-hideredirs": "隐藏重定向",
+       "whatlinkshere-hidetrans": "隐藏嵌入",
+       "whatlinkshere-hidelinks": "隐藏链接",
+       "whatlinkshere-hideimages": "隐藏文件链接",
        "whatlinkshere-filters": "过滤器",
        "whatlinkshere-submit": "提交",
        "autoblockid": "自动封禁#$1",
        "lockdbsuccesstext": "数据库已锁定。<br />请记住在维护工作完成后[[Special:UnlockDB|解锁数据库]]。",
        "unlockdbsuccesstext": "数据库已解锁。",
        "lockfilenotwritable": "数据库锁定文件不可写。要锁定和解锁数据库,该文件必须对网络服务器可写。",
+       "databaselocked": "数据库已被锁定。",
        "databasenotlocked": "数据库未被锁定。",
        "lockedbyandtime": "(由 {{GENDER:$1|$1}} 于$2 $3执行)",
        "move-page": "移动$1",
        "timezone-local": "本地",
        "duplicate-defaultsort": "<strong>警告:</strong>默认排序关键词“$2”覆盖了之前的默认排序关键词“$1”。",
        "duplicate-displaytitle": "<strong>警告:</strong>显示的标题“$2”重写了此前显示的标题“$1”。",
+       "restricted-displaytitle": "<strong>警告:</strong>显示标题“$1”被忽略,因为它不等同于页面的实际标题。",
        "invalid-indicator-name": "<strong>错误:</strong>页面状态指示器的<code>name</code>属性必须不为空。",
        "version": "版本",
        "version-extensions": "安装的扩展程序",
        "tags-delete-not-found": "标签“$1”不存在。",
        "tags-delete-too-many-uses": "“$1”标签已应用到超过$2个修订版本,这意味着它不能被删除。",
        "tags-delete-warnings-after-delete": "标签“$1”已删除,但遇到了以下{{PLURAL:$2|警告}}:",
+       "tags-delete-no-permission": "您没有权限删除更改标签。",
        "tags-activate-title": "激活标签",
        "tags-activate-question": "您将要激活标签“$1”。",
        "tags-activate-reason": "原因:",
        "feedback-useragent": "用户代理:",
        "searchsuggest-search": "搜索",
        "searchsuggest-containing": "含有...",
+       "api-error-autoblocked": "您的IP地址已被自动封禁,因为它曾被一位已封禁用户使用。",
        "api-error-badaccess-groups": "您没有将文件上传到此 wiki 的权限。",
        "api-error-badtoken": "内部错误:会话无效。",
+       "api-error-blocked": "您已被封禁,不能编辑。",
        "api-error-copyuploaddisabled": "通过URL上传的功能已被此服务器禁用。",
        "api-error-duplicate": "在网站上已经具有相同内容的{{PLURAL:$1|另一个文件|另一些文件}}。",
        "api-error-duplicate-archive": "在网站上曾经具有相同内容的{{PLURAL:$1|另一个文件|另一些文件}},但已被删除。",
        "api-error-nomodule": "内部错误:缺少上传模块集。",
        "api-error-ok-but-empty": "内部错误:服务器没有响应。",
        "api-error-overwrite": "不允许覆盖现有文件。",
+       "api-error-ratelimited": "您正在尝试在比该wiki允许时间更短的时间内上传更多文件。请等待几分钟后再试。",
        "api-error-stashfailed": "内部错误:服务器保存临时文件失败。",
        "api-error-publishfailed": "内部错误:服务器发布临时文件失败。",
        "api-error-stasherror": "上传文件存档时出现错误。",
        "log-action-filter-suppress-block": "通过封禁的用户屏蔽",
        "log-action-filter-suppress-reblock": "通过再封禁的用户屏蔽",
        "log-action-filter-upload-upload": "新上传",
-       "log-action-filter-upload-overwrite": "重新上传"
+       "log-action-filter-upload-overwrite": "重新上传",
+       "authmanager-authn-not-in-progress": "身份验证尚未进行,或会话数据丢失。请从头重新开始。",
+       "authmanager-authn-no-primary": "提供的证书不能被验证。",
+       "authmanager-authn-no-local-user": "提供的证书没有与该wiki上的任何用户相关联。",
+       "authmanager-authn-no-local-user-link": "提供的证书有效,但没有与该wiki上的任何用户相关联。请通过不同方式登录,或创建一个新用户,然后您将拥有一个把您之前的证书链接到对应账户的选项。",
+       "authmanager-authn-autocreate-failed": "所有账户的自动创建失败:$1",
+       "authmanager-change-not-supported": "提供的证书不能被更改,因为没有东西会使用它们。",
+       "authmanager-create-disabled": "账户创建已停用。",
+       "authmanager-create-from-login": "要创建您的账户,请填写下方的字段。",
+       "authmanager-create-not-in-progress": "账户创建尚未进行,或会话数据丢失。请从头重新开始。",
+       "authmanager-create-no-primary": "提供的证书不能用于账户创建。",
+       "authmanager-link-no-primary": "提供的证书不能用于账户链接。",
+       "authmanager-link-not-in-progress": "账户链接尚未进行,或会话数据丢失。请从头重新开始。",
+       "authmanager-authplugin-setpass-failed-title": "密码更改失败",
+       "authmanager-authplugin-setpass-failed-message": "身份验证插件拒绝了密码更改。",
+       "authmanager-authplugin-create-fail": "身份验证插件拒绝了账户创建。",
+       "authmanager-authplugin-setpass-denied": "身份验证插件不允许更改密码。",
+       "authmanager-authplugin-setpass-bad-domain": "无效域。",
+       "authmanager-autocreate-noperm": "不允许自动账户创建。",
+       "authmanager-autocreate-exception": "由于之前的错误,自动账户创建已临时停用。",
+       "authmanager-userdoesnotexist": "用户帐户“$1”尚未注册。",
+       "authmanager-userlogin-remembermypassword-help": "密码是否应为长于会话长度而被记住。",
+       "authmanager-username-help": "用于身份验证的用户名。",
+       "authmanager-password-help": "用于身份验证的密码。",
+       "authmanager-domain-help": "外部身份验证域。",
+       "authmanager-retype-help": "再次输入密码以确认。",
+       "authmanager-email-label": "电子邮件",
+       "authmanager-email-help": "电子邮件地址",
+       "authmanager-realname-label": "真实姓名",
+       "authmanager-realname-help": "用户的真实姓名",
+       "authmanager-provider-password": "基于密码的身份验证",
+       "authmanager-provider-password-domain": "基于密码和域的身份验证",
+       "authmanager-provider-temporarypassword": "临时密码",
+       "authprovider-confirmlink-message": "基于您最近的登录尝试,以下账户可被链接至您的wiki账户。链接它们会启用通过这些账户的登录。请选择应链接的账户。",
+       "authprovider-confirmlink-request-label": "应被链接的账户",
+       "authprovider-confirmlink-success-line": "$1:已成功连接。",
+       "authprovider-confirmlink-failed": "账户链接未完全成功:$1",
+       "authprovider-confirmlink-ok-help": "在显示链接失败消息后继续。",
+       "authprovider-resetpass-skip-label": "跳过",
+       "authprovider-resetpass-skip-help": "跳过重置密码。",
+       "authform-nosession-login": "身份验证已成功,但您的浏览器不能“记住”其登录。\n\n$1",
+       "authform-nosession-signup": "账户已创建,但您的浏览器不能“记住”其登录。\n\n$1",
+       "authform-newtoken": "丢失令牌。$1",
+       "authform-notoken": "丢失令牌",
+       "authform-wrongtoken": "错误令牌",
+       "specialpage-securitylevel-not-allowed-title": "不允许",
+       "specialpage-securitylevel-not-allowed": "对不起,您未被允许使用此页面,因为您的身份不能被验证。",
+       "authpage-cannot-login": "无法开始登录。",
+       "authpage-cannot-login-continue": "无法继续登录。您的会话大概已超时。",
+       "authpage-cannot-create": "无法开始账户创建。",
+       "authpage-cannot-create-continue": "无法继续账户创建。您的会话大概已超时。",
+       "authpage-cannot-link": "无法开始账户链接。",
+       "authpage-cannot-link-continue": "无法继续账户链接。您的会话大概已超时。",
+       "cannotauth-not-allowed-title": "权限被拒绝",
+       "cannotauth-not-allowed": "您不被允许使用此页面",
+       "changecredentials": "更改证书",
+       "changecredentials-submit": "更改",
+       "changecredentials-submit-cancel": "取消",
+       "changecredentials-invalidsubpage": "$1不是有效的证书类型。",
+       "changecredentials-success": "您的证书已被更改。",
+       "removecredentials": "移除证书",
+       "removecredentials-submit": "移除",
+       "removecredentials-submit-cancel": "取消",
+       "removecredentials-invalidsubpage": "$1不是有效的证书类型。",
+       "removecredentials-success": "您的证书已被移除。",
+       "credentialsform-provider": "证书类型:",
+       "credentialsform-account": "帐户名称:",
+       "cannotlink-no-provider-title": "没有可链接账户",
+       "cannotlink-no-provider": "没有可链接账户。",
+       "linkaccounts": "链接账户",
+       "linkaccounts-success-text": "账户已链接。",
+       "linkaccounts-submit": "链接帐户",
+       "unlinkaccounts": "取消链接账户",
+       "unlinkaccounts-success": "账户已取消链接。"
 }
index c3e5aaa..1e3c94d 100644 (file)
@@ -70,7 +70,9 @@
                        "Bowleerin",
                        "飞舞回堂前",
                        "Bbslam",
-                       "Zerng07"
+                       "Zerng07",
+                       "Reke",
+                       "Kly"
                ]
        },
        "tog-underline": "底線標示連結:",
        "password-change-forbidden": "您不可變更此 Wiki 上的密碼。",
        "externaldberror": "這可能是由於資料庫驗證錯誤,或是不允許您更新外部帳號。",
        "login": "登入",
+       "login-security": "驗証您的 ID",
        "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": "電子郵件地址 (選填)",
        "createaccountreason": "原因:",
        "createacct-reason": "原因",
        "createacct-reason-ph": "您為什麼要建立另一個帳號",
+       "createacct-reason-help": "顯示於帳號建立日誌的訊息",
        "createacct-submit": "建立您的帳號",
        "createacct-another-submit": "建立帳號",
+       "createacct-continue-submit": "繼續帳號建立",
+       "createacct-another-continue-submit": "繼續帳號建立",
        "createacct-benefit-heading": "{{SITENAME}} 是由像您一樣貢獻的人所建立的。",
        "createacct-benefit-body1": "{{PLURAL:$1|次編輯}}",
        "createacct-benefit-body2": "$1 頁",
        "nocookiesnew": "使用者帳號已建立成功,但您尚未登入。\n要登入 {{SITENAME}} 使用者需使用 Cookies,\n您的 Cookies 未尚開啟。\n請在開啟後使用您新的使用者名稱及密碼登入。",
        "nocookieslogin": "要登入 {{SITENAME}} 使用者需使用 Cookies,\n您的 Cookies 未尚開啟。\n請在開啟後重試。",
        "nocookiesfornew": "這個使用者的帳號未建立,我們不能確認它的來源。\n請確認您已開啟 Cookie,重新載入後再試。",
+       "createacct-loginerror": "已成功建立帳號,但無法自動登入。\n請繼續 [[Special:UserLogin|手動登入]]。",
        "noname": "您輸入的使用者名稱無效。",
        "loginsuccesstitle": "已登入",
        "loginsuccess": "<strong>{{GENDER:|您|妳|你}}現在已經以 \"$1\" 的身分登入了 {{SITENAME}}。</strong>",
-       "nosuchuser": "查無使用者 \"$1\"。\n使用者名稱有大小寫區分,\n請檢查您拼寫是否正確,或者 [[Special:UserLogin/signup|建立新帳號]]。",
+       "nosuchuser": "查無名稱為 \"$1\" 的使用者。\n使用者名稱有大小寫區分,\n請檢查您拼寫是否正確,或者 [[Special:CreateAccount|建立新帳號]]。",
        "nosuchusershort": "查無使用者 \"$1\",\n請檢查您拼寫是否正確。",
        "nouserspecified": "您必須指定一個使用者名稱。",
        "login-userblocked": "這位使用者已被封鎖,不允許登入。",
        "createacct-another-realname-tip": "真實姓名為選填欄位。\n若您提供真實姓名,它會用於使用者貢獻署名。",
        "pt-login": "登入",
        "pt-login-button": "登入",
+       "pt-login-continue-button": "繼續登入",
        "pt-createaccount": "建立帳號",
        "pt-userlogout": "登出",
        "php-mail-error-unknown": "PHP 的 mail() 函數發生不明錯誤。",
        "botpasswords-invalid-name": "指定的使用者名稱未包含機器人密碼分隔字元 (\"$1\")。",
        "botpasswords-not-exist": "使用者 \"$1\" 並沒有名稱為 \"$2\" 的機器人密碼。",
        "resetpass_forbidden": "無法變更密碼",
+       "resetpass_forbidden-reason": "無法變更密碼:$1",
        "resetpass-no-info": "您必須直接登入存取這個頁面。",
        "resetpass-submit-loggedin": "變更密碼",
        "resetpass-submit-cancel": "取消",
        "minoredit": "這是一個小修訂",
        "watchthis": "監視此頁面",
        "savearticle": "儲存頁面",
+       "publishpage": "發布頁面",
        "preview": "預覽",
        "showpreview": "顯示預覽",
        "showdiff": "顯示變更",
        "accmailtext": "[[User talk:$1|$1]] 的隨機密碼已經寄送至 $2,可登入後至 <em>[[Special:ChangePassword|變更密碼]] 頁面更改</em>。",
        "newarticle": "(新)",
        "newarticletext": "您正連結至一頁不存在頁面。\n要建立該頁面,請在下方的編輯方塊中輸入內容 (詳情請參考 [$1 説明頁面]) 。\n如果您是不小心來到此頁面,請點選瀏覽器的 <strong>返回</strong> 按鈕。",
-       "anontalkpagetext": "----\n<em>此討論頁面是給尚未建立帳號的匿名使用者使用</em>\n因此我們必須使用 IP 位址來辨識身份,但相同的 IP 位址可能由許多不同的使用者所共用。\n如果您是匿名使用者並且覺得評論的內容與您無關,請 [[Special:UserLogin/signup|建立新帳號]] 或 [[Special:UserLogin|登入]] 避免與其他匿名使用者混淆。",
+       "anontalkpagetext": "----\n<em>此討論頁面是給尚未建立帳號的匿名使用者使用</em>\n因此我們必須使用 IP 位址來辨識身份,但相同的 IP 位址可能由許多不同的使用者所共用。\n如果您是匿名使用者並且覺得評論的內容與您無關,請 [[Special:CreateAccount|建立新帳號]] 或 [[Special:UserLogin|登入]] 避免與其他匿名使用者混淆。",
        "noarticletext": "此頁面目前沒有內容,您可以在其它頁面中[[Special:Search/{{PAGENAME}}|搜尋此頁面標題]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜尋相關日誌]或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 建立此頁]</span>。",
        "noarticletext-nopermission": "此頁面目前沒有內容,\n您可以在其它頁面中 [[Special:Search/{{PAGENAME}}|搜尋此頁面標題]],或 <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜尋相關日誌]</span>,但您沒有權限建立此頁面。",
        "missing-revision": "頁面名稱 \"{{FULLPAGENAME}}\" 的 #$1 修訂版本不存在。\n\n通常是因連結到過期的歷史頁面,該頁面已被刪除。\n詳情請參考 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪除日誌]。",
        "upload-form-label-infoform-description-tooltip": "簡短描述該作品任何值得說明的事項。\n以照片為例,可提及照片中的事物、情境及地點。",
        "upload-form-label-usage-title": "用法",
        "upload-form-label-usage-filename": "檔案名稱",
-       "foreign-structured-upload-form-label-own-work": "這是我的作品",
-       "foreign-structured-upload-form-label-infoform-categories": "分類",
-       "foreign-structured-upload-form-label-infoform-date": "日期",
-       "foreign-structured-upload-form-label-own-work-message-local": "我確定我上傳的檔案已遵守下列 {{SITENAME}} 的服務條款與授權條款。",
-       "foreign-structured-upload-form-label-not-own-work-message-local": "若您無法同意遵守 {{SITENAME}} 的政策上傳檔案,請關閉此對話框並嘗試其他方法。",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "您也可嘗試[[Special:Upload|預設的上傳頁面]]。",
-       "foreign-structured-upload-form-label-own-work-message-default": "我明白我將上傳此檔案到一個共享的儲存庫,我確認已遵守本站的服務條款與授權政策。",
-       "foreign-structured-upload-form-label-not-own-work-message-default": "若您無法同意遵守共享儲存庫的政策上傳檔案,請關閉此對話框並嘗試其他方法。",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "若此檔案可遵守該站的授權政策上傳檔案,您可能會希望直接嘗試使用 [[Special:Upload|{{SITENAME}} 的上傳頁面]]。",
-       "foreign-structured-upload-form-label-own-work-message-shared": "我保証我擁有此檔案的版權,並且不反悔同意使用 [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] 授權條款發佈此檔案到維基媒體共享資源,並且我同意 [https://wikimediafoundation.org/wiki/Terms_of_Use 使用條款]。",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "若您並未擁有此檔案的版權,或者您希望使用其他的授權條款發佈此檔案,請考慮使用[https://commons.wikimedia.org/wiki/Special:UploadWizard 通用上傳精靈]。",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "若該站的授權政策允許上傳此檔案,您可能會希望直接嘗試使用 [[Special:Upload|{{SITENAME}} 的上傳頁面]]。",
+       "upload-form-label-own-work": "這是我的作品",
+       "upload-form-label-infoform-categories": "分類",
+       "upload-form-label-infoform-date": "日期",
+       "upload-form-label-own-work-message-generic-local": "我確定我上傳的檔案已遵守下列 {{SITENAME}} 的服務條款與授權條款。",
+       "upload-form-label-not-own-work-message-generic-local": "若您無法同意遵守 {{SITENAME}} 的政策上傳檔案,請關閉此對話框並嘗試其他方法。",
+       "upload-form-label-not-own-work-local-generic-local": "您也可嘗試[[Special:Upload|預設的上傳頁面]]。",
+       "upload-form-label-own-work-message-generic-foreign": "我明白我將上傳此檔案到一個共享的儲存庫,我確認已遵守本站的服務條款與授權政策。",
+       "upload-form-label-not-own-work-message-generic-foreign": "若您無法同意遵守共享儲存庫的政策上傳檔案,請關閉此對話框並嘗試其他方法。",
+       "upload-form-label-not-own-work-local-generic-foreign": "若此檔案可遵守該站的授權政策上傳檔案,您可能會希望直接嘗試使用 [[Special:Upload|{{SITENAME}} 的上傳頁面]]。",
        "backend-fail-stream": "無法傳輸檔案 \"$1\"。",
        "backend-fail-backup": "無法備份檔案 \"$1\"。",
        "backend-fail-notexists": "檔案 $1 不存在。",
        "changecontentmodel-success-text": "已變更 [[:$1]] 的內容類型。",
        "changecontentmodel-cannot-convert": "[[:$1]] 的內容無法轉換為 $2 類型。",
        "changecontentmodel-nodirectediting": "$1 的內容模型不支援直接編輯",
+       "changecontentmodel-emptymodels-title": "沒有內容模型可用",
+       "changecontentmodel-emptymodels-text": "[[:$1]]上的內容不能轉換為任何類型。",
        "log-name-contentmodel": "內容模型變更日誌",
        "log-description-contentmodel": "與頁面內容模型相關的事件",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|已使用}}非預設的內容模型 \"$5\" 建立頁面 $3",
        "whatlinkshere-prev": "前 $1 筆",
        "whatlinkshere-next": "{{PLURAL:$1|下筆|後 $1 筆}}",
        "whatlinkshere-links": "← 連結",
-       "whatlinkshere-hideredirs": "$1 重新導向",
-       "whatlinkshere-hidetrans": "$1 引用",
-       "whatlinkshere-hidelinks": "$1 連結",
+       "whatlinkshere-hideredirs": "隱藏重定向",
+       "whatlinkshere-hidetrans": "隱藏引用",
+       "whatlinkshere-hidelinks": "隱藏連結",
        "whatlinkshere-hideimages": "$1 檔案連結",
        "whatlinkshere-filters": "搜尋",
        "whatlinkshere-submit": "前往",
        "lockdbsuccesstext": "已鎖定資料庫。<br />\n請記得在維護完成後 [[Special:UnlockDB|解除鎖定]] 資料庫。",
        "unlockdbsuccesstext": "已解除鎖定資料庫。",
        "lockfilenotwritable": "沒有權限寫入資料庫鎖定檔案。\n網頁伺服器需要該檔案的寫入權限以鎖定和解除鎖定資料庫。",
+       "databaselocked": "資料庫已被鎖定。",
        "databasenotlocked": "資料庫尚未鎖定。",
        "lockedbyandtime": "(由 {{GENDER:$1|$1}} 於 $2 的 $3)",
        "move-page": "移動 $1",
        "tooltip-ca-nstab-category": "檢視分類頁面",
        "tooltip-minoredit": "標記為小修訂",
        "tooltip-save": "儲存您的變更",
+       "tooltip-publish": "發佈您的更改",
        "tooltip-preview": "請在儲存前預覽您的變更!",
        "tooltip-diff": "顯示您對內容所做的變更",
        "tooltip-compareselectedversions": "查閱此頁面兩個已選擇的修訂間的差異",
        "invalidateemail": "取消電子郵件確認",
        "notificationemail_subject_changed": "{{SITENAME}} 註冊的電子郵件位址已變更",
        "notificationemail_subject_removed": "{{SITENAME}} 註冊的電子郵件位址已移除",
+       "notificationemail_body_changed": "來自IP位址$1的某個人(可能是您),在{{SITENAME}}上將帳號\"$2\"的電子郵件位址改成\"$3\"。\n\n如果非您本人所為,請立即跟網站管理員聯繫。",
+       "notificationemail_body_removed": "來自IP位址$1的某人(可能是您),在{{SITENAME}}上移除了帳號$2的電子郵件位址。\n\n如果非您本人所為,請立即跟網站管理員聯繫。",
        "scarytranscludedisabled": "[Interwiki 轉換代碼不可用]",
        "scarytranscludefailed": "[模板 $1 讀取失敗]",
        "scarytranscludefailed-httpstatus": "[模板 $1 讀取失敗:HTTP $2]",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|已保護}} $3 $4 [連鎖]",
        "logentry-protect-modify": "$1 {{GENDER:$2|已更改}} $3 的保護層級 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|已更改}} $3 的保護層級 $4 [連鎖]",
-       "logentry-rights-rights": "$1 {{GENDER:$2|已更改}} $3 的群組成員資格由 $4 成為 $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2|已更改}} {{GENDER:$6|$3}} 的群組成員資格由 $4 成為 $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|已更改}} $3 的群組成員資格",
        "logentry-rights-autopromote": "$1 已自動{{GENDER:$2|提升}}從 $4 成為 $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|已上傳}} $3",
        "feedback-useragent": "使用者代理:",
        "searchsuggest-search": "搜尋",
        "searchsuggest-containing": "包含...",
+       "api-error-autoblocked": "您的IP位址已經被自動封禁,因為它曾經被一名已封禁的使用者使用過。",
        "api-error-badaccess-groups": "您沒有權限在此 Wiki 上傳檔案。",
        "api-error-badtoken": "內部錯誤:密鑰錯誤。",
+       "api-error-blocked": "您已被封禁,無法編輯。",
        "api-error-copyuploaddisabled": "此伺服器已停用使用 URL 上傳檔案的功能。",
        "api-error-duplicate": "在網站上已有相同內容的{{PLURAL:$1|其他檔案|其他檔案}}。",
        "api-error-duplicate-archive": "在網站上曾有相同內容的{{PLURAL:$1|其他檔案|其他檔案}},但已被刪除。",
        "api-error-nomodule": "內部錯誤:缺少上傳模組集。",
        "api-error-ok-but-empty": "內部錯誤:伺服器沒有回應。",
        "api-error-overwrite": "不允許覆蓋已存在的檔案。",
+       "api-error-ratelimited": "您正在嘗試在比本wiki所允許時間更短的時間內,上傳更多的檔案。請稍待幾分鐘之後再試一次。",
        "api-error-stashfailed": "內部錯誤:伺服器儲存暫存檔案失敗。",
        "api-error-publishfailed": "內部錯誤:伺服器發佈暫存檔案失敗。",
        "api-error-stasherror": "上傳檔案至儲藏庫時發生錯誤。",
        "api-error-unknownerror": "不明錯誤:\"$1\"。",
        "api-error-uploaddisabled": "此 Wiki 的上傳功能已停用。",
        "api-error-verification-error": "此檔案可能已損壞,或副檔名錯誤。",
+       "api-error-was-deleted": "與此名稱相同的檔案曾被上傳過,隨後遭到刪除。",
        "duration-seconds": "$1 秒",
        "duration-minutes": "$1 分鐘",
        "duration-hours": "$1 小時",
        "special-characters-group-ipa": "國際音標",
        "special-characters-group-symbols": "符號",
        "special-characters-group-greek": "希臘文",
+       "special-characters-group-greekextended": "希臘字母擴展",
        "special-characters-group-cyrillic": "斯拉夫文",
        "special-characters-group-arabic": "阿拉伯文",
        "special-characters-group-arabicextended": "阿拉伯文擴充",
        "log-action-filter-suppress-block": "由封鎖禁止顯示使用者",
        "log-action-filter-suppress-reblock": "由重新封鎖禁止顯示使用者",
        "log-action-filter-upload-upload": "新上傳",
-       "log-action-filter-upload-overwrite": "重新上傳"
+       "log-action-filter-upload-overwrite": "重新上傳",
+       "authmanager-userdoesnotexist": "使用者帳號 \"$1\" 尚未註冊。",
+       "authmanager-username-help": "認証用的使用者名稱。",
+       "authmanager-password-help": "認証用的密碼。",
+       "authmanager-domain-help": "外部認証用的網域。",
+       "authmanager-retype-help": "再輸入一次密碼確認。",
+       "authmanager-email-label": "電子郵件",
+       "authmanager-email-help": "電子郵件地址",
+       "authmanager-realname-label": "真實姓名",
+       "authmanager-realname-help": "使用者的真實姓名",
+       "authmanager-provider-password": "Password-based 認証",
+       "authmanager-provider-password-domain": "Password- 及 domain-based 認証",
+       "authmanager-provider-temporarypassword": "臨時密碼",
+       "authprovider-resetpass-skip-label": "略過",
+       "authprovider-resetpass-skip-help": "略過重設密碼。",
+       "specialpage-securitylevel-not-allowed-title": "不允許",
+       "cannotauth-not-allowed-title": "權限不足",
+       "cannotauth-not-allowed": "您不被允許使用此頁面",
+       "changecredentials": "更改憑證",
+       "changecredentials-submit": "更改",
+       "changecredentials-submit-cancel": "取消",
+       "changecredentials-invalidsubpage": "$1 不是有效的憑証類型。",
+       "changecredentials-success": "已更改您的憑證。",
+       "removecredentials": "移除憑證",
+       "removecredentials-submit": "移除",
+       "removecredentials-submit-cancel": "取消",
+       "removecredentials-invalidsubpage": "$1 不是有效的憑証類型。",
+       "removecredentials-success": "已移除您的憑證。",
+       "credentialsform-provider": "憑證類型:",
+       "credentialsform-account": "帳號名稱:",
+       "cannotlink-no-provider-title": "沒有可連結的帳號",
+       "cannotlink-no-provider": "沒有可連結的帳號。",
+       "linkaccounts": "連結帳號",
+       "linkaccounts-success-text": "已連結帳號。",
+       "linkaccounts-submit": "連結帳號",
+       "unlinkaccounts": "取消連結帳號",
+       "unlinkaccounts-success": "已取消連結帳號。"
 }
index ee04481..2c577ed 100644 (file)
@@ -48,22 +48,31 @@ $namespaceGenderAliases = [
 $specialPageAliases = [
        'Activeusers'               => [ 'Aktivní_uživatelé', 'Aktivni_uzivatele' ],
        'Allmessages'               => [ 'Všechna_hlášení', 'Všechny_zprávy', 'Vsechna_hlaseni', 'Vsechny_zpravy' ],
+       'AllMyUploads'              => [ 'Všechny_moje_soubory', 'Všechny_mé_soubory' ],
        'Allpages'                  => [ 'Všechny_stránky', 'Vsechny_stranky' ],
        'Ancientpages'              => [ 'Nejstarší_stránky', 'Staré_stránky', 'Stare_stranky' ],
+       'ApiHelp'                   => [ 'Nápověda_k_API' ],
+       'ApiSandbox'                => [ 'API_pískoviště' ],
+       'Badtitle'                  => [ 'Neplatný_název' ],
        'Blankpage'                 => [ 'Prázdná_stránka' ],
        'Block'                     => [ 'Blokování', 'Blokovani', 'Blokovat_uživatele', 'Blokovat_IP', 'Blokovat_uzivatele' ],
        'Booksources'               => [ 'Zdroje_knih' ],
+       'BotPasswords'              => [ 'Hesla_pro_boty' ],
        'BrokenRedirects'           => [ 'Přerušená_přesměrování', 'Prerusena_presmerovani' ],
        'Categories'                => [ 'Kategorie' ],
+       'ChangeContentModel'        => [ 'Změnit_model_obsahu_stránky' ],
        'ChangeEmail'               => [ 'Změna_emailu', 'Zmena_emailu' ],
-       'ChangePassword'            => [ 'Změna_hesla', 'Zmena_hesla', 'Resetovat_heslo' ],
+       'ChangePassword'            => [ 'Změna_hesla', 'Zmena_hesla' ],
        'ComparePages'              => [ 'Porovnání_stránek', 'PorovnáníStránek', 'Porovnani_stranek', 'PorovnaniStranek' ],
        'Confirmemail'              => [ 'Potvrdit_e-mail' ],
        'Contributions'             => [ 'Příspěvky', 'Prispevky' ],
        'CreateAccount'             => [ 'Vytvořit_účet', 'Vytvorit_ucet' ],
        'Deadendpages'              => [ 'Slepé_stránky', 'Slepe_stranky' ],
        'DeletedContributions'      => [ 'Smazané_příspěvky', 'Smazane_prispevky' ],
+       'Diff'                      => [ 'Rozdíl' ],
        'DoubleRedirects'           => [ 'Dvojitá_přesměrování', 'Dvojita_presmerovani' ],
+       'EditTags'                  => [ 'Upravit_značky' ],
+       'EditWatchlist'             => [ 'Upravit_seznam_sledovaných_stránek' ],
        'Emailuser'                 => [ 'E-mail' ],
        'ExpandTemplates'           => [ 'Testy_šablon' ],
        'Export'                    => [ 'Exportovat_stránky' ],
@@ -76,7 +85,9 @@ $specialPageAliases = [
        'LinkSearch'                => [ 'Hledání_odkazů', 'Hledani_odkazu' ],
        'Listadmins'                => [ 'Seznam_správců', 'Seznam_spravcu' ],
        'Listbots'                  => [ 'Seznam_botů', 'Seznam_botu' ],
+       'ListDuplicatedFiles'       => [ 'Seznam_duplicitních_souborů' ],
        'Listfiles'                 => [ 'Seznam_souborů', 'Seznam_souboru' ],
+       'Listgrants'                => [ 'Seznam_grantů' ],
        'Listgrouprights'           => [ 'Práva_uživatelských_skupin', 'Seznam_uživatelských_práv', 'Seznam_uzivatelskych_prav' ],
        'Listredirects'             => [ 'Seznam_přesměrování', 'Seznam_presmerovani' ],
        'Listusers'                 => [ 'Uživatelé', 'Uzivatele', 'Seznam_uživatelů', 'Seznam_uzivatelu' ],
@@ -84,6 +95,7 @@ $specialPageAliases = [
        'Log'                       => [ 'Protokolovací_záznamy', 'Protokoly', 'Protokol', 'Protokolovaci_zaznamy' ],
        'Lonelypages'               => [ 'Sirotčí_stránky', 'Sirotci_stranky' ],
        'Longpages'                 => [ 'Nejdelší_stránky', 'Nejdelsi_stranky' ],
+       'MediaStatistics'           => [ 'Statistika_souborů', 'Statistiky_souborů' ],
        'MergeHistory'              => [ 'Sloučení_historie', 'Slouceni_historie', 'Sloučit_historii' ],
        'MIMEsearch'                => [ 'Hledání_podle_MIME', 'Hledani_podle_MIME', 'Hledat_podle_MIME_typu' ],
        'Mostcategories'            => [ 'Stránky_s_nejvíce_kategoriemi', 'Stranky_s_nejvice_kategoriemi', 'Stránky_s_nejvyšším_počtem_kategorií' ],
@@ -94,17 +106,27 @@ $specialPageAliases = [
        'Mostrevisions'             => [ 'Stránky_s_nejvíce_editacemi', 'Stranky_s_nejvice_editacemi', 'Stránky_s_nejvyšším_počtem_editací' ],
        'Movepage'                  => [ 'Přesunout_stránku', 'Přejmenovat_stránku' ],
        'Mycontributions'           => [ 'Mé_příspěvky', 'Me_prispevky' ],
+       'MyLanguage'                => [ 'V_mém_jazyce', 'Můj_jazyk' ],
        'Mypage'                    => [ 'Moje_stránka', 'Moje_stranka' ],
        'Mytalk'                    => [ 'Moje_diskuse' ],
+       'Myuploads'                 => [ 'Moje_soubory', 'Mé_soubory' ],
        'Newimages'                 => [ 'Nové_obrázky', 'Galerie_nových_obrázků', 'Nove_obrazky' ],
        'Newpages'                  => [ 'Nové_stránky', 'Nove_stranky', 'Nejnovější_stránky', 'Nejnovejsi_stranky' ],
+       'PasswordReset'             => [ 'Reset_hesla', 'Resetovat_heslo' ],
+       'PagesWithProp'             => [ 'Stránky_s_vlastností', 'Stránky_s_vlastnostmi' ],
+       'PasswordReset'             => [ 'Reset_hesla', 'Resetovat_heslo', 'Obnova_hesla', 'Obnovit_heslo' ],
+       'PermanentLink'             => [ 'Trvalý_odkaz' ],
        'Preferences'               => [ 'Nastavení', 'Nastaveni' ],
+       'Prefixindex'               => [ 'Stránky_podle_začátku' ],
        'Protectedpages'            => [ 'Zamčené_stránky', 'Zamcene_stranky' ],
        'Protectedtitles'           => [ 'Zamčené_názvy', 'Zamcene_nazvy', 'Stránky_které_nelze_vytvořit' ],
        'Randompage'                => [ 'Náhodná_stránka', 'Nahodna_stranka' ],
        'Randomredirect'            => [ 'Náhodné_přesměrování', 'Nahodne_presmerovani' ],
+       'RandomInCategory'          => [ 'Náhodná_stránka_v_kategorii' ],
+       'Randomrootpage'            => [ 'Náhodná_kořenová_stránka' ],
        'Recentchanges'             => [ 'Poslední_změny', 'Posledni_zmeny' ],
        'Recentchangeslinked'       => [ 'Související_změny', 'Souvisejici_zmeny' ],
+       'Redirect'                  => [ 'Přesměrování', 'Přesměrovat' ],
        'Revisiondelete'            => [ 'Smazat_revizi' ],
        'Search'                    => [ 'Hledání', 'Hledani' ],
        'Shortpages'                => [ 'Nejkratší_stránky', 'Nejkratsi_stranky' ],
index 674be13..589144c 100644 (file)
@@ -407,6 +407,7 @@ $specialPageAliases = [
        'BrokenRedirects'           => [ 'BrokenRedirects' ],
        'Categories'                => [ 'Categories' ],
        'ChangeContentModel'        => [ 'ChangeContentModel' ],
+       'ChangeCredentials'         => [ 'ChangeCredentials' ],
        'ChangeEmail'               => [ 'ChangeEmail' ],
        'ChangePassword'            => [ 'ChangePassword', 'ResetPass', 'ResetPassword' ],
        'ComparePages'              => [ 'ComparePages' ],
@@ -430,6 +431,7 @@ $specialPageAliases = [
        'JavaScriptTest'            => [ 'JavaScriptTest' ],
        'BlockList'                 => [ 'BlockList', 'ListBlocks', 'IPBlockList' ],
        'LinkSearch'                => [ 'LinkSearch' ],
+       'LinkAccounts'              => [ 'LinkAccounts' ],
        'Listadmins'                => [ 'ListAdmins' ],
        'Listbots'                  => [ 'ListBots' ],
        'Listfiles'                 => [ 'ListFiles', 'FileList', 'ImageList' ],
@@ -475,6 +477,7 @@ $specialPageAliases = [
        'Recentchanges'             => [ 'RecentChanges' ],
        'Recentchangeslinked'       => [ 'RecentChangesLinked', 'RelatedChanges' ],
        'Redirect'                  => [ 'Redirect' ],
+       'RemoveCredentials'         => [ 'RemoveCredentials' ],
        'ResetTokens'               => [ 'ResetTokens' ],
        'Revisiondelete'            => [ 'RevisionDelete' ],
        'RunJobs'                   => [ 'RunJobs' ],
@@ -490,6 +493,7 @@ $specialPageAliases = [
        'Uncategorizedpages'        => [ 'UncategorizedPages' ],
        'Uncategorizedtemplates'    => [ 'UncategorizedTemplates' ],
        'Undelete'                  => [ 'Undelete' ],
+       'UnlinkAccounts'            => [ 'UnlinkAccounts' ],
        'Unlockdb'                  => [ 'UnlockDB' ],
        'Unusedcategories'          => [ 'UnusedCategories' ],
        'Unusedimages'              => [ 'UnusedFiles', 'UnusedImages' ],
index 2b97f7a..c832237 100644 (file)
--- a/load.php
+++ b/load.php
@@ -25,9 +25,8 @@
 use MediaWiki\Logger\LoggerFactory;
 
 // This endpoint is supposed to be independent of request cookies and other
-// details of the session. Log warnings for violations of the no-session
-// constraint.
-define( 'MW_NO_SESSION', 'warn' );
+// details of the session. Enforce this constraint with respect to session use.
+define( 'MW_NO_SESSION', 1 );
 
 require __DIR__ . '/includes/WebStart.php';
 
index 8993146..f9dd58c 100644 (file)
@@ -216,7 +216,7 @@ class ConvertExtensionToRegistration extends Maintenance {
        }
 
        public function handleHooks( $realName, $value ) {
-               foreach ( $value as $hookName => $handlers ) {
+               foreach ( $value as $hookName => &$handlers ) {
                        foreach ( $handlers as $func ) {
                                if ( $func instanceof Closure ) {
                                        $this->error( "Error: Closures cannot be converted to JSON. " .
@@ -230,6 +230,9 @@ class ConvertExtensionToRegistration extends Maintenance {
                                        );
                                }
                        }
+                       if ( count( $handlers ) === 1 ) {
+                               $handlers = $handlers[0];
+                       }
                }
                $this->json[$realName] = $value;
        }
index 915c699..fe99d8e 100644 (file)
@@ -54,6 +54,7 @@
 姊弟 姐弟
 姊夫 姐夫
 大姊 大姐
+大姊姊      大姐姐
 御姊 御姐
 表姊 表姐
 堂姊 堂姐
 著處 着处
 著她 着她
 著妳 着妳
-著姓 着姓
 著它 着它
 著定 着定
 著實 着实
index f76d875..997acfc 100644 (file)
 电影里      電影裏
 广播里      廣播裏
 电视里      電視裏
+公寓里      公寓裏
 窝里斗      窩裏鬥
 苑裡 苑裡
 霄裡 霄裡
index 12431b3..271a055 100644 (file)
@@ -747,6 +747,8 @@ IP地址    IP位址
 流動網絡   行動網路
 网络游戏   網路遊戲
 網絡遊戲   網路遊戲
+电脑网络   電腦網路
+電腦網絡   電腦網路
 咪高峰      麥克風
 電單車      機車
 搜索引擎   搜尋引擎
index 5c6a70b..a9e8f3b 100644 (file)
 騰格里      騰格里
 村里長      村里長
 進制 進制
+總裁制      總裁制
+獨裁制      獨裁制
 模范三軍   模范三軍
 陳冲 陳冲
 劉佳怜      劉佳怜
index 492a99d..94756f0 100644 (file)
 併骨
 併網
 併線
-併流
+江併流
+水併流
 逼併
 併名
 併火
 意大利麵
 湯下麵
 茶麵
-麵
+麵
 北山索麵
 土索麵
 米麵
 、麵點
 麵製品
 乾脆麵
+磨麵
 冷面相
 糞穢衊面
 僕僕
 犖确
 磽确
 确瘠
-言辯而确
-數與虜确
-關弓與我确
 拚捨
 廣捨
 齊王捨牛
 翻鬆
 浮鬆
 弄鬆
+旋鬆
 精鬆
 懈鬆
 鬆蛋
 跌扑
 轉向往
 酒帘
-裡面包
 金表態
 金表情
 金表揚
 長几
 隆准許
 雄斗斗
+裡面包
+表面包
+外面包
 面包住
 面包辦
 面包廂
 面粉碎
 面粉紅
 面食飯
+水表面
+費米面
 顛顛仆仆
 高干擾
 高干預
 折子戲
 搤肮拊背
 文采郁郁
-腊之以為餌
 腊毒
 蜡月
 蜡祭
 范文照
 機械系
 體系
+維系統
 心理
 鹰鵰
 天地志狼
 電影裡
 廣播裡
 電視裡
+公寓裡
+公寓里弄
 裏白 #植物常用名
 烏蘇里 #分詞用
 首發
 有只允
 有只採
 有只用
+所有只
 葉叶琹
 胡子昂
 胡子嬰
 製得
 製取
 譯製
+燉製
+煮製
 遏制 #以下分詞用
 管制
 抑制
 啊喂
 呵喂
 呦喂
-水表面
-表面包
-費米面
 松口鎮
 沙瑯
 琺瑯
diff --git a/maintenance/mssql/archives/named_constraints.sql b/maintenance/mssql/archives/named_constraints.sql
deleted file mode 100644 (file)
index 94b77ea..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-DECLARE @fullyQualifiedTableName nvarchar(max),
-@tableName sysname,
-@fieldName sysname,
-@constr sysname,
-@constrNew sysname,
-@sqlcmd nvarchar(max),
-@sqlcreate nvarchar(max)
-
-SET @fullyQualifiedTableName = '/*_*//*$tableName*/'
-SET @tableName = '/*$tableName*/'
-SET @fieldName = '/*$fieldName*/'
-
-SELECT @constr = CONSTRAINT_NAME
-FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
-WHERE TABLE_NAME = @tableName
-AND CONSTRAINT_CATALOG = '/*$wgDBname*/'
-AND CONSTRAINT_SCHEMA = '/*$wgDBmwschema*/'
-AND CONSTRAINT_TYPE = 'CHECK'
-AND CONSTRAINT_NAME LIKE ('CK__' + left(@tableName,9) + '__' + left(@fieldName,5) + '%')
-
-SELECT @constrNew = CONSTRAINT_NAME
-FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
-WHERE TABLE_NAME = @tableName
-AND CONSTRAINT_CATALOG = '/*$wgDBname*/'
-AND CONSTRAINT_SCHEMA = '/*$wgDBmwschema*/'
-AND CONSTRAINT_TYPE = 'CHECK'
-AND CONSTRAINT_NAME = (@fieldName + '_ckc')
-
-IF @constr IS NOT NULL
-BEGIN
-  SET @sqlcmd =  'ALTER TABLE ' + @fullyQualifiedTableName + ' DROP CONSTRAINT [' + @constr + ']'
-  EXECUTE sp_executesql @sqlcmd
-END
-IF @constrNew IS NULL
-BEGIN
-  SET @sqlcreate =  'ALTER TABLE ' + @fullyQualifiedTableName + ' WITH NOCHECK ADD CONSTRAINT ' + @fieldName + '_ckc CHECK /*$checkConstraint*/;'
-  EXECUTE sp_executesql @sqlcreate
-END
\ No newline at end of file
diff --git a/maintenance/mssql/archives/patch-add-cl_collation_ext_index.sql b/maintenance/mssql/archives/patch-add-cl_collation_ext_index.sql
new file mode 100644 (file)
index 0000000..8137dc6
--- /dev/null
@@ -0,0 +1,2 @@
+-- @since 1.27
+CREATE INDEX /*i*/cl_collation_ext ON /*_*/categorylinks (cl_collation, cl_to, cl_type, cl_from);
diff --git a/maintenance/mssql/archives/patch-archive-drop-fks.sql b/maintenance/mssql/archives/patch-archive-drop-fks.sql
new file mode 100644 (file)
index 0000000..3055ac9
--- /dev/null
@@ -0,0 +1,59 @@
+DECLARE @base nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @base = 'ALTER TABLE /*_*/archive DROP CONSTRAINT ';--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/archive')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/revision')
+       AND c.name = 'ar_parent_id';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+-- while we're at it, let's fix up the other foreign key constraints on archive
+-- as future patches touch constraints on other tables, they'll take the time to update constraint names there as well
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/archive')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/mwuser')
+       AND c.name = 'ar_user';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/archive ADD CONSTRAINT ar_user__user_id__fk FOREIGN KEY (ar_user) REFERENCES /*_*/mwuser(user_id);--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/archive')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/text')
+       AND c.name = 'ar_text_id';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/archive ADD CONSTRAINT ar_text_id__old_id__fk FOREIGN KEY (ar_text_id) REFERENCES /*_*/text(old_id) ON DELETE CASCADE;
diff --git a/maintenance/mssql/archives/patch-bot_passwords.sql b/maintenance/mssql/archives/patch-bot_passwords.sql
new file mode 100644 (file)
index 0000000..7718ffa
--- /dev/null
@@ -0,0 +1,13 @@
+--
+-- This table contains a user's bot passwords: passwords that allow access to
+-- the account via the API with limited rights.
+--
+CREATE TABLE /*_*/bot_passwords (
+       bp_user int NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE,
+       bp_app_id nvarchar(32) NOT NULL,
+       bp_password nvarchar(255) NOT NULL,
+       bp_token nvarchar(255) NOT NULL,
+       bp_restrictions nvarchar(max) NOT NULL,
+       bp_grants nvarchar(max) NOT NULL,
+       PRIMARY KEY (bp_user, bp_app_id)
+);
diff --git a/maintenance/mssql/archives/patch-categorylinks-constraints.sql b/maintenance/mssql/archives/patch-categorylinks-constraints.sql
new file mode 100644 (file)
index 0000000..cf9b565
--- /dev/null
@@ -0,0 +1,20 @@
+DECLARE @baseSQL nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @baseSQL = 'ALTER TABLE /*_*/categorylinks DROP CONSTRAINT ';--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/categorylinks')
+       AND c.name = 'cl_type';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/categorylinks ADD CONSTRAINT cl_type_ckc CHECK (cl_type IN('page', 'subcat', 'file'));
diff --git a/maintenance/mssql/archives/patch-drop-page_counter.sql b/maintenance/mssql/archives/patch-drop-page_counter.sql
new file mode 100644 (file)
index 0000000..54ab9f7
--- /dev/null
@@ -0,0 +1,19 @@
+DECLARE @sql nvarchar(max),
+       @id sysname;--
+
+SET @sql = 'ALTER TABLE /*_*/page DROP CONSTRAINT ';--
+
+SELECT @id = df.name
+FROM sys.default_constraints df
+JOIN sys.columns c
+       ON c.object_id = df.parent_object_id
+       AND c.column_id = df.parent_column_id
+WHERE
+       df.parent_object_id = OBJECT_ID('/*_*/page')
+       AND c.name = 'page_counter';--
+
+SET @sql = @sql + @id;--
+
+EXEC sp_executesql @sql;--
+
+ALTER TABLE /*_*/page DROP COLUMN page_counter;
diff --git a/maintenance/mssql/archives/patch-drop-rc_cur_time.sql b/maintenance/mssql/archives/patch-drop-rc_cur_time.sql
new file mode 100644 (file)
index 0000000..01c46d3
--- /dev/null
@@ -0,0 +1,19 @@
+DECLARE @sql nvarchar(max),
+       @id sysname;--
+
+SET @sql = 'ALTER TABLE /*_*/recentchanges DROP CONSTRAINT ';--
+
+SELECT @id = df.name
+FROM sys.default_constraints df
+JOIN sys.columns c
+       ON c.object_id = df.parent_object_id
+       AND c.column_id = df.parent_column_id
+WHERE
+       df.parent_object_id = OBJECT_ID('/*_*/recentchanges')
+       AND c.name = 'rc_cur_time';--
+
+SET @sql = @sql + @id;--
+
+EXEC sp_executesql @sql;--
+
+ALTER TABLE /*_*/recentchanges DROP COLUMN rc_cur_time;
diff --git a/maintenance/mssql/archives/patch-drop-ss_total_views.sql b/maintenance/mssql/archives/patch-drop-ss_total_views.sql
new file mode 100644 (file)
index 0000000..7525ed5
--- /dev/null
@@ -0,0 +1,19 @@
+DECLARE @sql nvarchar(max),
+       @id sysname;--
+
+SET @sql = 'ALTER TABLE /*_*/site_stats DROP CONSTRAINT ';--
+
+SELECT @id = df.name
+FROM sys.default_constraints df
+JOIN sys.columns c
+       ON c.object_id = df.parent_object_id
+       AND c.column_id = df.parent_column_id
+WHERE
+       df.parent_object_id = OBJECT_ID('/*_*/site_stats')
+       AND c.name = 'ss_total_views';--
+
+SET @sql = @sql + @id;--
+
+EXEC sp_executesql @sql;--
+
+ALTER TABLE /*_*/site_stats DROP COLUMN ss_total_views;
diff --git a/maintenance/mssql/archives/patch-drop-user_options.sql b/maintenance/mssql/archives/patch-drop-user_options.sql
new file mode 100644 (file)
index 0000000..ab37956
--- /dev/null
@@ -0,0 +1,19 @@
+DECLARE @sql nvarchar(max),
+       @id sysname;--
+
+SET @sql = 'ALTER TABLE /*_*/mwuser DROP CONSTRAINT ';--
+
+SELECT @id = df.name
+FROM sys.default_constraints df
+JOIN sys.columns c
+       ON c.object_id = df.parent_object_id
+       AND c.column_id = df.parent_column_id
+WHERE
+       df.parent_object_id = OBJECT_ID('/*_*/mwuser')
+       AND c.name = 'user_options';--
+
+SET @sql = @sql + @id;--
+
+EXEC sp_executesql @sql;--
+
+ALTER TABLE /*_*/mwuser DROP COLUMN user_options;
diff --git a/maintenance/mssql/archives/patch-filearchive-constraints.sql b/maintenance/mssql/archives/patch-filearchive-constraints.sql
new file mode 100644 (file)
index 0000000..cefead5
--- /dev/null
@@ -0,0 +1,34 @@
+DECLARE @baseSQL nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @baseSQL = 'ALTER TABLE /*_*/filearchive DROP CONSTRAINT ';--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/filearchive')
+       AND c.name = 'fa_major_mime';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/filearchive')
+       AND c.name = 'fa_media_type';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/filearchive ADD CONSTRAINT fa_major_mime_ckc check (fa_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart'));--
+ALTER TABLE /*_*/filearchive ADD CONSTRAINT fa_media_type_ckc check (fa_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'));
diff --git a/maintenance/mssql/archives/patch-filearchive-schema.sql b/maintenance/mssql/archives/patch-filearchive-schema.sql
new file mode 100644 (file)
index 0000000..cf1c01f
--- /dev/null
@@ -0,0 +1,120 @@
+-- MediaWiki looks for lines ending with semicolons and sends them as separate queries
+-- However here we *really* need this all to be sent as a single batch. As such, DO NOT
+-- remove the -- from the end of each statement.
+
+DECLARE @temp table (
+       fa_id int,
+       fa_name nvarchar(255),
+       fa_archive_name nvarchar(255),
+       fa_storage_group nvarchar(16),
+       fa_storage_key nvarchar(64),
+       fa_deleted_user int,
+       fa_deleted_timestamp varchar(14),
+       fa_deleted_reason nvarchar(max),
+       fa_size int,
+       fa_width int,
+       fa_height int,
+       fa_metadata nvarchar(max),
+       fa_bits int,
+       fa_media_type varchar(16),
+       fa_major_mime varchar(16),
+       fa_minor_mime nvarchar(100),
+       fa_description nvarchar(255),
+       fa_user int,
+       fa_user_text nvarchar(255),
+       fa_timestamp varchar(14),
+       fa_deleted tinyint,
+       fa_sha1 nvarchar(32)
+);--
+
+INSERT INTO @temp
+SELECT * FROM /*_*/filearchive;--
+
+DROP TABLE /*_*/filearchive;--
+
+CREATE TABLE /*_*/filearchive (
+  fa_id int NOT NULL PRIMARY KEY IDENTITY,
+  fa_name nvarchar(255) NOT NULL default '',
+  fa_archive_name nvarchar(255) default '',
+  fa_storage_group nvarchar(16),
+  fa_storage_key nvarchar(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp varchar(14) default '',
+  fa_deleted_reason nvarchar(max),
+  fa_size int default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata varbinary(max),
+  fa_bits int default 0,
+  fa_media_type varchar(16) default null,
+  fa_major_mime varchar(16) not null default 'unknown',
+  fa_minor_mime nvarchar(100) default 'unknown',
+  fa_description nvarchar(255),
+  fa_user int default 0 REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL,
+  fa_user_text nvarchar(255),
+  fa_timestamp varchar(14) default '',
+  fa_deleted tinyint NOT NULL default 0,
+  fa_sha1 nvarchar(32) NOT NULL default '',
+  CONSTRAINT fa_major_mime_ckc check (fa_major_mime in('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical')),
+  CONSTRAINT fa_media_type_ckc check (fa_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'))
+);--
+
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);--
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);--
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);--
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);--
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1);--
+
+SET IDENTITY_INSERT /*_*/filearchive ON;--
+
+INSERT INTO /*_*/filearchive
+(
+       fa_id,
+       fa_name,
+       fa_archive_name,
+       fa_storage_group,
+       fa_storage_key,
+       fa_deleted_user,
+       fa_deleted_timestamp,
+       fa_deleted_reason,
+       fa_size,
+       fa_width,
+       fa_height,
+       fa_metadata,
+       fa_bits,
+       fa_media_type,
+       fa_major_mime,
+       fa_minor_mime,
+       fa_description,
+       fa_user,
+       fa_user_text,
+       fa_timestamp,
+       fa_deleted,
+       fa_sha1
+)
+SELECT
+       fa_id,
+       fa_name,
+       fa_archive_name,
+       fa_storage_group,
+       fa_storage_key,
+       fa_deleted_user,
+       fa_deleted_timestamp,
+       fa_deleted_reason,
+       fa_size,
+       fa_width,
+       fa_height,
+       CONVERT(varbinary(max), fa_metadata, 0),
+       fa_bits,
+       fa_media_type,
+       fa_major_mime,
+       fa_minor_mime,
+       fa_description,
+       fa_user,
+       fa_user_text,
+       fa_timestamp,
+       fa_deleted,
+       fa_sha1
+FROM @temp t;--
+
+SET IDENTITY_INSERT /*_*/filearchive OFF;
diff --git a/maintenance/mssql/archives/patch-il_from_namespace.sql b/maintenance/mssql/archives/patch-il_from_namespace.sql
new file mode 100644 (file)
index 0000000..e4ac98f
--- /dev/null
@@ -0,0 +1,4 @@
+ALTER TABLE /*_*/imagelinks
+  ADD il_from_namespace int NOT NULL default 0;
+
+CREATE INDEX /*i*/il_backlinks_namespace ON /*_*/imagelinks (il_from_namespace,il_to,il_from);
\ No newline at end of file
diff --git a/maintenance/mssql/archives/patch-image-constraints.sql b/maintenance/mssql/archives/patch-image-constraints.sql
new file mode 100644 (file)
index 0000000..0aeb627
--- /dev/null
@@ -0,0 +1,34 @@
+DECLARE @baseSQL nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @baseSQL = 'ALTER TABLE /*_*/image DROP CONSTRAINT ';--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/image')
+       AND c.name = 'img_major_mime';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/image')
+       AND c.name = 'img_media_type';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/image ADD CONSTRAINT img_major_mime_ckc check (img_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart'));--
+ALTER TABLE /*_*/image ADD CONSTRAINT img_media_type_ckc check (img_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'));
diff --git a/maintenance/mssql/archives/patch-image-schema.sql b/maintenance/mssql/archives/patch-image-schema.sql
new file mode 100644 (file)
index 0000000..213b438
--- /dev/null
@@ -0,0 +1,84 @@
+-- MediaWiki looks for lines ending with semicolons and sends them as separate queries
+-- However here we *really* need this all to be sent as a single batch. As such, DO NOT
+-- remove the -- from the end of each statement.
+
+DECLARE @temp table (
+       img_name varbinary(255),
+       img_size int,
+       img_width int,
+       img_height int,
+       img_metadata varbinary(max),
+       img_bits int,
+       img_media_type varchar(16),
+       img_major_mime varchar(16),
+       img_minor_mime nvarchar(100),
+       img_description nvarchar(255),
+       img_user int,
+       img_user_text nvarchar(255),
+       img_timestamp nvarchar(14),
+       img_sha1 nvarchar(32)
+);--
+
+INSERT INTO @temp
+SELECT * FROM /*_*/image;--
+
+DROP TABLE /*_*/image;--
+
+CREATE TABLE /*_*/image (
+  img_name nvarchar(255) NOT NULL default '' PRIMARY KEY,
+  img_size int NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata varbinary(max) NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type varchar(16) default null,
+  img_major_mime varchar(16) not null default 'unknown',
+  img_minor_mime nvarchar(100) NOT NULL default 'unknown',
+  img_description nvarchar(255) NOT NULL,
+  img_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL,
+  img_user_text nvarchar(255) NOT NULL,
+  img_timestamp nvarchar(14) NOT NULL default '',
+  img_sha1 nvarchar(32) NOT NULL default '',
+  CONSTRAINT img_major_mime_ckc check (img_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical')),
+  CONSTRAINT img_media_type_ckc check (img_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'))
+);--
+
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);--
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);--
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);--
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);--
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);--
+
+INSERT INTO /*_*/image
+(
+       img_name,
+       img_size,
+       img_width,
+       img_height,
+       img_metadata,
+       img_bits,
+       img_media_type,
+       img_major_mime,
+       img_minor_mime,
+       img_description,
+       img_user,
+       img_user_text,
+       img_timestamp,
+       img_sha1
+)
+SELECT
+       img_name,
+       img_size,
+       img_width,
+       img_height,
+       img_metadata,
+       img_bits,
+       img_media_type,
+       img_major_mime,
+       img_minor_mime,
+       img_description,
+       img_user,
+       img_user_text,
+       img_timestamp,
+       img_sha1
+FROM @temp t;
diff --git a/maintenance/mssql/archives/patch-kill-cl_collation_index.sql b/maintenance/mssql/archives/patch-kill-cl_collation_index.sql
new file mode 100644 (file)
index 0000000..7f75a62
--- /dev/null
@@ -0,0 +1,7 @@
+--
+-- Kill cl_collation index.
+-- @since 1.27
+--
+
+DROP INDEX /*i*/cl_collation ON /*_*/categorylinks;
+
diff --git a/maintenance/mssql/archives/patch-logging-drop-fks.sql b/maintenance/mssql/archives/patch-logging-drop-fks.sql
new file mode 100644 (file)
index 0000000..c9cbca3
--- /dev/null
@@ -0,0 +1,37 @@
+DECLARE @base nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @base = 'ALTER TABLE /*_*/logging DROP CONSTRAINT ';--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/logging')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/mwuser')
+       AND c.name = 'log_user';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/logging')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/page')
+       AND c.name = 'log_page';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;
diff --git a/maintenance/mssql/archives/patch-oldimage-constraints.sql b/maintenance/mssql/archives/patch-oldimage-constraints.sql
new file mode 100644 (file)
index 0000000..69ede2c
--- /dev/null
@@ -0,0 +1,34 @@
+DECLARE @baseSQL nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @baseSQL = 'ALTER TABLE /*_*/oldimage DROP CONSTRAINT ';--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/oldimage')
+       AND c.name = 'oi_major_mime';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/oldimage')
+       AND c.name = 'oi_media_type';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/oldimage ADD CONSTRAINT oi_major_mime_ckc check (oi_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart'));--
+ALTER TABLE /*_*/oldimage ADD CONSTRAINT oi_media_type_ckc check (oi_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'));
diff --git a/maintenance/mssql/archives/patch-oldimage-schema.sql b/maintenance/mssql/archives/patch-oldimage-schema.sql
new file mode 100644 (file)
index 0000000..3391c1b
--- /dev/null
@@ -0,0 +1,91 @@
+-- MediaWiki looks for lines ending with semicolons and sends them as separate queries
+-- However here we *really* need this all to be sent as a single batch. As such, DO NOT
+-- remove the -- from the end of each statement.
+
+DECLARE @temp table (
+       oi_name varbinary(255),
+       oi_archive_name varbinary(255),
+       oi_size int,
+       oi_width int,
+       oi_height int,
+       oi_bits int,
+       oi_description nvarchar(255),
+       oi_user int,
+       oi_user_text nvarchar(255),
+       oi_timestamp varchar(14),
+       oi_metadata nvarchar(max),
+       oi_media_type varchar(16),
+       oi_major_mime varchar(16),
+       oi_minor_mime nvarchar(100),
+       oi_deleted tinyint,
+       oi_sha1 nvarchar(32)
+);--
+
+INSERT INTO @temp
+SELECT * FROM /*_*/oldimage;--
+
+DROP TABLE /*_*/oldimage;--
+
+CREATE TABLE /*_*/oldimage (
+  oi_name nvarchar(255) NOT NULL default '',
+  oi_archive_name nvarchar(255) NOT NULL default '',
+  oi_size int NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description nvarchar(255) NOT NULL,
+  oi_user int REFERENCES /*_*/mwuser(user_id),
+  oi_user_text nvarchar(255) NOT NULL,
+  oi_timestamp varchar(14) NOT NULL default '',
+  oi_metadata varbinary(max) NOT NULL,
+  oi_media_type varchar(16) default null,
+  oi_major_mime varchar(16) not null default 'unknown',
+  oi_minor_mime nvarchar(100) NOT NULL default 'unknown',
+  oi_deleted tinyint NOT NULL default 0,
+  oi_sha1 nvarchar(32) NOT NULL default '',
+  CONSTRAINT oi_major_mime_ckc check (oi_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical')),
+  CONSTRAINT oi_media_type_ckc check (oi_media_type IN('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'))
+);--
+
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text, oi_timestamp);--
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name, oi_timestamp);--
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name, oi_archive_name);--
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);--
+
+INSERT INTO /*_*/oldimage
+(
+       oi_name,
+       oi_archive_name,
+       oi_size,
+       oi_width,
+       oi_height,
+       oi_bits,
+       oi_description,
+       oi_user,
+       oi_user_text,
+       oi_timestamp,
+       oi_metadata,
+       oi_media_type,
+       oi_major_mime,
+       oi_minor_mime,
+       oi_deleted,
+       oi_sha1
+)
+SELECT
+       oi_name,
+       oi_archive_name,
+       oi_size,
+       oi_width,
+       oi_height,
+       oi_bits,
+       oi_description,
+       oi_user,
+       oi_user_text,
+       oi_timestamp,
+       CONVERT(varbinary(max), oi_metadata, 0),
+       oi_media_type,
+       oi_major_mime,
+       oi_minor_mime,
+       oi_deleted,
+       oi_sha1
+FROM @temp t;
diff --git a/maintenance/mssql/archives/patch-pl_from_namespace.sql b/maintenance/mssql/archives/patch-pl_from_namespace.sql
new file mode 100644 (file)
index 0000000..b3bbd78
--- /dev/null
@@ -0,0 +1,4 @@
+ALTER TABLE /*_*/pagelinks
+       ADD pl_from_namespace int NOT NULL default 0;
+
+CREATE INDEX /*i*/pl_backlinks_namespace ON /*_*/pagelinks (pl_from_namespace,pl_namespace,pl_title,pl_from);
diff --git a/maintenance/mssql/archives/patch-pp_sortkey.sql b/maintenance/mssql/archives/patch-pp_sortkey.sql
new file mode 100644 (file)
index 0000000..b13b605
--- /dev/null
@@ -0,0 +1,8 @@
+-- Add a 'sortkey' field to page_props so pages can be efficiently
+-- queried by the numeric value of a property.
+
+ALTER TABLE /*_*/page_props
+        ADD pp_sortkey float DEFAULT NULL;
+
+CREATE UNIQUE INDEX /*i*/pp_propname_sortkey_page
+        ON /*_*/page_props ( pp_propname, pp_sortkey, pp_page );
diff --git a/maintenance/mssql/archives/patch-recentchanges-drop-fks.sql b/maintenance/mssql/archives/patch-recentchanges-drop-fks.sql
new file mode 100644 (file)
index 0000000..24f78f6
--- /dev/null
@@ -0,0 +1,76 @@
+DECLARE @base nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @base = 'ALTER TABLE /*_*/recentchanges DROP CONSTRAINT ';--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/recentchanges')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/page')
+       AND c.name = 'rc_cur_id';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/recentchanges')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/revision')
+       AND c.name = 'rc_this_oldid';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/recentchanges')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/revision')
+       AND c.name = 'rc_last_oldid';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+-- while we're at it, let's fix up the other foreign key constraints on recentchanges
+-- as future patches touch constraints on other tables, they'll take the time to update constraint names there as well
+ALTER TABLE /*_*/recentchanges DROP CONSTRAINT FK_rc_logid_log_id;--
+ALTER TABLE /*_*/recentchanges ADD CONSTRAINT rc_logid__log_id__fk FOREIGN KEY (rc_logid) REFERENCES /*_*/logging(log_id) ON DELETE CASCADE;--
+
+SELECT @id = fk.name
+FROM sys.foreign_keys fk
+JOIN sys.foreign_key_columns fkc
+       ON fkc.constraint_object_id = fk.object_id
+JOIN sys.columns c
+       ON c.column_id = fkc.parent_column_id
+       AND c.object_id = fkc.parent_object_id
+WHERE
+       fk.parent_object_id = OBJECT_ID('/*_*/recentchanges')
+       AND fk.referenced_object_id = OBJECT_ID('/*_*/mwuser')
+       AND c.name = 'rc_user';--
+
+SET @SQL = @base + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/recentchanges ADD CONSTRAINT rc_user__user_id__fk FOREIGN KEY (rc_user) REFERENCES /*_*/mwuser(user_id);
diff --git a/maintenance/mssql/archives/patch-tl_from_namespace.sql b/maintenance/mssql/archives/patch-tl_from_namespace.sql
new file mode 100644 (file)
index 0000000..9655165
--- /dev/null
@@ -0,0 +1,4 @@
+ALTER TABLE /*_*/templatelinks
+       ADD tl_from_namespace int NOT NULL default 0;
+
+CREATE INDEX /*i*/tl_backlinks_namespace ON /*_*/templatelinks (tl_from_namespace,tl_namespace,tl_title,tl_from);
diff --git a/maintenance/mssql/archives/patch-uploadstash-constraints.sql b/maintenance/mssql/archives/patch-uploadstash-constraints.sql
new file mode 100644 (file)
index 0000000..1cd668c
--- /dev/null
@@ -0,0 +1,20 @@
+DECLARE @baseSQL nvarchar(max),
+       @SQL nvarchar(max),
+       @id sysname;--
+
+SET @baseSQL = 'ALTER TABLE /*_*/uploadstash DROP CONSTRAINT ';--
+
+SELECT @id = cc.name
+FROM sys.check_constraints cc
+JOIN sys.columns c
+       ON c.object_id = cc.parent_object_id
+       AND c.column_id = cc.parent_column_id
+WHERE
+       cc.parent_object_id = OBJECT_ID('/*_*/uploadstash')
+       AND c.name = 'us_media_type';--
+
+SET @SQL = @baseSQL + @id;--
+
+EXEC sp_executesql @SQL;--
+
+ALTER TABLE /*_*/uploadstash ADD CONSTRAINT us_media_type_ckc check (us_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'));
index 86bd735..12cfed8 100644 (file)
@@ -38,7 +38,6 @@ CREATE TABLE /*_*/mwuser (
    user_newpassword  NVARCHAR(255)  NOT NULL DEFAULT '',
    user_newpass_time varchar(14) NULL DEFAULT NULL,
    user_email        NVARCHAR(255)  NOT NULL DEFAULT '',
-   user_options      NVARCHAR(MAX) NOT NULL DEFAULT '',
    user_touched      varchar(14)      NOT NULL DEFAULT '',
    user_token        NCHAR(32)      NOT NULL DEFAULT '',
    user_email_authenticated varchar(14) DEFAULT NULL,
@@ -101,6 +100,20 @@ CREATE TABLE /*_*/user_properties (
 CREATE UNIQUE CLUSTERED INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
 CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
 
+--
+-- This table contains a user's bot passwords: passwords that allow access to
+-- the account via the API with limited rights.
+--
+CREATE TABLE /*_*/bot_passwords (
+       bp_user int NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE,
+       bp_app_id nvarchar(32) NOT NULL,
+       bp_password nvarchar(255) NOT NULL,
+       bp_token nvarchar(255) NOT NULL,
+       bp_restrictions nvarchar(max) NOT NULL,
+       bp_grants nvarchar(max) NOT NULL,
+       PRIMARY KEY (bp_user, bp_app_id)
+);
+
 
 --
 -- Core of the wiki: each page has an entry here which identifies
@@ -193,17 +206,17 @@ CREATE TABLE /*_*/archive (
    ar_title NVARCHAR(255) NOT NULL DEFAULT '',
    ar_text NVARCHAR(MAX) NOT NULL,
    ar_comment NVARCHAR(255) NOT NULL,
-   ar_user INT REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL,
+   ar_user INT CONSTRAINT ar_user__user_id__fk FOREIGN KEY REFERENCES /*_*/mwuser(user_id),
    ar_user_text NVARCHAR(255) NOT NULL,
    ar_timestamp varchar(14) NOT NULL default '',
    ar_minor_edit BIT NOT NULL DEFAULT 0,
    ar_flags NVARCHAR(255) NOT NULL,
    ar_rev_id INT NULL, -- NOT a FK, the row gets deleted from revision and moved here
-   ar_text_id INT REFERENCES /*_*/text(old_id) ON DELETE CASCADE,
+   ar_text_id INT CONSTRAINT ar_text_id__old_id__fk FOREIGN KEY REFERENCES /*_*/text(old_id) ON DELETE CASCADE,
    ar_deleted TINYINT NOT NULL DEFAULT 0,
    ar_len INT,
    ar_page_id INT NULL, -- NOT a FK, the row gets deleted from page and moved here
-   ar_parent_id INT NULL REFERENCES /*_*/revision(rev_id),
+   ar_parent_id INT NULL, -- NOT FK
    ar_sha1 nvarchar(32) default null,
    ar_content_model nvarchar(32) DEFAULT NULL,
   ar_content_format nvarchar(64) DEFAULT NULL
@@ -218,11 +231,13 @@ CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
 --
 CREATE TABLE /*_*/pagelinks (
    pl_from INT NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+   pl_from_namespace int NOT NULL DEFAULT 0,
    pl_namespace INT NOT NULL DEFAULT 0,
    pl_title NVARCHAR(255) NOT NULL DEFAULT '',
 );
 CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
 CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE INDEX /*i*/pl_backlinks_namespace ON /*_*/pagelinks (pl_from_namespace,pl_namespace,pl_title,pl_from);
 
 
 --
@@ -230,12 +245,14 @@ CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,p
 --
 CREATE TABLE /*_*/templatelinks (
   tl_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+  tl_from_namespace int NOT NULL default 0,
   tl_namespace int NOT NULL default 0,
   tl_title nvarchar(255) NOT NULL default ''
 );
 
 CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
 CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE INDEX /*i*/tl_backlinks_namespace ON /*_*/templatelinks (tl_from_namespace,tl_namespace,tl_title,tl_from);
 
 
 --
@@ -246,6 +263,7 @@ CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_tit
 CREATE TABLE /*_*/imagelinks (
   -- Key to page_id of the page containing the image / media link.
   il_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+  il_from_namespace int NOT NULL default 0,
 
   -- Filename of target image.
   -- This is also the page_title of the file's description page;
@@ -255,6 +273,7 @@ CREATE TABLE /*_*/imagelinks (
 
 CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
 CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE INDEX /*i*/il_backlinks_namespace ON /*_*/imagelinks (il_from_namespace,il_to,il_from);
 
 --
 -- Track category inclusions *used inline*
@@ -313,8 +332,8 @@ CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_
 -- Used by the API (and some extensions)
 CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
 
--- FIXME: Not used, delete this
-CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+-- Used when updating collation (e.g. updateCollation.php)
+CREATE INDEX /*i*/cl_collation_ext ON /*_*/categorylinks (cl_collation, cl_to, cl_type, cl_from);
 
 --
 -- Track all existing categories.  Something is a category if 1) it has an en-
@@ -375,6 +394,9 @@ CREATE TABLE /*_*/externallinks (
 
 CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from);
 CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index);
+-- 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.
 
 --
 -- Track interlanguage links
@@ -536,7 +558,7 @@ CREATE TABLE /*_*/image (
   -- Filename.
   -- This is also the title of the associated description page,
   -- which will be in namespace 6 (NS_FILE).
-  img_name varbinary(255) NOT NULL default 0x PRIMARY KEY,
+  img_name nvarchar(255) NOT NULL default '' PRIMARY KEY,
 
   -- File size in bytes.
   img_size int NOT NULL default 0,
@@ -600,11 +622,12 @@ CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,im
 --
 CREATE TABLE /*_*/oldimage (
   -- Base filename: key to image.img_name
-  oi_name varbinary(255) NOT NULL default 0x REFERENCES /*_*/image(img_name) ON DELETE CASCADE ON UPDATE CASCADE,
+  -- Not a FK because deleting images removes them from image
+  oi_name nvarchar(255) NOT NULL default '',
 
   -- Filename of the archived file.
   -- This is generally a timestamp and '!' prepended to the base name.
-  oi_archive_name varbinary(255) NOT NULL default 0x,
+  oi_archive_name nvarchar(255) NOT NULL default '',
 
   -- Other fields as in image...
   oi_size int NOT NULL default 0,
@@ -616,7 +639,7 @@ CREATE TABLE /*_*/oldimage (
   oi_user_text nvarchar(255) NOT NULL,
   oi_timestamp varchar(14) NOT NULL default '',
 
-  oi_metadata nvarchar(max) NOT NULL,
+  oi_metadata varbinary(max) NOT NULL,
   oi_media_type varchar(16) default null,
   oi_major_mime varchar(16) not null default 'unknown',
   oi_minor_mime nvarchar(100) NOT NULL default 'unknown',
@@ -629,7 +652,6 @@ CREATE TABLE /*_*/oldimage (
 
 CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
 CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
--- oi_archive_name truncated to 14 to avoid key length overflow
 CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name);
 CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
 
@@ -668,7 +690,7 @@ CREATE TABLE /*_*/filearchive (
   fa_size int default 0,
   fa_width int default 0,
   fa_height int default 0,
-  fa_metadata nvarchar(max),
+  fa_metadata varbinary(max),
   fa_bits int default 0,
   fa_media_type varchar(16) default null,
   fa_major_mime varchar(16) not null default 'unknown',
@@ -763,16 +785,11 @@ CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
 -- the last few days, see Article::editUpdates()
 --
 CREATE TABLE /*_*/recentchanges (
-  rc_id int NOT NULL PRIMARY KEY IDENTITY,
+  rc_id int NOT NULL CONSTRAINT recentchanges__pk PRIMARY KEY IDENTITY,
   rc_timestamp varchar(14) not null default '',
 
-  -- This is no longer used
-  -- Field kept in database for downgrades
-  -- @todo: add drop patch with 1.24
-  rc_cur_time varchar(14) NOT NULL default '',
-
   -- As in revision
-  rc_user int NOT NULL default 0 REFERENCES /*_*/mwuser(user_id),
+  rc_user int NOT NULL default 0 CONSTRAINT rc_user__user_id__fk FOREIGN KEY REFERENCES /*_*/mwuser(user_id),
   rc_user_text nvarchar(255) NOT NULL,
 
   -- When pages are renamed, their RC entries do _not_ change.
@@ -794,13 +811,13 @@ CREATE TABLE /*_*/recentchanges (
   -- Key to page_id (was cur_id prior to 1.5).
   -- This will keep links working after moves while
   -- retaining the at-the-time name in the changes list.
-  rc_cur_id int REFERENCES /*_*/page(page_id),
+  rc_cur_id int, -- NOT FK
 
   -- rev_id of the given revision
-  rc_this_oldid int REFERENCES /*_*/revision(rev_id),
+  rc_this_oldid int, -- NOT FK
 
   -- rev_id of the prior revision, for generating diff links.
-  rc_last_oldid int REFERENCES /*_*/revision(rev_id),
+  rc_last_oldid int, -- NOT FK
 
   -- The type of change entry (RC_EDIT,RC_NEW,RC_LOG,RC_EXTERNAL)
   rc_type tinyint NOT NULL default 0,
@@ -969,7 +986,7 @@ CREATE TABLE /*_*/logging (
   log_timestamp varchar(14) NOT NULL default '',
 
   -- The user who performed this action; key to user_id
-  log_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL,
+  log_user int, -- NOT an FK, if a user is deleted we still want to maintain a record of who did a thing
 
   -- Name of the user who performed this action
   log_user_text nvarchar(255) NOT NULL default '',
@@ -978,7 +995,7 @@ CREATE TABLE /*_*/logging (
   -- this will point to the user page.
   log_namespace int NOT NULL default 0,
   log_title nvarchar(255) NOT NULL default '',
-  log_page int NULL REFERENCES /*_*/page(page_id) ON DELETE SET NULL,
+  log_page int NULL, -- NOT an FK, logging entries are inserted for deleted pages which still reference the deleted page ids
 
   -- Freeform text. Interpreted as edit history comments.
   log_comment nvarchar(255) NOT NULL default '',
@@ -1003,7 +1020,7 @@ CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timesta
 
 INSERT INTO /*_*/logging (log_user,log_page,log_params) VALUES(0,0,'');
 
-ALTER TABLE /*_*/recentchanges ADD CONSTRAINT FK_rc_logid_log_id FOREIGN KEY (rc_logid) REFERENCES /*_*/logging(log_id) ON DELETE CASCADE;
+ALTER TABLE /*_*/recentchanges ADD CONSTRAINT rc_logid__log_id__fk FOREIGN KEY (rc_logid) REFERENCES /*_*/logging(log_id) ON DELETE CASCADE;
 
 CREATE TABLE /*_*/log_search (
   -- The type of ID (rev ID, log ID, rev timestamp, username)
@@ -1157,11 +1174,13 @@ CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
 CREATE TABLE /*_*/page_props (
   pp_page int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
   pp_propname nvarchar(60) NOT NULL,
-  pp_value nvarchar(max) NOT NULL
+  pp_value nvarchar(max) NOT NULL,
+  pp_sortkey float DEFAULT NULL
 );
 
 CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
 CREATE UNIQUE INDEX /*i*/pp_propname_page ON /*_*/page_props (pp_propname,pp_page);
+CREATE UNIQUE INDEX /*i*/pp_propname_sortkey_page ON /*_*/page_props (pp_propname,pp_sortkey,pp_page);
 
 
 -- A table to log updates, one text key row per update.
index 48d9705..dbce7a7 100755 (executable)
@@ -70,7 +70,7 @@ COMMITMSG=$(cat <<END
 Update OOjs UI to v$OOJSUI_VERSION
 
 Release notes:
- https://git.wikimedia.org/blob/oojs%2Fui.git/v$OOJSUI_VERSION/History.md
+ https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v$OOJSUI_VERSION
 END
 )
 
index b91cb28..d3e778c 100755 (executable)
@@ -49,7 +49,7 @@ COMMITMSG=$(cat <<END
 Update OOjs to v$OOJS_VERSION
 
 Release notes:
- https://git.wikimedia.org/blob/oojs%2Fcore.git/v$OOJS_VERSION/History.md
+ https://phabricator.wikimedia.org/diffusion/GOJS/browse/master/History.md;v$OOJS_VERSION
 END
 )
 
index 2f414df..292a25d 100644 (file)
@@ -59,7 +59,7 @@ class RecompressTracked {
        public $reportingInterval = 10;
        public $numProcs = 1;
        public $numBatches = 0;
-       public $useDiff, $pageBlobClass, $orphanBlobClass;
+       public $pageBlobClass, $orphanBlobClass;
        public $slavePipes, $slaveProcs, $prevSlaveId;
        public $copyOnly = false;
        public $isChild = false;
@@ -112,8 +112,8 @@ class RecompressTracked {
                } elseif ( $this->slaveId !== false ) {
                        $GLOBALS['wgDebugLogPrefix'] = "RCT {$this->slaveId}: ";
                }
-               $this->useDiff = function_exists( 'xdiff_string_bdiff' );
-               $this->pageBlobClass = $this->useDiff ? 'DiffHistoryBlob' : 'ConcatenatedGzipHistoryBlob';
+               $this->pageBlobClass = function_exists( 'xdiff_string_bdiff' ) ?
+                       'DiffHistoryBlob' : 'ConcatenatedGzipHistoryBlob';
                $this->orphanBlobClass = 'ConcatenatedGzipHistoryBlob';
        }
 
index 6d9a616..922cc87 100644 (file)
@@ -34,7 +34,7 @@ require_once __DIR__ . '/Maintenance.php';
  */
 class UpdateCollation extends Maintenance {
        const BATCH_SIZE = 100; // Number of rows to process in one batch
-       const SYNC_INTERVAL = 20; // Wait for slaves after this many batches
+       const SYNC_INTERVAL = 5; // Wait for slaves after this many batches
 
        public $sizeHistogram = [];
 
@@ -70,6 +70,7 @@ TEXT
                global $wgCategoryCollation;
 
                $dbw = $this->getDB( DB_MASTER );
+               $dbr = $this->getDB( DB_SLAVE );
                $force = $this->getOption( 'force' );
                $dryRun = $this->getOption( 'dry-run' );
                $verboseStats = $this->getOption( 'verbose-stats' );
@@ -97,6 +98,7 @@ TEXT
                $options = [
                        'LIMIT' => self::BATCH_SIZE,
                        'ORDER BY' => $orderBy,
+                       'STRAIGHT_JOIN' // per T58041
                ];
 
                if ( $force || $dryRun ) {
@@ -110,7 +112,7 @@ TEXT
                                ];
                        }
 
-                       $count = $dbw->estimateRowCount(
+                       $count = $dbr->estimateRowCount(
                                'categorylinks',
                                '*',
                                $collationConds,
@@ -118,7 +120,7 @@ TEXT
                        );
                        // Improve estimate if feasible
                        if ( $count < 1000000 ) {
-                               $count = $dbw->selectField(
+                               $count = $dbr->selectField(
                                        'categorylinks',
                                        'COUNT(*)',
                                        $collationConds,
@@ -131,6 +133,7 @@ TEXT
                                return;
                        }
                        $this->output( "Fixing collation for $count rows.\n" );
+                       wfWaitForSlaves();
                }
                $count = 0;
                $batchCount = 0;
index ecd10fa..f9c6117 100644 (file)
@@ -5,12 +5,12 @@
 body {
        margin: 0;
        background: #eee;
-       font-family: Verdana;
+       font-family: 'Verdana';
        color: #333;
 }
 
 #main {
-       border: 1px solid #D0D0D0;
+       border: 1px solid #d0d0d0;
        background: #fff;
        margin: 0.5em;
 }
@@ -25,7 +25,7 @@ body {
        border-left: 1px dotted #ccc;
        float: right;
        width: 230px;
-       background: white;
+       background: #fff;
        margin: 0 0 10px 10px;
 }
 
index 0e0b304..66f8578 100644 (file)
 }
 
 .error {
-       color: red;
+       color: #f00;
        background-color: #fff;
        font-weight: bold;
        left: 1em;
 .success-message {
        font-weight: bold;
        font-size: 110%;
-       color: green;
+       color: #0f0;
 }
 
 .success-box {
index bef1688..92c182d 100644 (file)
  * @file
  */
 
+// This endpoint is supposed to be independent of request cookies and other
+// details of the session. Log warnings for violations of the no-session
+// constraint.
+define( 'MW_NO_SESSION', 'warn' );
+
 require_once __DIR__ . '/includes/WebStart.php';
 
 if ( $wgRequest->getVal( 'ctype' ) == 'application/xml' ) {
index 394f36e..fb3815d 100644 (file)
     "grunt-contrib-watch": "1.0.0",
     "grunt-jscs": "2.8.0",
     "grunt-jsonlint": "1.0.7",
-    "grunt-karma": "0.12.2",
+    "grunt-karma": "1.0.0",
+    "grunt-stylelint": "0.2.0",
     "karma": "0.13.22",
-    "karma-chrome-launcher": "0.2.2",
-    "karma-firefox-launcher": "0.1.7",
-    "karma-qunit": "0.1.9",
-    "qunitjs": "1.22.0"
+    "karma-chrome-launcher": "1.0.1",
+    "karma-firefox-launcher": "1.0.0",
+    "karma-qunit": "1.0.0",
+    "qunitjs": "1.22.0",
+    "stylelint-config-wikimedia": "0.1.0"
   }
 }
index 9563609..466f26a 100644 (file)
  * @file
  */
 
+// This endpoint is supposed to be independent of request cookies and other
+// details of the session. Log warnings for violations of the no-session
+// constraint.
+define( 'MW_NO_SESSION', 'warn' );
+
 ini_set( 'zlib.output_compression', 'off' );
 
 $wgEnableProfileInfo = false;
index cb7adbe..9a5931f 100644 (file)
@@ -735,7 +735,6 @@ return [
                'scripts' => [
                        'resources/lib/moment/moment.js',
                        'resources/src/moment-global.js',
-                       'resources/src/moment-local-dmy.js',
                ],
                'languageScripts' => [
                        'af' => 'resources/lib/moment/locale/af.js',
@@ -757,6 +756,7 @@ return [
                        'de' => 'resources/lib/moment/locale/de.js',
                        'de-at' => 'resources/lib/moment/locale/de-at.js',
                        'el' => 'resources/lib/moment/locale/el.js',
+                       'en' => 'resources/src/moment-dmy.js',
                        'en-au' => 'resources/lib/moment/locale/en-au.js',
                        'en-ca' => 'resources/lib/moment/locale/en-ca.js',
                        'en-gb' => 'resources/lib/moment/locale/en-gb.js',
@@ -817,6 +817,13 @@ return [
                        'zh-hans' => 'resources/lib/moment/locale/zh-cn.js',
                        'zh-hant' => 'resources/lib/moment/locale/zh-tw.js',
                ],
+               // HACK: skinScripts come after languageScripts, and we need locale overrides to come
+               // after locale definitions
+               'skinScripts' => [
+                       'default' => [
+                               'resources/src/moment-locale-overrides.js',
+                       ],
+               ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
 
@@ -1163,10 +1170,14 @@ return [
                        'upload-foreign-cant-upload',
                ]
        ],
+       'mediawiki.ForeignStructuredUpload.config' => [
+               'class' => 'ResourceLoaderUploadDialogModule',
+       ],
        'mediawiki.ForeignStructuredUpload' => [
                'scripts' => 'resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js',
                'dependencies' => [
                        'mediawiki.ForeignUpload',
+                       'mediawiki.ForeignStructuredUpload.config',
                ],
        ],
        'mediawiki.Upload.Dialog' => [
@@ -1213,6 +1224,8 @@ return [
                        'upload-form-label-usage-filename',
                        'api-error-unknownerror',
                        'api-error-unknown-warning',
+                       'api-error-autoblocked',
+                       'api-error-blocked',
                        'api-error-badaccess-groups',
                        'api-error-badtoken',
                        'api-error-copyuploaddisabled',
@@ -1276,22 +1289,20 @@ return [
                        'mediawiki.widgets.CategorySelector',
                        'mediawiki.widgets.DateInputWidget',
                        'mediawiki.jqueryMsg',
+                       'mediawiki.api.messages',
                        'moment',
                        'mediawiki.libs.jpegmeta',
                ],
                'messages' => [
-                       'foreign-structured-upload-form-label-own-work',
-                       'foreign-structured-upload-form-label-infoform-categories',
-                       'foreign-structured-upload-form-label-infoform-date',
-                       'foreign-structured-upload-form-label-own-work-message-default',
-                       'foreign-structured-upload-form-label-not-own-work-message-default',
-                       'foreign-structured-upload-form-label-not-own-work-local-default',
-                       'foreign-structured-upload-form-label-own-work-message-shared',
-                       'foreign-structured-upload-form-label-not-own-work-message-shared',
-                       'foreign-structured-upload-form-label-not-own-work-local-shared',
-                       'foreign-structured-upload-form-label-own-work-message-local',
-                       'foreign-structured-upload-form-label-not-own-work-message-local',
-                       'foreign-structured-upload-form-label-not-own-work-local-local',
+                       'upload-form-label-own-work',
+                       'upload-form-label-infoform-categories',
+                       'upload-form-label-infoform-date',
+                       'upload-form-label-own-work-message-generic-local',
+                       'upload-form-label-not-own-work-message-generic-local',
+                       'upload-form-label-not-own-work-local-generic-local',
+                       'upload-form-label-own-work-message-generic-foreign',
+                       'upload-form-label-not-own-work-message-generic-foreign',
+                       'upload-form-label-not-own-work-local-generic-foreign',
                ],
        ],
        'mediawiki.toc' => [
@@ -1869,6 +1880,7 @@ return [
                        'thumbnail.html' => 'resources/src/mediawiki.special/templates/thumbnail.html',
                ],
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.upload.js',
+               'styles' => 'resources/src/mediawiki.special/mediawiki.special.upload.css',
                'messages' => [
                        'widthheight',
                        'size-bytes',
@@ -2288,6 +2300,26 @@ return [
                ],
        ],
 
+       'mediawiki.router' => [
+               'scripts' => [
+                       'resources/src/mediawiki.router/index.js',
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+               'dependencies' => [
+                       'oojs-router',
+               ],
+       ],
+
+       'oojs-router' => [
+               'scripts' => [
+                       'resources/lib/oojs-router/oojs-router.js',
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+               'dependencies' => [
+                       'oojs',
+               ],
+       ],
+
        /* OOjs UI */
        // WARNING: OOjs-UI is NOT TESTED with older browsers and is likely to break
        // if loaded in browsers that don't support ES5
index b2e2bd4..b31fe82 100644 (file)
@@ -88,9 +88,6 @@ return call_user_func( function () {
                'skipFunction' => 'resources/src/oojs-ui-styles-skip.js',
        ];
 
-       // Deprecated old name for the module 'oojs-ui-core.styles'.
-       $modules['oojs-ui.styles'] = $modules['oojs-ui-core.styles'];
-
        // Additional widgets and layouts module.
        $modules['oojs-ui-widgets'] = [
                'scripts' => 'resources/lib/oojs-ui/oojs-ui-widgets.js',
diff --git a/resources/lib/oojs-router/AUTHORS.txt b/resources/lib/oojs-router/AUTHORS.txt
new file mode 100644 (file)
index 0000000..5390c84
--- /dev/null
@@ -0,0 +1 @@
+Jon Robson <jdlrobson@gmail.com>
diff --git a/resources/lib/oojs-router/LICENSE-MIT b/resources/lib/oojs-router/LICENSE-MIT
new file mode 100644 (file)
index 0000000..acbe708
--- /dev/null
@@ -0,0 +1,20 @@
+Copyright 2011-2016 OOjs Team and other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/resources/lib/oojs-router/oojs-router.js b/resources/lib/oojs-router/oojs-router.js
new file mode 100644 (file)
index 0000000..b136923
--- /dev/null
@@ -0,0 +1,205 @@
+/*!
+ * OOjs Router v0.1.0
+ * https://www.mediawiki.org/wiki/OOjs
+ *
+ * Copyright 2011-2016 OOjs Team and other contributors.
+ * Released under the MIT license
+ * http://oojs-router.mit-license.org
+ *
+ * Date: 2016-05-05T19:27:58Z
+ */
+( function ( $ ) {
+
+'use strict';
+
+/**
+ * Does hash match entry.path? If it does apply the
+ * callback for the Entry object.
+ *
+ * @method
+ * @private
+ * @ignore
+ * @param {string} hash string to match
+ * @param {Object} entry Entry object
+ * @return {boolean} Whether hash matches entry.path
+ */
+function matchRoute( hash, entry ) {
+       var match = hash.match( entry.path );
+       if ( match ) {
+               entry.callback.apply( this, match.slice( 1 ) );
+               return true;
+       }
+       return false;
+}
+
+/**
+ * Provides navigation routing and location information
+ *
+ * @class Router
+ * @mixins OO.EventEmitter
+ */
+function Router() {
+       var self = this;
+       OO.EventEmitter.call( this );
+       // use an object instead of an array for routes so that we don't
+       // duplicate entries that already exist
+       this.routes = {};
+       this.enabled = true;
+       this.oldHash = this.getPath();
+
+       $( window ).on( 'popstate', function () {
+               self.emit( 'popstate' );
+       } );
+
+       $( window ).on( 'hashchange', function () {
+               self.emit( 'hashchange' );
+       } );
+
+       this.on( 'hashchange', function () {
+               // ev.originalEvent.newURL is undefined on Android 2.x
+               var routeEv;
+
+               if ( self.enabled ) {
+                       routeEv = $.Event( 'route', {
+                               path: self.getPath()
+                       } );
+                       self.emit( 'route', routeEv );
+
+                       if ( !routeEv.isDefaultPrevented() ) {
+                               self.checkRoute();
+                       } else {
+                               // if route was prevented, ignore the next hash change and revert the
+                               // hash to its old value
+                               self.enabled = false;
+                               self.navigate( self.oldHash );
+                       }
+               } else {
+                       self.enabled = true;
+               }
+
+               self.oldHash = self.getPath();
+       } );
+}
+OO.mixinClass( Router, OO.EventEmitter );
+
+/**
+ * Check the current route and run appropriate callback if it matches.
+ *
+ * @method
+ */
+Router.prototype.checkRoute = function () {
+       var hash = this.getPath();
+
+       $.each( this.routes, function ( id, entry ) {
+               return !matchRoute( hash, entry );
+       } );
+};
+
+/**
+ * Bind a specific callback to a hash-based route, e.g.
+ *
+ *     @example
+ *     route( 'alert', function () { alert( 'something' ); } );
+ *     route( /hi-(.*)/, function ( name ) { alert( 'Hi ' + name ) } );
+ * Note that after defining all available routes it is up to the caller
+ * to check the existing route via the checkRoute method.
+ *
+ * @method
+ * @param {Object} path string or RegExp to match.
+ * @param {Function} callback Callback to be run when hash changes to one
+ * that matches.
+ */
+Router.prototype.route = function ( path, callback ) {
+       var entry = {
+               path: typeof path === 'string' ?
+                       new RegExp( '^' + path.replace( /[\\^$*+?.()|[\]{}]/g, '\\$&' ) + '$' )
+                       : path,
+               callback: callback
+       };
+       this.routes[ entry.path ] = entry;
+};
+
+/**
+ * Navigate to a specific route.
+ *
+ * @method
+ * @param {string} path string with a route (hash without #).
+ */
+Router.prototype.navigate = function ( path ) {
+       var history = window.history;
+       // Take advantage of `pushState` when available, to clear the hash and
+       // not leave `#` in the history. An entry with `#` in the history has
+       // the side-effect of resetting the scroll position when navigating the
+       // history.
+       if ( path === '' && history && history.pushState ) {
+               // To clear the hash we need to cut the hash from the URL.
+               path = window.location.href.replace( /#.*$/, '' );
+               history.pushState( null, document.title, path );
+               this.checkRoute();
+       } else {
+               window.location.hash = path;
+       }
+};
+
+/**
+ * Triggers back on the window
+ */
+Router.prototype.goBack = function () {
+       window.history.back();
+};
+
+/**
+ * Navigate to the previous route. This is a wrapper for window.history.back
+ *
+ * @method
+ * @return {jQuery.Deferred}
+ */
+Router.prototype.back = function () {
+       var deferredRequest = $.Deferred(),
+               self = this,
+               timeoutID;
+
+       this.once( 'popstate', function () {
+               clearTimeout( timeoutID );
+               deferredRequest.resolve();
+       } );
+
+       this.goBack();
+
+       // If for some reason (old browser, bug in IE/windows 8.1, etc) popstate doesn't fire,
+       // resolve manually. Since we don't know for sure which browsers besides IE10/11 have
+       // this problem, it's better to fall back this way rather than singling out browsers
+       // and resolving the deferred request for them individually.
+       // See https://connect.microsoft.com/IE/feedback/details/793618/history-back-popstate-not-working-as-expected-in-webview-control
+       // Give browser a few ms to update its history.
+       timeoutID = setTimeout( function () {
+               self.off( 'popstate' );
+               deferredRequest.resolve();
+       }, 50 );
+
+       return deferredRequest;
+};
+
+/**
+ * Get current path (hash).
+ *
+ * @method
+ * @return {string} Current path.
+ */
+Router.prototype.getPath = function () {
+       return window.location.hash.slice( 1 );
+};
+
+/**
+ * Determine if current browser supports onhashchange event
+ *
+ * @method
+ * @return {boolean}
+ */
+Router.prototype.isSupported = function () {
+       return 'onhashchange' in window;
+};
+
+module.exports = Router;
+
+}( jQuery ) );
index 62613b8..9a8b058 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:01Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index c928838..9ec7278 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-element-hidden {
        display: none !important;
        display: none;
 }
 .oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-decoration {
+.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
        display: none;
 }
 .oo-ui-textInputWidget > .oo-ui-iconElement-icon,
index 72e4db8..ba293e4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-element-hidden {
        display: none !important;
        font-weight: bold;
        text-decoration: none;
 }
+.oo-ui-buttonElement > .oo-ui-buttonElement-button:focus {
+       outline: 0;
+}
 .oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
        margin-left: 0;
 }
 .oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
        margin-left: 0.46875em;
 }
-.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.2);
-       outline: 0;
-}
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator {
        margin-right: 0;
 }
@@ -75,6 +74,9 @@
        padding-right: 0.25em;
        color: #333333;
 }
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       box-shadow: inset 0 0 0 1px #347bff, 0 0 0 1px #347bff;
+}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled > input.oo-ui-buttonElement-button,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #555555;
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #444444;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus > .oo-ui-labelElement-label {
-       color: #2962cc;
-}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #347bff;
 }
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
+       color: #2962cc;
+}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #1f4999;
        box-shadow: none;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:focus > .oo-ui-labelElement-label {
-       color: #2962cc;
-}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #347bff;
 }
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
+       color: #2962cc;
+}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #1f4999;
        box-shadow: none;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus > .oo-ui-labelElement-label {
-       color: #8c130d;
-}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #d11d13;
 }
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
+       color: #8c130d;
+}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #73100a;
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
        color: #cccccc;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button:focus {
-       box-shadow: none;
-}
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
           -moz-transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
                transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
 }
-.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:hover,
-.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:focus {
-       outline: 0;
-}
 .oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        line-height: 1.2em;
        background-color: #ebebeb;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
        color: #347bff;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover {
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
        background-color: #ebf2ff;
        border-color: #859dcc;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #1f4999;
-       border-color: #1f4999;
-}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        color: #1f4999;
        background-color: #999999;
        color: #ffffff;
 }
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
+}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
        color: #347bff;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:hover {
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
        background-color: #ebf2ff;
        border-color: #859dcc;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #1f4999;
-       border-color: #1f4999;
-}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        color: #1f4999;
        background-color: #999999;
        color: #ffffff;
 }
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
+}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
        color: #d11d13;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover {
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
        background-color: #fbe8e7;
        border-color: #b77c79;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #73100a;
-       border-color: #73100a;
-}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        color: #73100a;
        background-color: #999999;
        color: #ffffff;
 }
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       border-color: #d11d13;
+       box-shadow: inset 0 0 0 1px #d11d13;
+}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
        color: #ffffff;
        background-color: #347bff;
        border-color: #347bff;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover {
-       background: #2962cc;
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
+       background-color: #2962cc;
        border-color: #2962cc;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #ffffff;
-       border-color: #347bff;
-}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        color: #ffffff;
        background-color: #999999;
        color: #ffffff;
 }
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #ffffff;
+}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
        color: #ffffff;
        background-color: #347bff;
        border-color: #347bff;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:hover {
-       background: #2962cc;
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
+       background-color: #2962cc;
        border-color: #2962cc;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #ffffff;
-       border-color: #347bff;
-}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        color: #ffffff;
        background-color: #999999;
        color: #ffffff;
 }
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #ffffff;
+}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
        color: #ffffff;
        background-color: #d11d13;
        border-color: #d11d13;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover {
-       background: #8c130d;
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
+       background-color: #8c130d;
        border-color: #8c130d;
 }
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #ffffff;
-       border-color: #d11d13;
-}
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        color: #ffffff;
        background-color: #999999;
        color: #ffffff;
 }
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       border-color: #d11d13;
+       box-shadow: inset 0 0 0 1px #ffffff;
+}
 .oo-ui-clippableElement-clippable {
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
 .oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
 }
+.oo-ui-radioSelectWidget:focus {
+       outline: 0;
+}
+.oo-ui-radioSelectWidget:focus .oo-ui-radioOptionWidget.oo-ui-optionWidget-selected .oo-ui-radioInputWidget [type="radio"] + span {
+       border-width: 2px;
+}
 .oo-ui-radioOptionWidget {
        cursor: default;
        padding: 0.25em 0;
        border-radius: 0;
        margin-left: -1px;
 }
+.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button:focus {
+       z-index: 2;
+}
 .oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button {
        border-bottom-left-radius: 2px;
        border-top-left-radius: 2px;
        display: none;
 }
 .oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-decoration {
+.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
        display: none;
 }
 .oo-ui-textInputWidget > .oo-ui-iconElement-icon,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
        outline: 0;
        border-color: #347bff;
-       box-shadow: inset 0 0 0 0.1em #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
index 7128e7a..cbc02eb 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:01Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
@@ -2001,10 +2001,13 @@ OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
 /**
  * Set the button's active state.
  *
- * The active state occurs when a {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} or
- * a {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} is pressed. This method does nothing
- * for other button types.
+ * The active state can be set on:
  *
+ *  - {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} when it is selected
+ *  - {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} when it is toggle on
+ *  - {@link OO.ui.ButtonWidget ButtonWidget} when clicking the button would only refresh the page
+ *
+ * @protected
  * @param {boolean} value Make button active
  * @chainable
  */
@@ -2017,6 +2020,7 @@ OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
 /**
  * Check if the button is active
  *
+ * @protected
  * @return {boolean} The button is active
  */
 OO.ui.mixin.ButtonElement.prototype.isActive = function () {
@@ -3332,6 +3336,7 @@ OO.ui.mixin.AccessKeyedElement.prototype.getAccessKey = function () {
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [active=false] Whether button should be shown as active
  * @cfg {string} [href] Hyperlink to visit when the button is clicked.
  * @cfg {string} [target] The frame or window in which to open the hyperlink.
  * @cfg {boolean} [noFollow] Search engine traversal hint (default: true)
@@ -3366,6 +3371,7 @@ OO.ui.ButtonWidget = function OoUiButtonWidget( config ) {
        this.$element
                .addClass( 'oo-ui-buttonWidget' )
                .append( this.$button );
+       this.setActive( config.active );
        this.setHref( config.href );
        this.setTarget( config.target );
        this.setNoFollow( config.noFollow );
@@ -3522,6 +3528,14 @@ OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
        return this;
 };
 
+// Override method visibility hints from ButtonElement
+/**
+ * @method setActive
+ */
+/**
+ * @method isActive
+ */
+
 /**
  * A ButtonGroupWidget groups related buttons and is used together with OO.ui.ButtonWidget and
  * its subclasses. Each button in a group is addressed by a unique reference. Buttons can be added,
@@ -5021,6 +5035,7 @@ OO.ui.SelectWidget = function OoUiSelectWidget( config ) {
                toggle: 'onToggle'
        } );
        this.$element.on( {
+               focus: this.onFocus.bind( this ),
                mousedown: this.onMouseDown.bind( this ),
                mouseover: this.onMouseOver.bind( this ),
                mouseleave: this.onMouseLeave.bind( this )
@@ -5101,6 +5116,19 @@ OO.ui.SelectWidget.static.passAllFilter = function () {
 
 /* Methods */
 
+/**
+ * Handle focus events
+ *
+ * @private
+ * @param {jQuery.Event} event
+ */
+OO.ui.SelectWidget.prototype.onFocus = function () {
+       // The styles for focus state depend on one of the items being selected.
+       if ( !this.getSelectedItem() ) {
+               this.selectItem( this.getFirstSelectableItem() );
+       }
+};
+
 /**
  * Handle mouse down events.
  *
index 33f1e0f..3a99fba 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:01Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index c4eff7c..d757813 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index 4194cdf..82335a4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index 2817324..d976448 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:01Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index 8851558..7a45a25 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
 .oo-ui-selectFileWidget-dropTarget {
        cursor: default;
        height: 5.5em;
-       text-align: left;
        padding: 0;
 }
 .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
        position: absolute;
        right: 0.5em;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget {
-       text-align: center;
-}
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
        display: block;
 }
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
        display: none;
 }
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
+       cursor: no-drop;
+}
 .oo-ui-selectFileWidget:last-child {
        margin-right: 0;
 }
index 2cd8473..a530235 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
 .oo-ui-buttonSelectWidget:last-child {
        margin-right: 0;
 }
+.oo-ui-buttonSelectWidget:focus {
+       outline: 0;
+}
+.oo-ui-buttonSelectWidget:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
+       z-index: 2;
+}
 .oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
        border-radius: 0;
        margin-left: -1px;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus {
        border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
        outline: 0;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus.oo-ui-toggleWidget-on {
 .oo-ui-selectFileWidget-dropTarget {
        cursor: default;
        height: 5.5em;
-       text-align: left;
        padding: 0;
 }
 .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
        position: absolute;
        right: 0.5em;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget {
-       text-align: center;
-}
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
        display: block;
 }
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
        display: none;
 }
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
+       cursor: no-drop;
+}
 .oo-ui-selectFileWidget:last-child {
        margin-right: 0;
 }
           -moz-box-sizing: border-box;
                box-sizing: border-box;
        vertical-align: middle;
-       padding: 0 0.4em;
-       margin: 0.1em;
        height: 1.7em;
        line-height: 1.7em;
        background-color: #eeeeee;
-       border: 1px solid #cccccc;
        color: #555555;
+       margin: 0.1em;
+       border: 1px solid #cccccc;
        border-radius: 2px;
+       padding: 0 0.4em;
 }
 .oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
        display: inline-block;
 .oo-ui-capsuleItemWidget:focus {
        outline: 0;
        border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
 }
 .oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
+       background-color: #f3f3f3;
        color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
-       background-color: #f3f3f3;
+       text-shadow: 0 1px 1px #ffffff;
 }
 .oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
-       margin-top: -1.4em;
-       padding-left: 0.3em;
+       display: none;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
+       padding-right: 1.5375em;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-enabled > .oo-ui-buttonElement {
+       display: block;
+       position: absolute;
+       top: 0;
+       right: 0;
+       bottom: 0;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-enabled .oo-ui-buttonElement-button {
+       display: block;
+       width: 1.5375em;
+       height: 100%;
+}
+.oo-ui-capsuleItemWidget.oo-ui-widget-enabled .oo-ui-buttonElement-button .oo-ui-indicator-clear {
+       position: absolute;
+       top: 0;
+       right: 0.3em;
+       bottom: 0;
+       height: auto;
 }
 .oo-ui-searchWidget-query {
        position: absolute;
 .oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
        width: 2.5em;
 }
-.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
        border-right-width: 0;
 }
-.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
        border-top-left-radius: 0;
        border-bottom-left-radius: 0;
        border-left-width: 0;
index 895cea4..e3c2bd5 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:01Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
@@ -332,74 +332,23 @@ OO.ui.mixin.DraggableGroupElement.prototype.onItemDropOrDragEnd = function () {
  * @fires reorder
  */
 OO.ui.mixin.DraggableGroupElement.prototype.onDragOver = function ( e ) {
-       var dragOverObj, $optionWidget, itemOffset, itemMidpoint, itemBoundingRect,
-               itemSize, cssOutput, dragPosition, overIndex, itemPosition, after,
-               targetIndex = null,
+       var overIndex, targetIndex,
                item = this.getDragItem(),
-               dragItemIndex = item.getIndex(),
-               clientX = e.originalEvent.clientX,
-               clientY = e.originalEvent.clientY;
+               dragItemIndex = item.getIndex();
 
        // Get the OptionWidget item we are dragging over
-       dragOverObj = this.getElementDocument().elementFromPoint( clientX, clientY );
-       $optionWidget = $( dragOverObj ).closest( '.oo-ui-draggableElement' );
-       if ( $optionWidget[ 0 ] ) {
-               itemOffset = $optionWidget.offset();
-               itemBoundingRect = $optionWidget[ 0 ].getBoundingClientRect();
-               itemPosition = $optionWidget.position();
-               overIndex = $optionWidget.data( 'index' );
-       }
+       overIndex = $( e.target ).closest( '.oo-ui-draggableElement' ).data( 'index' );
+
+       if ( overIndex !== undefined && overIndex !== dragItemIndex ) {
+               targetIndex = overIndex + ( overIndex > dragItemIndex ? 1 : 0 );
 
-       if (
-               itemOffset &&
-               overIndex !== dragItemIndex
-       ) {
-               if ( this.orientation === 'horizontal' ) {
-                       // Calculate where the mouse is relative to the item width
-                       itemSize = itemBoundingRect.width;
-                       itemMidpoint = itemBoundingRect.left + itemSize / 2;
-                       dragPosition = clientX;
-                       // Which side of the item we hover over will dictate
-                       // where to drop the selected item, on the left or
-                       // on the right
-                       cssOutput = {
-                               left: dragPosition < itemMidpoint ? itemPosition.left : itemPosition.left + itemSize,
-                               top: itemPosition.top
-                       };
-               } else {
-                       // Calculate where the mouse is relative to the item height
-                       itemSize = itemBoundingRect.height;
-                       itemMidpoint = itemBoundingRect.top + itemSize / 2;
-                       dragPosition = clientY;
-                       // Which side of the item we hover over will dictate
-                       // where to drop the selected item, on the top or
-                       // on the bottom
-                       cssOutput = {
-                               top: dragPosition < itemMidpoint ? itemPosition.top : itemPosition.top + itemSize,
-                               left: itemPosition.left
-                       };
-               }
-               // Store whether we are before or after an item to rearrange
-               // For horizontal layout, we need to account for RTL, as this is flipped
-               if ( this.orientation === 'horizontal' && this.dir === 'rtl' ) {
-                       after = dragPosition < itemMidpoint;
-               } else {
-                       after = dragPosition > itemMidpoint;
-               }
-               targetIndex = overIndex + ( after ? 1 : 0 );
-               // Check the targetIndex isn't immediately to the left or right of the current item (a no-op)
-               if ( targetIndex === dragItemIndex || targetIndex === dragItemIndex + 1 ) {
-                       targetIndex = null;
-               }
-       }
-       if ( targetIndex !== null ) {
                if ( targetIndex > 0 ) {
                        this.$group.children().eq( targetIndex - 1 ).after( item.$element );
                } else {
                        this.$group.prepend( item.$element );
                }
-               // Move item in itemsOrder array. Needs to account for left shift if the item is moved forward.
-               this.itemsOrder.splice( targetIndex - ( targetIndex > dragItemIndex ? 1 : 0 ), 0,
+               // Move item in itemsOrder array
+               this.itemsOrder.splice( overIndex, 0,
                        this.itemsOrder.splice( dragItemIndex, 1 )[ 0 ]
                );
                this.updateIndexes();
index b1a3c3b..6dfe142 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
index d323fec..9a544d6 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:05Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-window {
        background: transparent;
index 7f33fad..37b7d90 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.0
+ * OOjs UI v0.17.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-04-26T21:34:01Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index 33d9a00..5689256 100644 (file)
@@ -7,9 +7,9 @@
 .tipsy-inner {
        padding: 5px 8px 4px 8px;
        /*background-color: #e8f2f8;*/
-       background-color: #ffffff;
+       background-color: #fff;
        border: solid 1px #a7d7f9;
-       color: black;
+       color: #000;
        max-width: 15em;
        border-radius: 4px;
        /*
index f8f6e95..254836a 100644 (file)
@@ -1,7 +1,7 @@
 .arrowSteps {
        list-style-type: none;
        list-style-image: none;
-       border: 1px solid #666666;
+       border: 1px solid #666;
        position: relative;
 }
 
index 34cdf76..31158f7 100644 (file)
@@ -11,7 +11,7 @@
 
 .mw-badge-content {
        font-weight: bold;
-       color: white;
+       color: #fff;
        vertical-align: baseline;
        text-shadow: 0 1px rgba(0, 0, 0, 0.4);
 }
@@ -32,5 +32,5 @@
 }
 
 .mw-badge-important {
-       background-color: #cc0000;
+       background-color: #c00;
 }
index 79e8731..bdb5ce8 100644 (file)
         */
        function togglingHandler( $toggle, $collapsible, e, options ) {
                var wasCollapsed, $textContainer, collapseText, expandText;
-
-               if ( options === undefined ) {
-                       options = {};
-               }
+               options = options || {};
 
                if ( e ) {
                        if (
         * @chainable
         */
        $.fn.makeCollapsible = function ( options ) {
-               if ( options === undefined ) {
-                       options = {};
-               }
+               options = options || {};
 
                this.each( function () {
                        var $collapsible, collapseText, expandText, $caption, $toggle, actionHandler, buildDefaultToggleLink,
index 15cd926..f6b4fd1 100644 (file)
@@ -14,9 +14,9 @@
 
 .suggestions-special {
        position: relative;
-       background-color: white;
+       background-color: #fff;
        cursor: pointer;
-       border: solid 1px #aaaaaa;
+       border: solid 1px #aaa;
        padding: 0;
        margin: 0;
        margin-top: -2px;
 }
 
 .suggestions-results {
-       background-color: white;
+       background-color: #fff;
        cursor: pointer;
-       border: solid 1px #aaaaaa;
+       border: solid 1px #aaa;
        padding: 0;
        margin: 0;
 }
 
 .suggestions-result {
-       color: black;
+       color: #000;
        margin: 0;
        line-height: 1.5em;
        padding: 0.01em 0.25em;
 }
 
 .suggestions-result-current {
-       background-color: #4C59A6;
-       color: white;
+       background-color: #4c59a6;
+       color: #fff;
 }
 
 .suggestions-special .special-label {
-       color: gray;
+       color: #808080;
        text-align: left;
 }
 
 .suggestions-special .special-query {
-       color: black;
+       color: #000;
        font-style: italic;
        text-align: left;
 }
 
 .suggestions-special .special-hover {
-       background-color: silver;
+       background-color: #c0c0c0;
 }
 
 .suggestions-result-current .special-label,
 .suggestions-result-current .special-query {
-       color: white;
+       color: #fff;
 }
 
 .highlight {
index 603a965..835383e 100644 (file)
@@ -2,3 +2,8 @@
 #pagehistory li.after input[name="diff"] {
        visibility: hidden;
 }
+
+span.updatedmarker {
+       color: #000;
+       background-color: #0f0;
+}
index 0887476..9db6777 100644 (file)
@@ -72,7 +72,7 @@ td.diff-deletedline {
 td.diff-context {
        background: #f9f9f9;
        border-color: #e6e6e6;
-       color: #333333;
+       color: #333;
 }
 
 .diffchange {
index 5112728..450517e 100644 (file)
@@ -38,7 +38,7 @@ table.filehistory td.filehistory-selected {
 .filehistory a img,
 #file img:hover {
        /* @embed */
-       background: white url(images/checker.png) repeat;
+       background: #fff url(images/checker.png) repeat;
 }
 
 /*
@@ -46,7 +46,7 @@ table.filehistory td.filehistory-selected {
  */
 ul#filetoc {
        text-align: center;
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        background-color: #f9f9f9;
        padding: 5px;
        font-size: 95%;
index b1a63b0..4c75e33 100644 (file)
@@ -14,7 +14,7 @@
                        $table = $( '#mw_metadata' ),
                        $tbody = $table.find( 'tbody' );
 
-               if ( !$tbody.length || !$tbody.find( '.collapsable' ).length ) {
+               if ( !$tbody.find( '.collapsable' ).length ) {
                        return;
                }
 
index db89990..a02b4b4 100644 (file)
        font-size: 1.25em;
        font-weight: bold;
        line-height: 2.3em;
-       color: black;
-       text-shadow: 0 0.0625em 0 white;
+       color: #000;
+       text-shadow: 0 0.0625em 0 #fff;
        text-decoration: none;
        opacity: 0.2;
        filter: alpha(opacity=20);
 }
 
 .postedit-close:hover {
-       color: black;
+       color: #000;
        text-decoration: none;
        opacity: 0.4;
        filter: alpha(opacity=40);
index 975ec2a..d04e3a6 100644 (file)
@@ -84,6 +84,7 @@ pre,
 }
 
 img,
+figure,
 .wikitable,
 .thumb {
        /* Pagination */
@@ -172,6 +173,7 @@ a {
 .mw-body a.external.text:after,
 .mw-body a.external.autonumber:after {
        content: " (" attr( href ) ")";
+       word-break: break-all;
        word-wrap: break-word;
 }
 
index 9adfba1..c58bcc8 100644 (file)
@@ -80,7 +80,7 @@ table.rimage {
 div.thumb {
        margin-bottom: .5em;
        border-style: solid;
-       border-color: white;
+       border-color: #fff;
        width: auto;
 }
 
@@ -147,7 +147,7 @@ div.tleft {
 }
 
 img.thumbborder {
-       border: 1px solid #dddddd;
+       border: 1px solid #ddd;
 }
 
 /* Page history styling */
@@ -212,7 +212,7 @@ table.toc td {
 }
 
 .error {
-       color: red;
+       color: #f00;
        font-size: larger;
 }
 
@@ -224,12 +224,12 @@ table.toc td {
 }
 
 #preftoc li {
-       border: 1px solid White;
+       border: 1px solid #fff;
 }
 
 #preftoc li.selected {
        background-color: #f9f9f9;
-       border: 1px dashed #aaaaaa;
+       border: 1px dashed #aaa;
 }
 
 #preftoc a,
@@ -272,14 +272,14 @@ table.small {
 
 /* use this instead of #toc for page content */
 .toccolours {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        background-color: #f9f9f9;
        padding: 5px;
        font-size: 95%;
 }
 
 #siteNotice {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding-left: 0.5em;
        padding-right: 0.5em;
 }
@@ -290,12 +290,12 @@ table.small {
 
 span.unpatrolled {
        font-weight: bold;
-       color: red;
+       color: #f00;
 }
 
 span.updatedmarker {
-       color: black;
-       background-color: #00FF00;
+       color: #000;
+       background-color: #0f0;
 }
 
 div.gallerybox {
@@ -308,14 +308,14 @@ span.comment {
 
 .previewnote {
        text-align: center;
-       color: #cc0000;
+       color: #c00;
 }
 
 .editExternally {
        border-style: solid;
        border-width: 1px;
-       border-color: gray;
-       background: #ffffff;
+       border-color: #808080;
+       background: #fff;
        padding: 3px;
        margin-top: 0.5em;
        float: left;
@@ -325,7 +325,7 @@ span.comment {
 
 .editExternallyHelp {
        font-style: italic;
-       color: gray;
+       color: #808080;
 }
 
 li span.deleted {
@@ -358,7 +358,7 @@ table.mw_metadata {
 
 table.mw_metadata td,
 table.mw_metadata th {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding-left: 4px;
        padding-right: 4px;
 }
@@ -403,7 +403,7 @@ table.mw_metadata td.spacer {
 }
 
 div.multipageimagenavbox {
-       border: solid 1px silver;
+       border: solid 1px #c0c0c0;
        padding: 4px;
        margin: 1em;
        background: #f0f0f0;
@@ -450,7 +450,7 @@ body {
 }
 
 body.ns-0 {
-       background-color: white;
+       background-color: #fff;
 }
 
 /** RTL specific CSS starts here **/
index 4c618d4..e18ef69 100644 (file)
@@ -44,8 +44,8 @@
 
 /* User-Agent styles for new HTML5 elements */
 mark {
-       background-color: yellow;
-       color: black;
+       background-color: #ff0;
+       color: #000;
 }
 
 /* Helper for wbr element on IE 8+; in HTML5, but not supported by default as of IE 11. */
@@ -112,7 +112,6 @@ abbr[title],
 .mw-plusminus-neg,
 .mw-plusminus-null {
        unicode-bidi: -moz-isolate;
-       unicode-bidi: -webkit-isolate;
        unicode-bidi: isolate;
 }
 
@@ -120,7 +119,6 @@ abbr[title],
 span.comment {
        font-style: italic;
        unicode-bidi: -moz-isolate;
-       unicode-bidi: -webkit-isolate;
        unicode-bidi: isolate;
 }
 
@@ -158,7 +156,7 @@ span.history-deleted {
 
 .unpatrolled {
        font-weight: bold;
-       color: red;
+       color: #f00;
 }
 
 div.patrollink {
@@ -248,7 +246,7 @@ input#wpSummary {
 .catlinks li {
        display: inline-block;
        line-height: 1.25em;
-       border-left: 1px solid #AAA;
+       border-left: 1px solid #aaa;
        margin: 0.125em 0;
        padding: 0 0.5em;
        zoom: 1;
@@ -290,7 +288,7 @@ p.mw-delete-editreasons {
 
 /* The auto-generated edit comments */
 .autocomment {
-       color: gray;
+       color: #808080;
 }
 
 #pagehistory .history-user {
@@ -299,7 +297,7 @@ p.mw-delete-editreasons {
 }
 
 #pagehistory li {
-       border: 1px solid white;
+       border: 1px solid #fff;
 }
 
 #pagehistory li.selected {
@@ -327,7 +325,7 @@ p.mw-delete-editreasons {
 div.mw-warning-with-logexcerpt {
        padding: 3px;
        margin-bottom: 3px;
-       border: 2px solid #2F6FAB;
+       border: 2px solid #2f6fab;
        clear: both;
 }
 
@@ -354,7 +352,7 @@ th.mw-revdel-checkbox {
 
 /* red links; see bug 36276 */
 a.new {
-       color: #BA0000;
+       color: #ba0000;
 }
 
 /* Plainlinks - this can be used to switch
@@ -380,7 +378,7 @@ table.wikitable {
        background-color: #f9f9f9;
        border: 1px solid #aaa;
        border-collapse: collapse;
-       color: black;
+       color: #000;
 }
 
 table.wikitable > tr > th,
@@ -409,7 +407,7 @@ table.wikitable > caption {
 }
 
 .error {
-       color: #cc0000;
+       color: #c00;
 }
 
 .warning {
@@ -443,7 +441,7 @@ table.wikitable > caption {
 }
 
 .errorbox {
-       color: #cc0000;
+       color: #c00;
        border-color: #fac5c5;
        background-color: #fae3e3;
 }
@@ -506,20 +504,20 @@ table.wikitable > caption {
 .mw-datatable,
 .mw-datatable td,
 .mw-datatable th {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding: 0 0.15em 0 0.15em;
 }
 
 .mw-datatable th {
-       background-color: #ddddff;
+       background-color: #ddf;
 }
 
 .mw-datatable td {
-       background-color: #ffffff;
+       background-color: #fff;
 }
 
 .mw-datatable tr:hover td {
-       background-color: #eeeeff;
+       background-color: #eef;
 }
 
 /* Classes for Exif data display */
@@ -536,6 +534,7 @@ table.mw_metadata caption {
 
 table.mw_metadata th {
        font-weight: normal;
+       text-align: center;
 }
 
 table.mw_metadata td {
@@ -549,8 +548,7 @@ table.mw_metadata {
 
 table.mw_metadata td,
 table.mw_metadata th {
-       text-align: center;
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding-left: 5px;
        padding-right: 5px;
 }
diff --git a/resources/src/mediawiki.router/index.js b/resources/src/mediawiki.router/index.js
new file mode 100644 (file)
index 0000000..a49cfeb
--- /dev/null
@@ -0,0 +1,2 @@
+var Router = require( 'oojs-router' );
+module.exports = new Router();
index 89f8745..38f33be 100644 (file)
@@ -198,7 +198,7 @@ div.magnify a {
 }
 
 img.thumbborder {
-       border: 1px solid #dddddd;
+       border: 1px solid #ddd;
 }
 
 /* Directionality-specific styles for thumbnails - their positioning depends on content language */
index 66b3fb2..74911b7 100644 (file)
@@ -35,7 +35,6 @@ span.reference {
        line-height: 1;
        vertical-align: super;
        unicode-bidi: -moz-isolate;
-       unicode-bidi: -webkit-isolate;
        unicode-bidi: isolate;
 }
 
@@ -127,7 +126,7 @@ figure[typeof~='mw:Image/Frame'] {
 figure[typeof~='mw:Image/Thumb'] > *:first-child > img,
 figure[typeof~='mw:Image/Frame'] > *:first-child > img,
 .mw-image-border > *:first-child > img {
-       border: 1px solid #cccccc;
+       border: 1px solid #ccc;
        margin: 3px;
 }
 
index 7872085..7b0b071 100644 (file)
@@ -34,7 +34,7 @@ a:lang(ur) {
 }
 
 a.stub {
-       color: #772233;
+       color: #723;
 }
 
 a.new, #p-personal a.new {
@@ -97,7 +97,7 @@ h3,
 h4,
 h5,
 h6 {
-       color: black;
+       color: #000;
        background: none;
        font-weight: normal;
        margin: 0;
@@ -195,11 +195,11 @@ pre, code, tt, kbd, samp, .mw-code {
         * Some browsers will render the monospace text too small, namely Firefox, Chrome and Safari.
         * Specifying any valid, second value will trigger correct behavior without forcing a different font.
         */
-       font-family: monospace, Courier;
+       font-family: monospace, 'Courier';
 }
 
 code {
-       color: black;
+       color: #000;
        background-color: #f9f9f9;
        border: 1px solid #ddd;
        border-radius: 2px;
@@ -208,7 +208,7 @@ code {
 
 pre,
 .mw-code {
-       color: black;
+       color: #000;
        background-color: #f9f9f9;
        border: 1px solid #ddd;
        padding: 1em;
index b57ee36..8b07721 100644 (file)
@@ -16,8 +16,8 @@
 }
 
 .editOptions {
-       background-color: #F0F0F0;
-       border: 1px solid silver;
+       background-color: #f0f0f0;
+       border: 1px solid #c0c0c0;
        border-top: none;
        padding: 1em 1em 1.5em 1em;
        margin-bottom: 2em;
@@ -26,7 +26,7 @@
 .usermessage {
        background-color: #ffce7b;
        border: 1px solid #ffa500;
-       color: black;
+       color: #000;
        font-weight: bold;
        margin: 2em 0 1em;
        padding: .5em 1em;
index 15f4e4d..f2019e7 100644 (file)
@@ -103,7 +103,7 @@ span.mw-protectedpages-actions {
        font-size: 90%;
 }
 span.mw-protectedpages-unknown {
-       color: grey;
+       color: #808080;
        font-size: 90%;
 }
 
index e05d163..4c9c41e 100644 (file)
@@ -3,7 +3,7 @@
 .mw-email-none .mw-input{
        border: 1px solid #fde29b;
        background-color: #fdf1d1;
-       color: #000000;
+       color: #000;
 }
 /* Authenticated email field has its own class too. Unstyled by default */
 /*
old mode 100755 (executable)
new mode 100644 (file)
index 1ce9569..1e99361
@@ -44,13 +44,13 @@ div.searchresult {
        font-size: 108%;
 }
 .mw-search-result-data {
-       color: green;
+       color: #0f0;
        font-size: 97%;
 }
 .mw-search-profile-tabs {
        background-color: #f3f3f3;
        margin-top: 1em;
-       border: 1px solid silver;
+       border: 1px solid #c0c0c0;
 }
 .mw-search-profile-tabs div.search-types {
        float: left;
@@ -71,7 +71,7 @@ div.searchresult {
        padding: 0.5em;
 }
 .mw-search-profile-tabs div.search-types ul li.current a {
-       color: #333333;
+       color: #333;
        cursor: default;
 }
 .mw-search-profile-tabs div.search-types ul li.current a:hover {
@@ -90,7 +90,7 @@ fieldset#mw-searchoptions {
        padding: 0.5em 0.75em 0.75em 0.75em;
        border: none;
        background-color: #f9f9f9;
-       border: 1px solid silver;
+       border: 1px solid #c0c0c0;
        border-top-width: 0;
 }
 fieldset#mw-searchoptions legend {
@@ -121,18 +121,18 @@ fieldset#mw-searchoptions table td {
 }
 fieldset#mw-searchoptions div.divider {
        clear: both;
-       border-bottom: 1px solid #DDDDDD;
+       border-bottom: 1px solid #ddd;
        padding-top: 0.5em;
        margin-bottom: 0.5em;
 }
 td#mw-search-menu {
-       padding-left:6em;
-       font-size:85%;
+       padding-left: 6em;
+       font-size: 85%;
 }
 div#mw-search-interwiki {
        float: right;
        width: 18em;
-       border: 1px solid #AAAAAA;
+       border: 1px solid #aaa;
        margin-top: 2ex;
 }
 div#mw-search-interwiki li {
@@ -152,7 +152,7 @@ div#mw-search-interwiki-caption {
        text-align: left;
        padding: 0.15em 0.15em 0.2em 0.2em;
        background-color: #ececec;
-       border-top: 1px solid #BBBBBB;
+       border-top: 1px solid #bbb;
 }
 span.searchalttitle {
        font-size: 95%;
diff --git a/resources/src/mediawiki.special/mediawiki.special.upload.css b/resources/src/mediawiki.special/mediawiki.special.upload.css
new file mode 100644 (file)
index 0000000..b916248
--- /dev/null
@@ -0,0 +1,10 @@
+/*!
+ * Styling for Special:Upload
+ */
+.mw-destfile-warning {
+       border: 1px solid #fde29b;
+       padding: .5em 1em;
+       margin-bottom: 1em;
+       color: #705000;
+       background-color: #fdf1d1;
+}
index 9836403..dd48367 100644 (file)
                },
 
                setWarning: function ( warning ) {
-                       var $warning = $( $.parseHTML( warning ) );
+                       var $warningBox = $( '#wpDestFile-warning' ),
+                               $warning = $( $.parseHTML( warning ) );
                        mw.hook( 'wikipage.content' ).fire( $warning );
-                       $( '#wpDestFile-warning' ).empty().append( $warning );
+                       $warningBox.empty().append( $warning );
 
                        // Set a value in the form indicating that the warning is acknowledged and
                        // doesn't need to be redisplayed post-upload
                        if ( !warning ) {
                                $( '#wpDestFileWarningAck' ).val( '' );
+                               $warningBox.removeAttr( 'class' );
                        } else {
                                $( '#wpDestFileWarningAck' ).val( '1' );
+                               $warningBox.attr( 'class', 'mw-destfile-warning' );
                        }
 
                }
index 0998d4c..87cdb02 100644 (file)
@@ -45,7 +45,7 @@ div.mw-createacct-benefits-container h2 {
        margin: 0;
        padding: 0;
        color: #252525;
-       font-family: "Linux Libertine", Georgia, Times, serif;
+       font-family: 'Linux Libertine', 'Georgia', 'Times', serif;
        font-weight: normal;
        font-size: 2.2em;
        line-height: 1.2;
index a7beb0d..74c75ea 100644 (file)
                }
        }
 
-       background-color: white;
+       background-color: #fff;
        border: 1px solid #ccc;
 
        &.mw-widgets-datetime-calendarWidget-dependent {
                margin-top: -1px;
-               border-top: 1px solid white;
+               border-top: 1px solid #fff;
        }
 
        &-heading {
index bc387df..84788d2 100644 (file)
@@ -51,7 +51,7 @@
                padding: 0 1em;
                margin: 0;
                background-color: #fff;
-               color: black;
+               color: #000;
                border: solid 1px #ccc;
                box-shadow: inset 0 0 0 0 @progressive;
                border-radius: 0.1em;
 
                &.oo-ui-flaggedElement-invalid {
                        .mw-widgets-datetime-dateTimeInputWidget-handle {
-                               border-color: red;
-                               box-shadow: inset 0 0 0 0 red;
+                               border-color: #f00;
+                               box-shadow: inset 0 0 0 0 #f00;
                        }
 
                        .mw-widgets-datetime-dateTimeInputWidget-handle:focus {
-                               border-color: red;
-                               box-shadow: inset 0 0 0 0.1em red;
+                               border-color: #f00;
+                               box-shadow: inset 0 0 0 0.1em #f00;
                        }
                }
        }
        }
 
        &-editField.mw-widgets-datetime-dateTimeInputWidget-invalid {
-               border: 1px solid red;
-               box-shadow: inset 0 0 0 0 red;
+               border: 1px solid #f00;
+               box-shadow: inset 0 0 0 0 #f00;
 
                &:focus {
-                       border: 1px solid red;
-                       box-shadow: inset 0 0 0 0.1em red;
+                       border: 1px solid #f00;
+                       box-shadow: inset 0 0 0 0.1em #f00;
                }
        }
 
index 58115c3..2eb84e6 100644 (file)
         * @constructor
         * @inheritdoc
         */
-       function ForeignTitle() {
-               ForeignTitle.parent.apply( this, arguments );
+       function ForeignTitle( title, namespace ) {
+               // We only need to handle categories here... but we don't know the target language.
+               // So assume that any namespace-like prefix is the 'Category' namespace...
+               title = title.replace( /^(.+?)_*:_*(.*)$/, 'Category:$2' ); // HACK
+               ForeignTitle.parent.call( this, title, namespace );
        }
        OO.inheritClass( ForeignTitle, mw.Title );
        ForeignTitle.prototype.getNamespacePrefix = function () {
index 873cca1..ee571cb 100644 (file)
@@ -74,7 +74,7 @@
                border: 1px solid #ccc;
                border-radius: 0.1em;
                line-height: 1.275em;
-               background-color: white;
+               background-color: #fff;
        }
 
        &.oo-ui-indicatorElement .mw-widget-dateInputWidget-handle > .oo-ui-indicatorElement-indicator {
@@ -91,7 +91,7 @@
        }
 
        &-calendar {
-               background-color: white;
+               background-color: #fff;
                margin-top: -2px;
 
                &:focus {
 
        &.oo-ui-flaggedElement-invalid {
                .mw-widget-dateInputWidget-handle {
-                       border-color: red;
-                       box-shadow: inset 0 0 0 0 red;
+                       border-color: #f00;
+                       box-shadow: inset 0 0 0 0 #f00;
                }
        }
 
old mode 100644 (file)
new mode 100755 (executable)
index 8c2b53a..df03679
         * @cfg {boolean} [performSearchOnClick=true] If true, the script will start a search when-
         *  ever a user hits a suggestion. If false, the text of the suggestion is inserted into the
         *  text field only.
+        *  @cfg {string} [dataLocation='header'] Where the search input field will be
+        *  used (header or content).
         */
        mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) {
                config = $.extend( {
                        type: 'search',
                        icon: 'search',
                        maxLength: undefined,
-                       performSearchOnClick: true
+                       performSearchOnClick: true,
+                       dataLocation: 'header'
                }, config );
 
                // Parent constructor
index cf9496f..ecfc880 100644 (file)
@@ -20,8 +20,8 @@
 
 .mw-widgets-stashedFileWidget-info {
        height: 2.4em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #ccc;
        border-radius: 2px;
        width: 100%;
        display: table-cell;
@@ -51,7 +51,7 @@
                        float: left;
                }
                > .mw-widgets-stashedFileWidget-fileType {
-                       color: #888888;
+                       color: #888;
                        float: right;
                }
        }
@@ -79,9 +79,9 @@
 
        &.oo-ui-widget-disabled {
                .mw-widgets-stashedFileWidget-info {
-                       color: #cccccc;
-                       text-shadow: 0 1px 1px #ffffff;
-                       border-color: #dddddd;
+                       color: #ccc;
+                       text-shadow: 0 1px 1px #fff;
+                       border-color: #ddd;
                        background-color: #f3f3f3;
 
                        > .oo-ui-iconElement-icon,
@@ -97,8 +97,8 @@
        height: 5.5em;
        text-align: left;
        padding: 0;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #ccc;
        margin-bottom: 0.5em;
        vertical-align: middle;
        overflow: hidden;
 
                > .mw-widgets-stashedFileWidget-noThumbnail-icon {
                        opacity: 0.4;
-                       background-color: #cccccc;
+                       background-color: #ccc;
                        height: 5.5em;
                        width: 5.5em;
                }
        }
 
        .mw-widgets-stashedFileWidget-label {
-               color: #cccccc;
+               color: #ccc;
                right: 0.5em;
        }
 
index 3d97711..fdd4a8a 100644 (file)
                                disambiguation: OO.getProp( suggestionPage, 'pageprops', 'disambiguation' ) !== undefined,
                                imageUrl: OO.getProp( suggestionPage, 'thumbnail', 'source' ),
                                description: OO.getProp( suggestionPage, 'terms', 'description' ),
-                               // sort index
+                               // Sort index
                                index: suggestionPage.index
                        };
 
                                        missing: false,
                                        redirect: true,
                                        disambiguation: false,
-                                       description: mw.msg( 'mw-widgets-titleinput-description-redirect', suggestionPage.title )
+                                       description: mw.msg( 'mw-widgets-titleinput-description-redirect', suggestionPage.title ),
+                                       // Sort index, just below its target
+                                       index: suggestionPage.index + 0.5
                                };
                                titles.push( redirects[ i ] );
                        }
index 1f21fc6..3e010d0 100644 (file)
                 * @since 1.22
                 */
                postWithToken: function ( tokenType, params, ajaxOptions ) {
-                       var api = this;
+                       var api = this,
+                               abortable;
 
-                       return api.getToken( tokenType, params.assert ).then( function ( token ) {
+                       return ( abortable = api.getToken( tokenType, params.assert ) ).then( function ( token ) {
                                params.token = token;
-                               return api.post( params, ajaxOptions ).then(
+                               return ( abortable = api.post( params, ajaxOptions ) ).then(
                                        // If no error, return to caller as-is
                                        null,
                                        // Error handler
                                                        api.badToken( tokenType );
                                                        // Try again, once
                                                        params.token = undefined;
-                                                       return api.getToken( tokenType, params.assert ).then( function ( token ) {
+                                                       return ( abortable = api.getToken( tokenType, params.assert ) ).then( function ( token ) {
                                                                params.token = token;
-                                                               return api.post( params, ajaxOptions );
+                                                               return ( abortable = api.post( params, ajaxOptions ) ).promise();
                                                        } );
                                                }
 
                                                return this;
                                        }
                                );
-                       } );
+                       } ).promise( { abort: function () {
+                               abortable.abort();
+                       } } );
                },
 
                /**
                'fileexists-shared-forbidden',
                'invalidtitle',
                'notloggedin',
+               'autoblocked',
+               'blocked',
 
                // Stash-specific errors - expanded
                'stashfailed',
index 1fabe17..16fec73 100644 (file)
                var booklet = this;
                return mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ).then(
                        function () {
-                               // Point the CategorySelector to the right wiki
-                               return booklet.upload.getApi().then(
-                                       function ( api ) {
+                               return $.when(
+                                       // Point the CategorySelector to the right wiki
+                                       booklet.upload.getApi().then( function ( api ) {
                                                // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
                                                if ( api.apiUrl ) {
                                                        // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
                                                        booklet.categoriesWidget.api = new mw.ForeignApi( api.apiUrl );
                                                }
                                                return $.Deferred().resolve();
-                                       },
-                                       function () {
-                                               return $.Deferred().resolve();
-                                       }
+                                       } ),
+                                       // Set up booklet fields and license messages to match configuration
+                                       booklet.upload.loadConfig().then( function ( config ) {
+                                               var
+                                                       msgPromise,
+                                                       isLocal = booklet.upload.target === 'local',
+                                                       fields = config.fields,
+                                                       msgs = config.licensemessages[ isLocal ? 'local' : 'foreign' ];
+
+                                               // Hide disabled fields
+                                               booklet.descriptionField.toggle( !!fields.description );
+                                               booklet.categoriesField.toggle( !!fields.categories );
+                                               booklet.dateField.toggle( !!fields.date );
+                                               // Update form validity
+                                               booklet.onInfoFormChange();
+
+                                               // Load license messages from the remote wiki if we don't have these messages locally
+                                               // (this means that we only load messages from the foreign wiki for custom config)
+                                               if ( mw.message( 'upload-form-label-own-work-message-' + msgs ).exists() ) {
+                                                       msgPromise = $.Deferred().resolve();
+                                               } else {
+                                                       msgPromise = booklet.upload.apiPromise.then( function ( api ) {
+                                                               return api.loadMessages( [
+                                                                       'upload-form-label-own-work-message-' + msgs,
+                                                                       'upload-form-label-not-own-work-message-' + msgs,
+                                                                       'upload-form-label-not-own-work-local-' + msgs
+                                                               ] );
+                                                       } );
+                                               }
+
+                                               // Update license messages
+                                               return msgPromise.then( function () {
+                                                       booklet.$ownWorkMessage
+                                                               .msg( 'upload-form-label-own-work-message-' + msgs )
+                                                               .find( 'a' ).attr( 'target', '_blank' );
+                                                       booklet.$notOwnWorkMessage
+                                                               .msg( 'upload-form-label-not-own-work-message-' + msgs )
+                                                               .find( 'a' ).attr( 'target', '_blank' );
+                                                       booklet.$notOwnWorkLocal
+                                                               .msg( 'upload-form-label-not-own-work-local-' + msgs )
+                                                               .find( 'a' ).attr( 'target', '_blank' );
+                                               } );
+                                       } )
                                );
-                       },
-                       function () {
-                               return $.Deferred().resolve();
                        }
+               ).then(
+                       null,
+                       // Always resolve, never reject
+                       function () { return $.Deferred().resolve(); }
                );
        };
 
         * @inheritdoc
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
-               var fieldset, $ownWorkMessage, $notOwnWorkMessage,
-                       ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal,
-                       validTargets = mw.config.get( 'wgForeignUploadTargets' ),
-                       target = this.target || validTargets[ 0 ] || 'local',
+               var fieldset,
                        layout = this;
 
-               // foreign-structured-upload-form-label-own-work-message-local
-               // foreign-structured-upload-form-label-own-work-message-shared
-               ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-' + target );
-               // foreign-structured-upload-form-label-not-own-work-message-local
-               // foreign-structured-upload-form-label-not-own-work-message-shared
-               notOwnWorkMessage = mw.message( 'foreign-structured-upload-form-label-not-own-work-message-' + target );
-               // foreign-structured-upload-form-label-not-own-work-local-local
-               // foreign-structured-upload-form-label-not-own-work-local-shared
-               notOwnWorkLocal = mw.message( 'foreign-structured-upload-form-label-not-own-work-local-' + target );
-
-               if ( !ownWorkMessage.exists() ) {
-                       ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-default' );
-               }
-               if ( !notOwnWorkMessage.exists() ) {
-                       notOwnWorkMessage = mw.message( 'foreign-structured-upload-form-label-not-own-work-message-default' );
-               }
-               if ( !notOwnWorkLocal.exists() ) {
-                       notOwnWorkLocal = mw.message( 'foreign-structured-upload-form-label-not-own-work-local-default' );
-               }
-
-               $ownWorkMessage = $( '<p>' ).append( ownWorkMessage.parseDom() )
+               // These elements are filled with text in #initialize
+               // TODO Refactor this to be in one place
+               this.$ownWorkMessage = $( '<p>' )
                        .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' );
-               $notOwnWorkMessage = $( '<div>' ).append(
-                       $( '<p>' ).append( notOwnWorkMessage.parseDom() ),
-                       $( '<p>' ).append( notOwnWorkLocal.parseDom() )
-               );
-               $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' ).attr( 'target', '_blank' );
+               this.$notOwnWorkMessage = $( '<p>' );
+               this.$notOwnWorkLocal = $( '<p>' );
 
                this.selectFileWidget = new OO.ui.SelectFileWidget( {
                        showDropTarget: true
                } );
                this.messageLabel = new OO.ui.LabelWidget( {
-                       label: $notOwnWorkMessage
+                       label: $( '<div>' ).append(
+                               this.$notOwnWorkMessage,
+                               this.$notOwnWorkLocal
+                       )
                } );
                this.ownWorkCheckbox = new OO.ui.CheckboxInputWidget().on( 'change', function ( on ) {
                        layout.messageLabel.toggle( !on );
                        new OO.ui.FieldLayout( this.ownWorkCheckbox, {
                                align: 'inline',
                                label: $( '<div>' ).append(
-                                       $( '<p>' ).text( mw.msg( 'foreign-structured-upload-form-label-own-work' ) ),
-                                       $ownWorkMessage
+                                       $( '<p>' ).text( mw.msg( 'upload-form-label-own-work' ) ),
+                                       this.$ownWorkMessage
                                )
                        } ),
                        new OO.ui.FieldLayout( this.messageLabel, {
                        mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow
                } );
 
+               this.filenameField = new OO.ui.FieldLayout( this.filenameWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-name' ),
+                       align: 'top',
+                       classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
+                       notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ]
+               } );
+               this.descriptionField = new OO.ui.FieldLayout( this.descriptionWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-description' ),
+                       align: 'top',
+                       classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
+                       notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ]
+               } );
+               this.categoriesField = new OO.ui.FieldLayout( this.categoriesWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-categories' ),
+                       align: 'top'
+               } );
+               this.dateField = new OO.ui.FieldLayout( this.dateWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-date' ),
+                       align: 'top'
+               } );
+
                fieldset = new OO.ui.FieldsetLayout( {
                        label: mw.msg( 'upload-form-label-infoform-title' )
                } );
                fieldset.addItems( [
-                       new OO.ui.FieldLayout( this.filenameWidget, {
-                               label: mw.msg( 'upload-form-label-infoform-name' ),
-                               align: 'top',
-                               classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
-                               notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ]
-                       } ),
-                       new OO.ui.FieldLayout( this.descriptionWidget, {
-                               label: mw.msg( 'upload-form-label-infoform-description' ),
-                               align: 'top',
-                               classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
-                               notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ]
-                       } ),
-                       new OO.ui.FieldLayout( this.categoriesWidget, {
-                               label: mw.msg( 'foreign-structured-upload-form-label-infoform-categories' ),
-                               align: 'top'
-                       } ),
-                       new OO.ui.FieldLayout( this.dateWidget, {
-                               label: mw.msg( 'foreign-structured-upload-form-label-infoform-date' ),
-                               align: 'top'
-                       } )
+                       this.filenameField,
+                       this.descriptionField,
+                       this.categoriesField,
+                       this.dateField
                ] );
                this.infoForm = new OO.ui.FormLayout( {
                        classes: [ 'mw-upload-bookletLayout-infoForm' ],
         * @inheritdoc
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.onInfoFormChange = function () {
-               var layout = this;
-               $.when(
-                       this.filenameWidget.getValidity(),
-                       this.descriptionWidget.getValidity(),
-                       this.dateWidget.getValidity()
-               ).done( function () {
+               var layout = this,
+                       validityPromises = [];
+
+               validityPromises.push( this.filenameWidget.getValidity() );
+               if ( this.descriptionField.isVisible() ) {
+                       validityPromises.push( this.descriptionWidget.getValidity() );
+               }
+               if ( this.dateField.isVisible() ) {
+                       validityPromises.push( this.dateWidget.getValidity() );
+               }
+
+               $.when.apply( $, validityPromises ).done( function () {
                        layout.emit( 'infoValid', true );
                } ).fail( function () {
                        layout.emit( 'infoValid', false );
index c03c0d1..4a0366a 100644 (file)
@@ -1,4 +1,4 @@
-( function ( mw, OO ) {
+( function ( mw, $, OO ) {
        /**
         * @class mw.ForeignStructuredUpload
         * @extends mw.ForeignUpload
                this.descriptions = [];
                this.categories = [];
 
+               // Config for uploads to local wiki.
+               // Can be overridden with foreign wiki config when #loadConfig is called.
+               this.config = mw.config.get( 'wgUploadDialog' );
+
                mw.ForeignUpload.call( this, target, apiconfig );
        }
 
        OO.inheritClass( ForeignStructuredUpload, mw.ForeignUpload );
 
+       /**
+        * Get the configuration for the form and filepage from the foreign wiki, if any, and use it for
+        * this upload.
+        *
+        * @return {jQuery.Promise} Promise returning config object
+        */
+       ForeignStructuredUpload.prototype.loadConfig = function () {
+               var deferred,
+                       upload = this;
+
+               if ( this.configPromise ) {
+                       return this.configPromise;
+               }
+
+               if ( this.target === 'local' ) {
+                       deferred = $.Deferred();
+                       setTimeout( function () {
+                               // Resolve asynchronously, so that it's harder to accidentally write synchronous code that
+                               // will break for cross-wiki uploads
+                               deferred.resolve( upload.config );
+                       } );
+                       this.configPromise = deferred.promise();
+               } else {
+                       this.configPromise = this.apiPromise.then( function ( api ) {
+                               // Get the config from the foreign wiki
+                               return api.get( {
+                                       action: 'query',
+                                       meta: 'siteinfo',
+                                       siprop: 'uploaddialog',
+                                       // For convenient true/false booleans
+                                       formatversion: 2
+                               } ).then( function ( resp ) {
+                                       // Foreign wiki might be running a pre-1.27 MediaWiki, without support for this
+                                       if ( resp.query && resp.query.uploaddialog ) {
+                                               upload.config = resp.query.uploaddialog;
+                                       }
+                                       return upload.config;
+                               } );
+                       } );
+               }
+
+               return this.configPromise;
+       };
+
        /**
         * Add categories to the upload.
         *
         * @return {string}
         */
        ForeignStructuredUpload.prototype.getText = function () {
-               return (
-                       '== {{int:filedesc}} ==\n' +
-                       '{{' +
-                       this.getTemplateName() +
-                       '\n|description=' +
-                       this.getDescriptions() +
-                       '\n|date=' +
-                       this.getDate() +
-                       '\n|source=' +
-                       this.getSource() +
-                       '\n|author=' +
-                       this.getUser() +
-                       '\n}}\n\n' +
-                       '== {{int:license-header}} ==\n' +
-                       this.getLicense() +
-                       '\n\n' +
-                       this.getCategories()
-               );
+               return this.config.format.filepage
+                       // Replace "numbered parameters" with the given information
+                       .replace( '$DESCRIPTION', this.getDescriptions() )
+                       .replace( '$DATE', this.getDate() )
+                       .replace( '$SOURCE', this.getSource() )
+                       .replace( '$AUTHOR', this.getUser() )
+                       .replace( '$LICENSE', this.getLicense() )
+                       .replace( '$CATEGORIES', this.getCategories() );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       ForeignStructuredUpload.prototype.getComment = function () {
+               return this.config.comment
+                       .replace( '$PAGENAME', mw.config.get( 'wgPageName' ) )
+                       .replace( '$HOST', location.host );
        };
 
        /**
                return this.date.toString();
        };
 
-       /**
-        * Gets the name of the template to use for creating the file metadata.
-        * Override in subclasses for other templates.
-        *
-        * @private
-        * @return {string}
-        */
-       ForeignStructuredUpload.prototype.getTemplateName = function () {
-               return 'Information';
-       };
-
        /**
         * Fetches the wikitext for any descriptions that have been added
         * to the upload.
 
                for ( i = 0; i < this.descriptions.length; i++ ) {
                        desc = this.descriptions[ i ];
-                       templateCalls.push( '{{' + desc.language + '|1=' + desc.text + '}}' );
+                       templateCalls.push(
+                               this.config.format.description
+                                       .replace( '$LANGUAGE', desc.language )
+                                       .replace( '$TEXT', desc.text )
+                       );
                }
 
                return templateCalls.join( '\n' );
                var i, cat, categoryLinks = [];
 
                if ( this.categories.length === 0 ) {
-                       return '{{subst:unc}}';
+                       return this.config.format.uncategorized;
                }
 
                for ( i = 0; i < this.categories.length; i++ ) {
         * @return {string}
         */
        ForeignStructuredUpload.prototype.getLicense = function () {
-               // Make sure this matches the messages for different targets in
-               // mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm
-               return this.target === 'shared' ? '{{self|cc-by-sa-4.0}}' : '';
+               return this.config.format.license;
        };
 
        /**
         * @return {string}
         */
        ForeignStructuredUpload.prototype.getSource = function () {
-               return '{{own}}';
+               return this.config.format.ownwork;
        };
 
        /**
        };
 
        mw.ForeignStructuredUpload = ForeignStructuredUpload;
-}( mediaWiki, OO ) );
+}( mediaWiki, jQuery, OO ) );
index 1a0b59a..eeeab68 100644 (file)
                // actual API call methods to wait for the apiPromise to resolve
                // before continuing.
                mw.Upload.call( this, null );
-
-               if ( this.target !== 'local' ) {
-                       // Keep these untranslated. We don't know the content language of the foreign wiki, best to
-                       // stick to English in the text.
-                       this.setComment( 'Cross-wiki upload from ' + location.host );
-               }
        }
 
        OO.inheritClass( ForeignUpload, mw.Upload );
index 2b28cb4..7a7469a 100644 (file)
 
                return this.upload.getApi().then(
                        function ( api ) {
-                               // If the user can't upload anything, don't give them the option to.
-                               return api.getUserInfo().then(
-                                       function ( userInfo ) {
+                               return $.when(
+                                       booklet.upload.loadConfig(),
+                                       // If the user can't upload anything, don't give them the option to.
+                                       api.getUserInfo().then( function ( userInfo ) {
                                                if ( userInfo.rights.indexOf( 'upload' ) === -1 ) {
                                                        // TODO Use a better error message when not all logged-in users can upload
                                                        booklet.getPage( 'upload' ).$element.msg( 'api-error-mustbeloggedin' );
                                                }
                                                return $.Deferred().resolve();
-                                       },
-                                       function () {
-                                               return $.Deferred().resolve();
-                                       }
+                                       } )
+                               ).then(
+                                       null,
+                                       // Always resolve, never reject
+                                       function () { return $.Deferred().resolve(); }
                                );
                        },
                        function ( errorMsg ) {
index 7d7b413..365e988 100644 (file)
@@ -3,6 +3,16 @@
        margin-bottom: 0.1em;
 }
 
+.apihelp-header.apihelp-module-name {
+       /*
+        * This element is explicitly set to dir="ltr" in HTML.
+        * Set explicit alignment so that CSSJanus will flip it to "right";
+        * otherwise the alignment will be automatically set to "left" according
+        * to the element's direction, and this will have an inconsistent look.
+        */
+       text-align: left;
+}
+
 div.apihelp-linktrail {
        font-size: smaller;
 }
@@ -18,7 +28,7 @@ div.apihelp-linktrail {
 .apihelp-flags {
        font-size: smaller;
        float: right;
-       border: 1px solid black;
+       border: 1px solid #000;
        padding: 0.25em;
        width: 20em;
 }
@@ -26,7 +36,7 @@ div.apihelp-linktrail {
 .apihelp-deprecated, .apihelp-flag-deprecated,
 .apihelp-flag-internal strong {
        font-weight: bold;
-       color: red;
+       color: #f00;
 }
 
 .apihelp-unknown {
index 9e20264..91fa02a 100644 (file)
@@ -13,7 +13,7 @@
 
 .mw-json th,
 .mw-json td {
-       border: 1px solid gray;
+       border: 1px solid #808080;
        font-size: 16px;
        padding: 0.5em 1em;
 }
@@ -50,7 +50,7 @@
 }
 
 .mw-json table caption {
-       color: gray;
+       color: #808080;
        display: inline-block;
        font-size: 10px;
        font-style: italic;
index 949c558..54620f4 100644 (file)
@@ -65,7 +65,7 @@
        font-size: 13px;
        /* IE-hack for display: inline-block */
        zoom: 1;
-       *display:inline;
+       *display: inline;
 }
 
 .mw-debug-panelink {
@@ -115,7 +115,7 @@ a.mw-debug-panelabel:visited {
        th,
        td,
        table {
-               border: 1px solid #D0DBB3;
+               border: 1px solid #d0dbb3;
                border-collapse: collapse;
                margin: 0;
        }
@@ -127,12 +127,12 @@ a.mw-debug-panelabel:visited {
        }
 
        th {
-               background-color: #F1F7E2;
+               background-color: #f1f7e2;
                font-weight: bold;
        }
 
        td {
-               background-color: white;
+               background-color: #fff;
        }
 }
 
index f4af4ba..89efae3 100644 (file)
@@ -9,7 +9,7 @@
        }
 
        .mediawiki-filewarning-footer {
-               color: #888888;
+               color: #888;
        }
 
        .empty {
index c3341bb..9405f6b 100644 (file)
@@ -5,7 +5,7 @@ table.mw-htmlform-nolabel td.mw-label {
 }
 
 .mw-htmlform-invalid-input td.mw-input input {
-       border-color: red;
+       border-color: #f00;
 }
 
 .mw-htmlform-flatlist div.mw-htmlform-flatlist-item {
index 260fd37..8f0ad6b 100644 (file)
@@ -4,16 +4,6 @@
        margin: 1em 0;
 }
 
-.oo-ui-fieldLayout.mw-htmlform-ooui-header-empty,
-.oo-ui-fieldLayout.mw-htmlform-ooui-header-empty .oo-ui-fieldLayout-body {
-       display: none;
-}
-
-.oo-ui-fieldLayout.mw-htmlform-ooui-header-errors {
-       /* Override 'display: none' from above */
-       display: block;
-}
-
 .mw-htmlform-ooui .mw-htmlform-submit-buttons {
        margin-top: 1em;
 }
index e905f69..b8d0b09 100644 (file)
                        } catch ( e ) {
                                fallback = parser.settings.messages.get( key );
                                mw.log.warn( 'mediawiki.jqueryMsg: ' + key + ': ' + e.message );
+                               mw.track( 'mediawiki.jqueryMsg.error', {
+                                       messageKey: key,
+                                       errorMessage: e.message
+                               } );
                                return $( '<span>' ).text( fallback );
                        }
                };
                                return result === null ? null : result.join( '' );
                        }
 
-                       asciiAlphabetLiteral = makeRegexParser( /[A-Za-z]+/ );
+                       asciiAlphabetLiteral = makeRegexParser( /^[A-Za-z]+/ );
                        htmlDoubleQuoteAttributeValue = makeRegexParser( /^[^"]*/ );
                        htmlSingleQuoteAttributeValue = makeRegexParser( /^[^']*/ );
 
                                return result;
                        }
 
+                       // <nowiki>...</nowiki> tag. The tags are stripped and the contents are returned unparsed.
+                       function nowiki() {
+                               var parsedResult, plainText,
+                                       result = null;
+
+                               parsedResult = sequence( [
+                                       makeStringParser( '<nowiki>' ),
+                                       // We use a greedy non-backtracking parser, so we must ensure here that we don't take too much
+                                       makeRegexParser( /^.*?(?=<\/nowiki>)/ ),
+                                       makeStringParser( '</nowiki>' )
+                               ] );
+                               if ( parsedResult !== null ) {
+                                       plainText = parsedResult[ 1 ];
+                                       result = [ 'CONCAT' ].concat( plainText );
+                               }
+
+                               return result;
+                       }
+
                        templateName = transform(
                                // see $wgLegalTitleChars
                                // not allowing : due to the need to catch "PLURAL:$1"
                                wikilink,
                                extlink,
                                replacement,
+                               nowiki,
                                html,
                                literal
                        ] );
index 188af6a..5111d96 100644 (file)
 }
 
 .mw-notification-type-warn {
-       border-color: #F5BE00; /* yellow */
-       background-color: #FFFFE8;
+       border-color: #f5be00; /* yellow */
+       background-color: #ffffe8;
 }
 
 .mw-notification-type-error {
-       border-color: #EB3941; /* red */
-       background-color: #FFF8F8;
+       border-color: #eb3941; /* red */
+       background-color: #fff8f8;
 }
index df144ce..ce3cfbd 100644 (file)
@@ -4,7 +4,7 @@
 .suggestions a.mw-searchSuggest-link:hover,
 .suggestions a.mw-searchSuggest-link:active,
 .suggestions a.mw-searchSuggest-link:focus {
-       color: black;
+       color: #000;
        text-decoration: none;
 }
 
@@ -12,7 +12,7 @@
 .suggestions-result-current a.mw-searchSuggest-link:hover,
 .suggestions-result-current a.mw-searchSuggest-link:active,
 .suggestions-result-current a.mw-searchSuggest-link:focus {
-       color: white;
+       color: #fff;
 }
 
 .suggestions a.mw-searchSuggest-link .special-query {
index cccc468..8dc8a61 100644 (file)
                                RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])',
                                RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
 
-                       return address.search( new RegExp( '^' + RE_IP_ADD + block + '$' ) ) !== -1;
+                       return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) );
                },
 
                /**
                        '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' +
                        ')';
 
-                       if ( address.search( new RegExp( '^' + RE_IPV6_ADD + block + '$' ) ) !== -1 ) {
+                       if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) {
                                return true;
                        }
 
-                       RE_IPV6_ADD = // contains one "::" in the middle (single '::' check below)
-                               '[0-9A-Fa-f]{1,4}' + '(?:::?' + '[0-9A-Fa-f]{1,4}' + '){1,6}';
+                       // contains one "::" in the middle (single '::' check below)
+                       RE_IPV6_ADD = '[0-9A-Fa-f]{1,4}' + '(?:::?' + '[0-9A-Fa-f]{1,4}' + '){1,6}';
 
-                       return address.search( new RegExp( '^' + RE_IPV6_ADD + block + '$' ) ) !== -1
-                               && address.search( /::/ ) !== -1 && address.search( /::.*::/ ) === -1;
+                       return (
+                               new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address )
+                               && /::/.test( address )
+                               && !/::.*::/.test( address )
+                       );
                },
 
                /**
index 3c80bbb..4d43e6a 100644 (file)
@@ -84,7 +84,7 @@ ul.mw-gallery-packed-hover li.gallerybox:hover div.gallerytextwrapper,
 ul.mw-gallery-packed-overlay li.gallerybox div.gallerytextwrapper,
 ul.mw-gallery-packed-hover li.gallerybox.mw-gallery-focused div.gallerytextwrapper {
        position: absolute;
-       background: white;
+       background: #fff;
        background: rgba(255, 255, 255, 0.8);
        padding: 5px 10px;
        bottom: 0;
index a63202f..ec68b3c 100644 (file)
@@ -14,7 +14,7 @@
        $( function () {
                var $patrolLinks = $( '.patrollink a' );
                $patrolLinks.on( 'click', function ( e ) {
-                       var $spinner, href, rcid, apiRequest;
+                       var $spinner, rcid, apiRequest;
 
                        // Start preloading the notification module (normally loaded by mw.notify())
                        mw.loader.load( 'mediawiki.notification' );
@@ -26,8 +26,7 @@
                        } );
                        $( this ).hide().after( $spinner );
 
-                       href = $( this ).attr( 'href' );
-                       rcid = mw.util.getParamValue( 'rcid', href );
+                       rcid = mw.util.getParamValue( 'rcid', this.href );
                        apiRequest = new mw.Api();
 
                        apiRequest.postWithToken( 'patrol', {
diff --git a/resources/src/moment-dmy.js b/resources/src/moment-dmy.js
new file mode 100644 (file)
index 0000000..c67b93e
--- /dev/null
@@ -0,0 +1,16 @@
+// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
+// This affects English only (and languages without localisations, that fall back to English).
+// http://momentjs.com/docs/#/customization/long-date-formats/
+/*global moment */
+moment.locale( 'en', {
+       longDateFormat: {
+               // Unchanged, but have to be repeated here:
+               LT: 'h:mm A',
+               LTS: 'h:mm:ss A',
+               // Customized:
+               L: 'DD/MM/YYYY',
+               LL: 'D MMMM YYYY',
+               LLL: 'D MMMM YYYY LT',
+               LLLL: 'dddd, D MMMM YYYY LT'
+       }
+} );
diff --git a/resources/src/moment-local-dmy.js b/resources/src/moment-local-dmy.js
deleted file mode 100644 (file)
index c67b93e..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
-// This affects English only (and languages without localisations, that fall back to English).
-// http://momentjs.com/docs/#/customization/long-date-formats/
-/*global moment */
-moment.locale( 'en', {
-       longDateFormat: {
-               // Unchanged, but have to be repeated here:
-               LT: 'h:mm A',
-               LTS: 'h:mm:ss A',
-               // Customized:
-               L: 'DD/MM/YYYY',
-               LL: 'D MMMM YYYY',
-               LLL: 'D MMMM YYYY LT',
-               LLLL: 'dddd, D MMMM YYYY LT'
-       }
-} );
diff --git a/resources/src/moment-locale-overrides.js b/resources/src/moment-locale-overrides.js
new file mode 100644 (file)
index 0000000..9af0598
--- /dev/null
@@ -0,0 +1,42 @@
+/*global moment, mw */
+
+// HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
+// wgTranslateNumerals is respected.
+moment.locale( moment.locale(), {
+       preparse: function ( s ) {
+               var i,
+                       table = mw.language.getDigitTransformTable();
+               if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+                       for ( i = 0; i < 10; i++ ) {
+                               if ( table[ i ] !== undefined ) {
+                                       s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
+                               }
+                       }
+               }
+               // HACK: momentjs replaces commas in some languages, which is the only other use of preparse
+               // aside from digit transformation. We can only override preparse, not extend it, so we
+               // have to replicate the comma replacement functionality here.
+               if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+                       s = s.replace( /،/g, ',' );
+               }
+               return s;
+       },
+       postformat: function ( s ) {
+               var i,
+                       table = mw.language.getDigitTransformTable();
+               if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+                       for ( i = 0; i < 10; i++ ) {
+                               if ( table[ i ] !== undefined ) {
+                                       s = s.replace( new RegExp( mw.RegExp.escape( i ), 'g' ), table[ i ] );
+                               }
+                       }
+               }
+               // HACK: momentjs replaces commas in some languages, which is the only other use of postformat
+               // aside from digit transformation. We can only override postformat, not extend it, so we
+               // have to replicate the comma replacement functionality here.
+               if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+                       s = s.replace( /,/g, '،' );
+               }
+               return s;
+       }
+} );
index 7a980f7..572e2f8 100644 (file)
@@ -12,11 +12,10 @@ directory and make a symbolic link:
  mediawiki/skins$ ln -s ../../skins-trunk/FooBar
 
 The default skin Vector can be installed by cloning from Git:
-    git clone https://git.wikimedia.org/git/mediawiki/skins/Vector.git
+    git clone https://phabricator.wikimedia.org/diffusion/SVEC/Vector
 
 Other skins are also available:
-    https://gerrit.wikimedia.org/r/#/admin/projects/?filter=mediawiki%252Fskins%252F
-    https://git.wikimedia.org/project/mediawiki
+    https://phabricator.wikimedia.org/diffusion/SKIN/
 
 
 Please note that under POSIX systems (Linux...), parent of a symbolic path
index 26085b8..ebb6d90 100644 (file)
@@ -65,6 +65,10 @@ $wgAutoloadClasses += [
        'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
        'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php",
 
+       # tests/phpunit/includes/auth
+       'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
+               "$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
+
        # tests/phpunit/includes/changes
        'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
 
index ade6914..0a5d3cd 100644 (file)
@@ -1,4 +1,4 @@
-@chrome @firefox @vagrant
+@chrome @firefox @skip @vagrant
 Feature: Edit Page
 
   Scenario: Create and edit page
index 98e0f2c..fa0570c 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 Given(/^I go to Create account page at (.+)$/) do |path|
   visit(CreateAccountPage, using_params: { page_title: path })
 end
index a80ca50..15069b2 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 Given(/^I am at file that does not exist$/) do
   visit(FileDoesNotExistPage, using_params: { page_name: @random_string })
 end
index 788bfc4..bda0faa 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 Given(/^I am at Log in page$/) do
   visit LoginPage
 end
index e1953a0..8ffdaf1 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 When(/^I click Appearance$/) do
   visit(PreferencesPage).appearance_link_element.when_present.click
 end
index 0a98e88..f691ffd 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 When(/^I click Editing$/) do
   visit(PreferencesPage).editing_link_element.when_present.click
 end
index 9c65db8..5660d49 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 When(/^I click User profile$/) do
   visit(PreferencesPage).user_profile_link_element.when_present.click
 end
index 9aa00cd..9c1c3ba 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 class CreateAccountPage
   include PageObject
 
index 90762d2..632e303 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 class FileDoesNotExistPage
   include PageObject
 
index 4f8fb66..c871e64 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 class PreferencesAppearancePage
   include PageObject
 
index 25c384f..3b54d45 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 class PreferencesEditingPage
   include PageObject
 
index b305ee2..1d836ea 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 class PreferencesPage
   include PageObject
 
index 9e95eb5..ab5eb93 100644 (file)
@@ -1,14 +1,3 @@
-#
-# This file is subject to the license terms in the LICENSE file found in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/LICENSE. No part of
-# qa-browsertests, including this file, may be copied, modified, propagated, or
-# distributed except according to the terms contained in the LICENSE file.
-#
-# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
-# qa-browsertests top-level directory and at
-# https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
-#
 class PreferencesUserProfilePage
   include PageObject
 
index 95136d2..8e72bcf 100644 (file)
@@ -1,4 +1,4 @@
-@chrome @firefox @vagrant
+@chrome @firefox @skip @vagrant
 Feature: View History
 
   Scenario: Edit page and view history
index 78e5f6f..1be2d62 100644 (file)
@@ -27,6 +27,7 @@
  * @file
  * @ingroup Testing
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * @ingroup Testing
@@ -210,6 +211,7 @@ class ParserTest {
                # add a namespace shadowing a interwiki link, to test
                # proper precedence when resolving links. (bug 51680)
                $wgExtraNamespaces[100] = 'MemoryAlpha';
+               $wgExtraNamespaces[101] = 'MemoryAlpha talk';
 
                // XXX: tests won't run without this (for CACHE_DB)
                if ( $wgMainCacheType === CACHE_DB ) {
@@ -331,6 +333,18 @@ class ParserTest {
                Hooks::clear( 'InterwikiLoadPrefix' );
        }
 
+       /**
+        * Reset the Title-related services that need resetting
+        * for each test
+        */
+       public static function resetTitleServices() {
+               $services = MediaWikiServices::getInstance();
+               $services->resetServiceForTesting( 'TitleFormatter' );
+               $services->resetServiceForTesting( 'TitleParser' );
+               $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
+
+       }
+
        public function setupRecorder( $options ) {
                if ( isset( $options['record'] ) ) {
                        $this->recorder = new DbTestRecorder( $this );
@@ -900,7 +914,6 @@ class ParserTest {
                        'wgExperimentalHtmlIds' => false,
                        'wgExternalLinkTarget' => false,
                        'wgHtml5' => true,
-                       'wgWellFormedXml' => true,
                        'wgAdaptiveMessageCache' => true,
                        'wgDisableLangConversion' => false,
                        'wgDisableTitleConversion' => false,
@@ -958,6 +971,8 @@ class ParserTest {
                MWTidy::destroySingleton();
                RepoGroup::destroySingleton();
 
+               self::resetTitleServices();
+
                return $context;
        }
 
index 23bdbde..7051b4f 100644 (file)
@@ -5632,7 +5632,7 @@ Parenthesis in external links, w/ transclusion or comment
 </p><p>(<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>)
 </p>
 !! html/parsoid
-<p>(<a typeof="mw:ExpandedAttrs" about="#mwt2" rel="mw:ExtLink" href="http://example.com/hi" data-parsoid='{"stx":"url","a":{"href":"http://example.com/hi"},"sa":{"href":"http://example.com/{{echo|hi}}"}}' data-mw='{"attribs":[[{"txt":"href"},{"html":"http://example.com/&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[20,31,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;hi&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">hi&lt;/span>"}]]}'>http://example.com/hi</a>)</p>
+<p>(<a typeof="mw:ExpandedAttrs" about="#mwt2" rel="mw:ExtLink" href="http://example.com/hi" data-parsoid='{"stx":"url","a":{"href":"http://example.com/hi"},"sa":{"href":"http://example.com/{{echo|hi}}"}}' data-mw='{"attribs":[[{"txt":"href"},{"html":"http://example.com/&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[20,31,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"hi\"}},\"i\":0}}]}&#39;>hi&lt;/span>"}]]}'>http://example.com/hi</a>)</p>
 
 <p>(<a rel="mw:ExtLink" href="http://example.com" data-parsoid='{"stx":"url","a":{"href":"http://example.com"},"sa":{"href":"http://example.com&lt;!-- hi -->"}}'>http://example.com</a>)</p>
 !! end
@@ -5650,7 +5650,7 @@ parsoid={ "modes": ["html2wt"], "suppressErrors": true }
 text
 <nowiki>*</nowiki>text
 <nowiki>[[foo]]</nowiki>
-<nowiki>*[[foo]]</nowiki>
+<nowiki>*</nowiki>a <nowiki>[[foo]]</nowiki>
 !! end
 
 !! test
@@ -5658,7 +5658,7 @@ mw:ExtLink -vs- mw:WikiLink (T94723)
 !! options
 parsoid=html2wt
 !! html/parsoid
-<a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{"stx":"piped","a":{"href":"./Foo"},"sa":{"href":"Foo"},"dsr":[0,11,6,2]}'>Bar</a>
+<a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{"stx":"piped","a":{"href":"./Foo"},"sa":{"href":"Foo"}}'>Bar</a>
 <a rel="mw:WikiLink" href="./Foo" title="Foo">Bar</a>
 <a rel="mw:WikiLink" href="http://en.wikipedia.org/wiki/Foo" title="Foo">Bar</a>
 <a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" title="Foo">Bar</a>
@@ -7110,6 +7110,25 @@ parsoid=html2wt
 |}
 !! end
 
+!! test
+Testing serialization after deletion in references
+!! options
+parsoid={
+  "modes": ["wt2wt"],
+  "changes": [
+    ["#x", "remove"]
+  ]
+}
+!! wikitext
+hi <ref><div id="x">ho</div></ref>
+
+<references />
+!! wikitext/edited
+hi <ref></ref>
+
+<references />
+!! end
+
 !!test
 Testing serialization after deletion of table cells
 !!options
@@ -7572,10 +7591,10 @@ Broken image links with HTML captions (bug 39700)
 <a href="/index.php?title=Special:Upload&amp;wpDestFile=Nonexistent" class="new" title="File:Nonexistent">abc</a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span>
-<span typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="100" width="100"/></a></span>
-<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=\"{&amp;quot;src&amp;quot;:&amp;quot;&amp;amp;lt;&amp;quot;,&amp;quot;srcContent&amp;quot;:&amp;quot;&lt;&amp;quot;,&amp;quot;dsr&amp;quot;:[107,111,null,null]}\">&amp;lt;&lt;/span>"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span>
-<span class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"a&lt;i data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[134,142,3,4]}\">b&lt;/i>c"}'><a href="./File:Nonexistent"><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;script>&lt;/script>"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&amp;lt;script>&amp;lt;/script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></span>
+<span typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"100x100px"},{"ck":"caption","ak":"&lt;script>&lt;/script>"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&amp;lt;script>&amp;lt;/script>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="100" width="100" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"100","width":"100"},"sa":{"resource":"File:Nonexistent"}}'/></a></span>
+<span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp;lt;"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=&#39;{\"src\":\"&amp;amp;lt;\",\"srcContent\":\"&amp;lt;\",\"dsr\":[107,111,null,null]}&#39;>&amp;lt;&lt;/span>"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></span>
+<span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"a&lt;i>b&lt;/i>c"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"a&lt;i data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[134,142,3,4]}&#39;>b&lt;/i>c"}'><a href="./File:Nonexistent" data-parsoid='{"a":{"href":"./File:Nonexistent"},"sa":{}}'><img resource="./File:Nonexistent" src="./Special:FilePath/Nonexistent" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Nonexistent","height":"220","width":"220"},"sa":{"resource":"File:Nonexistent"}}'/></a></span></p>
 !! end
 
 !! test
@@ -8432,7 +8451,7 @@ Blah blah blah
 !! wikitext
 #REDIRECT [[{{echo|Foo}}bar]]
 !! html/parsoid
-<link typeof="mw:ExpandedAttrs" rel="mw:PageProp/redirect" href="./Foobar" data-mw='{"attribs":[[{"txt":"href"},{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[12,24,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;Foo&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">Foo&lt;/span>bar"}]]}'/>
+<link about="#mwt2" typeof="mw:ExpandedAttrs" rel="mw:PageProp/redirect" href="./Foobar" data-parsoid='{"a":{"href":"./Foobar"},"sa":{"href":"{{echo|Foo}}bar"}}' data-mw='{"attribs":[[{"txt":"href"},{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[12,24,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"Foo\"}},\"i\":0}}]}&#39;>Foo&lt;/span>bar"}]]}'/>
 !! end
 
 !! test
@@ -9912,7 +9931,7 @@ Parsoid: Page property magic word with magic word contents
 !! wikitext
 {{DISPLAYTITLE:''{{PAGENAME}}''}}
 !! html/parsoid
-<meta property="mw:PageProp/displaytitle" content="Main Page" about="#mwt2" typeof="mw:ExpandedAttrs" data-mw='{"attribs":[[{"txt":"content"},{"html":"&lt;i data-parsoid=\"{&amp;quot;dsr&amp;quot;:[15,31,2,2]}\">&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[]],&amp;quot;dsr&amp;quot;:[17,29,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;PAGENAME&amp;quot;,&amp;quot;function&amp;quot;:&amp;quot;pagename&amp;quot;},&amp;quot;params&amp;quot;:{},&amp;quot;i&amp;quot;:0}}]}\">Main Page&lt;/span>&lt;/i>"}]]}'/>
+<meta property="mw:PageProp/displaytitle" content="Main Page" about="#mwt2" typeof="mw:ExpandedAttrs" data-parsoid='{"src":"{{DISPLAYTITLE:&#39;&#39;{{PAGENAME}}&#39;&#39;}}"}' data-mw='{"attribs":[[{"txt":"content"},{"html":"&lt;i data-parsoid=&#39;{\"dsr\":[15,31,2,2]}&#39;>&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[]],\"dsr\":[17,29,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"PAGENAME\",\"function\":\"pagename\"},\"params\":{},\"i\":0}}]}&#39;>Main Page&lt;/span>&lt;/i>"}]]}'/>
 !! end
 
 !! test
@@ -9920,7 +9939,7 @@ Parsoid: Template-generated DISPLAYTITLE
 !! wikitext
 {{{{echo|DISPLAYTITLE}}:Foo}}
 !! html/parsoid
-<meta property="mw:PageProp/displaytitle" content="Foo" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"dsr":[0,29,null,null],"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|DISPLAYTITLE}}:Foo"},"params":{},"i":0}}]}'/>
+<meta property="mw:PageProp/displaytitle" content="Foo" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|DISPLAYTITLE}}:Foo"},"params":{},"i":0}}]}'/>
 !! end
 
 !! test
@@ -11211,10 +11230,9 @@ parsoid=wt2html
 |c
 |}
 !!html/parsoid
-<meta typeof="mw:Includes/IncludeOnly"/><meta typeof="mw:Includes/IncludeOnly/End"/><table about="#mwt2" typeof="mw:ExpandedAttrs" data-mw='{"attribs":[[{"txt":"{{{b}}}","html":"&lt;span about=\"#mwt1\" typeof=\"mw:Param\" data-parsoid=\"{&amp;quot;dsr&amp;quot;:[31,38,null,null],&amp;quot;src&amp;quot;:&amp;quot;{{{b}}}&amp;quot;}\">{{{b}}}&lt;/span>"},{"html":""}]]}' data-parsoid='{"a":{"{{{b}}}":null},"sa":{"{{{b}}}":""}}'>
+<meta typeof="mw:Includes/IncludeOnly" data-parsoid='{"src":"&lt;includeonly>a&lt;/includeonly>"}'/><meta typeof="mw:Includes/IncludeOnly/End" data-parsoid='{"src":""}'/><table about="#mwt2" typeof="mw:ExpandedAttrs" data-parsoid='{"a":{"{{{b}}}":null},"sa":{"{{{b}}}":""}}' data-mw='{"attribs":[[{"txt":"{{{b}}}","html":"&lt;span about=\"#mwt1\" typeof=\"mw:Param\" data-parsoid=&#39;{\"dsr\":[31,38,null,null],\"src\":\"{{{b}}}\"}&#39;>{{{b}}}&lt;/span>"},{"html":""}]]}'>
 <tbody><tr><td>c</td></tr>
 </tbody></table>
-
 !!end
 
 ###
@@ -11572,7 +11590,7 @@ Templates: Support for templates generating attributes and content
 <div style="background:#f9f9f9;">foo</div>
 
 !! html/parsoid
-<div style="background:#f9f9f9;" about="#mwt3" typeof="mw:ExpandedAttrs" data-parsoid='{"stx":"html"}' data-mw='{"attribs":[[{"txt":"style","html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[5,49,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;style{{=}}\\&amp;quot;background:&amp;amp;#35;f9f9f9;\\&amp;quot;&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">style&lt;/span>&lt;span typeof=\"mw:Nowiki\" about=\"#mwt1\" data-parsoid=\"{}\">=&lt;/span>&lt;span about=\"#mwt1\" data-parsoid=\"{}\">\"background:&lt;/span>&lt;span typeof=\"mw:Entity\" about=\"#mwt1\" data-parsoid=\"{&amp;quot;src&amp;quot;:&amp;quot;&amp;amp;#35;&amp;quot;,&amp;quot;srcContent&amp;quot;:&amp;quot;#&amp;quot;}\">#&lt;/span>&lt;span about=\"#mwt1\" data-parsoid=\"{}\">f9f9f9;\"&lt;/span>"},{"html":""}]]}'>foo</div>
+<div style="background:#f9f9f9;" about="#mwt3" typeof="mw:ExpandedAttrs" data-parsoid='{"stx":"html"}' data-mw='{"attribs":[[{"txt":"style","html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[5,49,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"style{{=}}\\\"background:&amp;amp;#35;f9f9f9;\\\"\"}},\"i\":0}}]}&#39;>style&lt;/span>&lt;span typeof=\"mw:Nowiki\" about=\"#mwt1\" data-parsoid=\"{}\">=&lt;/span>&lt;span about=\"#mwt1\" data-parsoid=\"{}\">\"background:&lt;/span>&lt;span typeof=\"mw:Entity\" about=\"#mwt1\" data-parsoid=&#39;{\"src\":\"&amp;amp;#35;\",\"srcContent\":\"#\"}&#39;>#&lt;/span>&lt;span about=\"#mwt1\" data-parsoid=\"{}\">f9f9f9;\"&lt;/span>"},{"html":""}]]}'>foo</div>
 !! end
 
 !! test
@@ -12931,7 +12949,7 @@ parsoid=wt2html,wt2wt,html2html
 <div class="thumb tright"><div class="thumbinner" style="width:139px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" width="137" height="16" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/206px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/274px-Foobar.jpg 2x" /></a>  <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is a caption</div></div></div>
 
 !! html/parsoid
-<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["width",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[24,38,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;137px&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">137px&lt;/span>"}]]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137"/></a><figcaption>This is a caption</figcaption></figure>
+<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"width","ak":"{{echo|137px}}"},{"ck":"caption","ak":"This is a caption"}]}' data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["width",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[24,38,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"137px\"}},\"i\":0}}]}&#39;>137px&lt;/span>"}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"16","width":"137"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>This is a caption</figcaption></figure>
 !! end
 
 !! test
@@ -12942,7 +12960,7 @@ parsoid=wt2html,wt2wt,html2html
 <div class="thumb tright"><div class="thumbinner" style="width:139px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" width="137" height="16" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/206px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/274px-Foobar.jpg 2x" /></a>  <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is a caption</div></div></div>
 
 !! html/parsoid
-<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt3" data-mw='{"attribs":[["thumbnail",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[18,32,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;thumb&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">thumb&lt;/span>"}],["width",{"html":"&lt;span about=\"#mwt2\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[33,47,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;137px&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">137px&lt;/span>"}]]}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137"/></a><figcaption>This is a caption</figcaption></figure>
+<figure typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt3" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"{{echo|thumb}}"},{"ck":"width","ak":"{{echo|137px}}"},{"ck":"caption","ak":"This is a caption"}]}' data-mw='{"attribs":[["thumbnail",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[18,32,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"thumb\"}},\"i\":0}}]}&#39;>thumb&lt;/span>"}],["width",{"html":"&lt;span about=\"#mwt2\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[33,47,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"137px\"}},\"i\":0}}]}&#39;>137px&lt;/span>"}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/137px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="16" width="137" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"16","width":"137"},"sa":{"resource":"File:Foobar.jpg"}}'/></a><figcaption>This is a caption</figcaption></figure>
 !! end
 
 !! test
@@ -12953,7 +12971,7 @@ parsoid=wt2html,wt2wt,html2html
 <p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" width="50" height="6" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/75px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/100px-Foobar.jpg 2x" /></a>
 </p>
 !! html/parsoid
-<p><span typeof="mw:Image mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"width","ak":"{{echo|50px}}"}]}' data-mw='{"attribs":[["width",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;dsr&amp;quot;:[18,31,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;50px&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">50px&lt;/span>"}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50"/></a></span></p>
+<p><span typeof="mw:Image mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"width","ak":"{{echo|50px}}"}]}' data-mw='{"attribs":[["width",{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[{\"k\":\"1\"}]],\"dsr\":[18,31,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"50px\"}},\"i\":0}}]}&#39;>50px&lt;/span>"}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="6" width="50" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"6","width":"50"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 ## Parsoid does not provide editing support for images where templates produce multiple image attributes.
@@ -13323,8 +13341,9 @@ Image with wiki markup in implicit alt
 </p><p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"testing &lt;b data-parsoid=\"{&amp;quot;dsr&amp;quot;:[27,37,3,3]}\">bold&lt;/b> in alt"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:Foobar.jpg"}}'/></a></span></p>
-<p><span class="mw-default-size" typeof="mw:Image"><a href="./File:Foobar.jpg"><img alt="testing bold in alt" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"testing bold in alt","resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt","resource":"Image:Foobar.jpg"}}'/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt"}]}' data-mw='{"caption":"testing &lt;b data-parsoid=&#39;{\"dsr\":[27,37,3,3]}&#39;>bold&lt;/b> in alt"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"Image:Foobar.jpg"}}'/></a></span></p>
+
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"alt","ak":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt"}]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="testing bold in alt" resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"alt":"testing bold in alt","resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"alt":"alt=testing &#39;&#39;&#39;bold&#39;&#39;&#39; in alt","resource":"Image:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13335,7 +13354,7 @@ Alt image option should handle most kinds of wikitext without barfing
 <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a link and a bold template." src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a>  <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"></a></div>This is the image caption</div></div></div>
 
 !! html/parsoid
-<figure class="mw-default-size" typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"This is the image caption"},{"ck":"alt","ak":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}."}]}' data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["alt",{"html":"alt=This is a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;simple&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;./Link&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;link&amp;quot;},&amp;quot;dsr&amp;quot;:[65,73,2,2]}\">link&lt;/a> and a &lt;i about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;dsr&amp;quot;:[80,106,null,null],&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;&#39;&#39;bold template&#39;&#39;&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">bold template&lt;/i>."}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="This is a link and a bold template." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"alt":"This is a link and a bold template.","resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"alt":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}.","resource":"Image:Foobar.jpg"}}'/></a><figcaption>This is the image caption</figcaption></figure>
+<figure class="mw-default-size" typeof="mw:Image/Thumb mw:ExpandedAttrs" about="#mwt2" data-parsoid='{"optList":[{"ck":"thumbnail","ak":"thumb"},{"ck":"caption","ak":"This is the image caption"},{"ck":"alt","ak":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}."}]}' data-mw='{"attribs":[["thumbnail",{"html":"thumb"}],["alt",{"html":"alt=This is a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[65,73,2,2]}&#39;>link&lt;/a> and a &lt;i about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"dsr\":[80,106,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;#39;&amp;#39;bold template&amp;#39;&amp;#39;\"}},\"i\":0}}]}&#39;>bold template&lt;/i>."}]]}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img alt="This is a link and a bold template." resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220" data-parsoid='{"a":{"alt":"This is a link and a bold template.","resource":"./File:Foobar.jpg","height":"25","width":"220"},"sa":{"alt":"alt=This is a [[link]] and a {{echo|&#39;&#39;bold template&#39;&#39;}}.","resource":"Image:Foobar.jpg"}}'/></a><figcaption>This is the image caption</figcaption></figure>
 !! end
 
 ###################
@@ -13562,7 +13581,7 @@ Frameless image caption with a free URL
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="http://example.com" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&lt;a rel=\"mw:ExtLink\" href=\"http://example.com\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;url&amp;quot;,&amp;quot;dsr&amp;quot;:[18,36,0,0]}\">http://example.com&lt;/a>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"http://example.com"}]}' data-mw='{"caption":"&lt;a rel=\"mw:ExtLink\" href=\"http://example.com\" data-parsoid=&#39;{\"stx\":\"url\",\"dsr\":[18,36,0,0]}&#39;>http://example.com&lt;/a>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13672,7 +13691,7 @@ BUG 648: Frameless image caption with a link
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;simple&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;./Link&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;link&amp;quot;},&amp;quot;dsr&amp;quot;:[30,38,2,2]}\">link&lt;/a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[30,38,2,2]}&#39;>link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13683,7 +13702,7 @@ BUG 648: Frameless image caption with a link (suffix)
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a linkfoo in it"><img alt="text with a linkfoo in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;simple&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;./Link&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;link&amp;quot;},&amp;quot;dsr&amp;quot;:[30,41,2,5],&amp;quot;tail&amp;quot;:&amp;quot;foo&amp;quot;}\">linkfoo&lt;/a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[link]]foo in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:WikiLink\" href=\"./Link\" title=\"Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Link\"},\"sa\":{\"href\":\"link\"},\"dsr\":[30,41,2,5],\"tail\":\"foo\"}&#39;>linkfoo&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13694,7 +13713,7 @@ BUG 648: Frameless image caption with an interwiki link
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a MeatBall:Link in it"><img alt="text with a MeatBall:Link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a &lt;a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;simple&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;http://www.usemod.com/cgi-bin/mb.pl?Link&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;MeatBall:Link&amp;quot;},&amp;quot;isIW&amp;quot;:true,&amp;quot;dsr&amp;quot;:[30,47,2,2]}\">MeatBall:Link&lt;/a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[MeatBall:Link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"http://www.usemod.com/cgi-bin/mb.pl?Link\"},\"sa\":{\"href\":\"MeatBall:Link\"},\"isIW\":true,\"dsr\":[30,47,2,2]}&#39;>MeatBall:Link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13705,7 +13724,7 @@ BUG 648: Frameless image caption with a piped interwiki link
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"text with a &lt;a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;piped&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;http://www.usemod.com/cgi-bin/mb.pl?Link&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;MeatBall:Link&amp;quot;},&amp;quot;isIW&amp;quot;:true,&amp;quot;dsr&amp;quot;:[30,52,16,2]}\">link&lt;/a> in it"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"text with a [[MeatBall:Link|link]] in it"}]}' data-mw='{"caption":"text with a &lt;a rel=\"mw:ExtLink\" href=\"http://www.usemod.com/cgi-bin/mb.pl?Link\" title=\"meatball:Link\" data-parsoid=&#39;{\"stx\":\"piped\",\"a\":{\"href\":\"http://www.usemod.com/cgi-bin/mb.pl?Link\"},\"sa\":{\"href\":\"MeatBall:Link\"},\"isIW\":true,\"dsr\":[30,52,16,2]}&#39;>link&lt;/a> in it"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13713,7 +13732,7 @@ T107474: Frameless image caption with <nowiki>
 !! wikitext
 [[File:Foobar.jpg|<nowiki>text with a [[MeatBall:Link|link]] in it</nowiki>]]
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&lt;span typeof=\"mw:Nowiki\" data-parsoid=\"{&amp;quot;dsr&amp;quot;:[18,75,8,9]}\">text with a [[MeatBall:Link|link]] in it&lt;/span>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;nowiki>text with a [[MeatBall:Link|link]] in it&lt;/nowiki>"}]}' data-mw='{"caption":"&lt;span typeof=\"mw:Nowiki\" data-parsoid=&#39;{\"dsr\":[18,75,8,9]}&#39;>text with a [[MeatBall:Link|link]] in it&lt;/span>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13724,7 +13743,7 @@ Escape HTML special chars in image alt text
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="&amp; &lt; &gt; &quot;"><img alt="&amp; &lt; &gt; &quot;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&amp;amp; &amp;lt; &amp;gt; \""}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp; &lt; > \""}]}' data-mw='{"caption":"&amp;amp; &amp;lt; > \""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -13735,7 +13754,7 @@ BUG 499: Alt text should have &#1234;, not &amp;1234;
 <p><a href="/wiki/File:Foobar.jpg" class="image" title="♀"><img alt="♀" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
 </p>
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=\"{&amp;quot;src&amp;quot;:&amp;quot;&amp;amp;#9792;&amp;quot;,&amp;quot;srcContent&amp;quot;:&amp;quot;♀&amp;quot;,&amp;quot;dsr&amp;quot;:[18,25,null,null]}\">♀&lt;/span>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&amp;#9792;"}]}' data-mw='{"caption":"&lt;span typeof=\"mw:Entity\" data-parsoid=&#39;{\"src\":\"&amp;amp;#9792;\",\"srcContent\":\"♀\",\"dsr\":[18,25,null,null]}&#39;>♀&lt;/span>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -14073,7 +14092,7 @@ Parsoid-specific image handling - simple image with a formatted caption
 !! wikitext
 [[File:Foobar.jpg|<table><tr><td>a</td><td>b</td></tr><tr><td>c</td></tr></table>]]
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"&lt;table data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[18,81,7,8]}\">&lt;tbody data-parsoid=\"{&amp;quot;dsr&amp;quot;:[25,73,0,0]}\">&lt;tr data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[25,54,4,5]}\">&lt;td data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[29,39,4,5]}\">a&lt;/td>&lt;td data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[39,49,4,5]}\">b&lt;/td>&lt;/tr>&lt;tr data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[54,73,4,5]}\">&lt;td data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;dsr&amp;quot;:[58,68,4,5]}\">c&lt;/td>&lt;/tr>&lt;/tbody>&lt;/table>"}'><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941"/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"&lt;table>&lt;tr>&lt;td>a&lt;/td>&lt;td>b&lt;/td>&lt;/tr>&lt;tr>&lt;td>c&lt;/td>&lt;/tr>&lt;/table>"}]}' data-mw='{"caption":"&lt;table data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[18,81,7,8]}&#39;>&lt;tbody data-parsoid=&#39;{\"dsr\":[25,73,0,0]}&#39;>&lt;tr data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[25,54,4,5]}&#39;>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[29,39,4,5]}&#39;>a&lt;/td>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[39,49,4,5]}&#39;>b&lt;/td>&lt;/tr>&lt;tr data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[54,73,4,5]}&#39;>&lt;td data-parsoid=&#39;{\"stx\":\"html\",\"dsr\":[58,68,4,5]}&#39;>c&lt;/td>&lt;/tr>&lt;/tbody>&lt;/table>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
 !! end
 
 !! test
@@ -14167,7 +14186,7 @@ T93580: 2. <ref> inside inline images
 
 <references />
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: &lt;ref>foo&lt;/ref>"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=\"{&amp;quot;dsr&amp;quot;:[64,78,5,6]}\" data-mw=\"{&amp;quot;name&amp;quot;:&amp;quot;ref&amp;quot;,&amp;quot;body&amp;quot;:{&amp;quot;id&amp;quot;:&amp;quot;mw-reference-text-cite_note-1&amp;quot;},&amp;quot;attrs&amp;quot;:{}}\">&lt;a href=\"#cite_note-1\" style=\"counter-reset: mw-Ref 1;\">&lt;span class=\"mw-reflink-text\">[1]&lt;/span>&lt;/a>&lt;/span>&lt;meta typeof=\"mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=\"{&amp;quot;group&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;foo&amp;quot;,&amp;quot;hasRefInRef&amp;quot;:false,&amp;quot;dsr&amp;quot;:[64,78,5,6],&amp;quot;tmp&amp;quot;:{}}\" data-mw=\"{}\">"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: &lt;ref>foo&lt;/ref>"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[64,78,5,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-1\"},\"attrs\":{}}&#39;>&lt;a href=\"#cite_note-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/span>&lt;meta typeof=\"mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=&#39;{\"group\":\"\",\"name\":\"\",\"content\":\"foo\",\"hasRefInRef\":false,\"dsr\":[64,78,5,6]}&#39;/>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></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</span></li></ol>
 !! end
@@ -14179,7 +14198,7 @@ T93580: 3. Templated <ref> inside inline images
 
 <references />
 !! html/parsoid
-<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: {{echo|&lt;ref>{{echo|foo}}&lt;/ref>}}"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Transclusion  mw:Extension/ref\" data-parsoid=\"{&amp;quot;dsr&amp;quot;:[64,96,null,null],&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;&lt;ref>{{echo|foo}}&lt;/ref>&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">&lt;a href=\"#cite_note-1\" style=\"counter-reset: mw-Ref 1;\">&lt;span class=\"mw-reflink-text\">[1]&lt;/span>&lt;/a>&lt;/span>&lt;meta typeof=\"mw:Transclusion mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=\"{&amp;quot;group&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;foo&amp;quot;,&amp;quot;hasRefInRef&amp;quot;:false,&amp;quot;dsr&amp;quot;:[64,96,null,null],&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;}]],&amp;quot;tmp&amp;quot;:{}}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;&lt;ref>{{echo|foo}}&lt;/ref>&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\">"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></a></span></p>
+<p><span class="mw-default-size" typeof="mw:Image" data-parsoid='{"optList":[{"ck":"caption","ak":"Undisplayed caption in inline image with ref: {{echo|&lt;ref>{{echo|foo}}&lt;/ref>}}"}]}' data-mw='{"caption":"Undisplayed caption in inline image with ref: &lt;span about=\"#mwt2\" class=\"mw-ref\" id=\"cite_ref-1\" rel=\"dc:references\" typeof=\"mw:Transclusion  mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[64,96,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;lt;ref>{{echo|foo}}&amp;lt;/ref>\"}},\"i\":0}}]}&#39;>&lt;a href=\"#cite_note-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/span>&lt;meta typeof=\"mw:Transclusion mw:Extension/ref/Marker\" about=\"#mwt2\" data-parsoid=&#39;{\"group\":\"\",\"name\":\"\",\"content\":\"foo\",\"hasRefInRef\":false,\"dsr\":[64,96,null,null],\"pi\":[[{\"k\":\"1\"}]]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"&amp;lt;ref>{{echo|foo}}&amp;lt;/ref>\"}},\"i\":0}}]}&#39;/>"}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="220" width="1941" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"1941"},"sa":{"resource":"File:Foobar.jpg"}}'/></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="{}">foo</span></li></ol>
 !! end
@@ -14781,7 +14800,7 @@ Parsoid: Defaultsort (template-generated)
 !! wikitext
 {{{{echo|DEFAULTSORT}}:Foo}}
 !! html/parsoid
-<meta property="mw:PageProp/categorydefaultsort" content="Foo" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"dsr":[0,28,null,null],"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|DEFAULTSORT}}:Foo"},"params":{},"i":0}}]}'/>
+<meta property="mw:PageProp/categorydefaultsort" content="Foo" about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"{{echo|DEFAULTSORT}}:Foo"},"params":{},"i":0}}]}'/>
 !! end
 
 ###
@@ -16010,7 +16029,7 @@ Bug 2304: HTML attribute safety (dangerous template; 2309)
 <div title=""></div>
 
 !! html/parsoid
-<div title='" onmouseover="alert(document.cookie)' about="#mwt2" typeof="mw:ExpandedAttrs" data-parsoid='{"stx":"html","a":{"title":"\" onmouseover=\"alert(document.cookie)"},"sa":{"title":"{{dangerous attribute}}"}}' data-mw='{"attribs":[[{"txt":"title"},{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=\"{&amp;quot;pi&amp;quot;:[[]],&amp;quot;dsr&amp;quot;:[12,35,null,null]}\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;dangerous attribute&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Dangerous_attribute&amp;quot;},&amp;quot;params&amp;quot;:{},&amp;quot;i&amp;quot;:0}}]}\">\" onmouseover=\"alert(document.cookie)&lt;/span>"}]]}'></div>
+<div title='" onmouseover="alert(document.cookie)' about="#mwt2" typeof="mw:ExpandedAttrs" data-parsoid='{"stx":"html","a":{"title":"\" onmouseover=\"alert(document.cookie)"},"sa":{"title":"{{dangerous attribute}}"}}' data-mw='{"attribs":[[{"txt":"title"},{"html":"&lt;span about=\"#mwt1\" typeof=\"mw:Transclusion\" data-parsoid=&#39;{\"pi\":[[]],\"dsr\":[12,35,null,null]}&#39; data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"dangerous attribute\",\"href\":\"./Template:Dangerous_attribute\"},\"params\":{},\"i\":0}}]}&#39;>\" onmouseover=\"alert(document.cookie)&lt;/span>"}]]}'></div>
 !! end
 
 !! test
@@ -16549,9 +16568,12 @@ array (
 <pre typeof="mw:Extension/tag" data-mw='{"name":"tag","attrs":{"foo":"bar"},"body":null}' data-parsoid='{}' about="#mwt2"></pre>text
 !! end
 
-# </tag> should be output literally since there is no matching tag that begins it
+## </tag> should be output literally since there is no matching tag that begins it
+## Don't expect parsoid to rt this form.
 !! test
 Parser hook: basic arguments using terminated empty elements (bug 2374)
+!! options
+parsoid=wt2html
 !! wikitext
 <tag width=200 height = "100" depth = '50' square/>
 other stuff
@@ -16569,6 +16591,28 @@ array (
 <p>other stuff
 &lt;/tag&gt;
 </p>
+!! html/parsoid
+<pre typeof="mw:Extension/tag" data-mw='{"name":"tag","attrs":{"width":"200","height":"100","depth":"50","square":""},"body":null}' about="#mwt2"></pre><p>other stuff
+&lt;/tag></p>
+!! end
+
+## Don't expect parsoid to rt this form.
+!! test
+Parser hook: Don't allow unclosed extension tags
+!! options
+parsoid=wt2html
+!! wikitext
+test <tag>123
+
+this is a '''test'''
+!! html/php
+<p>test &lt;tag&gt;123
+</p><p>this is a <b>test</b>
+</p>
+!! html/parsoid
+<p>test &lt;tag>123</p>
+
+<p>this is a <b>test</b></p>
 !! end
 
 ###
@@ -16891,7 +16935,7 @@ HTML nested bullet list, closed tags (bug 5497)
 </ul>
 </li>
 </ul>
-!! html
+!! html/php
 <ul>
 <li>One</li>
 <li>Two:
@@ -16902,6 +16946,16 @@ HTML nested bullet list, closed tags (bug 5497)
 </li>
 </ul>
 
+!! html/parsoid
+<ul data-parsoid='{"stx":"html"}'>
+<li data-parsoid='{"stx":"html"}'>One</li>
+<li data-parsoid='{"stx":"html"}'>Two:
+<ul data-parsoid='{"stx":"html"}'>
+<li data-parsoid='{"stx":"html"}'>Sub-one</li>
+<li data-parsoid='{"stx":"html"}'>Sub-two</li>
+</ul>
+</li>
+</ul>
 !! end
 
 !! test
@@ -17271,7 +17325,7 @@ Fuzz testing: image with bogus manual thumbnail
 <div class="thumb tright"><div class="thumbinner" style="width:182px;">Error creating thumbnail:   <div class="thumbcaption"></div></div></div>
 
 !! html/parsoid
-<figure class="mw-default-size" typeof="mw:Error mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"manualthumb","ak":"thumbnail= "}],"dsr":[0,32,2,2]}' data-mw='{"errors":[{"key":"missing-thumbnail","message":"This thumbnail does not exist.","params":{"name":""}}],"thumb":""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{},"dsr":[2,30,null,null]}'><img resource="./File:Foobar.jpg" src="./Special:FilePath/" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"220"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure>
+<figure class="mw-default-size" typeof="mw:Error mw:Image/Thumb" data-parsoid='{"optList":[{"ck":"manualthumb","ak":"thumbnail= "}]}' data-mw='{"errors":[{"key":"missing-thumbnail","message":"This thumbnail does not exist.","params":{"name":""}}],"thumb":""}'><a href="./File:Foobar.jpg" data-parsoid='{"a":{"href":"./File:Foobar.jpg"},"sa":{}}'><img resource="./File:Foobar.jpg" src="./Special:FilePath/" height="220" width="220" data-parsoid='{"a":{"resource":"./File:Foobar.jpg","height":"220","width":"220"},"sa":{"resource":"Image:foobar.jpg"}}'/></a></figure>
 !!end
 
 !! test
@@ -20592,6 +20646,7 @@ this is not the the title
 !! html/php
 Screen
 <p>this is not the the title
+<span class="error"><strong>Warning:</strong> Display title "whatever" was ignored since it is not equivalent to the page's actual title.</span>
 </p>
 !! end
 
@@ -20855,9 +20910,9 @@ percent-encoding and + signs in internal links (Bug 26410)
 <a href="/index.php?title=3E&amp;action=edit&amp;redlink=1" class="new" title="3E (page does not exist)">3E</a> <a href="/index.php?title=3E%2B&amp;action=edit&amp;redlink=1" class="new" title="3E+ (page does not exist)">3E+</a>
 </p>
 !! html/parsoid
-<p><a rel="mw:WikiLink" href="./User:+%25" title="User:+%">User:+%</a> <a rel="mw:WikiLink" href="Page+title%25" title="Page+title%">Page+title%</a>
-<a rel="mw:WikiLink" href="%25+" title="%+">%+</a> <a rel="mw:WikiLink" href="%25+" title="%+">%20</a> <a rel="mw:WikiLink" href="%25+" title="%+">%+ </a> <a rel="mw:WikiLink" href="%25+r" title="%+r">%+r</a>
-<a rel="mw:WikiLink" href="%25" title="%">%</a> <a rel="mw:WikiLink" href="+" title="+">+</a> <span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"foo"},{"ck":"caption","ak":"[[bar]]"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;a rel=\"mw:WikiLink\" href=\"./Bar\" title=\"Bar\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;simple&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;./Bar&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;bar&amp;quot;},&amp;quot;dsr&amp;quot;:[94,101,2,2]}\">bar&lt;/a>"}'><a href="./File:%25+abc9"><img resource="./File:%25+abc9" src="./Special:FilePath/%25+abc9" height="220" width="220" data-parsoid='{"a":{"resource":"./File:%25+abc9","height":"220","width":"220"},"sa":{"resource":"File:%+abc%39"}}'/></a></span>
+<p><a rel="mw:WikiLink" href="./User:+%25" title="User:+%" data-parsoid='{"stx":"simple","a":{"href":"./User:+%25"},"sa":{"href":"User:+%"}}'>User:+%</a> <a rel="mw:WikiLink" href="./Page+title%25" title="Page+title%" data-parsoid='{"stx":"simple","a":{"href":"./Page+title%25"},"sa":{"href":"Page+title%"}}'>Page+title%</a>
+<a rel="mw:WikiLink" href="./%25+" title="%+" data-parsoid='{"stx":"simple","a":{"href":"./%25+"},"sa":{"href":"%+"}}'>%+</a> <a rel="mw:WikiLink" href="./%25+" title="%+" data-parsoid='{"stx":"piped","a":{"href":"./%25+"},"sa":{"href":"%+"}}'>%20</a> <a rel="mw:WikiLink" href="./%25+" title="%+" data-parsoid='{"stx":"simple","a":{"href":"./%25+"},"sa":{"href":"%+ "}}'>%+ </a> <a rel="mw:WikiLink" href="./%25+r" title="%+r" data-parsoid='{"stx":"simple","a":{"href":"./%25+r"},"sa":{"href":"%+r"}}'>%+r</a>
+<a rel="mw:WikiLink" href="./%25" title="%" data-parsoid='{"stx":"simple","a":{"href":"./%25"},"sa":{"href":"%"}}'>%</a> <a rel="mw:WikiLink" href="./+" title="+" data-parsoid='{"stx":"simple","a":{"href":"./+"},"sa":{"href":"+"}}'>+</a> <span class="mw-default-size" typeof="mw:Error mw:Image" data-parsoid='{"optList":[{"ck":"bogus","ak":"foo"},{"ck":"caption","ak":"[[bar]]"}]}' data-mw='{"errors":[{"key":"missing-image","message":"This image does not exist."}],"caption":"&lt;a rel=\"mw:WikiLink\" href=\"./Bar\" title=\"Bar\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Bar\"},\"sa\":{\"href\":\"bar\"},\"dsr\":[94,101,2,2]}&#39;>bar&lt;/a>"}'><a href="./File:%25+abc9" data-parsoid='{"a":{"href":"./File:%25+abc9"},"sa":{}}'><img resource="./File:%25+abc9" src="./Special:FilePath/%25+abc9" height="220" width="220" data-parsoid='{"a":{"resource":"./File:%25+abc9","height":"220","width":"220"},"sa":{"resource":"File:%+abc%39"}}'/></a></span>
 <a rel="mw:WikiLink" href="./3E" title="3E" data-parsoid='{"stx":"simple","a":{"href":"./3E"},"sa":{"href":"%33%45"}}'>3E</a> <a rel="mw:WikiLink" href="./3E+" title="3E+" data-parsoid='{"stx":"simple","a":{"href":"./3E+"},"sa":{"href":"%33%45+"}}'>3E+</a></p>
 !! end
 
@@ -22108,7 +22163,7 @@ This should just get lost.
 B <span about="#mwt4" class="mw-ref" id="cite_ref-b_2-0" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-b-2"},"attrs":{"name":"b"}}'><a href="#cite_note-b-2"><span class="mw-reflink-text">[2]</span></a></span></p>
 
 
-<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{},"body":{"html":"\n&lt;span about=\"#mwt8\" class=\"mw-ref\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[59,82,14,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-a-1\"},\"attrs\":{\"name\":\"a\"}}&#39;>&lt;a href=\"#cite_note-a-1\" style=\"counter-reset: mw-Ref 1;\">&lt;span class=\"mw-reflink-text\">[1]&lt;/span>&lt;/a>&lt;/span>\n"}}'><li about="#cite_note-a-1" id="cite_note-a-1"><a href="#cite_ref-a_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-a-1" class="mw-reference-text">foo</span></li><li about="#cite_note-b-2" id="cite_note-b-2"><a href="#cite_ref-b_2-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">bar</span></li>
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{},"body":{"html":"\n&lt;span about=\"#mwt8\" class=\"mw-ref\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[59,82,14,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-a-1\"},\"attrs\":{\"name\":\"a\"}}&#39;>&lt;a href=\"#cite_note-a-1\" style=\"counter-reset: mw-Ref 1;\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[1]&lt;/span>&lt;/a>&lt;/span>\n"}}'><li about="#cite_note-a-1" id="cite_note-a-1"><a href="#cite_ref-a_1-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-a-1" class="mw-reference-text">foo</span></li><li about="#cite_note-b-2" id="cite_note-b-2"><a href="#cite_ref-b_2-0" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">bar</span></li>
 </ol>
 !! end
 
@@ -22141,7 +22196,7 @@ B <span about="#mwt4" class="mw-ref" id="cite_ref-b_2-0" rel="dc:references" typ
 <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 bar for a</span></li>
 </ol>
 
-<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw-group="X" data-mw='{"name":"references","attrs":{"group":"X"},"body":{"html":"\n&lt;span about=\"#mwt10\" class=\"mw-ref\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[96,119,14,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-b-2\"},\"attrs\":{\"name\":\"b\"}}&#39;>&lt;a href=\"#cite_note-b-2\" style=\"counter-reset: mw-Ref 1;\" data-mw-group=\"X\">&lt;span class=\"mw-reflink-text\">[X 1]&lt;/span>&lt;/a>&lt;/span>\n"}}'>
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt8" data-mw-group="X" data-mw='{"name":"references","attrs":{"group":"X"},"body":{"html":"\n&lt;span about=\"#mwt10\" class=\"mw-ref\" rel=\"dc:references\" typeof=\"mw:Extension/ref\" data-parsoid=&#39;{\"dsr\":[96,119,14,6]}&#39; data-mw=&#39;{\"name\":\"ref\",\"body\":{\"id\":\"mw-reference-text-cite_note-b-2\"},\"attrs\":{\"name\":\"b\"}}&#39;>&lt;a href=\"#cite_note-b-2\" style=\"counter-reset: mw-Ref 1;\" data-mw-group=\"X\" data-parsoid=\"{}\">&lt;span class=\"mw-reflink-text\" data-parsoid=\"{}\">[X 1]&lt;/span>&lt;/a>&lt;/span>\n"}}'>
 <li about="#cite_note-b-2" id="cite_note-b-2"><a href="#cite_ref-b_2-0" data-mw-group="X" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-b-2" class="mw-reference-text">foo</span></li>
 </ol>
 !! end
@@ -22204,8 +22259,11 @@ Entities in ref name
 </ol>
 !! end
 
-# This test is wt2html only because we're permitting the serializer to produce
-# dirty diffs, normalizing the unclosed references to the self-closed version.
+## The output here may look funny, but it's what the php parser will do.  The
+## unclosed references tag becomes escaped text, and then a new references
+## tag is auto-generated.  The test is wt2html only because it roundtrips with
+## nowiki tags, and the auto-generated references tag is only dropped in
+## rtTestMode.
 !! test
 Generate references for unclosed references tag
 !! options
@@ -22215,9 +22273,10 @@ a<ref>foo</ref>
 
 <references>
 !! html/parsoid
-<p>a<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="#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">foo</span></li></ol>
+<p>a<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></p>
+
+<p>&lt;references></p>
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt3" data-mw='{"name":"references","attrs":{},"autoGenerated":true}'><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</span></li></ol>
 !! end
 
 !! test
 !! test
 2. Leading whitespace in non-indent-pre contexts should not be escaped
 !! options
-parsoid=htm2wt
+parsoid=html2wt
 !! html/parsoid
 <p>foo <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="#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"><i data-parsoid='{"dsr":[9,14,2,2]}'>a</i>
+<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"><i>a</i>
  b</span></li>
 </ol>
 !! wikitext
@@ -24663,7 +24722,7 @@ T115289: Don't migrate newlines out of tables with fostered content
 !! wikitext
 <table><td></td>{{echo|<tr>[[Category:One]]}}<!--c-->[[Category:Two]]
 !! html/parsoid
-<link rel="mw:PageProp/Category" href="./Category:One" about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"stx":"simple","a":{"href":"./Category:One"},"sa":{"href":"Category:One"},"fostered":true,"pi":[[{"k":"1"}]]}' data-mw='{"parts":["&lt;table>&lt;td>&lt;/td>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;tr>[[Category:One]]"}},"i":0}},"&lt;!--c-->[[Category:Two]]"]}'/><link rel="mw:PageProp/Category" href="./Category:Two" about="#mwt2"/><table about="#mwt2" data-parsoid='{"stx":"html","autoInsertedEnd":true,"dsr":[0,53,7,0]}'><tbody><tr><td></td></tr><tr><!--c--></tr></tbody></table>
+<link rel="mw:PageProp/Category" href="./Category:One" about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"stx":"simple","a":{"href":"./Category:One"},"sa":{"href":"Category:One"},"fostered":true,"pi":[[{"k":"1"}]]}' data-mw='{"parts":["&lt;table>&lt;td>&lt;/td>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;tr>[[Category:One]]"}},"i":0}},"&lt;!--c-->[[Category:Two]]"]}'/><link rel="mw:PageProp/Category" href="./Category:Two" about="#mwt2"/><table about="#mwt2" data-parsoid='{"stx":"html","autoInsertedEnd":true}'><tbody><tr><td></td></tr><tr><!--c--></tr></tbody></table>
 !! end
 
 !! test
@@ -26689,8 +26748,8 @@ parsoid=html2wt
 !! html/parsoid
 <p>x<meta typeof="mw:Placeholder" data-parsoid='{"src":"&lt;nowiki/>"}'/><meta typeof="mw:Placeholder" data-parsoid='{"src":"&lt;nowiki/>"}'/>
 y</p>
-<p><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"dsr":[0,23,null,null],"pi":[[{"k":"1","named":true,"spc":["\n"," "," ",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;nowiki/>"}},"i":0}}]}'></span></p>
-<p><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"dsr":[0,24,null,null],"pi":[[{"k":"1","named":true,"spc":["\n"," "," ","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;nowiki/>"}},"i":0}}]}'></span></p>
+<p><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","named":true,"spc":["\n"," "," ",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;nowiki/>"}},"i":0}}]}'></span></p>
+<p><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","named":true,"spc":["\n"," "," ","\n"]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&lt;nowiki/>"}},"i":0}}]}'></span></p>
 !! wikitext
 x
 y
index d846b57..9672cdc 100644 (file)
@@ -2,12 +2,23 @@
 use MediaWiki\Logger\LegacySpi;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\Logger\MonologSpi;
+use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerInterface;
 
 /**
  * @since 1.18
  */
 abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
+
+       /**
+        * The service locator created by prepareServices(). This service locator will
+        * be restored after each test. Tests that pollute the global service locator
+        * instance should use overrideMwServices() to isolate the test.
+        *
+        * @var MediaWikiServices|null
+        */
+       private static $serviceLocator = null;
+
        /**
         * $called tracks whether the setUp and tearDown method has been called.
         * class extending MediaWikiTestCase usually override setUp and tearDown
@@ -108,18 +119,205 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
        }
 
-       public function run( PHPUnit_Framework_TestResult $result = null ) {
+       public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
+
+               // NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
+               // but let's make doubly sure.
+               self::prepareServices( new GlobalVarConfig() );
+       }
+
+       /**
+        * Prepare service configuration for unit testing.
+        *
+        * This calls MediaWikiServices::resetGlobalInstance() to allow some critical services
+        * to be overridden for testing.
+        *
+        * prepareServices() only needs to be called once, but should be called as early as possible,
+        * before any class has a chance to grab a reference to any of the global services
+        * instances that get discarded by prepareServices(). Only the first call has any effect,
+        * later calls are ignored.
+        *
+        * @note This is called by PHPUnitMaintClass::finalSetup.
+        *
+        * @see MediaWikiServices::resetGlobalInstance()
+        *
+        * @param Config $bootstrapConfig The bootstrap config to use with the new
+        *        MediaWikiServices. Only used for the first call to this method.
+        */
+       public static function prepareServices( Config $bootstrapConfig ) {
+               static $servicesPrepared = false;
+
+               if ( $servicesPrepared ) {
+                       return;
+               } else {
+                       $servicesPrepared = true;
+               }
+
+               self::resetGlobalServices( $bootstrapConfig );
+       }
+
+       /**
+        * Reset global services, and install testing environment.
+        * This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
+        * This should only be used to set up the testing environment, not when
+        * running unit tests. Use overrideMwServices() for that.
+        *
+        * @see MediaWikiServices::resetGlobalInstance()
+        * @see prepareServices()
+        * @see overrideMwServices()
+        *
+        * @param Config|null $bootstrapConfig The bootstrap config to use with the new
+        *        MediaWikiServices.
+        */
+       protected static function resetGlobalServices( Config $bootstrapConfig = null ) {
+               $oldServices = MediaWikiServices::getInstance();
+               $oldConfigFactory = $oldServices->getConfigFactory();
+
+               $testConfig = self::makeTestConfig( $bootstrapConfig );
+
+               MediaWikiServices::resetGlobalInstance( $testConfig );
+
+               self::$serviceLocator = MediaWikiServices::getInstance();
+               self::installTestServices(
+                       $oldConfigFactory,
+                       self::$serviceLocator
+               );
+       }
+
+       /**
+        * Create a config suitable for testing, based on a base config, default overrides,
+        * and custom overrides.
+        *
+        * @param Config|null $baseConfig
+        * @param Config|null $customOverrides
+        *
+        * @return Config
+        */
+       private static function makeTestConfig(
+               Config $baseConfig = null,
+               Config $customOverrides = null
+       ) {
+               $defaultOverrides = new HashConfig();
+
+               if ( !$baseConfig ) {
+                       $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
+               }
+
                /* Some functions require some kind of caching, and will end up using the db,
                 * which we can't allow, as that would open a new connection for mysql.
                 * Replace with a HashBag. They would not be going to persist anyway.
                 */
-               ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
+               $hashCache = [ 'class' => 'HashBagOStuff' ];
+               $objectCaches = [
+                               CACHE_DB => $hashCache,
+                               CACHE_ACCEL => $hashCache,
+                               CACHE_MEMCACHED => $hashCache,
+                               'apc' => $hashCache,
+                               'xcache' => $hashCache,
+                               'wincache' => $hashCache,
+                       ] + $baseConfig->get( 'ObjectCaches' );
+
+               $defaultOverrides->set( 'ObjectCaches', $objectCaches );
+               $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
+
+               // Use a fast hash algorithm to hash passwords.
+               $defaultOverrides->set( 'PasswordDefault', 'A' );
+
+               $testConfig = $customOverrides
+                       ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
+                       : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
+
+               return $testConfig;
+       }
+
+       /**
+        * @param ConfigFactory $oldConfigFactory
+        * @param MediaWikiServices $newServices
+        *
+        * @throws MWException
+        */
+       private static function installTestServices(
+               ConfigFactory $oldConfigFactory,
+               MediaWikiServices $newServices
+       ) {
+               // Use bootstrap config for all configuration.
+               // This allows config overrides via global variables to take effect.
+               $bootstrapConfig = $newServices->getBootstrapConfig();
+               $newServices->resetServiceForTesting( 'ConfigFactory' );
+               $newServices->redefineService(
+                       'ConfigFactory',
+                       self::makeTestConfigFactoryInstantiator(
+                               $oldConfigFactory,
+                               [ 'main' =>  $bootstrapConfig ]
+                       )
+               );
+       }
+
+       /**
+        * @param ConfigFactory $oldFactory
+        * @param Config[] $configurations
+        *
+        * @return Closure
+        */
+       private static function makeTestConfigFactoryInstantiator(
+               ConfigFactory $oldFactory,
+               array $configurations
+       ) {
+               return function( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
+                       $factory = new ConfigFactory();
+
+                       // clone configurations from $oldFactory that are not overwritten by $configurations
+                       $namesToClone = array_diff(
+                               $oldFactory->getConfigNames(),
+                               array_keys( $configurations )
+                       );
+
+                       foreach ( $namesToClone as $name ) {
+                               $factory->register( $name, $oldFactory->makeConfig( $name ) );
+                       }
+
+                       foreach ( $configurations as $name => $config ) {
+                               $factory->register( $name, $config );
+                       }
+
+                       return $factory;
+               };
+       }
+
+       /**
+        * Resets some well known services that typically have state that may interfere with unit tests.
+        * This is a lightweight alternative to resetGlobalServices().
+        *
+        * @note There is no guarantee that no references remain to stale service instances destroyed
+        * by a call to doLightweightServiceReset().
+        *
+        * @throws MWException if called outside of PHPUnit tests.
+        *
+        * @see resetGlobalServices()
+        */
+       private function doLightweightServiceReset() {
+               global $wgRequest;
 
-               // Sandbox APC by replacing with in-process hash instead.
-               // Ensures values are removed between tests.
-               ObjectCache::$instances['apc'] =
-               ObjectCache::$instances['xcache'] =
-               ObjectCache::$instances['wincache'] = new HashBagOStuff;
+               JobQueueGroup::destroySingletons();
+               ObjectCache::clear();
+               FileBackendGroup::destroySingleton();
+
+               // TODO: move global state into MediaWikiServices
+               RequestContext::resetMain();
+               MediaHandler::resetCache();
+               if ( session_id() !== '' ) {
+                       session_write_close();
+                       session_id( '' );
+               }
+
+               $wgRequest = new FauxRequest();
+               MediaWiki\Session\SessionManager::resetCache();
+       }
+
+       public function run( PHPUnit_Framework_TestResult $result = null ) {
+               // Reset all caches between tests.
+               $this->doLightweightServiceReset();
 
                $needsResetDB = false;
 
@@ -289,6 +487,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
                $this->mwGlobals = [];
                $this->restoreLoggers();
+
+               if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
+                       MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
+               }
+
+               // TODO: move global state into MediaWikiServices
                RequestContext::resetMain();
                MediaHandler::resetCache();
                if ( session_id() !== '' ) {
@@ -297,6 +501,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
                $wgRequest = new FauxRequest();
                MediaWiki\Session\SessionManager::resetCache();
+               MediaWiki\Auth\AuthManager::resetCache();
 
                $phpErrorLevel = intval( ini_get( 'error_reporting' ) );
 
@@ -324,6 +529,30 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                );
        }
 
+       /**
+        * Sets a service, maintaining a stashed version of the previous service to be
+        * restored in tearDown
+        *
+        * @since 1.27
+        *
+        * @param string $name
+        * @param object $object
+        */
+       protected function setService( $name, $object ) {
+               // If we did not yet override the service locator, so so now.
+               if ( MediaWikiServices::getInstance() === self::$serviceLocator ) {
+                       $this->overrideMwServices();
+               }
+
+               MediaWikiServices::getInstance()->disableService( $name );
+               MediaWikiServices::getInstance()->redefineService(
+                       $name,
+                       function () use ( $object ) {
+                               return $object;
+                       }
+               );
+       }
+
        /**
         * Sets a global, maintaining a stashed version of the previous global to be
         * restored in tearDown
@@ -354,6 +583,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @param mixed $value Value to set the global to (ignored
         *  if an array is given as first argument).
         *
+        * @note To allow changes to global variables to take effect on global service instances,
+        *       call overrideMwServices().
+        *
         * @since 1.21
         */
        protected function setMwGlobals( $pairs, $value = null ) {
@@ -368,6 +600,29 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
        }
 
+       /**
+        * Check if we can back up a value by performing a shallow copy.
+        * Values which fail this test are copied recursively.
+        *
+        * @param mixed $value
+        * @return bool True if a shallow copy will do; false if a deep copy
+        *  is required.
+        */
+       private static function canShallowCopy( $value ) {
+               if ( is_scalar( $value ) || $value === null ) {
+                       return true;
+               }
+               if ( is_array( $value ) ) {
+                       foreach ( $value as $subValue ) {
+                               if ( !is_scalar( $subValue ) && $subValue !== null ) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+               return false;
+       }
+
        /**
         * Stashes the global, will be restored in tearDown()
         *
@@ -381,6 +636,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @param array|string $globalKeys Key to the global variable, or an array of keys.
         *
         * @throws Exception When trying to stash an unset global
+        *
+        * @note To allow changes to global variables to take effect on global service instances,
+        *       call overrideMwServices().
+        *
         * @since 1.23
         */
        protected function stashMwGlobals( $globalKeys ) {
@@ -399,13 +658,22 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                                // NOTE: we serialize then unserialize the value in case it is an object
                                // this stops any objects being passed by reference. We could use clone
                                // and if is_object but this does account for objects within objects!
-                               try {
-                                       $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
-                               }
-                                       // NOTE; some things such as Closures are not serializable
-                                       // in this case just set the value!
-                               catch ( Exception $e ) {
+                               if ( self::canShallowCopy( $GLOBALS[$globalKey] ) ) {
                                        $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                               } elseif (
+                                       // Many MediaWiki types are safe to clone. These are the
+                                       // ones that are most commonly stashed.
+                                       $GLOBALS[$globalKey] instanceof Language ||
+                                       $GLOBALS[$globalKey] instanceof User ||
+                                       $GLOBALS[$globalKey] instanceof FauxRequest
+                               ) {
+                                       $this->mwGlobals[$globalKey] = clone $GLOBALS[$globalKey];
+                               } else {
+                                       try {
+                                               $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
+                                       } catch ( Exception $e ) {
+                                               $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                                       }
                                }
                        }
                }
@@ -421,6 +689,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         *
         * @throws MWException If the designated global is not an array.
         *
+        * @note To allow changes to global variables to take effect on global service instances,
+        *       call overrideMwServices().
+        *
         * @since 1.21
         */
        protected function mergeMwGlobalArrayValue( $name, $values ) {
@@ -441,6 +712,52 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                $this->setMwGlobals( $name, $merged );
        }
 
+       /**
+        * Stashes the global instance of MediaWikiServices, and installs a new one,
+        * allowing test cases to override settings and services.
+        * The previous instance of MediaWikiServices will be restored on tearDown.
+        *
+        * @since 1.27
+        *
+        * @param Config $configOverrides Configuration overrides for the new MediaWikiServices instance.
+        * @param callable[] $services An associative array of services to re-define. Keys are service
+        *        names, values are callables.
+        *
+        * @return MediaWikiServices
+        * @throws MWException
+        */
+       protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
+               if ( !$configOverrides ) {
+                       $configOverrides = new HashConfig();
+               }
+
+               $oldInstance = MediaWikiServices::getInstance();
+               $oldConfigFactory = $oldInstance->getConfigFactory();
+
+               $testConfig = self::makeTestConfig( null, $configOverrides );
+               $newInstance = new MediaWikiServices( $testConfig );
+
+               // Load the default wiring from the specified files.
+               // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
+               $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
+               $newInstance->loadWiringFiles( $wiringFiles );
+
+               // Provide a traditional hook point to allow extensions to configure services.
+               Hooks::run( 'MediaWikiServices', [ $newInstance ] );
+
+               foreach ( $services as $name => $callback ) {
+                       $newInstance->redefineService( $name, $callback );
+               }
+
+               self::installTestServices(
+                       $oldConfigFactory,
+                       $newInstance
+               );
+               MediaWikiServices::forceGlobalInstance( $newInstance );
+
+               return $newInstance;
+       }
+
        /**
         * @since 1.27
         * @param string|Language $lang
@@ -475,6 +792,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @param LoggerInterface $logger
         */
        protected function setLogger( $channel, LoggerInterface $logger ) {
+               // TODO: Once loggers are managed by MediaWikiServices, use
+               //       overrideMwServices() to set loggers.
+
                $provider = LoggerFactory::getProvider();
                $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
                $singletons = $wrappedProvider->singletons;
@@ -566,6 +886,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                $user = User::newFromName( 'UTSysop' );
                $comment = __METHOD__ . ': Sample page for unit test.';
 
+               // Avoid memory leak...?
+               // LinkCache::singleton()->clear();
+               // Maybe.  But doing this absolutely breaks $title->isRedirect() when called during unit tests....
+
                $page = WikiPage::factory( $title );
                $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
 
@@ -763,6 +1087,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                        return;
                }
 
+               // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
+               // and DatabaseBase no longer use global state.
+
                self::$dbSetup = true;
 
                if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
index eb9adea..bb71610 100644 (file)
@@ -61,6 +61,18 @@ class WfAppendQueryTest extends MediaWikiTestCase {
                                'baz=quux&foo=baz',
                                'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
                                'Modify query string'
+                       ],
+                       [
+                               'http://www.example.org/index.php#baz',
+                               'foo=bar',
+                               'http://www.example.org/index.php?foo=bar#baz',
+                               'URL with fragment'
+                       ],
+                       [
+                               'http://www.example.org/index.php?foo=bar#baz',
+                               'quux=blah',
+                               'http://www.example.org/index.php?foo=bar&quux=blah#baz',
+                               'URL with query string and fragment'
                        ]
                ];
        }
index 6c92b8c..4721793 100644 (file)
@@ -7,7 +7,6 @@ class HtmlTest extends MediaWikiTestCase {
                parent::setUp();
 
                $this->setMwGlobals( [
-                       'wgWellFormedXml' => false,
                        'wgUseMediaWikiUIEverywhere' => false,
                ] );
 
@@ -45,9 +44,9 @@ class HtmlTest extends MediaWikiTestCase {
         */
        public function testElementBasics() {
                $this->assertEquals(
-                       '<img>',
+                       '<img/>',
                        Html::element( 'img', null, '' ),
-                       'No close tag for short-tag elements'
+                       'Self-closing tag for short-tag elements'
                );
 
                $this->assertEquals(
@@ -61,14 +60,6 @@ class HtmlTest extends MediaWikiTestCase {
                        Html::element( 'element', [], '' ),
                        'Close tag for empty element (array, string)'
                );
-
-               $this->setMwGlobals( 'wgWellFormedXml', true );
-
-               $this->assertEquals(
-                       '<img/>',
-                       Html::element( 'img', null, '' ),
-                       'Self-closing tag for short-tag elements (wgWellFormedXml = true)'
-               );
        }
 
        public function dataXmlMimeType() {
@@ -134,23 +125,15 @@ class HtmlTest extends MediaWikiTestCase {
                );
 
                $this->assertEquals(
-                       ' selected',
+                       ' selected=""',
                        Html::expandAttributes( [ 'selected' => true ] ),
                        'Boolean attributes have no value when value is true'
                );
                $this->assertEquals(
-                       ' selected',
+                       ' selected=""',
                        Html::expandAttributes( [ 'selected' ] ),
                        'Boolean attributes have no value when value is true (passed as numerical array)'
                );
-
-               $this->setMwGlobals( 'wgWellFormedXml', true );
-
-               $this->assertEquals(
-                       ' selected=""',
-                       Html::expandAttributes( [ 'selected' => true ] ),
-                       'Boolean attributes have empty string value when value is true (wgWellFormedXml)'
-               );
        }
 
        /**
@@ -158,12 +141,12 @@ class HtmlTest extends MediaWikiTestCase {
         */
        public function testExpandAttributesForNumbers() {
                $this->assertEquals(
-                       ' value=1',
+                       ' value="1"',
                        Html::expandAttributes( [ 'value' => 1 ] ),
                        'Integer value is cast to a string'
                );
                $this->assertEquals(
-                       ' value=1.1',
+                       ' value="1.1"',
                        Html::expandAttributes( [ 'value' => 1.1 ] ),
                        'Float value is cast to a string'
                );
@@ -174,7 +157,7 @@ class HtmlTest extends MediaWikiTestCase {
         */
        public function testExpandAttributesForObjects() {
                $this->assertEquals(
-                       ' value=stringValue',
+                       ' value="stringValue"',
                        Html::expandAttributes( [ 'value' => new HtmlTestValue() ] ),
                        'Object value is converted to a string'
                );
@@ -193,43 +176,21 @@ class HtmlTest extends MediaWikiTestCase {
                        'Empty string is always quoted'
                );
                $this->assertEquals(
-                       ' key=value',
+                       ' key="value"',
                        Html::expandAttributes( [ 'key' => 'value' ] ),
                        'Simple string value needs no quotes'
                );
                $this->assertEquals(
-                       ' one=1',
+                       ' one="1"',
                        Html::expandAttributes( [ 'one' => 1 ] ),
                        'Number 1 value needs no quotes'
                );
                $this->assertEquals(
-                       ' zero=0',
+                       ' zero="0"',
                        Html::expandAttributes( [ 'zero' => 0 ] ),
                        'Number 0 value needs no quotes'
                );
 
-               $this->setMwGlobals( 'wgWellFormedXml', true );
-
-               $this->assertEquals(
-                       ' empty_string=""',
-                       Html::expandAttributes( [ 'empty_string' => '' ] ),
-                       'Attribute values are always quoted (wgWellFormedXml): Empty string'
-               );
-               $this->assertEquals(
-                       ' key="value"',
-                       Html::expandAttributes( [ 'key' => 'value' ] ),
-                       'Attribute values are always quoted (wgWellFormedXml): Simple string'
-               );
-               $this->assertEquals(
-                       ' one="1"',
-                       Html::expandAttributes( [ 'one' => 1 ] ),
-                       'Attribute values are always quoted (wgWellFormedXml): Number 1'
-               );
-               $this->assertEquals(
-                       ' zero="0"',
-                       Html::expandAttributes( [ 'zero' => 0 ] ),
-                       'Attribute values are always quoted (wgWellFormedXml): Number 0'
-               );
        }
 
        /**
@@ -346,48 +307,48 @@ class HtmlTest extends MediaWikiTestCase {
         */
        public function testNamespaceSelector() {
                $this->assertEquals(
-                       '<select id=namespace name=namespace>' . "\n" .
-                               '<option value=0>(Main)</option>' . "\n" .
-                               '<option value=1>Talk</option>' . "\n" .
-                               '<option value=2>User</option>' . "\n" .
-                               '<option value=3>User talk</option>' . "\n" .
-                               '<option value=4>MyWiki</option>' . "\n" .
-                               '<option value=5>MyWiki Talk</option>' . "\n" .
-                               '<option value=6>File</option>' . "\n" .
-                               '<option value=7>File talk</option>' . "\n" .
-                               '<option value=8>MediaWiki</option>' . "\n" .
-                               '<option value=9>MediaWiki talk</option>' . "\n" .
-                               '<option value=10>Template</option>' . "\n" .
-                               '<option value=11>Template talk</option>' . "\n" .
-                               '<option value=14>Category</option>' . "\n" .
-                               '<option value=15>Category talk</option>' . "\n" .
-                               '<option value=100>Custom</option>' . "\n" .
-                               '<option value=101>Custom talk</option>' . "\n" .
+                       '<select id="namespace" name="namespace">' . "\n" .
+                               '<option value="0">(Main)</option>' . "\n" .
+                               '<option value="1">Talk</option>' . "\n" .
+                               '<option value="2">User</option>' . "\n" .
+                               '<option value="3">User talk</option>' . "\n" .
+                               '<option value="4">MyWiki</option>' . "\n" .
+                               '<option value="5">MyWiki Talk</option>' . "\n" .
+                               '<option value="6">File</option>' . "\n" .
+                               '<option value="7">File talk</option>' . "\n" .
+                               '<option value="8">MediaWiki</option>' . "\n" .
+                               '<option value="9">MediaWiki talk</option>' . "\n" .
+                               '<option value="10">Template</option>' . "\n" .
+                               '<option value="11">Template talk</option>' . "\n" .
+                               '<option value="14">Category</option>' . "\n" .
+                               '<option value="15">Category talk</option>' . "\n" .
+                               '<option value="100">Custom</option>' . "\n" .
+                               '<option value="101">Custom talk</option>' . "\n" .
                                '</select>',
                        Html::namespaceSelector(),
                        'Basic namespace selector without custom options'
                );
 
                $this->assertEquals(
-                       '<label for=mw-test-namespace>Select a namespace:</label>&#160;' .
-                               '<select id=mw-test-namespace name=wpNamespace>' . "\n" .
-                               '<option value=all>all</option>' . "\n" .
-                               '<option value=0>(Main)</option>' . "\n" .
-                               '<option value=1>Talk</option>' . "\n" .
-                               '<option value=2 selected>User</option>' . "\n" .
-                               '<option value=3>User talk</option>' . "\n" .
-                               '<option value=4>MyWiki</option>' . "\n" .
-                               '<option value=5>MyWiki Talk</option>' . "\n" .
-                               '<option value=6>File</option>' . "\n" .
-                               '<option value=7>File talk</option>' . "\n" .
-                               '<option value=8>MediaWiki</option>' . "\n" .
-                               '<option value=9>MediaWiki talk</option>' . "\n" .
-                               '<option value=10>Template</option>' . "\n" .
-                               '<option value=11>Template talk</option>' . "\n" .
-                               '<option value=14>Category</option>' . "\n" .
-                               '<option value=15>Category talk</option>' . "\n" .
-                               '<option value=100>Custom</option>' . "\n" .
-                               '<option value=101>Custom talk</option>' . "\n" .
+                       '<label for="mw-test-namespace">Select a namespace:</label>&#160;' .
+                               '<select id="mw-test-namespace" name="wpNamespace">' . "\n" .
+                               '<option value="all">all</option>' . "\n" .
+                               '<option value="0">(Main)</option>' . "\n" .
+                               '<option value="1">Talk</option>' . "\n" .
+                               '<option value="2" selected="">User</option>' . "\n" .
+                               '<option value="3">User talk</option>' . "\n" .
+                               '<option value="4">MyWiki</option>' . "\n" .
+                               '<option value="5">MyWiki Talk</option>' . "\n" .
+                               '<option value="6">File</option>' . "\n" .
+                               '<option value="7">File talk</option>' . "\n" .
+                               '<option value="8">MediaWiki</option>' . "\n" .
+                               '<option value="9">MediaWiki talk</option>' . "\n" .
+                               '<option value="10">Template</option>' . "\n" .
+                               '<option value="11">Template talk</option>' . "\n" .
+                               '<option value="14">Category</option>' . "\n" .
+                               '<option value="15">Category talk</option>' . "\n" .
+                               '<option value="100">Custom</option>' . "\n" .
+                               '<option value="101">Custom talk</option>' . "\n" .
                                '</select>',
                        Html::namespaceSelector(
                                [ 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ],
@@ -397,24 +358,24 @@ class HtmlTest extends MediaWikiTestCase {
                );
 
                $this->assertEquals(
-                       '<label for=namespace>Select a namespace:</label>&#160;' .
-                               '<select id=namespace name=namespace>' . "\n" .
-                               '<option value=0>(Main)</option>' . "\n" .
-                               '<option value=1>Talk</option>' . "\n" .
-                               '<option value=2>User</option>' . "\n" .
-                               '<option value=3>User talk</option>' . "\n" .
-                               '<option value=4>MyWiki</option>' . "\n" .
-                               '<option value=5>MyWiki Talk</option>' . "\n" .
-                               '<option value=6>File</option>' . "\n" .
-                               '<option value=7>File talk</option>' . "\n" .
-                               '<option value=8>MediaWiki</option>' . "\n" .
-                               '<option value=9>MediaWiki talk</option>' . "\n" .
-                               '<option value=10>Template</option>' . "\n" .
-                               '<option value=11>Template talk</option>' . "\n" .
-                               '<option value=14>Category</option>' . "\n" .
-                               '<option value=15>Category talk</option>' . "\n" .
-                               '<option value=100>Custom</option>' . "\n" .
-                               '<option value=101>Custom talk</option>' . "\n" .
+                       '<label for="namespace">Select a namespace:</label>&#160;' .
+                               '<select id="namespace" name="namespace">' . "\n" .
+                               '<option value="0">(Main)</option>' . "\n" .
+                               '<option value="1">Talk</option>' . "\n" .
+                               '<option value="2">User</option>' . "\n" .
+                               '<option value="3">User talk</option>' . "\n" .
+                               '<option value="4">MyWiki</option>' . "\n" .
+                               '<option value="5">MyWiki Talk</option>' . "\n" .
+                               '<option value="6">File</option>' . "\n" .
+                               '<option value="7">File talk</option>' . "\n" .
+                               '<option value="8">MediaWiki</option>' . "\n" .
+                               '<option value="9">MediaWiki talk</option>' . "\n" .
+                               '<option value="10">Template</option>' . "\n" .
+                               '<option value="11">Template talk</option>' . "\n" .
+                               '<option value="14">Category</option>' . "\n" .
+                               '<option value="15">Category talk</option>' . "\n" .
+                               '<option value="100">Custom</option>' . "\n" .
+                               '<option value="101">Custom talk</option>' . "\n" .
                                '</select>',
                        Html::namespaceSelector(
                                [ 'label' => 'Select a namespace:' ]
@@ -425,18 +386,18 @@ class HtmlTest extends MediaWikiTestCase {
 
        public function testCanFilterOutNamespaces() {
                $this->assertEquals(
-                       '<select id=namespace name=namespace>' . "\n" .
-                               '<option value=2>User</option>' . "\n" .
-                               '<option value=4>MyWiki</option>' . "\n" .
-                               '<option value=5>MyWiki Talk</option>' . "\n" .
-                               '<option value=6>File</option>' . "\n" .
-                               '<option value=7>File talk</option>' . "\n" .
-                               '<option value=8>MediaWiki</option>' . "\n" .
-                               '<option value=9>MediaWiki talk</option>' . "\n" .
-                               '<option value=10>Template</option>' . "\n" .
-                               '<option value=11>Template talk</option>' . "\n" .
-                               '<option value=14>Category</option>' . "\n" .
-                               '<option value=15>Category talk</option>' . "\n" .
+                       '<select id="namespace" name="namespace">' . "\n" .
+                               '<option value="2">User</option>' . "\n" .
+                               '<option value="4">MyWiki</option>' . "\n" .
+                               '<option value="5">MyWiki Talk</option>' . "\n" .
+                               '<option value="6">File</option>' . "\n" .
+                               '<option value="7">File talk</option>' . "\n" .
+                               '<option value="8">MediaWiki</option>' . "\n" .
+                               '<option value="9">MediaWiki talk</option>' . "\n" .
+                               '<option value="10">Template</option>' . "\n" .
+                               '<option value="11">Template talk</option>' . "\n" .
+                               '<option value="14">Category</option>' . "\n" .
+                               '<option value="15">Category talk</option>' . "\n" .
                                '</select>',
                        Html::namespaceSelector(
                                [ 'exclude' => [ 0, 1, 3, 100, 101 ] ]
@@ -447,23 +408,23 @@ class HtmlTest extends MediaWikiTestCase {
 
        public function testCanDisableANamespaces() {
                $this->assertEquals(
-                       '<select id=namespace name=namespace>' . "\n" .
-                               '<option disabled value=0>(Main)</option>' . "\n" .
-                               '<option disabled value=1>Talk</option>' . "\n" .
-                               '<option disabled value=2>User</option>' . "\n" .
-                               '<option disabled value=3>User talk</option>' . "\n" .
-                               '<option disabled value=4>MyWiki</option>' . "\n" .
-                               '<option value=5>MyWiki Talk</option>' . "\n" .
-                               '<option value=6>File</option>' . "\n" .
-                               '<option value=7>File talk</option>' . "\n" .
-                               '<option value=8>MediaWiki</option>' . "\n" .
-                               '<option value=9>MediaWiki talk</option>' . "\n" .
-                               '<option value=10>Template</option>' . "\n" .
-                               '<option value=11>Template talk</option>' . "\n" .
-                               '<option value=14>Category</option>' . "\n" .
-                               '<option value=15>Category talk</option>' . "\n" .
-                               '<option value=100>Custom</option>' . "\n" .
-                               '<option value=101>Custom talk</option>' . "\n" .
+                       '<select id="namespace" name="namespace">' . "\n" .
+                               '<option disabled="" value="0">(Main)</option>' . "\n" .
+                               '<option disabled="" value="1">Talk</option>' . "\n" .
+                               '<option disabled="" value="2">User</option>' . "\n" .
+                               '<option disabled="" value="3">User talk</option>' . "\n" .
+                               '<option disabled="" value="4">MyWiki</option>' . "\n" .
+                               '<option value="5">MyWiki Talk</option>' . "\n" .
+                               '<option value="6">File</option>' . "\n" .
+                               '<option value="7">File talk</option>' . "\n" .
+                               '<option value="8">MediaWiki</option>' . "\n" .
+                               '<option value="9">MediaWiki talk</option>' . "\n" .
+                               '<option value="10">Template</option>' . "\n" .
+                               '<option value="11">Template talk</option>' . "\n" .
+                               '<option value="14">Category</option>' . "\n" .
+                               '<option value="15">Category talk</option>' . "\n" .
+                               '<option value="100">Custom</option>' . "\n" .
+                               '<option value="101">Custom talk</option>' . "\n" .
                                '</select>',
                        Html::namespaceSelector( [
                                'disable' => [ 0, 1, 2, 3, 4 ]
@@ -478,7 +439,7 @@ class HtmlTest extends MediaWikiTestCase {
         */
        public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
                $this->assertEquals(
-                       '<input type=' . $HTML5InputType . '>',
+                       '<input type="' . $HTML5InputType . '"/>',
                        Html::element( 'input', [ 'type' => $HTML5InputType ] ),
                        'In HTML5, Html::element() should accept type="' . $HTML5InputType . '"'
                );
@@ -528,14 +489,14 @@ class HtmlTest extends MediaWikiTestCase {
                $cases = [];
 
                # ## Generic cases, match $attribDefault static array
-               $cases[] = [ '<area>',
+               $cases[] = [ '<area/>',
                        'area', [ 'shape' => 'rect' ]
                ];
 
-               $cases[] = [ '<button type=submit></button>',
+               $cases[] = [ '<button type="submit"></button>',
                        'button', [ 'formaction' => 'GET' ]
                ];
-               $cases[] = [ '<button type=submit></button>',
+               $cases[] = [ '<button type="submit"></button>',
                        'button', [ 'formenctype' => 'application/x-www-form-urlencoded' ]
                ];
 
@@ -553,7 +514,7 @@ class HtmlTest extends MediaWikiTestCase {
                        'canvas', [ 'width' => 300 ]
                ];
 
-               $cases[] = [ '<command>',
+               $cases[] = [ '<command/>',
                        'command', [ 'type' => 'command' ]
                ];
 
@@ -567,18 +528,18 @@ class HtmlTest extends MediaWikiTestCase {
                        'form', [ 'enctype' => 'application/x-www-form-urlencoded' ]
                ];
 
-               $cases[] = [ '<input>',
+               $cases[] = [ '<input/>',
                        'input', [ 'formaction' => 'GET' ]
                ];
-               $cases[] = [ '<input>',
+               $cases[] = [ '<input/>',
                        'input', [ 'type' => 'text' ]
                ];
 
-               $cases[] = [ '<keygen>',
+               $cases[] = [ '<keygen/>',
                        'keygen', [ 'keytype' => 'rsa' ]
                ];
 
-               $cases[] = [ '<link>',
+               $cases[] = [ '<link/>',
                        'link', [ 'media' => 'all' ]
                ];
 
@@ -604,44 +565,44 @@ class HtmlTest extends MediaWikiTestCase {
                # ## SPECIFIC CASES
 
                # <link type="text/css">
-               $cases[] = [ '<link>',
+               $cases[] = [ '<link/>',
                        'link', [ 'type' => 'text/css' ]
                ];
 
                # <input> specific handling
-               $cases[] = [ '<input type=checkbox>',
+               $cases[] = [ '<input type="checkbox"/>',
                        'input', [ 'type' => 'checkbox', 'value' => 'on' ],
                        'Default value "on" is stripped of checkboxes',
                ];
-               $cases[] = [ '<input type=radio>',
+               $cases[] = [ '<input type="radio"/>',
                        'input', [ 'type' => 'radio', 'value' => 'on' ],
                        'Default value "on" is stripped of radio buttons',
                ];
-               $cases[] = [ '<input type=submit value=Submit>',
+               $cases[] = [ '<input type="submit" value="Submit"/>',
                        'input', [ 'type' => 'submit', 'value' => 'Submit' ],
                        'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
                ];
-               $cases[] = [ '<input type=color>',
+               $cases[] = [ '<input type="color"/>',
                        'input', [ 'type' => 'color', 'value' => '' ],
                ];
-               $cases[] = [ '<input type=range>',
+               $cases[] = [ '<input type="range"/>',
                        'input', [ 'type' => 'range', 'value' => '' ],
                ];
 
                # <button> specific handling
                # see remarks on http://msdn.microsoft.com/en-us/library/ie/ms535211%28v=vs.85%29.aspx
-               $cases[] = [ '<button type=submit></button>',
+               $cases[] = [ '<button type="submit"></button>',
                        'button', [ 'type' => 'submit' ],
                        'According to standard the default type is "submit". '
                                . 'Depending on compatibility mode IE might use "button", instead.',
                ];
 
                # <select> specific handling
-               $cases[] = [ '<select multiple></select>',
+               $cases[] = [ '<select multiple=""></select>',
                        'select', [ 'size' => '4', 'multiple' => true ],
                ];
                # .. with numeric value
-               $cases[] = [ '<select multiple></select>',
+               $cases[] = [ '<select multiple=""></select>',
                        'select', [ 'size' => 4, 'multiple' => true ],
                ];
                $cases[] = [ '<select></select>',
@@ -693,7 +654,7 @@ class HtmlTest extends MediaWikiTestCase {
                        'Blacklist form validation attributes.'
                );
                $this->assertEquals(
-                       ' step=any',
+                       ' step="any"',
                        Html::expandAttributes(
                                [
                                        'min' => 1,
@@ -709,12 +670,12 @@ class HtmlTest extends MediaWikiTestCase {
 
        public function testWrapperInput() {
                $this->assertEquals(
-                       '<input type=radio value=testval name=testname>',
+                       '<input type="radio" value="testval" name="testname"/>',
                        Html::input( 'testname', 'testval', 'radio' ),
                        'Input wrapper with type and value.'
                );
                $this->assertEquals(
-                       '<input name=testname>',
+                       '<input name="testname"/>',
                        Html::input( 'testname' ),
                        'Input wrapper with all default values.'
                );
@@ -722,17 +683,17 @@ class HtmlTest extends MediaWikiTestCase {
 
        public function testWrapperCheck() {
                $this->assertEquals(
-                       '<input type=checkbox value=1 name=testname>',
+                       '<input type="checkbox" value="1" name="testname"/>',
                        Html::check( 'testname' ),
                        'Checkbox wrapper unchecked.'
                );
                $this->assertEquals(
-                       '<input checked type=checkbox value=1 name=testname>',
+                       '<input checked="" type="checkbox" value="1" name="testname"/>',
                        Html::check( 'testname', true ),
                        'Checkbox wrapper checked.'
                );
                $this->assertEquals(
-                       '<input type=checkbox value=testval name=testname>',
+                       '<input type="checkbox" value="testval" name="testname"/>',
                        Html::check( 'testname', false, [ 'value' => 'testval' ] ),
                        'Checkbox wrapper with a value override.'
                );
@@ -740,17 +701,17 @@ class HtmlTest extends MediaWikiTestCase {
 
        public function testWrapperRadio() {
                $this->assertEquals(
-                       '<input type=radio value=1 name=testname>',
+                       '<input type="radio" value="1" name="testname"/>',
                        Html::radio( 'testname' ),
                        'Radio wrapper unchecked.'
                );
                $this->assertEquals(
-                       '<input checked type=radio value=1 name=testname>',
+                       '<input checked="" type="radio" value="1" name="testname"/>',
                        Html::radio( 'testname', true ),
                        'Radio wrapper checked.'
                );
                $this->assertEquals(
-                       '<input type=radio value=testval name=testname>',
+                       '<input type="radio" value="testval" name="testname"/>',
                        Html::radio( 'testname', false, [ 'value' => 'testval' ] ),
                        'Radio wrapper with a value override.'
                );
@@ -758,7 +719,7 @@ class HtmlTest extends MediaWikiTestCase {
 
        public function testWrapperLabel() {
                $this->assertEquals(
-                       '<label for=testid>testlabel</label>',
+                       '<label for="testid">testlabel</label>',
                        Html::label( 'testlabel', 'testid' ),
                        'Label wrapper'
                );
index e50b4f1..1bf8729 100644 (file)
@@ -13,11 +13,12 @@ class LinkerTest extends MediaWikiLangTestCase {
        public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) {
                $this->setMwGlobals( [
                        'wgArticlePath' => '/wiki/$1',
-                       'wgWellFormedXml' => true,
                ] );
 
-               $this->assertEquals( $expected,
-                       Linker::userLink( $userId, $userName, $altUserName, $msg )
+               $this->assertEquals(
+                       $expected,
+                       Linker::userLink( $userId, $userName, $altUserName ),
+                       $msg
                );
        }
 
@@ -112,7 +113,6 @@ class LinkerTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( [
                        'wgScript' => '/wiki/index.php',
                        'wgArticlePath' => '/wiki/$1',
-                       'wgWellFormedXml' => true,
                        'wgCapitalLinks' => true,
                        'wgConf' => $conf,
                ] );
@@ -277,7 +277,6 @@ class LinkerTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( [
                        'wgScript' => '/wiki/index.php',
                        'wgArticlePath' => '/wiki/$1',
-                       'wgWellFormedXml' => true,
                        'wgCapitalLinks' => true,
                        'wgConf' => $conf,
                ] );
@@ -309,4 +308,116 @@ class LinkerTest extends MediaWikiLangTestCase {
                ];
                // @codingStandardsIgnoreEnd
        }
+
+       public static function provideLinkBeginHook() {
+               // @codingStandardsIgnoreStart Generic.Files.LineLength
+               return [
+                       // Modify $html
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $html = 'foobar';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
+                       ],
+                       // Modify $attribs
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $attribs['bar'] = 'baz';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
+                       ],
+                       // Modify $query
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $query['bar'] = 'baz';
+                               },
+                               '<a href="/w/index.php?title=Special:BlankPage&amp;bar=baz" title="Special:BlankPage">Special:BlankPage</a>'
+                       ],
+                       // Force HTTP $options
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $options = [ 'http' ];
+                               },
+                               '<a href="http://example.org/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>'
+                       ],
+                       // Force 'forcearticlepath' in $options
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $options = [ 'forcearticlepath' ];
+                                       $query['foo'] = 'bar';
+                               },
+                               '<a href="/wiki/Special:BlankPage?foo=bar" title="Special:BlankPage">Special:BlankPage</a>'
+                       ],
+                       // Abort early
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $ret = 'foobar';
+                                       return false;
+                               },
+                               'foobar'
+                       ],
+               ];
+               // @codingStandardsIgnoreEnd
+       }
+
+       /**
+        * @dataProvider provideLinkBeginHook
+        */
+       public function testLinkBeginHook( $callback, $expected ) {
+               $this->setMwGlobals( [
+                       'wgArticlePath' => '/wiki/$1',
+                       'wgServer' => '//example.org',
+                       'wgCanonicalServer' => 'http://example.org',
+                       'wgScriptPath' => '/w',
+                       'wgScript' => '/w/index.php',
+               ] );
+
+               $this->setMwGlobals( 'wgHooks', [ 'LinkBegin' => [ $callback ] ] );
+               $title = SpecialPage::getTitleFor( 'Blankpage' );
+               $out = Linker::link( $title );
+               $this->assertEquals( $expected, $out );
+       }
+
+       public static function provideLinkEndHook() {
+               return [
+                       // Override $html
+                       [
+                               function( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
+                                       $html = 'foobar';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
+                       ],
+                       // Modify $attribs
+                       [
+                               function( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
+                                       $attribs['bar'] = 'baz';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
+                       ],
+                       // Fully override return value and abort hook
+                       [
+                               function( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
+                                       $ret = 'blahblahblah';
+                                       return false;
+                               },
+                               'blahblahblah'
+                       ],
+
+               ];
+       }
+
+       /**
+        * @dataProvider provideLinkEndHook
+        */
+       public function testLinkEndHook( $callback, $expected ) {
+               $this->setMwGlobals( [
+                       'wgArticlePath' => '/wiki/$1',
+               ] );
+
+               $this->setMwGlobals( 'wgHooks', [ 'LinkEnd' => [ $callback ] ] );
+
+               $title = SpecialPage::getTitleFor( 'Blankpage' );
+               $out = Linker::link( $title );
+               $this->assertEquals( $expected, $out );
+       }
 }
index f5c215b..0e646ea 100644 (file)
 <?php
 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+use MediaWiki\Interwiki\InterwikiLookup;
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Services\DestructibleService;
+use MediaWiki\Services\SalvageableService;
+use MediaWiki\Services\ServiceDisabledException;
 
 /**
  * @covers MediaWiki\MediaWikiServices
  *
  * @group MediaWiki
  */
-class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
+class MediaWikiServicesTest extends MediaWikiTestCase {
+
+       /**
+        * @return Config
+        */
+       private function newTestConfig() {
+               $globalConfig = new GlobalVarConfig();
+
+               $testConfig = new HashConfig();
+               $testConfig->set( 'ServiceWiringFiles', $globalConfig->get( 'ServiceWiringFiles' ) );
+               $testConfig->set( 'ConfigRegistry', $globalConfig->get( 'ConfigRegistry' ) );
+
+               return $testConfig;
+       }
+
+       /**
+        * @return MediaWikiServices
+        */
+       private function newMediaWikiServices( Config $config = null ) {
+               if ( $config === null ) {
+                       $config = $this->newTestConfig();
+               }
+
+               $instance = new MediaWikiServices( $config );
+
+               // Load the default wiring from the specified files.
+               $wiringFiles = $config->get( 'ServiceWiringFiles' );
+               $instance->loadWiringFiles( $wiringFiles );
+
+               return $instance;
+       }
 
        public function testGetInstance() {
                $services = MediaWikiServices::getInstance();
                $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $services );
        }
 
-       public function provideGetters() {
-               // NOTE: This should list all service getters defined in MediaWikiServices.
-               // NOTE: For every test case defined here there should be a corresponding
-               // test case defined in provideGetService().
-               return [
-                       'BootstrapConfig' => [ 'getBootstrapConfig', Config::class ],
-                       'ConfigFactory' => [ 'getConfigFactory', ConfigFactory::class ],
-                       'MainConfig' => [ 'getMainConfig', Config::class ],
-                       'SiteStore' => [ 'getSiteStore', SiteStore::class ],
-                       'SiteLookup' => [ 'getSiteLookup', SiteLookup::class ],
-                       'StatsdDataFactory' => [ 'getStatsdDataFactory', StatsdDataFactory::class ],
-                       'EventRelayerGroup' => [ 'getEventRelayerGroup', EventRelayerGroup::class ],
-                       'SearchEngine' => [ 'newSearchEngine', SearchEngine::class ],
-                       'SearchEngineFactory' => [ 'getSearchEngineFactory', SearchEngineFactory::class ],
-                       'SearchEngineConfig' => [ 'getSearchEngineConfig', SearchEngineConfig::class ],
-                       'SkinFactory' => [ 'getSkinFactory', SkinFactory::class ],
+       public function testForceGlobalInstance() {
+               $newServices = $this->newMediaWikiServices();
+               $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+               $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $oldServices );
+               $this->assertNotSame( $oldServices, $newServices );
+
+               $theServices = MediaWikiServices::getInstance();
+               $this->assertSame( $theServices, $newServices );
+
+               MediaWikiServices::forceGlobalInstance( $oldServices );
+
+               $theServices = MediaWikiServices::getInstance();
+               $this->assertSame( $theServices, $oldServices );
+       }
+
+       public function testResetGlobalInstance() {
+               $newServices = $this->newMediaWikiServices();
+               $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+               $service1 = $this->getMock( SalvageableService::class );
+               $service1->expects( $this->never() )
+                       ->method( 'salvage' );
+
+               $newServices->defineService(
+                       'Test',
+                       function() use ( $service1 ) {
+                               return $service1;
+                       }
+               );
+
+               // force instantiation
+               $newServices->getService( 'Test' );
+
+               MediaWikiServices::resetGlobalInstance( $this->newTestConfig() );
+               $theServices = MediaWikiServices::getInstance();
+
+               $this->assertSame(
+                       $service1,
+                       $theServices->getService( 'Test' ),
+                       'service definition should survive reset'
+               );
+
+               $this->assertNotSame( $theServices, $newServices );
+               $this->assertNotSame( $theServices, $oldServices );
+
+               MediaWikiServices::forceGlobalInstance( $oldServices );
+       }
+
+       public function testResetGlobalInstance_quick() {
+               $newServices = $this->newMediaWikiServices();
+               $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+               $service1 = $this->getMock( SalvageableService::class );
+               $service1->expects( $this->never() )
+                       ->method( 'salvage' );
+
+               $service2 = $this->getMock( SalvageableService::class );
+               $service2->expects( $this->once() )
+                       ->method( 'salvage' )
+                       ->with( $service1 );
+
+               // sequence of values the instantiator will return
+               $instantiatorReturnValues = [
+                       $service1,
+                       $service2,
+               ];
+
+               $newServices->defineService(
+                       'Test',
+                       function() use ( &$instantiatorReturnValues ) {
+                               return array_shift( $instantiatorReturnValues );
+                       }
+               );
+
+               // force instantiation
+               $newServices->getService( 'Test' );
+
+               MediaWikiServices::resetGlobalInstance( $this->newTestConfig(), 'quick' );
+               $theServices = MediaWikiServices::getInstance();
+
+               $this->assertSame( $service2, $theServices->getService( 'Test' ) );
+
+               $this->assertNotSame( $theServices, $newServices );
+               $this->assertNotSame( $theServices, $oldServices );
+
+               MediaWikiServices::forceGlobalInstance( $oldServices );
+       }
+
+       public function testDisableStorageBackend() {
+               $newServices = $this->newMediaWikiServices();
+               $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+               $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $lbFactory->expects( $this->once() )
+                       ->method( 'destroy' );
+
+               $newServices->redefineService(
+                       'DBLoadBalancerFactory',
+                       function() use ( $lbFactory ) {
+                               return $lbFactory;
+                       }
+               );
+
+               // force the service to become active, so we can check that it does get destroyed
+               $newServices->getService( 'DBLoadBalancerFactory' );
+
+               MediaWikiServices::disableStorageBackend(); // should destroy DBLoadBalancerFactory
+
+               try {
+                       MediaWikiServices::getInstance()->getService( 'DBLoadBalancerFactory' );
+                       $this->fail( 'DBLoadBalancerFactory shoudl have been disabled' );
+               }
+               catch ( ServiceDisabledException $ex ) {
+                       // ok, as expected
+               }
+               catch ( Throwable $ex ) {
+                       $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) );
+               }
+
+               MediaWikiServices::forceGlobalInstance( $oldServices );
+               $newServices->destroy();
+       }
+
+       public function testResetChildProcessServices() {
+               $newServices = $this->newMediaWikiServices();
+               $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+               $service1 = $this->getMock( DestructibleService::class );
+               $service1->expects( $this->once() )
+                       ->method( 'destroy' );
+
+               $service2 = $this->getMock( DestructibleService::class );
+               $service2->expects( $this->never() )
+                       ->method( 'destroy' );
+
+               // sequence of values the instantiator will return
+               $instantiatorReturnValues = [
+                       $service1,
+                       $service2,
                ];
+
+               $newServices->defineService(
+                       'Test',
+                       function() use ( &$instantiatorReturnValues ) {
+                               return array_shift( $instantiatorReturnValues );
+                       }
+               );
+
+               // force the service to become active, so we can check that it does get destroyed
+               $oldTestService = $newServices->getService( 'Test' );
+
+               MediaWikiServices::resetChildProcessServices();
+               $finalServices = MediaWikiServices::getInstance();
+
+               $newTestService = $finalServices->getService( 'Test' );
+               $this->assertNotSame( $oldTestService, $newTestService );
+
+               MediaWikiServices::forceGlobalInstance( $oldServices );
+       }
+
+       public function testResetServiceForTesting() {
+               $services = $this->newMediaWikiServices();
+               $serviceCounter = 0;
+
+               $services->defineService(
+                       'Test',
+                       function() use ( &$serviceCounter ) {
+                               $serviceCounter++;
+                               $service = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+                               $service->expects( $this->once() )->method( 'destroy' );
+                               return $service;
+                       }
+               );
+
+               // This should do nothing. In particular, it should not create a service instance.
+               $services->resetServiceForTesting( 'Test' );
+               $this->assertEquals( 0, $serviceCounter, 'No service instance should be created yet.' );
+
+               $oldInstance = $services->getService( 'Test' );
+               $this->assertEquals( 1, $serviceCounter, 'A service instance should exit now.' );
+
+               // The old instance should be detached, and destroy() called.
+               $services->resetServiceForTesting( 'Test' );
+               $newInstance = $services->getService( 'Test' );
+
+               $this->assertNotSame( $oldInstance, $newInstance );
+
+               // Satisfy the expectation that destroy() is called also for the second service instance.
+               $newInstance->destroy();
+       }
+
+       public function testResetServiceForTesting_noDestroy() {
+               $services = $this->newMediaWikiServices();
+
+               $services->defineService(
+                       'Test',
+                       function() {
+                               $service = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+                               $service->expects( $this->never() )->method( 'destroy' );
+                               return $service;
+                       }
+               );
+
+               $oldInstance = $services->getService( 'Test' );
+
+               // The old instance should be detached, but destroy() not called.
+               $services->resetServiceForTesting( 'Test', false );
+               $newInstance = $services->getService( 'Test' );
+
+               $this->assertNotSame( $oldInstance, $newInstance );
+       }
+
+       public function provideGetters() {
+               $getServiceCases = $this->provideGetService();
+               $getterCases = [];
+
+               // All getters should be named just like the service, with "get" added.
+               foreach ( $getServiceCases as $name => $case ) {
+                       if ( $name[0] === '_' ) {
+                               // Internal service, no getter
+                               continue;
+                       }
+                       list( $service, $class ) = $case;
+                       $getterCases[$name] = [
+                               'get' . $service,
+                               $class,
+                       ];
+               }
+
+               return $getterCases;
        }
 
        /**
@@ -54,10 +305,19 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
                        'SiteStore' => [ 'SiteStore', SiteStore::class ],
                        'SiteLookup' => [ 'SiteLookup', SiteLookup::class ],
                        'StatsdDataFactory' => [ 'StatsdDataFactory', StatsdDataFactory::class ],
+                       'InterwikiLookup' => [ 'InterwikiLookup', InterwikiLookup::class ],
                        'EventRelayerGroup' => [ 'EventRelayerGroup', EventRelayerGroup::class ],
                        'SearchEngineFactory' => [ 'SearchEngineFactory', SearchEngineFactory::class ],
                        'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig::class ],
                        'SkinFactory' => [ 'SkinFactory', SkinFactory::class ],
+                       'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', 'LBFactory' ],
+                       'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
+                       'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
+                       'GenderCache' => [ 'GenderCache', GenderCache::class ],
+                       'LinkCache' => [ 'LinkCache', LinkCache::class ],
+                       '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
+                       'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
+                       'TitleParser' => [ 'TitleParser', TitleParser::class ],
                ];
        }
 
index 5ea0cdf..22f6fa6 100644 (file)
@@ -21,10 +21,10 @@ class MergeHistoryTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideIsValidMerge
         * @covers MergeHistory::isValidMerge
-        * @param $source string Source page
-        * @param $dest string Destination page
-        * @param $timestamp string|bool Timestamp up to which revisions are merged (or false for all)
-        * @param $error string|bool Expected error for test (or true for no error)
+        * @param string $source Source page
+        * @param string $dest Destination page
+        * @param string|bool $timestamp Timestamp up to which revisions are merged (or false for all)
+        * @param string|bool $error Expected error for test (or true for no error)
         */
        public function testIsValidMerge( $source, $dest, $timestamp, $error ) {
                $this->setMwGlobals( 'wgContentHandlerUseDB', false );
index cf34b18..224b0cb 100644 (file)
@@ -581,4 +581,29 @@ class MessageTest extends MediaWikiLangTestCase {
                $msg = unserialize( serialize( $msg ) );
                $this->assertEquals( 'Hauptseite', $msg->plain() );
        }
+
+       /**
+        * @covers Message::newFromSpecifier
+        * @dataProvider provideNewFromSpecifier
+        */
+       public function testNewFromSpecifier( $value, $expectedText ) {
+               $message = Message::newFromSpecifier( $value );
+               $this->assertInstanceOf( Message::class, $message );
+               $this->assertSame( $expectedText, $message->text() );
+       }
+
+       public function provideNewFromSpecifier() {
+               $messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
+               $messageSpecifier->expects( $this->any() )->method( 'getKey' )->willReturn( 'mainpage' );
+               $messageSpecifier->expects( $this->any() )->method( 'getParams' )->willReturn( [] );
+
+               return [
+                       'string' => [ 'mainpage', 'Main Page' ],
+                       'array' => [ [ 'youhavenewmessages', 'foo', 'bar' ], 'You have foo (bar).' ],
+                       'Message' => [ new Message( 'youhavenewmessages', [ 'foo', 'bar' ] ), 'You have foo (bar).' ],
+                       'RawMessage' => [ new RawMessage( 'foo ($1)', [ 'bar' ] ), 'foo (bar)' ],
+                       'MessageSpecifier' => [ $messageSpecifier, 'Main Page' ],
+               ];
+       }
 }
+
index 8d4a347..9934749 100644 (file)
@@ -149,14 +149,14 @@ class OutputPageTest extends MediaWikiTestCase {
                        [
                                // Don't condition wrap raw modules (like the startup module)
                                [ 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ],
-                               '<script async src="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.raw&amp;only=scripts&amp;skin=fallback"></script>'
+                               '<script async="" src="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.raw&amp;only=scripts&amp;skin=fallback"></script>'
                        ],
                        // Load module styles only
                        // This also tests the order the modules are put into the url
                        [
                                [ [ 'test.baz', 'test.foo', 'test.bar' ], ResourceLoaderModule::TYPE_STYLES ],
 
-                               '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.bar%2Cbaz%2Cfoo&amp;only=styles&amp;skin=fallback">'
+                               '<link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.bar%2Cbaz%2Cfoo&amp;only=styles&amp;skin=fallback"/>'
                        ],
                        // Load private module (only=scripts)
                        [
@@ -181,7 +181,7 @@ class OutputPageTest extends MediaWikiTestCase {
                        // noscript group
                        [
                                [ 'test.noscript', ResourceLoaderModule::TYPE_STYLES ],
-                               '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.noscript&amp;only=styles&amp;skin=fallback"></noscript>'
+                               '<noscript><link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&amp;lang=en&amp;modules=test.noscript&amp;only=styles&amp;skin=fallback"/></noscript>'
                        ],
                        // Load two modules in separate groups
                        [
@@ -210,8 +210,6 @@ class OutputPageTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgResourceLoaderDebug' => false,
                        'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php',
-                       // Affects whether CDATA is inserted
-                       'wgWellFormedXml' => false,
                ] );
                $class = new ReflectionClass( 'OutputPage' );
                $method = $class->getMethod( 'makeResourceLoaderLink' );
index 6d5154f..0ec200c 100644 (file)
@@ -4,6 +4,7 @@
  * @group Database
  */
 class PrefixSearchTest extends MediaWikiLangTestCase {
+       private $originalHandlers;
 
        public function addDBDataOnce() {
                if ( !$this->isWikitextNS( NS_MAIN ) ) {
@@ -40,7 +41,23 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                }
 
                // Avoid special pages from extensions interferring with the tests
-               $this->setMwGlobals( 'wgSpecialPages', [] );
+               $this->setMwGlobals( [
+                       'wgSpecialPages' => [],
+                       'wgHooks' => [],
+               ] );
+
+               $this->originalHandlers = TestingAccessWrapper::newFromClass( 'Hooks' )->handlers;
+               TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = [];
+
+               SpecialPageFactory::resetList();
+       }
+
+       public function tearDown() {
+               parent::tearDown();
+
+               TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = $this->originalHandlers;
+
+               SpecialPageFactory::resetList();
        }
 
        protected function searchProvision( array $results = null ) {
index 942c45e..f22e123 100644 (file)
@@ -69,11 +69,53 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
 
                $name = 'TestService92834576';
 
-               $this->setExpectedException( 'InvalidArgumentException' );
+               $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
 
                $services->getService( $name );
        }
 
+       public function testPeekService() {
+               $services = $this->newServiceContainer();
+
+               $services->defineService(
+                       'Foo',
+                       function() {
+                               return new stdClass();
+                       }
+               );
+
+               $services->defineService(
+                       'Bar',
+                       function() {
+                               return new stdClass();
+                       }
+               );
+
+               // trigger instantiation of Foo
+               $services->getService( 'Foo' );
+
+               $this->assertInternalType(
+                       'object',
+                       $services->peekService( 'Foo' ),
+                       'Peek should return the service object if it had been accessed before.'
+               );
+
+               $this->assertNull(
+                       $services->peekService( 'Bar' ),
+                       'Peek should return null if the service was never accessed.'
+               );
+       }
+
+       public function testPeekService_fail_unknown() {
+               $services = $this->newServiceContainer();
+
+               $name = 'TestService92834576';
+
+               $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+
+               $services->peekService( $name );
+       }
+
        public function testDefineService() {
                $services = $this->newServiceContainer();
 
@@ -99,7 +141,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                        return $theService;
                } );
 
-               $this->setExpectedException( 'RuntimeException' );
+               $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
 
                $services->defineService( $name, function() use ( $theService ) {
                        return $theService;
@@ -124,6 +166,55 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
        }
 
+       public function testImportWiring() {
+               $services = $this->newServiceContainer();
+
+               $wiring = [
+                       'Foo' => function() {
+                               return 'Foo!';
+                       },
+                       'Bar' => function() {
+                               return 'Bar!';
+                       },
+                       'Car' => function() {
+                               return 'FUBAR!';
+                       },
+               ];
+
+               $services->applyWiring( $wiring );
+
+               $newServices = $this->newServiceContainer();
+
+               // define a service before importing, so we can later check that
+               // existing service instances survive importWiring()
+               $newServices->defineService( 'Car', function() {
+                       return 'Car!';
+               } );
+
+               // force instantiation
+               $newServices->getService( 'Car' );
+
+               // Define another service, so we can later check that extra wiring
+               // is not lost.
+               $newServices->defineService( 'Xar', function() {
+                       return 'Xar!';
+               } );
+
+               // import wiring, but skip `Bar`
+               $newServices->importWiring( $services, [ 'Bar' ] );
+
+               $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
+               $this->assertSame( 'Foo!', $newServices->getService( 'Foo' ) );
+
+               // import all wiring, but preserve existing service instance
+               $newServices->importWiring( $services );
+
+               $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
+               $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
+               $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
+               $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
+       }
+
        public function testLoadWiringFiles() {
                $services = $this->newServiceContainer();
 
@@ -147,7 +238,7 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                ];
 
                // loading the same file twice should fail, because
-               $this->setExpectedException( 'RuntimeException' );
+               $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
 
                $services->loadWiringFiles( $wiringFiles );
        }
@@ -178,13 +269,34 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( $theService1, $services->getService( $name ) );
        }
 
+       public function testRedefineService_disabled() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $theService1 = new stdClass();
+               $name = 'TestService92834576';
+
+               $services->defineService( $name, function() {
+                       return 'Foo';
+               } );
+
+               // disable the service. we should be able to redefine it anyway.
+               $services->disableService( $name );
+
+               $services->redefineService( $name, function() use ( $theService1 ) {
+                       return $theService1;
+               } );
+
+               // force instantiation, check result
+               $this->assertSame( $theService1, $services->getService( $name ) );
+       }
+
        public function testRedefineService_fail_undefined() {
                $services = $this->newServiceContainer();
 
                $theService = new stdClass();
                $name = 'TestService92834576';
 
-               $this->setExpectedException( 'RuntimeException' );
+               $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
 
                $services->redefineService( $name, function() use ( $theService ) {
                        return $theService;
@@ -204,11 +316,94 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                // create the service, so it can no longer be redefined
                $services->getService( $name );
 
-               $this->setExpectedException( 'RuntimeException' );
+               $this->setExpectedException( 'MediaWiki\Services\CannotReplaceActiveServiceException' );
 
                $services->redefineService( $name, function() use ( $theService ) {
                        return $theService;
                } );
        }
 
+       public function testDisableService() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $destructible = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+               $destructible->expects( $this->once() )
+                       ->method( 'destroy' );
+
+               $services->defineService( 'Foo', function() use ( $destructible ) {
+                       return $destructible;
+               } );
+               $services->defineService( 'Bar', function() {
+                       return new stdClass();
+               } );
+               $services->defineService( 'Qux', function() {
+                       return new stdClass();
+               } );
+
+               // instantiate Foo and Bar services
+               $services->getService( 'Foo' );
+               $services->getService( 'Bar' );
+
+               // disable service, should call destroy() once.
+               $services->disableService( 'Foo' );
+
+               // disabled service should still be listed
+               $this->assertContains( 'Foo', $services->getServiceNames() );
+
+               // getting other services should still work
+               $services->getService( 'Bar' );
+
+               // disable non-destructible service, and not-yet-instantiated service
+               $services->disableService( 'Bar' );
+               $services->disableService( 'Qux' );
+
+               $this->assertNull( $services->peekService( 'Bar' ) );
+               $this->assertNull( $services->peekService( 'Qux' ) );
+
+               // disabled service should still be listed
+               $this->assertContains( 'Bar', $services->getServiceNames() );
+               $this->assertContains( 'Qux', $services->getServiceNames() );
+
+               $this->setExpectedException( 'MediaWiki\Services\ServiceDisabledException' );
+               $services->getService( 'Qux' );
+       }
+
+       public function testDisableService_fail_undefined() {
+               $services = $this->newServiceContainer();
+
+               $theService = new stdClass();
+               $name = 'TestService92834576';
+
+               $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+
+               $services->redefineService( $name, function() use ( $theService ) {
+                       return $theService;
+               } );
+       }
+
+       public function testDestroy() {
+               $services = $this->newServiceContainer();
+
+               $destructible = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+               $destructible->expects( $this->once() )
+                       ->method( 'destroy' );
+
+               $services->defineService( 'Foo', function() use ( $destructible ) {
+                       return $destructible;
+               } );
+
+               $services->defineService( 'Bar', function() {
+                       return new stdClass();
+               } );
+
+               // create the service
+               $services->getService( 'Foo' );
+
+               // destroy the container
+               $services->destroy();
+
+               $this->setExpectedException( 'MediaWiki\Services\ContainerDisabledException' );
+               $services->getService( 'Bar' );
+       }
+
 }
index b506cb8..247b6e4 100644 (file)
@@ -129,20 +129,32 @@ class TestUser {
                        throw new MWException( "Passed User has not been added to the database yet!" );
                }
 
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordFactory->setDefaultType( 'A' );
-               $pwhash = $passwordFactory->newFromPlaintext( $password );
-               wfGetDB( DB_MASTER )->update(
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
                        'user',
-                       [ 'user_password' => $pwhash->toString() ],
+                       [ 'user_password' ],
                        [ 'user_id' => $user->getId() ],
                        __METHOD__
                );
+               if ( !$row ) {
+                       throw new MWException( "Passed User has an ID but is not in the database?" );
+               }
+
+               $passwordFactory = new PasswordFactory();
+               $passwordFactory->init( RequestContext::getMain()->getConfig() );
+               if ( !$passwordFactory->newFromCiphertext( $row->user_password )->equals( $password ) ) {
+                       $passwordHash = $passwordFactory->newFromPlaintext( $password );
+                       $dbw->update(
+                               'user',
+                               [ 'user_password' => $passwordHash->toString() ],
+                               [ 'user_id' => $user->getId() ],
+                               __METHOD__
+                       );
+               }
        }
 
        /**
+        * @since 1.25
         * @return User
         */
        public function getUser() {
@@ -150,6 +162,7 @@ class TestUser {
        }
 
        /**
+        * @since 1.25
         * @return string
         */
        public function getPassword() {
index 7d025d2..7850f24 100644 (file)
@@ -144,6 +144,13 @@ class TitleTest extends MediaWikiTestCase {
                                ]
                        ]
                ] );
+
+               // Reset TitleParser since we modified $wgLocalInterwikis
+               $this->setService( 'TitleParser', new MediaWikiTitleCodec(
+                               Language::factory( 'en' ),
+                               new GenderCache(),
+                               [ 'localtestiw' ]
+               ) );
        }
 
        /**
@@ -702,4 +709,42 @@ class TitleTest extends MediaWikiTestCase {
                $this->assertEquals( $title->getInterwiki(), $fragmentTitle->getInterwiki() );
                $this->assertEquals( $fragment, $fragmentTitle->getFragment() );
        }
+
+       public function provideGetPrefixedText() {
+               return [
+                       // ns = 0
+                       [
+                               Title::makeTitle( NS_MAIN, 'Foobar' ),
+                               'Foobar'
+                       ],
+                       // ns = 2
+                       [
+                               Title::makeTitle( NS_USER, 'Foobar' ),
+                               'User:Foobar'
+                       ],
+                       // fragment not included
+                       [
+                               Title::makeTitle( NS_MAIN, 'Foobar', 'fragment' ),
+                               'Foobar'
+                       ],
+                       // ns = -2
+                       [
+                               Title::makeTitle( NS_MEDIA, 'Foobar' ),
+                               'Media:Foobar'
+                       ],
+                       // non-existent namespace
+                       [
+                               Title::makeTitle( 100000, 'Foobar' ),
+                               ':Foobar'
+                       ],
+               ];
+       }
+
+       /**
+        * @covers Title::getPrefixedText
+        * @dataProvider provideGetPrefixedText
+        */
+       public function testGetPrefixedText( Title $title, $expected ) {
+               $this->assertEquals( $expected, $title->getPrefixedText() );
+       }
 }
index e536205..be22260 100644 (file)
@@ -13,6 +13,14 @@ class WatchedItemIntegrationTest extends MediaWikiTestCase {
                parent::setUp();
                self::$users['WatchedItemIntegrationTestUser']
                        = new TestUser( 'WatchedItemIntegrationTestUser' );
+
+               $this->hideDeprecated( 'WatchedItem::fromUserTitle' );
+               $this->hideDeprecated( 'WatchedItem::addWatch' );
+               $this->hideDeprecated( 'WatchedItem::removeWatch' );
+               $this->hideDeprecated( 'WatchedItem::isWatched' );
+               $this->hideDeprecated( 'WatchedItem::resetNotificationTimestamp' );
+               $this->hideDeprecated( 'WatchedItem::duplicateEntries' );
+               $this->hideDeprecated( 'WatchedItem::batchAddWatch' );
        }
 
        private function getUser() {
@@ -20,6 +28,7 @@ class WatchedItemIntegrationTest extends MediaWikiTestCase {
        }
 
        public function testWatchAndUnWatchItem() {
+
                $user = $this->getUser();
                $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
                // Cleanup after previous tests
index 5b2873a..61b62aa 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @author Addshore
  *
@@ -22,7 +24,7 @@ class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
        public function testWatchAndUnWatchItem() {
                $user = $this->getUser();
                $title = Title::newFromText( 'WatchedItemStoreIntegrationTestPage' );
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                // Cleanup after previous tests
                $store->removeWatch( $user, $title );
                $initialWatchers = $store->countWatchers( $title );
@@ -104,11 +106,11 @@ class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
                );
        }
 
-       public function testUpdateAndResetNotificationTimestamp() {
+       public function testUpdateResetAndSetNotificationTimestamp() {
                $user = $this->getUser();
                $otherUser = ( new TestUser( 'WatchedItemStoreIntegrationTestUser_otherUser' ) )->getUser();
                $title = Title::newFromText( 'WatchedItemStoreIntegrationTestPage' );
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                $store->addWatch( $user, $title );
                $this->assertNull( $store->loadWatchedItem( $user, $title )->getNotificationTimestamp() );
                $initialVisitingWatchers = $store->countVisitingWatchers( $title, '20150202020202' );
@@ -170,13 +172,31 @@ class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
                                [ [ $title, '20150202020202' ] ], $initialVisitingWatchers + 1
                        )
                );
+
+               // setNotificationTimestampsForUser specifying a title
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, '20200202020202', [ $title ] )
+               );
+               $this->assertEquals(
+                       '20200202020202',
+                       $store->getWatchedItem( $user, $title )->getNotificationTimestamp()
+               );
+
+               // setNotificationTimestampsForUser not specifying a title
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, '20210202020202' )
+               );
+               $this->assertEquals(
+                       '20210202020202',
+                       $store->getWatchedItem( $user, $title )->getNotificationTimestamp()
+               );
        }
 
        public function testDuplicateAllAssociatedEntries() {
                $user = $this->getUser();
                $titleOld = Title::newFromText( 'WatchedItemStoreIntegrationTestPageOld' );
                $titleNew = Title::newFromText( 'WatchedItemStoreIntegrationTestPageNew' );
-               $store = WatchedItemStore::getDefaultInstance();
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
                $store->addWatch( $user, $titleOld->getSubjectPage() );
                $store->addWatch( $user, $titleOld->getTalkPage() );
                // Cleanup after previous tests
index d9fd4de..2d2e726 100644 (file)
@@ -6,7 +6,7 @@ use MediaWiki\Linker\LinkTarget;
  *
  * @covers WatchedItemStore
  */
-class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
+class WatchedItemStoreUnitTest extends MediaWikiTestCase {
 
        /**
         * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
@@ -94,23 +94,6 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                );
        }
 
-       public function testGetDefaultInstance() {
-               $instanceOne = WatchedItemStore::getDefaultInstance();
-               $instanceTwo = WatchedItemStore::getDefaultInstance();
-               $this->assertSame( $instanceOne, $instanceTwo );
-       }
-
-       public function testOverrideDefaultInstance() {
-               $instance = WatchedItemStore::getDefaultInstance();
-               $scopedOverride = $instance->overrideDefaultInstance( null );
-
-               $this->assertNotSame( $instance, WatchedItemStore::getDefaultInstance() );
-
-               unset( $scopedOverride );
-
-               $this->assertSame( $instance, WatchedItemStore::getDefaultInstance() );
-       }
-
        public function testCountWatchedItems() {
                $user = $this->getMockNonAnonUserWithId( 1 );
 
@@ -2007,6 +1990,33 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                return $title;
        }
 
+       private function verifyCallbackJob(
+               $callback,
+               LinkTarget $expectedTitle,
+               $expectedUserId,
+               callable $notificationTimestampCondition
+       ) {
+               $this->assertInternalType( 'callable', $callback );
+
+               $callbackReflector = new ReflectionFunction( $callback );
+               $vars = $callbackReflector->getStaticVariables();
+               $this->assertArrayHasKey( 'job', $vars );
+               $this->assertInstanceOf( ActivityUpdateJob::class, $vars['job'] );
+
+               /** @var ActivityUpdateJob $job */
+               $job = $vars['job'];
+               $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
+               $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
+
+               $jobParams = $job->getParams();
+               $this->assertArrayHasKey( 'type', $jobParams );
+               $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
+               $this->assertArrayHasKey( 'userid', $jobParams );
+               $this->assertEquals( $expectedUserId, $jobParams['userid'] );
+               $this->assertArrayHasKey( 'notifTime', $jobParams );
+               $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
+       }
+
        public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
                $user = $this->getMockNonAnonUserWithId( 1 );
                $oldid = 22;
@@ -2033,12 +2043,18 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                        $mockCache
                );
 
-               // Note: This does not actually assert the job is correct
                $callableCallCounter = 0;
                $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
-                       function( $callable ) use ( &$callableCallCounter ) {
+                       function( $callable ) use ( &$callableCallCounter, $title, $user ) {
                                $callableCallCounter++;
-                               $this->assertInternalType( 'callable', $callable );
+                               $this->verifyCallbackJob(
+                                       $callable,
+                                       $title,
+                                       $user->getId(),
+                                       function( $time ) {
+                                               return $time === null;
+                                       }
+                               );
                        }
                );
 
@@ -2093,12 +2109,159 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                        $mockCache
                );
 
-               // Note: This does not actually assert the job is correct
                $addUpdateCallCounter = 0;
                $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
-                       function( $callable ) use ( &$addUpdateCallCounter ) {
+                       function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
+                               $addUpdateCallCounter++;
+                               $this->verifyCallbackJob(
+                                       $callable,
+                                       $title,
+                                       $user->getId(),
+                                       function( $time ) {
+                                               return $time !== null && $time > '20151212010101';
+                                       }
+                               );
+                       }
+               );
+
+               $getTimestampCallCounter = 0;
+               $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
+                       function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
+                               $getTimestampCallCounter++;
+                               $this->assertEquals( $title, $titleParam );
+                               $this->assertEquals( $oldid, $oldidParam );
+                       }
+               );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title,
+                               'force',
+                               $oldid
+                       )
+               );
+               $this->assertEquals( 1, $addUpdateCallCounter );
+               $this->assertEquals( 1, $getTimestampCallCounter );
+
+               ScopedCallback::consume( $scopedOverrideDeferred );
+               ScopedCallback::consume( $scopedOverrideRevision );
+       }
+
+       public function testResetNotificationTimestamp_notWatchedPageForced() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $oldid = 22;
+               $title = $this->getMockTitle( 'SomeDbKey' );
+               $title->expects( $this->once() )
+                       ->method( 'getNextRevisionID' )
+                       ->with( $oldid )
+                       ->will( $this->returnValue( 33 ) );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue( false ) );
+
+               $mockCache = $this->getMockCache();
+               $mockDb->expects( $this->never() )
+                       ->method( 'get' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'set' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $callableCallCounter = 0;
+               $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+                       function( $callable ) use ( &$callableCallCounter, $title, $user ) {
+                               $callableCallCounter++;
+                               $this->verifyCallbackJob(
+                                       $callable,
+                                       $title,
+                                       $user->getId(),
+                                       function( $time ) {
+                                               return $time === null;
+                                       }
+                               );
+                       }
+               );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title,
+                               'force',
+                               $oldid
+                       )
+               );
+               $this->assertEquals( 1, $callableCallCounter );
+
+               ScopedCallback::consume( $scopedOverride );
+       }
+
+       public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $oldid = 22;
+               $title = $this->getMockTitle( 'SomeDbKey' );
+               $title->expects( $this->once() )
+                       ->method( 'getNextRevisionID' )
+                       ->with( $oldid )
+                       ->will( $this->returnValue( 33 ) );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockDb->expects( $this->never() )
+                       ->method( 'get' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'set' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $addUpdateCallCounter = 0;
+               $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+                       function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
                                $addUpdateCallCounter++;
-                               $this->assertInternalType( 'callable', $callable );
+                               $this->verifyCallbackJob(
+                                       $callable,
+                                       $title,
+                                       $user->getId(),
+                                       function( $time ) {
+                                               return $time === '30151212010101';
+                                       }
+                               );
                        }
                );
 
@@ -2126,6 +2289,158 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                ScopedCallback::consume( $scopedOverrideRevision );
        }
 
+       public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $oldid = 22;
+               $title = $this->getMockTitle( 'SomeDbKey' );
+               $title->expects( $this->once() )
+                       ->method( 'getNextRevisionID' )
+                       ->with( $oldid )
+                       ->will( $this->returnValue( 33 ) );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockDb->expects( $this->never() )
+                       ->method( 'get' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'set' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $addUpdateCallCounter = 0;
+               $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+                       function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
+                               $addUpdateCallCounter++;
+                               $this->verifyCallbackJob(
+                                       $callable,
+                                       $title,
+                                       $user->getId(),
+                                       function( $time ) {
+                                               return $time === false;
+                                       }
+                               );
+                       }
+               );
+
+               $getTimestampCallCounter = 0;
+               $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
+                       function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
+                               $getTimestampCallCounter++;
+                               $this->assertEquals( $title, $titleParam );
+                               $this->assertEquals( $oldid, $oldidParam );
+                       }
+               );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title,
+                               '',
+                               $oldid
+                       )
+               );
+               $this->assertEquals( 1, $addUpdateCallCounter );
+               $this->assertEquals( 1, $getTimestampCallCounter );
+
+               ScopedCallback::consume( $scopedOverrideDeferred );
+               ScopedCallback::consume( $scopedOverrideRevision );
+       }
+
+       public function testSetNotificationTimestampsForUser_anonUser() {
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $this->getMockDb() ),
+                       $this->getMockCache()
+               );
+               $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
+       }
+
+       public function testSetNotificationTimestampsForUser_allRows() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $timestamp = '20100101010101';
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'update' )
+                       ->with(
+                               'watchlist',
+                               [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
+                               [ 'wl_user' => 1 ]
+                       )
+                       ->will( $this->returnValue( true ) );
+               $mockDb->expects( $this->exactly( 1 ) )
+                       ->method( 'timestamp' )
+                       ->will( $this->returnCallback( function( $value ) {
+                               return 'TS' . $value . 'TS';
+                       } ) );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $this->getMockCache()
+               );
+
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, $timestamp )
+               );
+       }
+
+       public function testSetNotificationTimestampsForUser_specificTargets() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $timestamp = '20100101010101';
+               $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'update' )
+                       ->with(
+                               'watchlist',
+                               [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
+                               [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
+                       )
+                       ->will( $this->returnValue( true ) );
+               $mockDb->expects( $this->exactly( 1 ) )
+                       ->method( 'timestamp' )
+                       ->will( $this->returnCallback( function( $value ) {
+                               return 'TS' . $value . 'TS';
+                       } ) );
+               $mockDb->expects( $this->once() )
+                       ->method( 'makeWhereFrom2d' )
+                       ->with(
+                               [ [ 'Foo' => 1, 'Bar' => 1 ] ],
+                               $this->isType( 'string' ),
+                               $this->isType( 'string' )
+                       )
+                       ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $this->getMockCache()
+               );
+
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
+               );
+       }
+
        public function testUpdateNotificationTimestamp_watchersExist() {
                $mockDb = $this->getMockDb();
                $mockDb->expects( $this->once() )
index c33ba7e..0182eb7 100644 (file)
@@ -6,13 +6,30 @@ use MediaWiki\Linker\LinkTarget;
  *
  * @covers WatchedItem
  */
-class WatchedItemUnitTest extends PHPUnit_Framework_TestCase {
+class WatchedItemUnitTest extends MediaWikiTestCase {
+
+       /**
+        * @param int $id
+        *
+        * @return PHPUnit_Framework_MockObject_MockObject|User
+        */
+       private function getMockUser( $id ) {
+               $user = $this->getMock( User::class );
+               $user->expects( $this->any() )
+                       ->method( 'getId' )
+                       ->will( $this->returnValue( $id ) );
+               $user->expects( $this->any() )
+                       ->method( 'isAllowed' )
+                       ->will( $this->returnValue( true ) );
+               return $user;
+       }
 
        public function provideUserTitleTimestamp() {
+               $user = $this->getMockUser( 111 );
                return [
-                       [ User::newFromId( 111 ), Title::newFromText( 'SomeTitle' ), null ],
-                       [ User::newFromId( 111 ), Title::newFromText( 'SomeTitle' ), '20150101010101' ],
-                       [ User::newFromId( 111 ), new TitleValue( 0, 'TVTitle', 'frag' ), '20150101010101' ],
+                       [ $user, Title::newFromText( 'SomeTitle' ), null ],
+                       [ $user, Title::newFromText( 'SomeTitle' ), '20150101010101' ],
+                       [ $user, new TitleValue( 0, 'TVTitle', 'frag' ), '20150101010101' ],
                ];
        }
 
@@ -52,15 +69,13 @@ class WatchedItemUnitTest extends PHPUnit_Framework_TestCase {
                        ->method( 'loadWatchedItem' )
                        ->with( $user, $linkTarget )
                        ->will( $this->returnValue( new WatchedItem( $user, $linkTarget, $timestamp ) ) );
-               $scopedOverride = WatchedItemStore::overrideDefaultInstance( $store );
+               $this->setService( 'WatchedItemStore', $store );
 
                $item = WatchedItem::fromUserTitle( $user, $linkTarget, User::IGNORE_USER_RIGHTS );
 
                $this->assertEquals( $user, $item->getUser() );
                $this->assertEquals( $linkTarget, $item->getLinkTarget() );
                $this->assertEquals( $timestamp, $item->getNotificationTimestamp() );
-
-               ScopedCallback::consume( $scopedOverride );
        }
 
        /**
@@ -86,12 +101,10 @@ class WatchedItemUnitTest extends PHPUnit_Framework_TestCase {
                                        return true;
                                }
                        ) );
-               $scopedOverride = WatchedItemStore::overrideDefaultInstance( $store );
+               $this->setService( 'WatchedItemStore', $store );
 
                $item = new WatchedItem( $user, $linkTarget, $timestamp );
                $item->resetNotificationTimestamp( $force, $oldid );
-
-               ScopedCallback::consume( $scopedOverride );
        }
 
        public function testAddWatch() {
@@ -158,17 +171,15 @@ class WatchedItemUnitTest extends PHPUnit_Framework_TestCase {
                $store->expects( $this->once() )
                        ->method( 'duplicateAllAssociatedEntries' )
                        ->with( $oldTitle, $newTitle );
-               $scopedOverride = WatchedItemStore::overrideDefaultInstance( $store );
+               $this->setService( 'WatchedItemStore', $store );
 
                WatchedItem::duplicateEntries( $oldTitle, $newTitle );
-
-               ScopedCallback::consume( $scopedOverride );
        }
 
        public function testBatchAddWatch() {
-               $itemOne = new WatchedItem( User::newFromId( 1 ), new TitleValue( 0, 'Title1' ), null );
+               $itemOne = new WatchedItem( $this->getMockUser( 1 ), new TitleValue( 0, 'Title1' ), null );
                $itemTwo = new WatchedItem(
-                       User::newFromId( 3 ),
+                       $this->getMockUser( 3 ),
                        Title::newFromText( 'Title2' ),
                        '20150101010101'
                );
@@ -194,11 +205,9 @@ class WatchedItemUnitTest extends PHPUnit_Framework_TestCase {
                                        $itemTwo->getTitle()->getTalkPage(),
                                ]
                        );
-               $scopedOverride = WatchedItemStore::overrideDefaultInstance( $store );
+               $this->setService( 'WatchedItemStore', $store );
 
                WatchedItem::batchAddWatch( [ $itemOne, $itemTwo ] );
-
-               ScopedCallback::consume( $scopedOverride );
        }
 
 }
index 0d10c1a..f80f512 100644 (file)
@@ -12,9 +12,6 @@ class XmlSelectTest extends MediaWikiTestCase {
 
        protected function setUp() {
                parent::setUp();
-               $this->setMwGlobals( [
-                       'wgWellFormedXml' => true,
-               ] );
                $this->select = new XmlSelect();
        }
 
index 00d429e..dbd1299 100644 (file)
@@ -30,7 +30,6 @@ class XmlTest extends MediaWikiTestCase {
 
                $this->setMwGlobals( [
                        'wgLang' => $langObj,
-                       'wgWellFormedXml' => true,
                        'wgUseMediaWikiUIEverywhere' => false,
                ] );
        }
diff --git a/tests/phpunit/includes/api/ApiCreateAccountTest.php b/tests/phpunit/includes/api/ApiCreateAccountTest.php
deleted file mode 100644 (file)
index 9a83e61..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-<?php
-
-/**
- * @group Database
- * @group API
- * @group medium
- *
- * @covers ApiCreateAccount
- */
-class ApiCreateAccountTest extends ApiTestCase {
-       protected function setUp() {
-               parent::setUp();
-               $this->setMwGlobals( [ 'wgEnableEmail' => true ] );
-       }
-
-       /**
-        * Test the account creation API with a valid request. Also
-        * make sure the new account can log in and is valid.
-        *
-        * This test does multiple API requests so it might end up being
-        * a bit slow. Raise the default timeout.
-        * @group medium
-        */
-       public function testValid() {
-               global $wgServer;
-
-               if ( !isset( $wgServer ) ) {
-                       $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
-               }
-
-               $password = PasswordFactory::generateRandomPasswordString();
-
-               $ret = $this->doApiRequest( [
-                       'action' => 'createaccount',
-                       'name' => 'Apitestnew',
-                       'password' => $password,
-                       'email' => 'test@domain.test',
-                       'realname' => 'Test Name'
-               ] );
-
-               $result = $ret[0];
-               $this->assertNotInternalType( 'bool', $result );
-               $this->assertNotInternalType( 'null', $result['createaccount'] );
-
-               // Should first ask for token.
-               $a = $result['createaccount'];
-               $this->assertEquals( 'NeedToken', $a['result'] );
-               $token = $a['token'];
-
-               // Finally create the account
-               $ret = $this->doApiRequest(
-                       [
-                               'action' => 'createaccount',
-                               'name' => 'Apitestnew',
-                               'password' => $password,
-                               'token' => $token,
-                               'email' => 'test@domain.test',
-                               'realname' => 'Test Name'
-                       ],
-                       $ret[2]
-               );
-
-               $result = $ret[0];
-               $this->assertNotInternalType( 'bool', $result );
-               $this->assertEquals( 'Success', $result['createaccount']['result'] );
-
-               // Try logging in with the new user.
-               $ret = $this->doApiRequest( [
-                       'action' => 'login',
-                       'lgname' => 'Apitestnew',
-                       'lgpassword' => $password,
-               ] );
-
-               $result = $ret[0];
-               $this->assertNotInternalType( 'bool', $result );
-               $this->assertNotInternalType( 'null', $result['login'] );
-
-               $a = $result['login']['result'];
-               $this->assertEquals( 'NeedToken', $a );
-               $token = $result['login']['token'];
-
-               $ret = $this->doApiRequest(
-                       [
-                               'action' => 'login',
-                               'lgtoken' => $token,
-                               'lgname' => 'Apitestnew',
-                               'lgpassword' => $password,
-                       ],
-                       $ret[2]
-               );
-
-               $result = $ret[0];
-
-               $this->assertNotInternalType( 'bool', $result );
-               $a = $result['login']['result'];
-
-               $this->assertEquals( 'Success', $a );
-
-               // log out to destroy the session
-               $ret = $this->doApiRequest(
-                       [
-                               'action' => 'logout',
-                       ],
-                       $ret[2]
-               );
-               $this->assertEquals( [], $ret[0] );
-       }
-
-       /**
-        * Make sure requests with no names are invalid.
-        * @expectedException UsageException
-        */
-       public function testNoName() {
-               $this->doApiRequest( [
-                       'action' => 'createaccount',
-                       'token' => LoginForm::getCreateaccountToken()->toString(),
-                       'password' => 'password',
-               ] );
-       }
-
-       /**
-        * Make sure requests with no password are invalid.
-        * @expectedException UsageException
-        */
-       public function testNoPassword() {
-               $this->doApiRequest( [
-                       'action' => 'createaccount',
-                       'name' => 'testName',
-                       'token' => LoginForm::getCreateaccountToken()->toString(),
-               ] );
-       }
-
-       /**
-        * Make sure requests with existing users are invalid.
-        * @expectedException UsageException
-        */
-       public function testExistingUser() {
-               $this->doApiRequest( [
-                       'action' => 'createaccount',
-                       'name' => 'Apitestsysop',
-                       'token' => LoginForm::getCreateaccountToken()->toString(),
-                       'password' => 'password',
-                       'email' => 'test@domain.test',
-               ] );
-       }
-
-       /**
-        * Make sure requests with invalid emails are invalid.
-        * @expectedException UsageException
-        */
-       public function testInvalidEmail() {
-               $this->doApiRequest( [
-                       'action' => 'createaccount',
-                       'name' => 'Test User',
-                       'token' => LoginForm::getCreateaccountToken()->toString(),
-                       'password' => 'password',
-                       'email' => 'invalid',
-               ] );
-       }
-}
index bcd884e..2f8ffcc 100644 (file)
@@ -13,6 +13,8 @@ class ApiLoginTest extends ApiTestCase {
         * Test result of attempted login with an empty username
         */
        public function testApiLoginNoName() {
+               global $wgDisableAuthManager;
+
                $session = [
                        'wsTokenSecrets' => [ 'login' => 'foobar' ],
                ];
@@ -20,11 +22,11 @@ class ApiLoginTest extends ApiTestCase {
                        'lgname' => '', 'lgpassword' => self::$users['sysop']->password,
                        'lgtoken' => (string)( new MediaWiki\Session\Token( 'foobar', '' ) )
                ], $session );
-               $this->assertEquals( 'NoName', $data[0]['login']['result'] );
+               $this->assertEquals( $wgDisableAuthManager ? 'NoName' : 'Failed', $data[0]['login']['result'] );
        }
 
        public function testApiLoginBadPass() {
-               global $wgServer;
+               global $wgServer, $wgDisableAuthManager;
 
                $user = self::$users['sysop'];
                $user->getUser()->logout();
@@ -61,7 +63,7 @@ class ApiLoginTest extends ApiTestCase {
                $this->assertNotInternalType( "bool", $result );
                $a = $result["login"]["result"];
 
-               $this->assertEquals( "WrongPass", $a );
+               $this->assertEquals( $wgDisableAuthManager ? 'WrongPass' : 'Failed', $a );
        }
 
        public function testApiLoginGoodPass() {
@@ -226,8 +228,7 @@ class ApiLoginTest extends ApiTestCase {
                $passwordFactory = new PasswordFactory();
                $passwordFactory->init( RequestContext::getMain()->getConfig() );
                // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordFactory->setDefaultType( 'A' );
-               $pwhash = $passwordFactory->newFromPlaintext( 'foobaz' );
+               $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
 
                $dbw = wfGetDB( DB_MASTER );
                $dbw->insert(
@@ -235,7 +236,7 @@ class ApiLoginTest extends ApiTestCase {
                        [
                                'bp_user' => $centralId,
                                'bp_app_id' => 'foo',
-                               'bp_password' => $pwhash->toString(),
+                               'bp_password' => $passwordHash->toString(),
                                'bp_token' => '',
                                'bp_restrictions' => MWRestrictions::newDefault()->toJson(),
                                'bp_grants' => '["test"]',
diff --git a/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php b/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
new file mode 100644 (file)
index 0000000..f1f9295
--- /dev/null
@@ -0,0 +1,1592 @@
+<?php
+
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiQueryWatchlist
+ */
+class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
+
+       public function __construct( $name = null, array $data = [], $dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+               $this->tablesUsed = array_unique(
+                       array_merge( $this->tablesUsed, [ 'watchlist', 'recentchanges', 'page' ] )
+               );
+       }
+
+       protected function setUp() {
+               parent::setUp();
+               self::$users['ApiQueryWatchlistIntegrationTestUser']
+                       = new TestUser( 'ApiQueryWatchlistIntegrationTestUser' );
+               self::$users['ApiQueryWatchlistIntegrationTestUser2']
+                       = new TestUser( 'ApiQueryWatchlistIntegrationTestUser2' );
+               $this->doLogin( 'ApiQueryWatchlistIntegrationTestUser' );
+       }
+
+       private function getTestUser() {
+               return self::$users['ApiQueryWatchlistIntegrationTestUser']->getUser();
+       }
+
+       private function getNonLoggedInTestUser() {
+               return self::$users['ApiQueryWatchlistIntegrationTestUser2']->getUser();
+       }
+
+       private function getSysopTestUser() {
+               return self::$users['sysop']->getUser();
+       }
+
+       private function doPageEdit( User $user, LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       0,
+                       false,
+                       $user
+               );
+       }
+
+       private function doMinorPageEdit( User $user, LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       EDIT_MINOR,
+                       false,
+                       $user
+               );
+       }
+
+       private function doBotPageEdit( User $user, LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       EDIT_FORCE_BOT,
+                       false,
+                       $user
+               );
+       }
+
+       private function doAnonPageEdit( LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       0,
+                       false,
+                       User::newFromId( 0 )
+               );
+       }
+
+       private function doPatrolledPageEdit(
+               User $user,
+               LinkTarget $target,
+               $content,
+               $summary,
+               User $patrollingUser
+       ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $status = $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       0,
+                       false,
+                       $user
+               );
+               /** @var Revision $rev */
+               $rev = $status->value['revision'];
+               $rc = $rev->getRecentChange();
+               $rc->doMarkPatrolled( $patrollingUser, false, [] );
+       }
+
+       private function deletePage( LinkTarget $target, $reason ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doDeleteArticleReal( $reason );
+       }
+
+       /**
+        * Performs a batch of page edits as a specified user
+        * @param User $user
+        * @param array $editData associative array, keys:
+        *                        - target    => LinkTarget page to edit
+        *                        - content   => string new content
+        *                        - summary   => string edit summary
+        *                        - minorEdit => bool mark as minor edit if true (defaults to false)
+        *                        - botEdit   => bool mark as bot edit if true (defaults to false)
+        */
+       private function doPageEdits( User $user, array $editData ) {
+               foreach ( $editData as $singleEditData ) {
+                       if ( array_key_exists( 'minorEdit', $singleEditData ) && $singleEditData['minorEdit'] ) {
+                               $this->doMinorPageEdit(
+                                       $user,
+                                       $singleEditData['target'],
+                                       $singleEditData['content'],
+                                       $singleEditData['summary']
+                               );
+                               continue;
+                       }
+                       if ( array_key_exists( 'botEdit', $singleEditData ) && $singleEditData['botEdit'] ) {
+                               $this->doBotPageEdit(
+                                       $user,
+                                       $singleEditData['target'],
+                                       $singleEditData['content'],
+                                       $singleEditData['summary']
+                               );
+                               continue;
+                       }
+                       $this->doPageEdit(
+                               $user,
+                               $singleEditData['target'],
+                               $singleEditData['content'],
+                               $singleEditData['summary']
+                       );
+               }
+       }
+
+       private function getWatchedItemStore() {
+               return MediaWikiServices::getInstance()->getWatchedItemStore();
+       }
+
+       /**
+        * @param User $user
+        * @param LinkTarget[] $targets
+        */
+       private function watchPages( User $user, array $targets ) {
+               $store = $this->getWatchedItemStore();
+               $store->addWatchBatchForUser( $user, $targets );
+       }
+
+       private function doListWatchlistRequest( array $params = [], $user = null ) {
+               return $this->doApiRequest(
+                       array_merge(
+                               [ 'action' => 'query', 'list' => 'watchlist' ],
+                               $params
+                       ), null, false, $user
+               );
+       }
+
+       private function doGeneratorWatchlistRequest( array $params = [] ) {
+               return $this->doApiRequest(
+                       array_merge(
+                               [ 'action' => 'query', 'generator' => 'watchlist' ],
+                               $params
+                       )
+               );
+       }
+
+       private function getItemsFromApiResponse( array $response ) {
+               return $response[0]['query']['watchlist'];
+       }
+
+       /**
+        * Convenience method to assert that actual items array fetched from API is equal to the expected
+        * array, Unlike assertEquals this only checks if values of specified keys are equal in both
+        * arrays. This could be used e.g. not to compare IDs that could change between test run
+        * but only stable keys.
+        * Optionally this also checks that specified keys are present in the actual item without
+        * performing any checks on the related values.
+        *
+        * @param array $actualItems               array of actual items (associative arrays)
+        * @param array $expectedItems             array of expected items (associative arrays),
+        *                                         those items have less keys than actual items
+        * @param array $keysUsedInValueComparison list of keys of the actual item that will be used
+        *                                         in the comparison of values
+        * @param array $requiredKeys              optional, list of keys that must be present in the
+        *                                         actual items. Values of those keys are not checked.
+        */
+       private function assertArraySubsetsEqual(
+               array $actualItems,
+               array $expectedItems,
+               array $keysUsedInValueComparison,
+               array $requiredKeys = []
+       ) {
+               $this->assertCount( count( $expectedItems ), $actualItems );
+
+               // not checking values of all keys of the actual item, so removing unwanted keys from comparison
+               $actualItemsOnlyComparedValues = array_map(
+                       function( array $item ) use ( $keysUsedInValueComparison ) {
+                               return array_intersect_key( $item, array_flip( $keysUsedInValueComparison ) );
+                       },
+                       $actualItems
+               );
+
+               $this->assertEquals(
+                       $expectedItems,
+                       $actualItemsOnlyComparedValues
+               );
+
+               // Check that each item in $actualItems contains all of keys specified in $requiredKeys
+               $actualItemsKeysOnly = array_map( 'array_keys', $actualItems );
+               foreach ( $actualItemsKeysOnly as $keysOfTheItem ) {
+                       $this->assertEmpty( array_diff( $requiredKeys, $keysOfTheItem ) );
+               }
+       }
+
+       private function getTitleFormatter() {
+               return new MediaWikiTitleCodec( Language::factory( 'en' ), GenderCache::singleton() );
+       }
+
+       private function getPrefixedText( LinkTarget $target ) {
+               $formatter = $this->getTitleFormatter();
+               return $formatter->getPrefixedText( $target );
+       }
+
+       private function cleanTestUsersWatchlist() {
+               $user = $this->getTestUser();
+               $store = $this->getWatchedItemStore();
+               $items = $store->getWatchedItemsForUser( $user );
+               foreach ( $items as $item ) {
+                       $store->removeWatch( $user, $item->getLinkTarget() );
+               }
+       }
+
+       public function testListWatchlist_returnsWatchedItemsWithRCInfo() {
+               // Clean up after previous tests that might have added something to the watchlist of
+               // the user with the same user ID as user used here as the test user
+               $this->cleanTestUsersWatchlist();
+
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest();
+
+               $this->assertArrayHasKey( 'query', $result[0] );
+               $this->assertArrayHasKey( 'watchlist', $result[0]['query'] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $result ),
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                                       'bot' => false,
+                                       'new' => true,
+                                       'minor' => false,
+                               ]
+                       ],
+                       [ 'type', 'ns', 'title', 'bot', 'new', 'minor' ],
+                       [ 'pageid', 'revid', 'old_revid' ]
+               );
+       }
+
+       public function testIdsPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'ids', ] );
+               $items = $this->getItemsFromApiResponse( $result );
+
+               $this->assertCount( 1, $items );
+               $this->assertArrayHasKey( 'pageid', $items[0] );
+               $this->assertArrayHasKey( 'revid', $items[0] );
+               $this->assertArrayHasKey( 'old_revid', $items[0] );
+               $this->assertEquals( 'new', $items[0]['type'] );
+       }
+
+       public function testTitlePropParameter() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget ),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testFlagsPropParameter() {
+               $user = $this->getTestUser();
+               $normalEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $minorEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageM' );
+               $botEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageB' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $normalEditTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $minorEditTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $minorEditTarget,
+                                       'content' => 'Slightly Better Content',
+                                       'summary' => 'Change content',
+                                       'minorEdit' => true,
+                               ],
+                               [
+                                       'target' => $botEditTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page with a bot',
+                                       'botEdit' => true,
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $normalEditTarget, $minorEditTarget, $botEditTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'flags', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'new' => true,
+                                       'minor' => false,
+                                       'bot' => true,
+                               ],
+                               [
+                                       'type' => 'edit',
+                                       'new' => false,
+                                       'minor' => true,
+                                       'bot' => false,
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'new' => true,
+                                       'minor' => false,
+                                       'bot' => false,
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testUserPropParameter() {
+               $user = $this->getTestUser();
+               $userEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $anonEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageA' );
+               $this->doPageEdit(
+                       $user,
+                       $userEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doAnonPageEdit(
+                       $anonEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $userEditTarget, $anonEditTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'user', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'anon' => true,
+                                       'user' => User::newFromId( 0 )->getName(),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'user' => $user->getName(),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testUserIdPropParameter() {
+               $user = $this->getTestUser();
+               $userEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $anonEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageA' );
+               $this->doPageEdit(
+                       $user,
+                       $userEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doAnonPageEdit(
+                       $anonEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $userEditTarget, $anonEditTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'userid', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'anon' => true,
+                                       'user' => 0,
+                                       'userid' => 0,
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'user' => $user->getId(),
+                                       'userid' => $user->getId(),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testCommentPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the <b>page</b>'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'comment', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'comment' => 'Create the <b>page</b>',
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testParsedCommentPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the <b>page</b>'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'parsedcomment', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'parsedcomment' => 'Create the &lt;b&gt;page&lt;/b&gt;',
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testTimestampPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'timestamp', ] );
+               $items = $this->getItemsFromApiResponse( $result );
+
+               $this->assertCount( 1, $items );
+               $this->assertArrayHasKey( 'timestamp', $items[0] );
+               $this->assertInternalType( 'string', $items[0]['timestamp'] );
+       }
+
+       public function testSizesPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'sizes', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'oldlen' => 0,
+                                       'newlen' => 12,
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testNotificationTimestampPropParameter() {
+               $otherUser = $this->getNonLoggedInTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $otherUser,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $store = $this->getWatchedItemStore();
+               $store->addWatch( $this->getTestUser(), $target );
+               $store->updateNotificationTimestamp(
+                       $otherUser,
+                       $target,
+                       '20151212010101'
+               );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'notificationtimestamp', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'notificationtimestamp' => '2015-12-12T01:01:01Z',
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       private function setupPatrolledSpecificFixtures( User $user ) {
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+
+               $this->doPatrolledPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page (this gets patrolled)',
+                       $user
+               );
+
+               $this->watchPages( $user, [ $target ] );
+       }
+
+       public function testPatrolPropParameter() {
+               $user = $this->getSysopTestUser();
+               $this->setupPatrolledSpecificFixtures( $user );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'patrol', ], $user );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'patrolled' => true,
+                                       'unpatrolled' => false,
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       private function createPageAndDeleteIt( LinkTarget $target ) {
+               $this->doPageEdit(
+                       $this->getTestUser(),
+                       $target,
+                       'Some Content',
+                       'Create the page that will be deleted'
+               );
+               $this->deletePage( $target, 'Important Reason' );
+       }
+
+       public function testLoginfoPropParameter() {
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->createPageAndDeleteIt( $target );
+
+               $this->watchPages( $this->getTestUser(), [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'loginfo', ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $result ),
+                       [
+                               [
+                                       'type' => 'log',
+                                       'logtype' => 'delete',
+                                       'logaction' => 'delete',
+                                       'logparams' => [],
+                               ],
+                       ],
+                       [ 'type', 'logtype', 'logaction', 'logparams' ],
+                       [ 'logid' ]
+               );
+       }
+
+       public function testEmptyPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => '', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testNamespaceParam() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlnamespace' => '0', ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $result ),
+                       [
+                               [
+                                       'ns' => 0,
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       [ 'ns', 'title' ]
+               );
+       }
+
+       public function testUserParam() {
+               $user = $this->getTestUser();
+               $otherUser = $this->getNonLoggedInTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $otherUser,
+                       $talkTarget,
+                       'What is this page about?',
+                       'Create the talk page'
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user|title',
+                       'wluser' => $otherUser->getName(),
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget ),
+                                       'user' => $otherUser->getName(),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testExcludeUserParam() {
+               $user = $this->getTestUser();
+               $otherUser = $this->getNonLoggedInTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $otherUser,
+                       $talkTarget,
+                       'What is this page about?',
+                       'Create the talk page'
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user|title',
+                       'wlexcludeuser' => $otherUser->getName(),
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                                       'user' => $user->getName(),
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testShowMinorParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target,
+                                       'content' => 'Slightly Better Content',
+                                       'summary' => 'Change content',
+                                       'minorEdit' => true,
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultMinor = $this->doListWatchlistRequest( [ 'wlshow' => 'minor', 'wlprop' => 'flags' ] );
+               $resultNotMinor = $this->doListWatchlistRequest( [ 'wlshow' => '!minor', 'wlprop' => 'flags' ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $resultMinor ),
+                       [
+                               [ 'minor' => true, ]
+                       ],
+                       [ 'minor' ]
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotMinor ) );
+       }
+
+       public function testShowBotParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doBotPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultBot = $this->doListWatchlistRequest( [ 'wlshow' => 'bot' ] );
+               $resultNotBot = $this->doListWatchlistRequest( [ 'wlshow' => '!bot' ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $resultBot ),
+                       [
+                               [ 'bot' => true ],
+                       ],
+                       [ 'bot' ]
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotBot ) );
+       }
+
+       public function testShowAnonParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doAnonPageEdit(
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultAnon = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user',
+                       'wlshow' => 'anon'
+               ] );
+               $resultNotAnon = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user',
+                       'wlshow' => '!anon'
+               ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $resultAnon ),
+                       [
+                               [ 'anon' => true ],
+                       ],
+                       [ 'anon' ]
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotAnon ) );
+       }
+
+       public function testShowUnreadParams() {
+               $user = $this->getTestUser();
+               $otherUser = $this->getNonLoggedInTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $otherUser,
+                       $talkTarget,
+                       'Some Content',
+                       'Create the talk page'
+               );
+               $store = $this->getWatchedItemStore();
+               $store->addWatchBatchForUser( $user, [ $subjectTarget, $talkTarget ] );
+               $store->updateNotificationTimestamp(
+                       $otherUser,
+                       $talkTarget,
+                       '20151212010101'
+               );
+
+               $resultUnread = $this->doListWatchlistRequest( [
+                       'wlprop' => 'notificationtimestamp|title',
+                       'wlshow' => 'unread'
+               ] );
+               $resultNotUnread = $this->doListWatchlistRequest( [
+                       'wlprop' => 'notificationtimestamp|title',
+                       'wlshow' => '!unread'
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'notificationtimestamp' => '2015-12-12T01:01:01Z',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultUnread )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'notificationtimestamp' => '',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultNotUnread )
+               );
+       }
+
+       public function testShowPatrolledParams() {
+               $user = $this->getSysopTestUser();
+               $this->setupPatrolledSpecificFixtures( $user );
+
+               $resultPatrolled = $this->doListWatchlistRequest( [
+                       'wlprop' => 'patrol',
+                       'wlshow' => 'patrolled'
+               ], $user );
+               $resultNotPatrolled = $this->doListWatchlistRequest( [
+                       'wlprop' => 'patrol',
+                       'wlshow' => '!patrolled'
+               ], $user );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'patrolled' => true,
+                                       'unpatrolled' => false,
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultPatrolled )
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotPatrolled ) );
+       }
+
+       public function testNewAndEditTypeParameters() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Change the content',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $resultNew = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'new' ] );
+               $resultEdit = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'edit' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultNew )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'edit',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultEdit )
+               );
+       }
+
+       public function testLogTypeParameters() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->createPageAndDeleteIt( $subjectTarget );
+               $this->doPageEdit(
+                       $user,
+                       $talkTarget,
+                       'Some Talk Page Content',
+                       'Create Talk page'
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'log' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'log',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       private function getExternalRC( LinkTarget $target ) {
+               $title = Title::newFromLinkTarget( $target );
+
+               $rc = new RecentChange;
+               $rc->mTitle = $title;
+               $rc->mAttribs = [
+                       'rc_timestamp' => wfTimestamp( TS_MW ),
+                       'rc_namespace' => $title->getNamespace(),
+                       'rc_title' => $title->getDBkey(),
+                       'rc_type' => RC_EXTERNAL,
+                       'rc_source' => 'foo',
+                       'rc_minor' => 0,
+                       'rc_cur_id' => $title->getArticleID(),
+                       'rc_user' => 0,
+                       'rc_user_text' => 'External User',
+                       'rc_comment' => '',
+                       'rc_this_oldid' => $title->getLatestRevID(),
+                       'rc_last_oldid' => $title->getLatestRevID(),
+                       'rc_bot' => 0,
+                       'rc_ip' => '',
+                       'rc_patrolled' => 0,
+                       'rc_new' => 0,
+                       'rc_old_len' => $title->getLength(),
+                       'rc_new_len' => $title->getLength(),
+                       'rc_deleted' => 0,
+                       'rc_logid' => 0,
+                       'rc_log_type' => null,
+                       'rc_log_action' => '',
+                       'rc_params' => '',
+               ];
+               $rc->mExtra = [
+                       'prefixedDBkey' => $title->getPrefixedDBkey(),
+                       'lastTimestamp' => 0,
+                       'oldSize' => $title->getLength(),
+                       'newSize' => $title->getLength(),
+                       'pageStatus' => 'changed'
+               ];
+
+               return $rc;
+       }
+
+       public function testExternalTypeParameters() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $user,
+                       $talkTarget,
+                       'Some Talk Page Content',
+                       'Create Talk page'
+               );
+
+               $rc = $this->getExternalRC( $subjectTarget );
+               $rc->save();
+
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'external' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'external',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testCategorizeTypeParameter() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $categoryTarget = new TitleValue( NS_CATEGORY, 'ApiQueryWatchlistIntegrationTestCategory' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $categoryTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the category',
+                               ],
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content [[Category:ApiQueryWatchlistIntegrationTestCategory]]t',
+                                       'summary' => 'Create the page and add it to the category',
+                               ],
+                       ]
+               );
+               $title = Title::newFromLinkTarget( $subjectTarget );
+               $revision = Revision::newFromTitle( $title );
+
+               $rc = RecentChange::newForCategorization(
+                       $revision->getTimestamp(),
+                       Title::newFromLinkTarget( $categoryTarget ),
+                       $user,
+                       $revision->getComment(),
+                       $title,
+                       0,
+                       $revision->getId(),
+                       null,
+                       false
+               );
+               $rc->save();
+
+               $this->watchPages( $user, [ $subjectTarget, $categoryTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'categorize' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'categorize',
+                                       'ns' => $categoryTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $categoryTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testLimitParam() {
+               $user = $this->getTestUser();
+               $target1 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target2 = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target3 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage2' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target1,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target2,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                               [
+                                       'target' => $target3,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Create the page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target1, $target2, $target3 ] );
+
+               $resultWithoutLimit = $this->doListWatchlistRequest( [ 'wlprop' => 'title' ] );
+               $resultWithLimit = $this->doListWatchlistRequest( [ 'wllimit' => 2, 'wlprop' => 'title' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target3->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target3 )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target2->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target2 )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target1->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target1 )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultWithoutLimit )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target3->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target3 )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target2->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target2 )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultWithLimit )
+               );
+               $this->assertArrayHasKey( 'continue', $resultWithLimit[0] );
+               $this->assertArrayHasKey( 'wlcontinue', $resultWithLimit[0]['continue'] );
+       }
+
+       public function testAllRevParam() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Change the content',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultAllRev = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wlallrev' => '', ] );
+               $resultNoAllRev = $this->doListWatchlistRequest( [ 'wlprop' => 'title' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'edit',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultNoAllRev )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'edit',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultAllRev )
+               );
+       }
+
+       public function testDirParams() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $resultDirOlder = $this->doListWatchlistRequest( [ 'wldir' => 'older', 'wlprop' => 'title' ] );
+               $resultDirNewer = $this->doListWatchlistRequest( [ 'wldir' => 'newer', 'wlprop' => 'title' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultDirOlder )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultDirNewer )
+               );
+       }
+
+       public function testStartEndParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultStart = $this->doListWatchlistRequest( [
+                       'wlstart' => '20010115000000',
+                       'wldir' => 'newer',
+                       'wlprop' => 'title',
+               ] );
+               $resultEnd = $this->doListWatchlistRequest( [
+                       'wlend' => '20010115000000',
+                       'wldir' => 'newer',
+                       'wlprop' => 'title',
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultStart )
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultEnd ) );
+       }
+
+       public function testContinueParam() {
+               $user = $this->getTestUser();
+               $target1 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target2 = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target3 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage2' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target1,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target2,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                               [
+                                       'target' => $target3,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Create the page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target1, $target2, $target3 ] );
+
+               $firstResult = $this->doListWatchlistRequest( [ 'wllimit' => 2, 'wlprop' => 'title' ] );
+               $this->assertArrayHasKey( 'continue', $firstResult[0] );
+               $this->assertArrayHasKey( 'wlcontinue', $firstResult[0]['continue'] );
+
+               $continuationParam = $firstResult[0]['continue']['wlcontinue'];
+
+               $continuedResult = $this->doListWatchlistRequest(
+                       [ 'wlcontinue' => $continuationParam, 'wlprop' => 'title' ]
+               );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target3->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target3 ),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target2->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target2 ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $firstResult )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target1->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target1 )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $continuedResult )
+               );
+       }
+
+       public function testOwnerAndTokenParams() {
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $this->getTestUser(),
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+
+               $otherUser = $this->getNonLoggedInTestUser();
+               $otherUser->setOption( 'watchlisttoken', '1234567890' );
+               $otherUser->saveSettings();
+
+               $this->watchPages( $otherUser, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [
+                       'wlowner' => $otherUser->getName(),
+                       'wltoken' => '1234567890',
+                       'wlprop' => 'title',
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testOwnerAndTokenParams_wrongToken() {
+               $otherUser = $this->getNonLoggedInTestUser();
+               $otherUser->setOption( 'watchlisttoken', '1234567890' );
+               $otherUser->saveSettings();
+
+               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+
+               $this->doListWatchlistRequest( [
+                       'wlowner' => $otherUser->getName(),
+                       'wltoken' => 'wrong-token',
+               ] );
+       }
+
+       public function testOwnerAndTokenParams_noWatchlistTokenSet() {
+               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+
+               $this->doListWatchlistRequest( [
+                       'wlowner' => $this->getNonLoggedInTestUser()->getName(),
+                       'wltoken' => 'some-token',
+               ] );
+       }
+
+       public function testGeneratorWatchlistPropInfo_returnsWatchedPages() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doGeneratorWatchlistRequest( [ 'prop' => 'info' ] );
+
+               $this->assertArrayHasKey( 'query', $result[0] );
+               $this->assertArrayHasKey( 'pages', $result[0]['query'] );
+
+               // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them
+               $pages = array_values( $result[0]['query']['pages'] );
+
+               $this->assertArraySubsetsEqual(
+                       $pages,
+                       [
+                               [
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                                       'new' => true,
+                               ]
+                       ],
+                       [ 'ns', 'title', 'new' ]
+               );
+       }
+
+       public function testGeneratorWatchlistPropRevisions_returnsWatchedItemsRevisions() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Change the content',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doGeneratorWatchlistRequest( [ 'prop' => 'revisions', 'gwlallrev' => '' ] );
+
+               $this->assertArrayHasKey( 'query', $result[0] );
+               $this->assertArrayHasKey( 'pages', $result[0]['query'] );
+
+               // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them
+               $pages = array_values( $result[0]['query']['pages'] );
+
+               $this->assertCount( 1, $pages );
+               $this->assertEquals( 0, $pages[0]['ns'] );
+               $this->assertEquals( $this->getPrefixedText( $target ), $pages[0]['title'] );
+               $this->assertArraySubsetsEqual(
+                       $pages[0]['revisions'],
+                       [
+                               [
+                                       'comment' => 'Create the page',
+                                       'user' => $user->getName(),
+                                       'minor' => false,
+                               ],
+                               [
+                                       'comment' => 'Change the content',
+                                       'user' => $user->getName(),
+                                       'minor' => false,
+                               ],
+                       ],
+                       [ 'comment', 'user', 'minor' ]
+               );
+       }
+
+}
diff --git a/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php b/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php
new file mode 100644 (file)
index 0000000..ef4f513
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @author Addshore
+ * @covers ApiSetNotificationTimestamp
+ * @group API
+ * @group medium
+ * @group Database
+ */
+class ApiSetNotificationTimestampIntegrationTest extends ApiTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+               self::$users[__CLASS__] = new TestUser( __CLASS__ );
+               $this->doLogin( __CLASS__ );
+       }
+
+       public function testStuff() {
+               $user = self::$users[__CLASS__]->getUser();
+               $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+
+               $user->addWatch( $page->getTitle() );
+
+               $result = $this->doApiRequestWithToken(
+                       [
+                               'action' => 'setnotificationtimestamp',
+                               'timestamp' => '20160101020202',
+                               'pageids' => $page->getId(),
+                       ],
+                       null,
+                       $user
+               );
+
+               $this->assertEquals(
+                       [
+                               'batchcomplete' => true,
+                               'setnotificationtimestamp' => [
+                                       [ 'ns' => 0, 'title' => 'UTPage', 'notificationtimestamp' => '2016-01-01T02:02:02Z' ]
+                               ],
+                       ],
+                       $result[0]
+               );
+
+               $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
+               $this->assertEquals(
+                       $watchedItemStore->getNotificationTimestampsBatch( $user, [ $page->getTitle() ] ),
+                       [ [ 'UTPage' => '20160101020202' ] ]
+               );
+       }
+
+}
index 246ea3d..ff5640a 100644 (file)
@@ -14,7 +14,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
        protected $tablesUsed = [ 'user', 'user_groups', 'user_properties' ];
 
        protected function setUp() {
-               global $wgServer;
+               global $wgServer, $wgDisableAuthManager;
 
                parent::setUp();
                self::$apiUrl = $wgServer . wfScript( 'api' );
@@ -37,7 +37,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
                ];
 
                $this->setMwGlobals( [
-                       'wgAuth' => new AuthPlugin,
+                       'wgAuth' => $wgDisableAuthManager ? new AuthPlugin : new MediaWiki\Auth\AuthManagerAuthPlugin,
                        'wgRequest' => new FauxRequest( [] ),
                        'wgUser' => self::$users['sysop']->user,
                ] );
@@ -101,6 +101,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
                $wgRequest = new FauxRequest( $params, true, $session );
                RequestContext::getMain()->setRequest( $wgRequest );
                RequestContext::getMain()->setUser( $wgUser );
+               MediaWiki\Auth\AuthManager::resetCache();
 
                // set up local environment
                $context = $this->apiContext->newTestContext( $wgRequest, $wgUser );
@@ -183,6 +184,13 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
                        $data[2]
                );
 
+               if ( $data[0]['login']['result'] === 'Success' ) {
+                       // DWIM
+                       global $wgUser;
+                       $wgUser = self::$users[$user]->getUser();
+                       RequestContext::getMain()->setUser( $wgUser );
+               }
+
                return $data;
        }
 
diff --git a/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..1ded0df
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AbstractAuthenticationProvider
+ */
+class AbstractAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testAbstractAuthenticationProvider() {
+               $provider = $this->getMockForAbstractClass( AbstractAuthenticationProvider::class );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $obj = $this->getMockForAbstractClass( 'Psr\Log\LoggerInterface' );
+               $provider->setLogger( $obj );
+               $this->assertSame( $obj, $providerPriv->logger, 'setLogger' );
+
+               $obj = AuthManager::singleton();
+               $provider->setManager( $obj );
+               $this->assertSame( $obj, $providerPriv->manager, 'setManager' );
+
+               $obj = $this->getMockForAbstractClass( 'Config' );
+               $provider->setConfig( $obj );
+               $this->assertSame( $obj, $providerPriv->config, 'setConfig' );
+
+               $this->assertType( 'string', $provider->getUniqueId(), 'getUniqueId' );
+       }
+}
diff --git a/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..ecce932
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider
+ */
+class AbstractPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testConstructor() {
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $this->assertTrue( $providerPriv->authoritative );
+
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class,
+                       [ [ 'authoritative' => false ] ]
+               );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $this->assertFalse( $providerPriv->authoritative );
+       }
+
+       public function testGetPasswordFactory() {
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $provider->setConfig( \ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $obj = $providerPriv->getPasswordFactory();
+               $this->assertInstanceOf( 'PasswordFactory', $obj );
+               $this->assertSame( $obj, $providerPriv->getPasswordFactory() );
+       }
+
+       public function testGetPassword() {
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $provider->setConfig( \ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $obj = $providerPriv->getPassword( null );
+               $this->assertInstanceOf( 'Password', $obj );
+
+               $obj = $providerPriv->getPassword( 'invalid' );
+               $this->assertInstanceOf( 'Password', $obj );
+       }
+
+       public function testGetNewPasswordExpiry() {
+               $config = new \HashConfig;
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $provider->setConfig( new \MultiConfig( [
+                       $config,
+                       \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               ] ) );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'ResetPasswordExpiration' => [] ] );
+
+               $config->set( 'PasswordExpirationDays', 0 );
+               $this->assertNull( $providerPriv->getNewPasswordExpiry( 'UTSysop' ) );
+
+               $config->set( 'PasswordExpirationDays', 5 );
+               $this->assertEquals(
+                       time() + 5 * 86400,
+                       wfTimestamp( TS_UNIX, $providerPriv->getNewPasswordExpiry( 'UTSysop' ) ),
+                       '',
+                       2 /* Fuzz */
+               );
+
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'ResetPasswordExpiration' => [ function ( $user, &$expires ) {
+                               $this->assertSame( 'UTSysop', $user->getName() );
+                               $expires = '30001231235959';
+                       } ]
+               ] );
+               $this->assertEquals( '30001231235959', $providerPriv->getNewPasswordExpiry( 'UTSysop' ) );
+       }
+
+       public function testCheckPasswordValidity() {
+               $uppCalled = 0;
+               $uppStatus = \Status::newGood();
+               $this->setMwGlobals( [
+                       'wgPasswordPolicy' => [
+                               'policies' => [
+                                       'default' => [
+                                               'Check' => true,
+                                       ],
+                               ],
+                               'checks' => [
+                                       'Check' => function () use ( &$uppCalled, &$uppStatus ) {
+                                               $uppCalled++;
+                                               return $uppStatus;
+                                       },
+                               ],
+                       ]
+               ] );
+
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $provider->setConfig( \ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $this->assertEquals( $uppStatus, $providerPriv->checkPasswordValidity( 'foo', 'bar' ) );
+
+               $uppStatus->fatal( 'arbitrary-warning' );
+               $this->assertEquals( $uppStatus, $providerPriv->checkPasswordValidity( 'foo', 'bar' ) );
+       }
+
+       public function testSetPasswordResetFlag() {
+               $config = new \HashConfig( [
+                       'InvalidPasswordReset' => true,
+               ] );
+
+               $manager = new AuthManager(
+                       new \FauxRequest(), \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               );
+
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $provider->setConfig( $config );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setManager( $manager );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $manager->removeAuthenticationSessionData( null );
+               $status = \Status::newGood();
+               $providerPriv->setPasswordResetFlag( 'Foo', $status );
+               $this->assertNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+
+               $manager->removeAuthenticationSessionData( null );
+               $status = \Status::newGood();
+               $status->error( 'testing' );
+               $providerPriv->setPasswordResetFlag( 'Foo', $status );
+               $ret = $manager->getAuthenticationSessionData( 'reset-pass' );
+               $this->assertNotNull( $ret );
+               $this->assertSame( 'resetpass-validity-soft', $ret->msg->getKey() );
+               $this->assertFalse( $ret->hard );
+
+               $config->set( 'InvalidPasswordReset', false );
+               $manager->removeAuthenticationSessionData( null );
+               $providerPriv->setPasswordResetFlag( 'Foo', $status );
+               $ret = $manager->getAuthenticationSessionData( 'reset-pass' );
+               $this->assertNull( $ret );
+       }
+
+       public function testFailResponse() {
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class,
+                       [ [ 'authoritative' => false ] ]
+               );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $req = new PasswordAuthenticationRequest;
+
+               $ret = $providerPriv->failResponse( $req );
+               $this->assertSame( AuthenticationResponse::ABSTAIN, $ret->status );
+
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class,
+                       [ [ 'authoritative' => true ] ]
+               );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $req->password = '';
+               $ret = $providerPriv->failResponse( $req );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'wrongpasswordempty', $ret->message->getKey() );
+
+               $req->password = 'X';
+               $ret = $providerPriv->failResponse( $req );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'wrongpassword', $ret->message->getKey() );
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $response
+        */
+       public function testGetAuthenticationRequests( $action, $response ) {
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+
+               $this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) );
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               return [
+                       [ AuthManager::ACTION_LOGIN, [ new PasswordAuthenticationRequest() ] ],
+                       [ AuthManager::ACTION_CREATE, [ new PasswordAuthenticationRequest() ] ],
+                       [ AuthManager::ACTION_LINK, [] ],
+                       [ AuthManager::ACTION_CHANGE, [ new PasswordAuthenticationRequest() ] ],
+                       [ AuthManager::ACTION_REMOVE, [ new PasswordAuthenticationRequest() ] ],
+               ];
+       }
+
+       public function testProviderRevokeAccessForUser() {
+               $req = new PasswordAuthenticationRequest;
+               $req->action = AuthManager::ACTION_REMOVE;
+               $req->username = 'foo';
+               $req->password = null;
+
+               $provider = $this->getMockForAbstractClass(
+                       AbstractPasswordPrimaryAuthenticationProvider::class
+               );
+               $provider->expects( $this->once() )
+                       ->method( 'providerChangeAuthenticationData' )
+                       ->with( $this->equalTo( $req ) );
+
+               $provider->providerRevokeAccessForUser( 'foo' );
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/AbstractPreAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractPreAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..c35430e
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AbstractPreAuthenticationProvider
+ */
+class AbstractPreAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testAbstractPreAuthenticationProvider() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $provider = $this->getMockForAbstractClass( AbstractPreAuthenticationProvider::class );
+
+               $this->assertEquals(
+                       [],
+                       $provider->getAuthenticationRequests( AuthManager::ACTION_LOGIN, [] )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAuthentication( [] )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, [] )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $user, AuthManager::AUTOCREATE_SOURCE_SESSION )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $user, false )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountLink( $user )
+               );
+
+               $res = AuthenticationResponse::newPass();
+               $provider->postAuthentication( $user, $res );
+               $provider->postAccountCreation( $user, $user, $res );
+               $provider->postAccountLink( $user, $res );
+       }
+}
diff --git a/tests/phpunit/includes/auth/AbstractPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractPrimaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..420a330
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AbstractPrimaryAuthenticationProvider
+ */
+class AbstractPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testAbstractPrimaryAuthenticationProvider() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $provider = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
+
+               try {
+                       $provider->continuePrimaryAuthentication( [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+               }
+
+               try {
+                       $provider->continuePrimaryAccountCreation( $user, $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+               }
+
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+
+               $this->assertTrue( $provider->providerAllowsPropertyChange( 'foo' ) );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, [] )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $user, AuthManager::AUTOCREATE_SOURCE_SESSION )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $user, false )
+               );
+
+               $this->assertNull(
+                       $provider->finishAccountCreation( $user, $user, AuthenticationResponse::newPass() )
+               );
+               $provider->autoCreatedAccount( $user, AuthManager::AUTOCREATE_SOURCE_SESSION );
+
+               $res = AuthenticationResponse::newPass();
+               $provider->postAuthentication( $user, $res );
+               $provider->postAccountCreation( $user, $user, $res );
+               $provider->postAccountLink( $user, $res );
+
+               $provider->expects( $this->once() )
+                       ->method( 'testUserExists' )
+                       ->with( $this->equalTo( 'foo' ) )
+                       ->will( $this->returnValue( true ) );
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'foo' ) );
+       }
+
+       public function testProviderRevokeAccessForUser() {
+               $reqs = [];
+               for ( $i = 0; $i < 3; $i++ ) {
+                       $reqs[$i] = $this->getMock( AuthenticationRequest::class );
+                       $reqs[$i]->done = false;
+               }
+
+               $provider = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
+               $provider->expects( $this->once() )->method( 'getAuthenticationRequests' )
+                       ->with(
+                               $this->identicalTo( AuthManager::ACTION_REMOVE ),
+                               $this->identicalTo( [ 'username' => 'UTSysop' ] )
+                       )
+                       ->will( $this->returnValue( $reqs ) );
+               $provider->expects( $this->exactly( 3 ) )->method( 'providerChangeAuthenticationData' )
+                       ->will( $this->returnCallback( function ( $req ) {
+                               $this->assertSame( 'UTSysop', $req->username );
+                               $this->assertFalse( $req->done );
+                               $req->done = true;
+                       } ) );
+
+               $provider->providerRevokeAccessForUser( 'UTSysop' );
+
+               foreach ( $reqs as $i => $req ) {
+                       $this->assertTrue( $req->done, "#$i" );
+               }
+       }
+
+       /**
+        * @dataProvider providePrimaryAccountLink
+        * @param string $type PrimaryAuthenticationProvider::TYPE_* constant
+        * @param string $msg Error message from beginPrimaryAccountLink
+        */
+       public function testPrimaryAccountLink( $type, $msg ) {
+               $provider = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
+               $provider->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( $type ) );
+
+               $class = AbstractPrimaryAuthenticationProvider::class;
+               $msg1 = "{$class}::beginPrimaryAccountLink $msg";
+               $msg2 = "{$class}::continuePrimaryAccountLink is not implemented.";
+
+               $user = \User::newFromName( 'Whatever' );
+
+               try {
+                       $provider->beginPrimaryAccountLink( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+                       $this->assertSame( $msg1, $ex->getMessage() );
+               }
+               try {
+                       $provider->continuePrimaryAccountLink( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+                       $this->assertSame( $msg2, $ex->getMessage() );
+               }
+       }
+
+       public static function providePrimaryAccountLink() {
+               return [
+                       [
+                               PrimaryAuthenticationProvider::TYPE_NONE,
+                               'should not be called on a non-link provider.',
+                       ],
+                       [
+                               PrimaryAuthenticationProvider::TYPE_CREATE,
+                               'should not be called on a non-link provider.',
+                       ],
+                       [
+                               PrimaryAuthenticationProvider::TYPE_LINK,
+                               'is not implemented.',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideProviderNormalizeUsername
+        */
+       public function testProviderNormalizeUsername( $name, $expect ) {
+               // fake interwiki map for the 'Interwiki prefix' testcase
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'InterwikiLoadPrefix' => [
+                               function ( $prefix, &$iwdata ) {
+                                       if ( $prefix === 'interwiki' ) {
+                                               $iwdata = [
+                                                       'iw_url' => 'http://example.com/',
+                                                       'iw_local' => 0,
+                                                       'iw_trans' => 0,
+                                               ];
+                                               return false;
+                                       }
+                               },
+                       ],
+               ] );
+
+               $provider = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
+               $this->assertSame( $expect, $provider->providerNormalizeUsername( $name ) );
+       }
+
+       public static function provideProviderNormalizeUsername() {
+               return [
+                       'Leading space' => [ ' Leading space', 'Leading space' ],
+                       'Trailing space ' => [ 'Trailing space ', 'Trailing space' ],
+                       'Namespace prefix' => [ 'Talk:Username', null ],
+                       'Interwiki prefix' => [ 'interwiki:Username', null ],
+                       'With hash' => [ 'name with # hash', null ],
+                       'Multi spaces' => [ 'Multi  spaces', 'Multi spaces' ],
+                       'Lowercase' => [ 'lowercase', 'Lowercase' ],
+                       'Invalid character' => [ 'in[]valid', null ],
+                       'With slash' => [ 'with / slash', null ],
+                       'Underscores' => [ '___under__scores___', 'Under scores' ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/AbstractSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AbstractSecondaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..9cdc051
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AbstractSecondaryAuthenticationProvider
+ */
+class AbstractSecondaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testAbstractSecondaryAuthenticationProvider() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $provider = $this->getMockForAbstractClass( AbstractSecondaryAuthenticationProvider::class );
+
+               try {
+                       $provider->continueSecondaryAuthentication( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+               }
+
+               try {
+                       $provider->continueSecondaryAccountCreation( $user, $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+               }
+
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+
+               $this->assertTrue( $provider->providerAllowsPropertyChange( 'foo' ) );
+               $this->assertEquals(
+                       \StatusValue::newGood( 'ignored' ),
+                       $provider->providerAllowsAuthenticationDataChange( $req )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, [] )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $user, AuthManager::AUTOCREATE_SOURCE_SESSION )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $user, false )
+               );
+
+               $provider->providerChangeAuthenticationData( $req );
+               $provider->autoCreatedAccount( $user, AuthManager::AUTOCREATE_SOURCE_SESSION );
+
+               $res = AuthenticationResponse::newPass();
+               $provider->postAuthentication( $user, $res );
+               $provider->postAccountCreation( $user, $user, $res );
+       }
+
+       public function testProviderRevokeAccessForUser() {
+               $reqs = [];
+               for ( $i = 0; $i < 3; $i++ ) {
+                       $reqs[$i] = $this->getMock( AuthenticationRequest::class );
+                       $reqs[$i]->done = false;
+               }
+
+               $provider = $this->getMockBuilder( AbstractSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'providerChangeAuthenticationData' ] )
+                       ->getMockForAbstractClass();
+               $provider->expects( $this->once() )->method( 'getAuthenticationRequests' )
+                       ->with(
+                               $this->identicalTo( AuthManager::ACTION_REMOVE ),
+                               $this->identicalTo( [ 'username' => 'UTSysop' ] )
+                       )
+                       ->will( $this->returnValue( $reqs ) );
+               $provider->expects( $this->exactly( 3 ) )->method( 'providerChangeAuthenticationData' )
+                       ->will( $this->returnCallback( function ( $req ) {
+                               $this->assertSame( 'UTSysop', $req->username );
+                               $this->assertFalse( $req->done );
+                               $req->done = true;
+                       } ) );
+
+               $provider->providerRevokeAccessForUser( 'UTSysop' );
+
+               foreach ( $reqs as $i => $req ) {
+                       $this->assertTrue( $req->done, "#$i" );
+               }
+       }
+}
diff --git a/tests/phpunit/includes/auth/AuthManagerTest.php b/tests/phpunit/includes/auth/AuthManagerTest.php
new file mode 100644 (file)
index 0000000..377abe2
--- /dev/null
@@ -0,0 +1,3654 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+use MediaWiki\Session\SessionInfo;
+use MediaWiki\Session\UserInfo;
+use Psr\Log\LogLevel;
+use StatusValue;
+
+/**
+ * @group AuthManager
+ * @group Database
+ * @covers MediaWiki\Auth\AuthManager
+ */
+class AuthManagerTest extends \MediaWikiTestCase {
+       /** @var WebRequest */
+       protected $request;
+       /** @var Config */
+       protected $config;
+       /** @var \\Psr\\Log\\LoggerInterface */
+       protected $logger;
+
+       protected $preauthMocks = [];
+       protected $primaryauthMocks = [];
+       protected $secondaryauthMocks = [];
+
+       /** @var AuthManager */
+       protected $manager;
+       /** @var TestingAccessWrapper */
+       protected $managerPriv;
+
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+
+               $this->setMwGlobals( [ 'wgAuth' => null ] );
+               $this->stashMwGlobals( [ 'wgHooks' ] );
+       }
+
+       /**
+        * Sets a mock on a hook
+        * @param string $hook
+        * @param object $expect From $this->once(), $this->never(), etc.
+        * @return object $mock->expects( $expect )->method( ... ).
+        */
+       protected function hook( $hook, $expect ) {
+               global $wgHooks;
+               $mock = $this->getMock( __CLASS__, [ "on$hook" ] );
+               $wgHooks[$hook] = [ $mock ];
+               return $mock->expects( $expect )->method( "on$hook" );
+       }
+
+       /**
+        * Unsets a hook
+        * @param string $hook
+        */
+       protected function unhook( $hook ) {
+               global $wgHooks;
+               $wgHooks[$hook] = [];
+       }
+
+       /**
+        * Ensure a value is a clean Message object
+        * @param string|Message $key
+        * @param array $params
+        * @return Message
+        */
+       protected function message( $key, $params = [] ) {
+               if ( $key === null ) {
+                       return null;
+               }
+               if ( $key instanceof \MessageSpecifier ) {
+                       $params = $key->getParams();
+                       $key = $key->getKey();
+               }
+               return new \Message( $key, $params, \Language::factory( 'en' ) );
+       }
+
+       /**
+        * Initialize the AuthManagerConfig variable in $this->config
+        *
+        * Uses data from the various 'mocks' fields.
+        */
+       protected function initializeConfig() {
+               $config = [
+                       'preauth' => [
+                       ],
+                       'primaryauth' => [
+                       ],
+                       'secondaryauth' => [
+                       ],
+               ];
+
+               foreach ( [ 'preauth', 'primaryauth', 'secondaryauth' ] as $type ) {
+                       $key = $type . 'Mocks';
+                       foreach ( $this->$key as $mock ) {
+                               $config[$type][$mock->getUniqueId()] = [ 'factory' => function () use ( $mock ) {
+                                       return $mock;
+                               } ];
+                       }
+               }
+
+               $this->config->set( 'AuthManagerConfig', $config );
+               $this->config->set( 'LanguageCode', 'en' );
+               $this->config->set( 'NewUserLog', false );
+       }
+
+       /**
+        * Initialize $this->manager
+        * @param bool $regen Force a call to $this->initializeConfig()
+        */
+       protected function initializeManager( $regen = false ) {
+               if ( $regen || !$this->config ) {
+                       $this->config = new \HashConfig();
+               }
+               if ( $regen || !$this->request ) {
+                       $this->request = new \FauxRequest();
+               }
+               if ( !$this->logger ) {
+                       $this->logger = new \TestLogger();
+               }
+
+               if ( $regen || !$this->config->has( 'AuthManagerConfig' ) ) {
+                       $this->initializeConfig();
+               }
+               $this->manager = new AuthManager( $this->request, $this->config );
+               $this->manager->setLogger( $this->logger );
+               $this->managerPriv = \TestingAccessWrapper::newFromObject( $this->manager );
+       }
+
+       /**
+        * Setup SessionManager with a mock session provider
+        * @param bool|null $canChangeUser If non-null, canChangeUser will be mocked to return this
+        * @param array $methods Additional methods to mock
+        * @return array (MediaWiki\Session\SessionProvider, ScopedCallback)
+        */
+       protected function getMockSessionProvider( $canChangeUser = null, array $methods = [] ) {
+               if ( !$this->config ) {
+                       $this->config = new \HashConfig();
+                       $this->initializeConfig();
+               }
+               $this->config->set( 'ObjectCacheSessionExpiry', 100 );
+
+               $methods[] = '__toString';
+               $methods[] = 'describe';
+               if ( $canChangeUser !== null ) {
+                       $methods[] = 'canChangeUser';
+               }
+               $provider = $this->getMockBuilder( 'DummySessionProvider' )
+                       ->setMethods( $methods )
+                       ->getMock();
+               $provider->expects( $this->any() )->method( '__toString' )
+                       ->will( $this->returnValue( 'MockSessionProvider' ) );
+               $provider->expects( $this->any() )->method( 'describe' )
+                       ->will( $this->returnValue( 'MockSessionProvider sessions' ) );
+               if ( $canChangeUser !== null ) {
+                       $provider->expects( $this->any() )->method( 'canChangeUser' )
+                               ->will( $this->returnValue( $canChangeUser ) );
+               }
+               $this->config->set( 'SessionProviders', [
+                       [ 'factory' => function () use ( $provider ) {
+                               return $provider;
+                       } ],
+               ] );
+
+               $manager = new \MediaWiki\Session\SessionManager( [
+                       'config' => $this->config,
+                       'logger' => new \Psr\Log\NullLogger(),
+                       'store' => new \HashBagOStuff(),
+               ] );
+               \TestingAccessWrapper::newFromObject( $manager )->getProvider( (string)$provider );
+
+               $reset = \MediaWiki\Session\TestUtils::setSessionManagerSingleton( $manager );
+
+               if ( $this->request ) {
+                       $manager->getSessionForRequest( $this->request );
+               }
+
+               return [ $provider, $reset ];
+       }
+
+       public function testSingleton() {
+               // Temporarily clear out the global singleton, if any, to test creating
+               // one.
+               $rProp = new \ReflectionProperty( AuthManager::class, 'instance' );
+               $rProp->setAccessible( true );
+               $old = $rProp->getValue();
+               $cb = new \ScopedCallback( [ $rProp, 'setValue' ], [ $old ] );
+               $rProp->setValue( null );
+
+               $singleton = AuthManager::singleton();
+               $this->assertInstanceOf( AuthManager::class, AuthManager::singleton() );
+               $this->assertSame( $singleton, AuthManager::singleton() );
+               $this->assertSame( \RequestContext::getMain()->getRequest(), $singleton->getRequest() );
+               $this->assertSame(
+                       \RequestContext::getMain()->getConfig(),
+                       \TestingAccessWrapper::newFromObject( $singleton )->config
+               );
+
+               $this->setMwGlobals( [ 'wgDisableAuthManager' => true ] );
+               try {
+                       AuthManager::singleton();
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+                       $this->assertSame( '$wgDisableAuthManager is set', $ex->getMessage() );
+               }
+       }
+
+       public function testCanAuthenticateNow() {
+               $this->initializeManager();
+
+               list( $provider, $reset ) = $this->getMockSessionProvider( false );
+               $this->assertFalse( $this->manager->canAuthenticateNow() );
+               \ScopedCallback::consume( $reset );
+
+               list( $provider, $reset ) = $this->getMockSessionProvider( true );
+               $this->assertTrue( $this->manager->canAuthenticateNow() );
+               \ScopedCallback::consume( $reset );
+       }
+
+       public function testNormalizeUsername() {
+               $mocks = [
+                       $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
+                       $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
+                       $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
+                       $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
+               ];
+               foreach ( $mocks as $key => $mock ) {
+                       $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $key ) );
+               }
+               $mocks[0]->expects( $this->once() )->method( 'providerNormalizeUsername' )
+                       ->with( $this->identicalTo( 'XYZ' ) )
+                       ->willReturn( 'Foo' );
+               $mocks[1]->expects( $this->once() )->method( 'providerNormalizeUsername' )
+                       ->with( $this->identicalTo( 'XYZ' ) )
+                       ->willReturn( 'Foo' );
+               $mocks[2]->expects( $this->once() )->method( 'providerNormalizeUsername' )
+                       ->with( $this->identicalTo( 'XYZ' ) )
+                       ->willReturn( null );
+               $mocks[3]->expects( $this->once() )->method( 'providerNormalizeUsername' )
+                       ->with( $this->identicalTo( 'XYZ' ) )
+                       ->willReturn( 'Bar!' );
+
+               $this->primaryauthMocks = $mocks;
+
+               $this->initializeManager();
+
+               $this->assertSame( [ 'Foo', 'Bar!' ], $this->manager->normalizeUsername( 'XYZ' ) );
+       }
+
+       /**
+        * @dataProvider provideSecuritySensitiveOperationStatus
+        * @param bool $mutableSession
+        */
+       public function testSecuritySensitiveOperationStatus( $mutableSession ) {
+               $this->logger = new \Psr\Log\NullLogger();
+               $user = \User::newFromName( 'UTSysop' );
+               $provideUser = null;
+               $reauth = $mutableSession ? AuthManager::SEC_REAUTH : AuthManager::SEC_FAIL;
+
+               list( $provider, $reset ) = $this->getMockSessionProvider(
+                       $mutableSession, [ 'provideSessionInfo' ]
+               );
+               $provider->expects( $this->any() )->method( 'provideSessionInfo' )
+                       ->will( $this->returnCallback( function () use ( $provider, &$provideUser ) {
+                               return new SessionInfo( SessionInfo::MIN_PRIORITY, [
+                                       'provider' => $provider,
+                                       'id' => \DummySessionProvider::ID,
+                                       'persisted' => true,
+                                       'userInfo' => UserInfo::newFromUser( $provideUser, true )
+                               ] );
+                       } ) );
+               $this->initializeManager();
+
+               $this->config->set( 'ReauthenticateTime', [] );
+               $this->config->set( 'AllowSecuritySensitiveOperationIfCannotReauthenticate', [] );
+               $provideUser = new \User;
+               $session = $provider->getManager()->getSessionForRequest( $this->request );
+               $this->assertSame( 0, $session->getUser()->getId(), 'sanity check' );
+
+               // Anonymous user => reauth
+               $session->set( 'AuthManager:lastAuthId', 0 );
+               $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
+               $this->assertSame( $reauth, $this->manager->securitySensitiveOperationStatus( 'foo' ) );
+
+               $provideUser = $user;
+               $session = $provider->getManager()->getSessionForRequest( $this->request );
+               $this->assertSame( $user->getId(), $session->getUser()->getId(), 'sanity check' );
+
+               // Error for no default (only gets thrown for non-anonymous user)
+               $session->set( 'AuthManager:lastAuthId', $user->getId() + 1 );
+               $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
+               try {
+                       $this->manager->securitySensitiveOperationStatus( 'foo' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               $mutableSession
+                                       ? '$wgReauthenticateTime lacks a default'
+                                       : '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default',
+                               $ex->getMessage()
+                       );
+               }
+
+               if ( $mutableSession ) {
+                       $this->config->set( 'ReauthenticateTime', [
+                               'test' => 100,
+                               'test2' => -1,
+                               'default' => 10,
+                       ] );
+
+                       // Mismatched user ID
+                       $session->set( 'AuthManager:lastAuthId', $user->getId() + 1 );
+                       $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
+                       $this->assertSame(
+                               AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
+                       );
+                       $this->assertSame(
+                               AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'test' )
+                       );
+                       $this->assertSame(
+                               AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test2' )
+                       );
+
+                       // Missing time
+                       $session->set( 'AuthManager:lastAuthId', $user->getId() );
+                       $session->set( 'AuthManager:lastAuthTimestamp', null );
+                       $this->assertSame(
+                               AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
+                       );
+                       $this->assertSame(
+                               AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'test' )
+                       );
+                       $this->assertSame(
+                               AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test2' )
+                       );
+
+                       // Recent enough to pass
+                       $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
+                       $this->assertSame(
+                               AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'foo' )
+                       );
+
+                       // Not recent enough to pass
+                       $session->set( 'AuthManager:lastAuthTimestamp', time() - 20 );
+                       $this->assertSame(
+                               AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
+                       );
+                       // But recent enough for the 'test' operation
+                       $this->assertSame(
+                               AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test' )
+                       );
+               } else {
+                       $this->config->set( 'AllowSecuritySensitiveOperationIfCannotReauthenticate', [
+                               'test' => false,
+                               'default' => true,
+                       ] );
+
+                       $this->assertEquals(
+                               AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'foo' )
+                       );
+
+                       $this->assertEquals(
+                               AuthManager::SEC_FAIL, $this->manager->securitySensitiveOperationStatus( 'test' )
+                       );
+               }
+
+               // Test hook, all three possible values
+               foreach ( [
+                       AuthManager::SEC_OK => AuthManager::SEC_OK,
+                       AuthManager::SEC_REAUTH => $reauth,
+                       AuthManager::SEC_FAIL => AuthManager::SEC_FAIL,
+               ] as $hook => $expect ) {
+                       $this->hook( 'SecuritySensitiveOperationStatus', $this->exactly( 2 ) )
+                               ->with(
+                                       $this->anything(),
+                                       $this->anything(),
+                                       $this->callback( function ( $s ) use ( $session ) {
+                                               return $s->getId() === $session->getId();
+                                       } ),
+                                       $mutableSession ? $this->equalTo( 500, 1 ) : $this->equalTo( -1 )
+                               )
+                               ->will( $this->returnCallback( function ( &$v ) use ( $hook ) {
+                                       $v = $hook;
+                                       return true;
+                               } ) );
+                       $session->set( 'AuthManager:lastAuthTimestamp', time() - 500 );
+                       $this->assertEquals(
+                               $expect, $this->manager->securitySensitiveOperationStatus( 'test' ), "hook $hook"
+                       );
+                       $this->assertEquals(
+                               $expect, $this->manager->securitySensitiveOperationStatus( 'test2' ), "hook $hook"
+                       );
+                       $this->unhook( 'SecuritySensitiveOperationStatus' );
+               }
+
+               \ScopedCallback::consume( $reset );
+       }
+
+       public function onSecuritySensitiveOperationStatus( &$status, $operation, $session, $time ) {
+       }
+
+       public static function provideSecuritySensitiveOperationStatus() {
+               return [
+                       [ true ],
+                       [ false ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideUserCanAuthenticate
+        * @param bool $primary1Can
+        * @param bool $primary2Can
+        * @param bool $expect
+        */
+       public function testUserCanAuthenticate( $primary1Can, $primary2Can, $expect ) {
+               $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock1->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary1' ) );
+               $mock1->expects( $this->any() )->method( 'testUserCanAuthenticate' )
+                       ->with( $this->equalTo( 'UTSysop' ) )
+                       ->will( $this->returnValue( $primary1Can ) );
+               $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary2' ) );
+               $mock2->expects( $this->any() )->method( 'testUserCanAuthenticate' )
+                       ->with( $this->equalTo( 'UTSysop' ) )
+                       ->will( $this->returnValue( $primary2Can ) );
+               $this->primaryauthMocks = [ $mock1, $mock2 ];
+
+               $this->initializeManager( true );
+               $this->assertSame( $expect, $this->manager->userCanAuthenticate( 'UTSysop' ) );
+       }
+
+       public static function provideUserCanAuthenticate() {
+               return [
+                       [ false, false, false ],
+                       [ true, false, true ],
+                       [ false, true, true ],
+                       [ true, true, true ],
+               ];
+       }
+
+       public function testRevokeAccessForUser() {
+               $this->initializeManager();
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary' ) );
+               $mock->expects( $this->once() )->method( 'providerRevokeAccessForUser' )
+                       ->with( $this->equalTo( 'UTSysop' ) );
+               $this->primaryauthMocks = [ $mock ];
+
+               $this->initializeManager( true );
+               $this->logger->setCollect( true );
+
+               $this->manager->revokeAccessForUser( 'UTSysop' );
+
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'Revoking access for {user}' ],
+               ], $this->logger->getBuffer() );
+       }
+
+       public function testProviderCreation() {
+               $mocks = [
+                       'pre' => $this->getMockForAbstractClass( PreAuthenticationProvider::class ),
+                       'primary' => $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
+                       'secondary' => $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class ),
+               ];
+               foreach ( $mocks as $key => $mock ) {
+                       $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $key ) );
+                       $mock->expects( $this->once() )->method( 'setLogger' );
+                       $mock->expects( $this->once() )->method( 'setManager' );
+                       $mock->expects( $this->once() )->method( 'setConfig' );
+               }
+               $this->preauthMocks = [ $mocks['pre'] ];
+               $this->primaryauthMocks = [ $mocks['primary'] ];
+               $this->secondaryauthMocks = [ $mocks['secondary'] ];
+
+               // Normal operation
+               $this->initializeManager();
+               $this->assertSame(
+                       $mocks['primary'],
+                       $this->managerPriv->getAuthenticationProvider( 'primary' )
+               );
+               $this->assertSame(
+                       $mocks['secondary'],
+                       $this->managerPriv->getAuthenticationProvider( 'secondary' )
+               );
+               $this->assertSame(
+                       $mocks['pre'],
+                       $this->managerPriv->getAuthenticationProvider( 'pre' )
+               );
+               $this->assertSame(
+                       [ 'pre' => $mocks['pre'] ],
+                       $this->managerPriv->getPreAuthenticationProviders()
+               );
+               $this->assertSame(
+                       [ 'primary' => $mocks['primary'] ],
+                       $this->managerPriv->getPrimaryAuthenticationProviders()
+               );
+               $this->assertSame(
+                       [ 'secondary' => $mocks['secondary'] ],
+                       $this->managerPriv->getSecondaryAuthenticationProviders()
+               );
+
+               // Duplicate IDs
+               $mock1 = $this->getMockForAbstractClass( PreAuthenticationProvider::class );
+               $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $this->preauthMocks = [ $mock1 ];
+               $this->primaryauthMocks = [ $mock2 ];
+               $this->secondaryauthMocks = [];
+               $this->initializeManager( true );
+               try {
+                       $this->managerPriv->getAuthenticationProvider( 'Y' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \RuntimeException $ex ) {
+                       $class1 = get_class( $mock1 );
+                       $class2 = get_class( $mock2 );
+                       $this->assertSame(
+                               "Duplicate specifications for id X (classes $class1 and $class2)", $ex->getMessage()
+                       );
+               }
+
+               // Wrong classes
+               $mock = $this->getMockForAbstractClass( AuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $class = get_class( $mock );
+               $this->preauthMocks = [ $mock ];
+               $this->primaryauthMocks = [ $mock ];
+               $this->secondaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+               try {
+                       $this->managerPriv->getPreAuthenticationProviders();
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \RuntimeException $ex ) {
+                       $this->assertSame(
+                               "Expected instance of MediaWiki\\Auth\\PreAuthenticationProvider, got $class",
+                               $ex->getMessage()
+                       );
+               }
+               try {
+                       $this->managerPriv->getPrimaryAuthenticationProviders();
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \RuntimeException $ex ) {
+                       $this->assertSame(
+                               "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
+                               $ex->getMessage()
+                       );
+               }
+               try {
+                       $this->managerPriv->getSecondaryAuthenticationProviders();
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \RuntimeException $ex ) {
+                       $this->assertSame(
+                               "Expected instance of MediaWiki\\Auth\\SecondaryAuthenticationProvider, got $class",
+                               $ex->getMessage()
+                       );
+               }
+
+               // Sorting
+               $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock3 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'A' ) );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
+               $mock3->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'C' ) );
+               $this->preauthMocks = [];
+               $this->primaryauthMocks = [ $mock1, $mock2, $mock3 ];
+               $this->secondaryauthMocks = [];
+               $this->initializeConfig();
+               $config = $this->config->get( 'AuthManagerConfig' );
+
+               $this->initializeManager( false );
+               $this->assertSame(
+                       [ 'A' => $mock1, 'B' => $mock2, 'C' => $mock3 ],
+                       $this->managerPriv->getPrimaryAuthenticationProviders(),
+                       'sanity check'
+               );
+
+               $config['primaryauth']['A']['sort'] = 100;
+               $config['primaryauth']['C']['sort'] = -1;
+               $this->config->set( 'AuthManagerConfig', $config );
+               $this->initializeManager( false );
+               $this->assertSame(
+                       [ 'C' => $mock3, 'B' => $mock2, 'A' => $mock1 ],
+                       $this->managerPriv->getPrimaryAuthenticationProviders()
+               );
+       }
+
+       public function testSetDefaultUserOptions() {
+               $this->initializeManager();
+
+               $context = \RequestContext::getMain();
+               $reset = new \ScopedCallback( [ $context, 'setLanguage' ], [ $context->getLanguage() ] );
+               $context->setLanguage( 'de' );
+               $this->setMwGlobals( 'wgContLang', \Language::factory( 'zh' ) );
+
+               $user = \User::newFromName( self::usernameForCreation() );
+               $user->addToDatabase();
+               $oldToken = $user->getToken();
+               $this->managerPriv->setDefaultUserOptions( $user, false );
+               $user->saveSettings();
+               $this->assertNotEquals( $oldToken, $user->getToken() );
+               $this->assertSame( 'zh', $user->getOption( 'language' ) );
+               $this->assertSame( 'zh', $user->getOption( 'variant' ) );
+
+               $user = \User::newFromName( self::usernameForCreation() );
+               $user->addToDatabase();
+               $oldToken = $user->getToken();
+               $this->managerPriv->setDefaultUserOptions( $user, true );
+               $user->saveSettings();
+               $this->assertNotEquals( $oldToken, $user->getToken() );
+               $this->assertSame( 'de', $user->getOption( 'language' ) );
+               $this->assertSame( 'zh', $user->getOption( 'variant' ) );
+
+               $this->setMwGlobals( 'wgContLang', \Language::factory( 'en' ) );
+
+               $user = \User::newFromName( self::usernameForCreation() );
+               $user->addToDatabase();
+               $oldToken = $user->getToken();
+               $this->managerPriv->setDefaultUserOptions( $user, true );
+               $user->saveSettings();
+               $this->assertNotEquals( $oldToken, $user->getToken() );
+               $this->assertSame( 'de', $user->getOption( 'language' ) );
+               $this->assertSame( null, $user->getOption( 'variant' ) );
+       }
+
+       public function testForcePrimaryAuthenticationProviders() {
+               $mockA = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mockB = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mockB2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mockA->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'A' ) );
+               $mockB->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
+               $mockB2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
+               $this->primaryauthMocks = [ $mockA ];
+
+               $this->logger = new \TestLogger( true );
+
+               // Test without first initializing the configured providers
+               $this->initializeManager();
+               $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ], 'testing' );
+               $this->assertSame(
+                       [ 'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
+               );
+               $this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'A' ) );
+               $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider( 'B' ) );
+               $this->assertSame( [
+                       [ LogLevel::WARNING, 'Overriding AuthManager primary authn because testing' ],
+               ], $this->logger->getBuffer() );
+               $this->logger->clearBuffer();
+
+               // Test with first initializing the configured providers
+               $this->initializeManager();
+               $this->assertSame( $mockA, $this->managerPriv->getAuthenticationProvider( 'A' ) );
+               $this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'B' ) );
+               $this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', 'test' );
+               $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ], 'testing' );
+               $this->assertSame(
+                       [ 'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
+               );
+               $this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'A' ) );
+               $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider( 'B' ) );
+               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+               $this->assertSame( [
+                       [ LogLevel::WARNING, 'Overriding AuthManager primary authn because testing' ],
+                       [
+                               LogLevel::WARNING,
+                               'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
+                       ],
+               ], $this->logger->getBuffer() );
+               $this->logger->clearBuffer();
+
+               // Test duplicate IDs
+               $this->initializeManager();
+               try {
+                       $this->manager->forcePrimaryAuthenticationProviders( [ $mockB, $mockB2 ], 'testing' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \RuntimeException $ex ) {
+                       $class1 = get_class( $mockB );
+                       $class2 = get_class( $mockB2 );
+                       $this->assertSame(
+                               "Duplicate specifications for id B (classes $class2 and $class1)", $ex->getMessage()
+                       );
+               }
+
+               // Wrong classes
+               $mock = $this->getMockForAbstractClass( AuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $class = get_class( $mock );
+               try {
+                       $this->manager->forcePrimaryAuthenticationProviders( [ $mock ], 'testing' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \RuntimeException $ex ) {
+                       $this->assertSame(
+                               "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
+                               $ex->getMessage()
+                       );
+               }
+
+       }
+
+       public function testBeginAuthentication() {
+               $this->initializeManager();
+
+               // Immutable session
+               list( $provider, $reset ) = $this->getMockSessionProvider( false );
+               $this->hook( 'UserLoggedIn', $this->never() );
+               $this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
+               try {
+                       $this->manager->beginAuthentication( [], 'http://localhost/' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \LogicException $ex ) {
+                       $this->assertSame( 'Authentication is not possible now', $ex->getMessage() );
+               }
+               $this->unhook( 'UserLoggedIn' );
+               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
+               \ScopedCallback::consume( $reset );
+               $this->initializeManager( true );
+
+               // CreatedAccountAuthenticationRequest
+               $user = \User::newFromName( 'UTSysop' );
+               $reqs = [
+                       new CreatedAccountAuthenticationRequest( $user->getId(), $user->getName() )
+               ];
+               $this->hook( 'UserLoggedIn', $this->never() );
+               try {
+                       $this->manager->beginAuthentication( $reqs, 'http://localhost/' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \LogicException $ex ) {
+                       $this->assertSame(
+                               'CreatedAccountAuthenticationRequests are only valid on the same AuthManager ' .
+                                       'that created the account',
+                               $ex->getMessage()
+                       );
+               }
+               $this->unhook( 'UserLoggedIn' );
+
+               $this->request->getSession()->clear();
+               $this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
+               $this->managerPriv->createdAccountAuthenticationRequests = [ $reqs[0] ];
+               $this->hook( 'UserLoggedIn', $this->once() )
+                       ->with( $this->callback( function ( $u ) use ( $user ) {
+                               return $user->getId() === $u->getId() && $user->getName() === $u->getName();
+                       } ) );
+               $this->hook( 'AuthManagerLoginAuthenticateAudit', $this->once() );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->beginAuthentication( $reqs, 'http://localhost/' );
+               $this->logger->setCollect( false );
+               $this->unhook( 'UserLoggedIn' );
+               $this->unhook( 'AuthManagerLoginAuthenticateAudit' );
+               $this->assertSame( AuthenticationResponse::PASS, $ret->status );
+               $this->assertSame( $user->getName(), $ret->username );
+               $this->assertSame( $user->getId(), $this->request->getSessionData( 'AuthManager:lastAuthId' ) );
+               $this->assertEquals(
+                       time(), $this->request->getSessionData( 'AuthManager:lastAuthTimestamp' ),
+                       'timestamp ±1', 1
+               );
+               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
+               $this->assertSame( $user->getId(), $this->request->getSession()->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'Logging in {user} after account creation' ],
+               ], $this->logger->getBuffer() );
+       }
+
+       public function testCreateFromLogin() {
+               $user = \User::newFromName( 'UTSysop' );
+               $req1 = $this->getMock( AuthenticationRequest::class );
+               $req2 = $this->getMock( AuthenticationRequest::class );
+               $req3 = $this->getMock( AuthenticationRequest::class );
+               $userReq = new UsernameAuthenticationRequest;
+               $userReq->username = 'UTDummy';
+
+               // Passing one into beginAuthentication(), and an immediate FAIL
+               $primary = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
+               $this->primaryauthMocks = [ $primary ];
+               $this->initializeManager( true );
+               $res = AuthenticationResponse::newFail( wfMessage( 'foo' ) );
+               $res->createRequest = $req1;
+               $primary->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
+                       ->will( $this->returnValue( $res ) );
+               $createReq = new CreateFromLoginAuthenticationRequest(
+                       null, [ $req2->getUniqueId() => $req2 ]
+               );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->beginAuthentication( [ $createReq ], 'http://localhost/' );
+               $this->logger->setCollect( false );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->createRequest );
+               $this->assertSame( $req1, $ret->createRequest->createRequest );
+               $this->assertEquals( [ $req2->getUniqueId() => $req2 ], $ret->createRequest->maybeLink );
+
+               // UI, then FAIL in beginAuthentication()
+               $primary = $this->getMockBuilder( AbstractPrimaryAuthenticationProvider::class )
+                       ->setMethods( [ 'continuePrimaryAuthentication' ] )
+                       ->getMockForAbstractClass();
+               $this->primaryauthMocks = [ $primary ];
+               $this->initializeManager( true );
+               $primary->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
+                       ->will( $this->returnValue(
+                               AuthenticationResponse::newUI( [ $req1 ], wfMessage( 'foo' ) )
+                       ) );
+               $res = AuthenticationResponse::newFail( wfMessage( 'foo' ) );
+               $res->createRequest = $req2;
+               $primary->expects( $this->any() )->method( 'continuePrimaryAuthentication' )
+                       ->will( $this->returnValue( $res ) );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
+               $this->assertSame( AuthenticationResponse::UI, $ret->status, 'sanity check' );
+               $ret = $this->manager->continueAuthentication( [] );
+               $this->logger->setCollect( false );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->createRequest );
+               $this->assertSame( $req2, $ret->createRequest->createRequest );
+               $this->assertEquals( [], $ret->createRequest->maybeLink );
+
+               // Pass into beginAccountCreation(), no createRequest, primary needs reqs
+               $primary = $this->getMockBuilder( AbstractPrimaryAuthenticationProvider::class )
+                       ->setMethods( [ 'testForAccountCreation' ] )
+                       ->getMockForAbstractClass();
+               $this->primaryauthMocks = [ $primary ];
+               $this->initializeManager( true );
+               $primary->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $primary->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                       ->will( $this->returnValue( [ $req1 ] ) );
+               $primary->expects( $this->any() )->method( 'testForAccountCreation' )
+                       ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
+               $createReq = new CreateFromLoginAuthenticationRequest(
+                       null, [ $req2->getUniqueId() => $req2 ]
+               );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->beginAccountCreation(
+                       $user, [ $userReq, $createReq ], 'http://localhost/'
+               );
+               $this->logger->setCollect( false );
+               $this->assertSame( AuthenticationResponse::UI, $ret->status );
+               $this->assertCount( 4, $ret->neededRequests );
+               $this->assertSame( $req1, $ret->neededRequests[0] );
+               $this->assertInstanceOf( UsernameAuthenticationRequest::class, $ret->neededRequests[1] );
+               $this->assertInstanceOf( UserDataAuthenticationRequest::class, $ret->neededRequests[2] );
+               $this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->neededRequests[3] );
+               $this->assertSame( null, $ret->neededRequests[3]->createRequest );
+               $this->assertEquals( [], $ret->neededRequests[3]->maybeLink );
+
+               // Pass into beginAccountCreation(), with createRequest, primary needs reqs
+               $createReq = new CreateFromLoginAuthenticationRequest( $req2, [] );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->beginAccountCreation(
+                       $user, [ $userReq, $createReq ], 'http://localhost/'
+               );
+               $this->logger->setCollect( false );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'fail', $ret->message->getKey() );
+
+               // Again, with a secondary needing reqs too
+               $secondary = $this->getMockBuilder( AbstractSecondaryAuthenticationProvider::class )
+                       ->getMockForAbstractClass();
+               $this->secondaryauthMocks = [ $secondary ];
+               $this->initializeManager( true );
+               $secondary->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                       ->will( $this->returnValue( [ $req3 ] ) );
+               $createReq = new CreateFromLoginAuthenticationRequest( $req2, [] );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->beginAccountCreation(
+                       $user, [ $userReq, $createReq ], 'http://localhost/'
+               );
+               $this->logger->setCollect( false );
+               $this->assertSame( AuthenticationResponse::UI, $ret->status );
+               $this->assertCount( 4, $ret->neededRequests );
+               $this->assertSame( $req3, $ret->neededRequests[0] );
+               $this->assertInstanceOf( UsernameAuthenticationRequest::class, $ret->neededRequests[1] );
+               $this->assertInstanceOf( UserDataAuthenticationRequest::class, $ret->neededRequests[2] );
+               $this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->neededRequests[3] );
+               $this->assertSame( $req2, $ret->neededRequests[3]->createRequest );
+               $this->assertEquals( [], $ret->neededRequests[3]->maybeLink );
+               $this->logger->setCollect( true );
+               $ret = $this->manager->continueAccountCreation( $ret->neededRequests );
+               $this->logger->setCollect( false );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'fail', $ret->message->getKey() );
+       }
+
+       /**
+        * @dataProvider provideAuthentication
+        * @param StatusValue $preResponse
+        * @param array $primaryResponses
+        * @param array $secondaryResponses
+        * @param array $managerResponses
+        * @param bool $link Whether the primary authentication provider is a "link" provider
+        */
+       public function testAuthentication(
+               StatusValue $preResponse, array $primaryResponses, array $secondaryResponses,
+               array $managerResponses, $link = false
+       ) {
+               $this->initializeManager();
+               $user = \User::newFromName( 'UTSysop' );
+               $id = $user->getId();
+               $name = $user->getName();
+
+               // Set up lots of mocks...
+               $req = new RememberMeAuthenticationRequest;
+               $req->rememberMe = (bool)rand( 0, 1 );
+               $req->pre = $preResponse;
+               $req->primary = $primaryResponses;
+               $req->secondary = $secondaryResponses;
+               $mocks = [];
+               foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
+                       $class = ucfirst( $key ) . 'AuthenticationProvider';
+                       $mocks[$key] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+                       $mocks[$key . '2'] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key . '2']->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key . '2' ) );
+                       $mocks[$key . '3'] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key . '3']->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key . '3' ) );
+               }
+               foreach ( $mocks as $mock ) {
+                       $mock->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                               ->will( $this->returnValue( [] ) );
+               }
+
+               $mocks['pre']->expects( $this->once() )->method( 'testForAuthentication' )
+                       ->will( $this->returnCallback( function ( $reqs ) use ( $req ) {
+                               $this->assertContains( $req, $reqs );
+                               return $req->pre;
+                       } ) );
+
+               $ct = count( $req->primary );
+               $callback = $this->returnCallback( function ( $reqs ) use ( $req ) {
+                       $this->assertContains( $req, $reqs );
+                       return array_shift( $req->primary );
+               } );
+               $mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
+                       ->method( 'beginPrimaryAuthentication' )
+                       ->will( $callback );
+               $mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
+                       ->method( 'continuePrimaryAuthentication' )
+                       ->will( $callback );
+               if ( $link ) {
+                       $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
+                               ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               }
+
+               $ct = count( $req->secondary );
+               $callback = $this->returnCallback( function ( $user, $reqs ) use ( $id, $name, $req ) {
+                       $this->assertSame( $id, $user->getId() );
+                       $this->assertSame( $name, $user->getName() );
+                       $this->assertContains( $req, $reqs );
+                       return array_shift( $req->secondary );
+               } );
+               $mocks['secondary']->expects( $this->exactly( min( 1, $ct ) ) )
+                       ->method( 'beginSecondaryAuthentication' )
+                       ->will( $callback );
+               $mocks['secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
+                       ->method( 'continueSecondaryAuthentication' )
+                       ->will( $callback );
+
+               $abstain = AuthenticationResponse::newAbstain();
+               $mocks['pre2']->expects( $this->atMost( 1 ) )->method( 'testForAuthentication' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAuthentication' )
+                               ->will( $this->returnValue( $abstain ) );
+               $mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAuthentication' );
+               $mocks['secondary2']->expects( $this->atMost( 1 ) )->method( 'beginSecondaryAuthentication' )
+                               ->will( $this->returnValue( $abstain ) );
+               $mocks['secondary2']->expects( $this->never() )->method( 'continueSecondaryAuthentication' );
+               $mocks['secondary3']->expects( $this->atMost( 1 ) )->method( 'beginSecondaryAuthentication' )
+                               ->will( $this->returnValue( $abstain ) );
+               $mocks['secondary3']->expects( $this->never() )->method( 'continueSecondaryAuthentication' );
+
+               $this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
+               $this->primaryauthMocks = [ $mocks['primary'], $mocks['primary2'] ];
+               $this->secondaryauthMocks = [
+                       $mocks['secondary3'], $mocks['secondary'], $mocks['secondary2'],
+                       // So linking happens
+                       new ConfirmLinkSecondaryAuthenticationProvider,
+               ];
+               $this->initializeManager( true );
+               $this->logger->setCollect( true );
+
+               $constraint = \PHPUnit_Framework_Assert::logicalOr(
+                       $this->equalTo( AuthenticationResponse::PASS ),
+                       $this->equalTo( AuthenticationResponse::FAIL )
+               );
+               $providers = array_filter(
+                       array_merge(
+                               $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
+                       ),
+                       function ( $p ) {
+                               return is_callable( [ $p, 'expects' ] );
+                       }
+               );
+               foreach ( $providers as $p ) {
+                       $p->postCalled = false;
+                       $p->expects( $this->atMost( 1 ) )->method( 'postAuthentication' )
+                               ->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
+                                       if ( $user !== null ) {
+                                               $this->assertInstanceOf( 'User', $user );
+                                               $this->assertSame( 'UTSysop', $user->getName() );
+                                       }
+                                       $this->assertInstanceOf( AuthenticationResponse::class, $response );
+                                       $this->assertThat( $response->status, $constraint );
+                                       $p->postCalled = $response->status;
+                               } );
+               }
+
+               $session = $this->request->getSession();
+               $session->setRememberUser( !$req->rememberMe );
+
+               foreach ( $managerResponses as $i => $response ) {
+                       $success = $response instanceof AuthenticationResponse &&
+                               $response->status === AuthenticationResponse::PASS;
+                       if ( $success ) {
+                               $this->hook( 'UserLoggedIn', $this->once() )
+                                       ->with( $this->callback( function ( $user ) use ( $id, $name ) {
+                                               return $user->getId() === $id && $user->getName() === $name;
+                                       } ) );
+                       } else {
+                               $this->hook( 'UserLoggedIn', $this->never() );
+                       }
+                       if ( $success || (
+                                       $response instanceof AuthenticationResponse &&
+                                       $response->status === AuthenticationResponse::FAIL &&
+                                       $response->message->getKey() !== 'authmanager-authn-not-in-progress' &&
+                                       $response->message->getKey() !== 'authmanager-authn-no-primary'
+                               )
+                       ) {
+                               $this->hook( 'AuthManagerLoginAuthenticateAudit', $this->once() );
+                       } else {
+                               $this->hook( 'AuthManagerLoginAuthenticateAudit', $this->never() );
+                       }
+
+                       $ex = null;
+                       try {
+                               if ( !$i ) {
+                                       $ret = $this->manager->beginAuthentication( [ $req ], 'http://localhost/' );
+                               } else {
+                                       $ret = $this->manager->continueAuthentication( [ $req ] );
+                               }
+                               if ( $response instanceof \Exception ) {
+                                       $this->fail( 'Expected exception not thrown', "Response $i" );
+                               }
+                       } catch ( \Exception $ex ) {
+                               if ( !$response instanceof \Exception ) {
+                                       throw $ex;
+                               }
+                               $this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
+                               $this->assertNull( $session->getSecret( 'AuthManager::authnState' ),
+                                       "Response $i, exception, session state" );
+                               $this->unhook( 'UserLoggedIn' );
+                               $this->unhook( 'AuthManagerLoginAuthenticateAudit' );
+                               return;
+                       }
+
+                       $this->unhook( 'UserLoggedIn' );
+                       $this->unhook( 'AuthManagerLoginAuthenticateAudit' );
+
+                       $this->assertSame( 'http://localhost/', $req->returnToUrl );
+
+                       $ret->message = $this->message( $ret->message );
+                       $this->assertEquals( $response, $ret, "Response $i, response" );
+                       if ( $success ) {
+                               $this->assertSame( $id, $session->getUser()->getId(),
+                                       "Response $i, authn" );
+                       } else {
+                               $this->assertSame( 0, $session->getUser()->getId(),
+                                       "Response $i, authn" );
+                       }
+                       if ( $success || $response->status === AuthenticationResponse::FAIL ) {
+                               $this->assertNull( $session->getSecret( 'AuthManager::authnState' ),
+                                       "Response $i, session state" );
+                               foreach ( $providers as $p ) {
+                                       $this->assertSame( $response->status, $p->postCalled,
+                                               "Response $i, post-auth callback called" );
+                               }
+                       } else {
+                               $this->assertNotNull( $session->getSecret( 'AuthManager::authnState' ),
+                                       "Response $i, session state" );
+                               $this->assertEquals(
+                                       $ret->neededRequests,
+                                       $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN_CONTINUE ),
+                                       "Response $i, continuation check"
+                               );
+                               foreach ( $providers as $p ) {
+                                       $this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
+                               }
+                       }
+
+                       $state = $session->getSecret( 'AuthManager::authnState' );
+                       $maybeLink = isset( $state['maybeLink'] ) ? $state['maybeLink'] : [];
+                       if ( $link && $response->status === AuthenticationResponse::RESTART ) {
+                               $this->assertEquals(
+                                       $response->createRequest->maybeLink,
+                                       $maybeLink,
+                                       "Response $i, maybeLink"
+                               );
+                       } else {
+                               $this->assertEquals( [], $maybeLink, "Response $i, maybeLink" );
+                       }
+               }
+
+               if ( $success ) {
+                       $this->assertSame( $req->rememberMe, $session->shouldRememberUser(),
+                               'rememberMe checkbox had effect' );
+               } else {
+                       $this->assertNotSame( $req->rememberMe, $session->shouldRememberUser(),
+                               'rememberMe checkbox wasn\'t applied' );
+               }
+       }
+
+       public function provideAuthentication() {
+               $user = \User::newFromName( 'UTSysop' );
+               $id = $user->getId();
+               $name = $user->getName();
+
+               $rememberReq = new RememberMeAuthenticationRequest;
+               $rememberReq->action = AuthManager::ACTION_LOGIN;
+
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $req->foobar = 'baz';
+               $restartResponse = AuthenticationResponse::newRestart(
+                       $this->message( 'authmanager-authn-no-local-user' )
+               );
+               $restartResponse->neededRequests = [ $rememberReq ];
+
+               $restartResponse2Pass = AuthenticationResponse::newPass( null );
+               $restartResponse2Pass->linkRequest = $req;
+               $restartResponse2 = AuthenticationResponse::newRestart(
+                       $this->message( 'authmanager-authn-no-local-user-link' )
+               );
+               $restartResponse2->createRequest = new CreateFromLoginAuthenticationRequest(
+                       null, [ $req->getUniqueId() => $req ]
+               );
+               $restartResponse2->neededRequests = [ $rememberReq, $restartResponse2->createRequest ];
+
+               return [
+                       'Failure in pre-auth' => [
+                               StatusValue::newFatal( 'fail-from-pre' ),
+                               [],
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
+                                       AuthenticationResponse::newFail(
+                                               $this->message( 'authmanager-authn-not-in-progress' )
+                                       ),
+                               ]
+                       ],
+                       'Failure in primary' => [
+                               StatusValue::newGood(),
+                               $tmp = [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
+                               ],
+                               [],
+                               $tmp
+                       ],
+                       'All primary abstain' => [
+                               StatusValue::newGood(),
+                               [
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'authmanager-authn-no-primary' ) )
+                               ]
+                       ],
+                       'Primary UI, then redirect, then fail' => [
+                               StatusValue::newGood(),
+                               $tmp = [
+                                       AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
+                                       AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
+                               ],
+                               [],
+                               $tmp
+                       ],
+                       'Primary redirect, then abstain' => [
+                               StatusValue::newGood(),
+                               [
+                                       $tmp = AuthenticationResponse::newRedirect(
+                                               [ $req ], '/foo.html', [ 'foo' => 'bar' ]
+                                       ),
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [],
+                               [
+                                       $tmp,
+                                       new \DomainException(
+                                               'MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN'
+                                       )
+                               ]
+                       ],
+                       'Primary UI, then pass with no local user' => [
+                               StatusValue::newGood(),
+                               [
+                                       $tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newPass( null ),
+                               ],
+                               [],
+                               [
+                                       $tmp,
+                                       $restartResponse,
+                               ]
+                       ],
+                       'Primary UI, then pass with no local user (link type)' => [
+                               StatusValue::newGood(),
+                               [
+                                       $tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       $restartResponse2Pass,
+                               ],
+                               [],
+                               [
+                                       $tmp,
+                                       $restartResponse2,
+                               ],
+                               true
+                       ],
+                       'Primary pass with invalid username' => [
+                               StatusValue::newGood(),
+                               [
+                                       AuthenticationResponse::newPass( '<>' ),
+                               ],
+                               [],
+                               [
+                                       new \DomainException( 'MockPrimaryAuthenticationProvider returned an invalid username: <>' ),
+                               ]
+                       ],
+                       'Secondary fail' => [
+                               StatusValue::newGood(),
+                               [
+                                       AuthenticationResponse::newPass( $name ),
+                               ],
+                               $tmp = [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-in-secondary' ) ),
+                               ],
+                               $tmp
+                       ],
+                       'Secondary UI, then abstain' => [
+                               StatusValue::newGood(),
+                               [
+                                       AuthenticationResponse::newPass( $name ),
+                               ],
+                               [
+                                       $tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newAbstain()
+                               ],
+                               [
+                                       $tmp,
+                                       AuthenticationResponse::newPass( $name ),
+                               ]
+                       ],
+                       'Secondary pass' => [
+                               StatusValue::newGood(),
+                               [
+                                       AuthenticationResponse::newPass( $name ),
+                               ],
+                               [
+                                       AuthenticationResponse::newPass()
+                               ],
+                               [
+                                       AuthenticationResponse::newPass( $name ),
+                               ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideUserExists
+        * @param bool $primary1Exists
+        * @param bool $primary2Exists
+        * @param bool $expect
+        */
+       public function testUserExists( $primary1Exists, $primary2Exists, $expect ) {
+               $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock1->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary1' ) );
+               $mock1->expects( $this->any() )->method( 'testUserExists' )
+                       ->with( $this->equalTo( 'UTSysop' ) )
+                       ->will( $this->returnValue( $primary1Exists ) );
+               $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary2' ) );
+               $mock2->expects( $this->any() )->method( 'testUserExists' )
+                       ->with( $this->equalTo( 'UTSysop' ) )
+                       ->will( $this->returnValue( $primary2Exists ) );
+               $this->primaryauthMocks = [ $mock1, $mock2 ];
+
+               $this->initializeManager( true );
+               $this->assertSame( $expect, $this->manager->userExists( 'UTSysop' ) );
+       }
+
+       public static function provideUserExists() {
+               return [
+                       [ false, false, false ],
+                       [ true, false, true ],
+                       [ false, true, true ],
+                       [ true, true, true ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideAllowsAuthenticationDataChange
+        * @param StatusValue $primaryReturn
+        * @param StatusValue $secondaryReturn
+        * @param Status $expect
+        */
+       public function testAllowsAuthenticationDataChange( $primaryReturn, $secondaryReturn, $expect ) {
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+
+               $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '1' ) );
+               $mock1->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
+                       ->with( $this->equalTo( $req ) )
+                       ->will( $this->returnValue( $primaryReturn ) );
+               $mock2 = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '2' ) );
+               $mock2->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
+                       ->with( $this->equalTo( $req ) )
+                       ->will( $this->returnValue( $secondaryReturn ) );
+
+               $this->primaryauthMocks = [ $mock1 ];
+               $this->secondaryauthMocks = [ $mock2 ];
+               $this->initializeManager( true );
+               $this->assertEquals( $expect, $this->manager->allowsAuthenticationDataChange( $req ) );
+       }
+
+       public static function provideAllowsAuthenticationDataChange() {
+               $ignored = \Status::newGood( 'ignored' );
+               $ignored->warning( 'authmanager-change-not-supported' );
+
+               $okFromPrimary = StatusValue::newGood();
+               $okFromPrimary->warning( 'warning-from-primary' );
+               $okFromSecondary = StatusValue::newGood();
+               $okFromSecondary->warning( 'warning-from-secondary' );
+
+               return [
+                       [
+                               StatusValue::newGood(),
+                               StatusValue::newGood(),
+                               \Status::newGood(),
+                       ],
+                       [
+                               StatusValue::newGood(),
+                               StatusValue::newGood( 'ignore' ),
+                               \Status::newGood(),
+                       ],
+                       [
+                               StatusValue::newGood( 'ignored' ),
+                               StatusValue::newGood(),
+                               \Status::newGood(),
+                       ],
+                       [
+                               StatusValue::newGood( 'ignored' ),
+                               StatusValue::newGood( 'ignored' ),
+                               $ignored,
+                       ],
+                       [
+                               StatusValue::newFatal( 'fail from primary' ),
+                               StatusValue::newGood(),
+                               \Status::newFatal( 'fail from primary' ),
+                       ],
+                       [
+                               $okFromPrimary,
+                               StatusValue::newGood(),
+                               \Status::wrap( $okFromPrimary ),
+                       ],
+                       [
+                               StatusValue::newGood(),
+                               StatusValue::newFatal( 'fail from secondary' ),
+                               \Status::newFatal( 'fail from secondary' ),
+                       ],
+                       [
+                               StatusValue::newGood(),
+                               $okFromSecondary,
+                               \Status::wrap( $okFromSecondary ),
+                       ],
+               ];
+       }
+
+       public function testChangeAuthenticationData() {
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $req->username = 'UTSysop';
+
+               $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '1' ) );
+               $mock1->expects( $this->once() )->method( 'providerChangeAuthenticationData' )
+                       ->with( $this->equalTo( $req ) );
+               $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '2' ) );
+               $mock2->expects( $this->once() )->method( 'providerChangeAuthenticationData' )
+                       ->with( $this->equalTo( $req ) );
+
+               $this->primaryauthMocks = [ $mock1, $mock2 ];
+               $this->initializeManager( true );
+               $this->logger->setCollect( true );
+               $this->manager->changeAuthenticationData( $req );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'Changing authentication data for {user} class {what}' ],
+               ], $this->logger->getBuffer() );
+       }
+
+       public function testCanCreateAccounts() {
+               $types = [
+                       PrimaryAuthenticationProvider::TYPE_CREATE => true,
+                       PrimaryAuthenticationProvider::TYPE_LINK => true,
+                       PrimaryAuthenticationProvider::TYPE_NONE => false,
+               ];
+
+               foreach ( $types as $type => $can ) {
+                       $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+                       $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $type ) );
+                       $mock->expects( $this->any() )->method( 'accountCreationType' )
+                               ->will( $this->returnValue( $type ) );
+                       $this->primaryauthMocks = [ $mock ];
+                       $this->initializeManager( true );
+                       $this->assertSame( $can, $this->manager->canCreateAccounts(), $type );
+               }
+       }
+
+       public function testCheckAccountCreatePermissions() {
+               global $wgGroupPermissions;
+
+               $this->stashMwGlobals( [ 'wgGroupPermissions' ] );
+
+               $this->initializeManager( true );
+
+               $wgGroupPermissions['*']['createaccount'] = true;
+               $this->assertEquals(
+                       \Status::newGood(),
+                       $this->manager->checkAccountCreatePermissions( new \User )
+               );
+
+               $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
+               $this->assertEquals(
+                       \Status::newFatal( 'readonlytext', 'Because' ),
+                       $this->manager->checkAccountCreatePermissions( new \User )
+               );
+               $this->setMwGlobals( [ 'wgReadOnly' => false ] );
+
+               $wgGroupPermissions['*']['createaccount'] = false;
+               $status = $this->manager->checkAccountCreatePermissions( new \User );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'badaccess-groups' ) );
+               $wgGroupPermissions['*']['createaccount'] = true;
+
+               $user = \User::newFromName( 'UTBlockee' );
+               if ( $user->getID() == 0 ) {
+                       $user->addToDatabase();
+                       \TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
+                       $user->saveSettings();
+               }
+               $oldBlock = \Block::newFromTarget( 'UTBlockee' );
+               if ( $oldBlock ) {
+                       // An old block will prevent our new one from saving.
+                       $oldBlock->delete();
+               }
+               $blockOptions = [
+                       'address' => 'UTBlockee',
+                       'user' => $user->getID(),
+                       'reason' => __METHOD__,
+                       'expiry' => time() + 100500,
+                       'createAccount' => true,
+               ];
+               $block = new \Block( $blockOptions );
+               $block->insert();
+               $status = $this->manager->checkAccountCreatePermissions( $user );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
+
+               $blockOptions = [
+                       'address' => '127.0.0.0/24',
+                       'reason' => __METHOD__,
+                       'expiry' => time() + 100500,
+                       'createAccount' => true,
+               ];
+               $block = new \Block( $blockOptions );
+               $block->insert();
+               $scopeVariable = new \ScopedCallback( [ $block, 'delete' ] );
+               $status = $this->manager->checkAccountCreatePermissions( new \User );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
+               \ScopedCallback::consume( $scopeVariable );
+
+               $this->setMwGlobals( [
+                       'wgEnableDnsBlacklist' => true,
+                       'wgDnsBlacklistUrls' => [
+                               'local.wmftest.net', // This will resolve for every subdomain, which works to test "listed?"
+                       ],
+                       'wgProxyWhitelist' => [],
+               ] );
+               $status = $this->manager->checkAccountCreatePermissions( new \User );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
+               $this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
+               $status = $this->manager->checkAccountCreatePermissions( new \User );
+               $this->assertTrue( $status->isGood() );
+       }
+
+       /**
+        * @param string $uniq
+        * @return string
+        */
+       private static function usernameForCreation( $uniq = '' ) {
+               $i = 0;
+               do {
+                       $username = "UTAuthManagerTestAccountCreation" . $uniq . ++$i;
+               } while ( \User::newFromName( $username )->getId() !== 0 );
+               return $username;
+       }
+
+       public function testCanCreateAccount() {
+               $username = self::usernameForCreation();
+               $this->initializeManager();
+
+               $this->assertEquals(
+                       \Status::newFatal( 'authmanager-create-disabled' ),
+                       $this->manager->canCreateAccount( $username )
+               );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->assertEquals(
+                       \Status::newFatal( 'userexists' ),
+                       $this->manager->canCreateAccount( $username )
+               );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->assertEquals(
+                       \Status::newFatal( 'noname' ),
+                       $this->manager->canCreateAccount( $username . '<>' )
+               );
+
+               $this->assertEquals(
+                       \Status::newFatal( 'userexists' ),
+                       $this->manager->canCreateAccount( 'UTSysop' )
+               );
+
+               $this->assertEquals(
+                       \Status::newGood(),
+                       $this->manager->canCreateAccount( $username )
+               );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->assertEquals(
+                       \Status::newFatal( 'fail' ),
+                       $this->manager->canCreateAccount( $username )
+               );
+       }
+
+       public function testBeginAccountCreation() {
+               $creator = \User::newFromName( 'UTSysop' );
+               $userReq = new UsernameAuthenticationRequest;
+               $this->logger = new \TestLogger( false, function ( $message, $level ) {
+                       return $level === LogLevel::DEBUG ? null : $message;
+               } );
+               $this->initializeManager();
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', 'test' );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               try {
+                       $this->manager->beginAccountCreation(
+                               $creator, [], 'http://localhost/'
+                       );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \LogicException $ex ) {
+                       $this->assertEquals( 'Account creation is not possible', $ex->getMessage() );
+               }
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->beginAccountCreation( $creator, [], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'noname', $ret->message->getKey() );
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $userReq->username = self::usernameForCreation();
+               $userReq2 = new UsernameAuthenticationRequest;
+               $userReq2->username = $userReq->username . 'X';
+               $ret = $this->manager->beginAccountCreation(
+                       $creator, [ $userReq, $userReq2 ], 'http://localhost/'
+               );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'noname', $ret->message->getKey() );
+
+               $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $userReq->username = self::usernameForCreation();
+               $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'readonlytext', $ret->message->getKey() );
+               $this->assertSame( [ 'Because' ], $ret->message->getParams() );
+               $this->setMwGlobals( [ 'wgReadOnly' => false ] );
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $userReq->username = self::usernameForCreation();
+               $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'userexists', $ret->message->getKey() );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $userReq->username = self::usernameForCreation();
+               $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'fail', $ret->message->getKey() );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $userReq->username = self::usernameForCreation() . '<>';
+               $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'noname', $ret->message->getKey() );
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $userReq->username = $creator->getName();
+               $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'userexists', $ret->message->getKey() );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $mock->expects( $this->any() )->method( 'testForAccountCreation' )
+                       ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $req = $this->getMockBuilder( UserDataAuthenticationRequest::class )
+                       ->setMethods( [ 'populateUser' ] )
+                       ->getMock();
+               $req->expects( $this->any() )->method( 'populateUser' )
+                       ->willReturn( \StatusValue::newFatal( 'populatefail' ) );
+               $userReq->username = self::usernameForCreation();
+               $ret = $this->manager->beginAccountCreation(
+                       $creator, [ $userReq, $req ], 'http://localhost/'
+               );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'populatefail', $ret->message->getKey() );
+
+               $req = new UserDataAuthenticationRequest;
+               $userReq->username = self::usernameForCreation();
+
+               $ret = $this->manager->beginAccountCreation(
+                       $creator, [ $userReq, $req ], 'http://localhost/'
+               );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'fail', $ret->message->getKey() );
+
+               $this->manager->beginAccountCreation(
+                       \User::newFromName( $userReq->username ), [ $userReq, $req ], 'http://localhost/'
+               );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'fail', $ret->message->getKey() );
+       }
+
+       public function testContinueAccountCreation() {
+               $creator = \User::newFromName( 'UTSysop' );
+               $username = self::usernameForCreation();
+               $this->logger = new \TestLogger( false, function ( $message, $level ) {
+                       return $level === LogLevel::DEBUG ? null : $message;
+               } );
+               $this->initializeManager();
+
+               $session = [
+                       'userid' => 0,
+                       'username' => $username,
+                       'creatorid' => 0,
+                       'creatorname' => $username,
+                       'reqs' => [],
+                       'primary' => null,
+                       'primaryResponse' => null,
+                       'secondary' => [],
+                       'ranPreTests' => true,
+               ];
+
+               $this->hook( 'LocalUserCreated', $this->never() );
+               try {
+                       $this->manager->continueAccountCreation( [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \LogicException $ex ) {
+                       $this->assertEquals( 'Account creation is not possible', $ex->getMessage() );
+               }
+               $this->unhook( 'LocalUserCreated' );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )->will(
+                       $this->returnValue( AuthenticationResponse::newFail( $this->message( 'fail' ) ) )
+               );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', null );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->continueAccountCreation( [] );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'authmanager-create-not-in-progress', $ret->message->getKey() );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
+                       [ 'username' => "$username<>" ] + $session );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->continueAccountCreation( [] );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'noname', $ret->message->getKey() );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', $session );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $cache = \ObjectCache::getLocalClusterInstance();
+               $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
+               $ret = $this->manager->continueAccountCreation( [] );
+               unset( $lock );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'usernameinprogress', $ret->message->getKey() );
+               // This error shouldn't remove the existing session, because the
+               // raced-with process "owns" it.
+               $this->assertSame(
+                       $session, $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
+                       [ 'username' => $creator->getName() ] + $session );
+               $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->continueAccountCreation( [] );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'readonlytext', $ret->message->getKey() );
+               $this->assertSame( [ 'Because' ], $ret->message->getParams() );
+               $this->setMwGlobals( [ 'wgReadOnly' => false ] );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
+                       [ 'username' => $creator->getName() ] + $session );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->continueAccountCreation( [] );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'userexists', $ret->message->getKey() );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
+                       [ 'userid' => $creator->getId() ] + $session );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               try {
+                       $ret = $this->manager->continueAccountCreation( [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertEquals( "User \"{$username}\" should exist now, but doesn't!", $ex->getMessage() );
+               }
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+
+               $id = $creator->getId();
+               $name = $creator->getName();
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
+                       [ 'username' => $name, 'userid' => $id + 1 ] + $session );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               try {
+                       $ret = $this->manager->continueAccountCreation( [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertEquals(
+                               "User \"{$name}\" exists, but ID $id != " . ( $id + 1 ) . '!', $ex->getMessage()
+                       );
+               }
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+
+               $req = $this->getMockBuilder( UserDataAuthenticationRequest::class )
+                       ->setMethods( [ 'populateUser' ] )
+                       ->getMock();
+               $req->expects( $this->any() )->method( 'populateUser' )
+                       ->willReturn( \StatusValue::newFatal( 'populatefail' ) );
+               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
+                       [ 'reqs' => [ $req ] ] + $session );
+               $ret = $this->manager->continueAccountCreation( [] );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'populatefail', $ret->message->getKey() );
+               $this->assertNull(
+                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
+               );
+       }
+
+       /**
+        * @dataProvider provideAccountCreation
+        * @param StatusValue $preTest
+        * @param StatusValue $primaryTest
+        * @param StatusValue $secondaryTest
+        * @param array $primaryResponses
+        * @param array $secondaryResponses
+        * @param array $managerResponses
+        */
+       public function testAccountCreation(
+               StatusValue $preTest, $primaryTest, $secondaryTest,
+               array $primaryResponses, array $secondaryResponses, array $managerResponses
+       ) {
+               $creator = \User::newFromName( 'UTSysop' );
+               $username = self::usernameForCreation();
+
+               $this->initializeManager();
+
+               // Set up lots of mocks...
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $req->preTest = $preTest;
+               $req->primaryTest = $primaryTest;
+               $req->secondaryTest = $secondaryTest;
+               $req->primary = $primaryResponses;
+               $req->secondary = $secondaryResponses;
+               $mocks = [];
+               foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
+                       $class = ucfirst( $key ) . 'AuthenticationProvider';
+                       $mocks[$key] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+                       $mocks[$key]->expects( $this->any() )->method( 'testUserForCreation' )
+                               ->will( $this->returnValue( StatusValue::newGood() ) );
+                       $mocks[$key]->expects( $this->any() )->method( 'testForAccountCreation' )
+                               ->will( $this->returnCallback(
+                                       function ( $user, $creatorIn, $reqs )
+                                               use ( $username, $creator, $req, $key )
+                                       {
+                                               $this->assertSame( $username, $user->getName() );
+                                               $this->assertSame( $creator->getId(), $creatorIn->getId() );
+                                               $this->assertSame( $creator->getName(), $creatorIn->getName() );
+                                               $foundReq = false;
+                                               foreach ( $reqs as $r ) {
+                                                       $this->assertSame( $username, $r->username );
+                                                       $foundReq = $foundReq || get_class( $r ) === get_class( $req );
+                                               }
+                                               $this->assertTrue( $foundReq, '$reqs contains $req' );
+                                               $k = $key . 'Test';
+                                               return $req->$k;
+                                       }
+                               ) );
+
+                       for ( $i = 2; $i <= 3; $i++ ) {
+                               $mocks[$key . $i] = $this->getMockForAbstractClass(
+                                       "MediaWiki\\Auth\\$class", [], "Mock$class"
+                               );
+                               $mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
+                                       ->will( $this->returnValue( $key . $i ) );
+                               $mocks[$key . $i]->expects( $this->any() )->method( 'testUserForCreation' )
+                                       ->will( $this->returnValue( StatusValue::newGood() ) );
+                               $mocks[$key . $i]->expects( $this->atMost( 1 ) )->method( 'testForAccountCreation' )
+                                       ->will( $this->returnValue( StatusValue::newGood() ) );
+                       }
+               }
+
+               $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mocks['primary']->expects( $this->any() )->method( 'testUserExists' )
+                       ->will( $this->returnValue( false ) );
+               $ct = count( $req->primary );
+               $callback = $this->returnCallback( function ( $user, $creator, $reqs ) use ( $username, $req ) {
+                       $this->assertSame( $username, $user->getName() );
+                       $this->assertSame( 'UTSysop', $creator->getName() );
+                       $foundReq = false;
+                       foreach ( $reqs as $r ) {
+                               $this->assertSame( $username, $r->username );
+                               $foundReq = $foundReq || get_class( $r ) === get_class( $req );
+                       }
+                       $this->assertTrue( $foundReq, '$reqs contains $req' );
+                       return array_shift( $req->primary );
+               } );
+               $mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
+                       ->method( 'beginPrimaryAccountCreation' )
+                       ->will( $callback );
+               $mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
+                       ->method( 'continuePrimaryAccountCreation' )
+                       ->will( $callback );
+
+               $ct = count( $req->secondary );
+               $callback = $this->returnCallback( function ( $user, $creator, $reqs ) use ( $username, $req ) {
+                       $this->assertSame( $username, $user->getName() );
+                       $this->assertSame( 'UTSysop', $creator->getName() );
+                       $foundReq = false;
+                       foreach ( $reqs as $r ) {
+                               $this->assertSame( $username, $r->username );
+                               $foundReq = $foundReq || get_class( $r ) === get_class( $req );
+                       }
+                       $this->assertTrue( $foundReq, '$reqs contains $req' );
+                       return array_shift( $req->secondary );
+               } );
+               $mocks['secondary']->expects( $this->exactly( min( 1, $ct ) ) )
+                       ->method( 'beginSecondaryAccountCreation' )
+                       ->will( $callback );
+               $mocks['secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
+                       ->method( 'continueSecondaryAccountCreation' )
+                       ->will( $callback );
+
+               $abstain = AuthenticationResponse::newAbstain();
+               $mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               $mocks['primary2']->expects( $this->any() )->method( 'testUserExists' )
+                       ->will( $this->returnValue( false ) );
+               $mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAccountCreation' )
+                       ->will( $this->returnValue( $abstain ) );
+               $mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAccountCreation' );
+               $mocks['primary3']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_NONE ) );
+               $mocks['primary3']->expects( $this->any() )->method( 'testUserExists' )
+                       ->will( $this->returnValue( false ) );
+               $mocks['primary3']->expects( $this->never() )->method( 'beginPrimaryAccountCreation' );
+               $mocks['primary3']->expects( $this->never() )->method( 'continuePrimaryAccountCreation' );
+               $mocks['secondary2']->expects( $this->atMost( 1 ) )
+                       ->method( 'beginSecondaryAccountCreation' )
+                       ->will( $this->returnValue( $abstain ) );
+               $mocks['secondary2']->expects( $this->never() )->method( 'continueSecondaryAccountCreation' );
+               $mocks['secondary3']->expects( $this->atMost( 1 ) )
+                       ->method( 'beginSecondaryAccountCreation' )
+                       ->will( $this->returnValue( $abstain ) );
+               $mocks['secondary3']->expects( $this->never() )->method( 'continueSecondaryAccountCreation' );
+
+               $this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
+               $this->primaryauthMocks = [ $mocks['primary3'], $mocks['primary'], $mocks['primary2'] ];
+               $this->secondaryauthMocks = [
+                       $mocks['secondary3'], $mocks['secondary'], $mocks['secondary2']
+               ];
+
+               $this->logger = new \TestLogger( true, function ( $message, $level ) {
+                       return $level === LogLevel::DEBUG ? null : $message;
+               } );
+               $expectLog = [];
+               $this->initializeManager( true );
+
+               $constraint = \PHPUnit_Framework_Assert::logicalOr(
+                       $this->equalTo( AuthenticationResponse::PASS ),
+                       $this->equalTo( AuthenticationResponse::FAIL )
+               );
+               $providers = array_merge(
+                       $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
+               );
+               foreach ( $providers as $p ) {
+                       $p->postCalled = false;
+                       $p->expects( $this->atMost( 1 ) )->method( 'postAccountCreation' )
+                               ->willReturnCallback( function ( $user, $creator, $response )
+                                       use ( $constraint, $p, $username )
+                               {
+                                       $this->assertInstanceOf( 'User', $user );
+                                       $this->assertSame( $username, $user->getName() );
+                                       $this->assertSame( 'UTSysop', $creator->getName() );
+                                       $this->assertInstanceOf( AuthenticationResponse::class, $response );
+                                       $this->assertThat( $response->status, $constraint );
+                                       $p->postCalled = $response->status;
+                               } );
+               }
+
+               // We're testing with $wgNewUserLog = false, so assert that it worked
+               $dbw = wfGetDB( DB_MASTER );
+               $maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
+
+               $first = true;
+               $created = false;
+               foreach ( $managerResponses as $i => $response ) {
+                       $success = $response instanceof AuthenticationResponse &&
+                               $response->status === AuthenticationResponse::PASS;
+                       if ( $i === 'created' ) {
+                               $created = true;
+                               $this->hook( 'LocalUserCreated', $this->once() )
+                                       ->with(
+                                               $this->callback( function ( $user ) use ( $username ) {
+                                                       return $user->getName() === $username;
+                                               } ),
+                                               $this->equalTo( false )
+                                       );
+                               $expectLog[] = [ LogLevel::INFO, "Creating user {user} during account creation" ];
+                       } else {
+                               $this->hook( 'LocalUserCreated', $this->never() );
+                       }
+
+                       $ex = null;
+                       try {
+                               if ( $first ) {
+                                       $userReq = new UsernameAuthenticationRequest;
+                                       $userReq->username = $username;
+                                       $ret = $this->manager->beginAccountCreation(
+                                               $creator, [ $userReq, $req ], 'http://localhost/'
+                                       );
+                               } else {
+                                       $ret = $this->manager->continueAccountCreation( [ $req ] );
+                               }
+                               if ( $response instanceof \Exception ) {
+                                       $this->fail( 'Expected exception not thrown', "Response $i" );
+                               }
+                       } catch ( \Exception $ex ) {
+                               if ( !$response instanceof \Exception ) {
+                                       throw $ex;
+                               }
+                               $this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
+                               $this->assertNull(
+                                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
+                                       "Response $i, exception, session state"
+                               );
+                               $this->unhook( 'LocalUserCreated' );
+                               return;
+                       }
+
+                       $this->unhook( 'LocalUserCreated' );
+
+                       $this->assertSame( 'http://localhost/', $req->returnToUrl );
+
+                       if ( $success ) {
+                               $this->assertNotNull( $ret->loginRequest, "Response $i, login marker" );
+                               $this->assertContains(
+                                       $ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests,
+                                       "Response $i, login marker"
+                               );
+
+                               $expectLog[] = [
+                                       LogLevel::INFO,
+                                       "MediaWiki\Auth\AuthManager::continueAccountCreation: Account creation succeeded for {user}"
+                               ];
+
+                               // Set some fields in the expected $response that we couldn't
+                               // know in provideAccountCreation().
+                               $response->username = $username;
+                               $response->loginRequest = $ret->loginRequest;
+                       } else {
+                               $this->assertNull( $ret->loginRequest, "Response $i, login marker" );
+                               $this->assertSame( [], $this->managerPriv->createdAccountAuthenticationRequests,
+                                       "Response $i, login marker" );
+                       }
+                       $ret->message = $this->message( $ret->message );
+                       $this->assertEquals( $response, $ret, "Response $i, response" );
+                       if ( $success || $response->status === AuthenticationResponse::FAIL ) {
+                               $this->assertNull(
+                                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
+                                       "Response $i, session state"
+                               );
+                               foreach ( $providers as $p ) {
+                                       $this->assertSame( $response->status, $p->postCalled,
+                                               "Response $i, post-auth callback called" );
+                               }
+                       } else {
+                               $this->assertNotNull(
+                                       $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
+                                       "Response $i, session state"
+                               );
+                               $this->assertEquals(
+                                       $ret->neededRequests,
+                                       $this->manager->getAuthenticationRequests( AuthManager::ACTION_CREATE_CONTINUE ),
+                                       "Response $i, continuation check"
+                               );
+                               foreach ( $providers as $p ) {
+                                       $this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
+                               }
+                       }
+
+                       if ( $created ) {
+                               $this->assertNotEquals( 0, \User::idFromName( $username ) );
+                       } else {
+                               $this->assertEquals( 0, \User::idFromName( $username ) );
+                       }
+
+                       $first = false;
+               }
+
+               $this->assertSame( $expectLog, $this->logger->getBuffer() );
+
+               $this->assertSame(
+                       $maxLogId,
+                       $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] )
+               );
+       }
+
+       public function provideAccountCreation() {
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $good = StatusValue::newGood();
+
+               return [
+                       'Pre-creation test fail in pre' => [
+                               StatusValue::newFatal( 'fail-from-pre' ), $good, $good,
+                               [],
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
+                               ]
+                       ],
+                       'Pre-creation test fail in primary' => [
+                               $good, StatusValue::newFatal( 'fail-from-primary' ), $good,
+                               [],
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
+                               ]
+                       ],
+                       'Pre-creation test fail in secondary' => [
+                               $good, $good, StatusValue::newFatal( 'fail-from-secondary' ),
+                               [],
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-secondary' ) ),
+                               ]
+                       ],
+                       'Failure in primary' => [
+                               $good, $good, $good,
+                               $tmp = [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
+                               ],
+                               [],
+                               $tmp
+                       ],
+                       'All primary abstain' => [
+                               $good, $good, $good,
+                               [
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'authmanager-create-no-primary' ) )
+                               ]
+                       ],
+                       'Primary UI, then redirect, then fail' => [
+                               $good, $good, $good,
+                               $tmp = [
+                                       AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
+                                       AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
+                               ],
+                               [],
+                               $tmp
+                       ],
+                       'Primary redirect, then abstain' => [
+                               $good, $good, $good,
+                               [
+                                       $tmp = AuthenticationResponse::newRedirect(
+                                               [ $req ], '/foo.html', [ 'foo' => 'bar' ]
+                                       ),
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [],
+                               [
+                                       $tmp,
+                                       new \DomainException(
+                                               'MockPrimaryAuthenticationProvider::continuePrimaryAccountCreation() returned ABSTAIN'
+                                       )
+                               ]
+                       ],
+                       'Primary UI, then pass; secondary abstain' => [
+                               $good, $good, $good,
+                               [
+                                       $tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newPass(),
+                               ],
+                               [
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [
+                                       $tmp1,
+                                       'created' => AuthenticationResponse::newPass( '' ),
+                               ]
+                       ],
+                       'Primary pass; secondary UI then pass' => [
+                               $good, $good, $good,
+                               [
+                                       AuthenticationResponse::newPass( '' ),
+                               ],
+                               [
+                                       $tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newPass( '' ),
+                               ],
+                               [
+                                       'created' => $tmp1,
+                                       AuthenticationResponse::newPass( '' ),
+                               ]
+                       ],
+                       'Primary pass; secondary fail' => [
+                               $good, $good, $good,
+                               [
+                                       AuthenticationResponse::newPass(),
+                               ],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( '...' ) ),
+                               ],
+                               [
+                                       'created' => new \DomainException(
+                                               'MockSecondaryAuthenticationProvider::beginSecondaryAccountCreation() returned FAIL. ' .
+                                                       'Secondary providers are not allowed to fail account creation, ' .
+                                                       'that should have been done via testForAccountCreation().'
+                                       )
+                               ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideAccountCreationLogging
+        * @param bool $isAnon
+        * @param string|null $logSubtype
+        */
+       public function testAccountCreationLogging( $isAnon, $logSubtype ) {
+               $creator = $isAnon ? new \User : \User::newFromName( 'UTSysop' );
+               $username = self::usernameForCreation();
+
+               $this->initializeManager();
+
+               // Set up lots of mocks...
+               $mock = $this->getMockForAbstractClass(
+                       "MediaWiki\\Auth\\PrimaryAuthenticationProvider", []
+               );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary' ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $mock->expects( $this->any() )->method( 'testForAccountCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )
+                       ->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )
+                       ->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
+               $mock->expects( $this->any() )->method( 'finishAccountCreation' )
+                       ->will( $this->returnValue( $logSubtype ) );
+
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+               $this->logger->setCollect( true );
+
+               $this->config->set( 'NewUserLog', true );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
+
+               $userReq = new UsernameAuthenticationRequest;
+               $userReq->username = $username;
+               $reasonReq = new CreationReasonAuthenticationRequest;
+               $reasonReq->reason = $this->toString();
+               $ret = $this->manager->beginAccountCreation(
+                       $creator, [ $userReq, $reasonReq ], 'http://localhost/'
+               );
+
+               $this->assertSame( AuthenticationResponse::PASS, $ret->status );
+
+               $user = \User::newFromName( $username );
+               $this->assertNotEquals( 0, $user->getId(), 'sanity check' );
+               $this->assertNotEquals( $creator->getId(), $user->getId(), 'sanity check' );
+
+               $data = \DatabaseLogEntry::getSelectQueryData();
+               $rows = iterator_to_array( $dbw->select(
+                       $data['tables'],
+                       $data['fields'],
+                       [
+                               'log_id > ' . (int)$maxLogId,
+                               'log_type' => 'newusers'
+                       ] + $data['conds'],
+                       __METHOD__,
+                       $data['options'],
+                       $data['join_conds']
+               ) );
+               $this->assertCount( 1, $rows );
+               $entry = \DatabaseLogEntry::newFromRow( reset( $rows ) );
+
+               $this->assertSame( $logSubtype ?: ( $isAnon ? 'create' : 'create2' ), $entry->getSubtype() );
+               $this->assertSame(
+                       $isAnon ? $user->getId() : $creator->getId(),
+                       $entry->getPerformer()->getId()
+               );
+               $this->assertSame(
+                       $isAnon ? $user->getName() : $creator->getName(),
+                       $entry->getPerformer()->getName()
+               );
+               $this->assertSame( $user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
+               $this->assertSame( [ '4::userid' => $user->getId() ], $entry->getParameters() );
+               $this->assertSame( $this->toString(), $entry->getComment() );
+       }
+
+       public static function provideAccountCreationLogging() {
+               return [
+                       [ true, null ],
+                       [ true, 'foobar' ],
+                       [ false, null ],
+                       [ false, 'byemail' ],
+               ];
+       }
+
+       public function testAutoAccountCreation() {
+               global $wgGroupPermissions, $wgHooks;
+
+               // PHPUnit seems to have a bug where it will call the ->with()
+               // callbacks for our hooks again after the test is run (WTF?), which
+               // breaks here because $username no longer matches $user by the end of
+               // the testing.
+               $workaroundPHPUnitBug = false;
+
+               $username = self::usernameForCreation();
+               $this->initializeManager();
+
+               $this->stashMwGlobals( [ 'wgGroupPermissions' ] );
+               $wgGroupPermissions['*']['createaccount'] = true;
+               $wgGroupPermissions['*']['autocreateaccount'] = false;
+
+               \ObjectCache::$instances[__METHOD__] = new \HashBagOStuff();
+               $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
+
+               // Set up lots of mocks...
+               $mocks = [];
+               foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
+                       $class = ucfirst( $key ) . 'AuthenticationProvider';
+                       $mocks[$key] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+               }
+
+               $good = StatusValue::newGood();
+               $callback = $this->callback( function ( $user ) use ( &$username, &$workaroundPHPUnitBug ) {
+                       return $workaroundPHPUnitBug || $user->getName() === $username;
+               } );
+
+               $mocks['pre']->expects( $this->exactly( 12 ) )->method( 'testUserForCreation' )
+                       ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
+                       ->will( $this->onConsecutiveCalls(
+                               StatusValue::newFatal( 'ok' ), StatusValue::newFatal( 'ok' ), // For testing permissions
+                               StatusValue::newFatal( 'fail-in-pre' ), $good, $good,
+                               $good, // backoff test
+                               $good, // addToDatabase fails test
+                               $good, // addToDatabase throws test
+                               $good, // addToDatabase exists test
+                               $good, $good, $good // success
+                       ) );
+
+               $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mocks['primary']->expects( $this->any() )->method( 'testUserExists' )
+                       ->will( $this->returnValue( true ) );
+               $mocks['primary']->expects( $this->exactly( 9 ) )->method( 'testUserForCreation' )
+                       ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
+                       ->will( $this->onConsecutiveCalls(
+                               StatusValue::newFatal( 'fail-in-primary' ), $good,
+                               $good, // backoff test
+                               $good, // addToDatabase fails test
+                               $good, // addToDatabase throws test
+                               $good, // addToDatabase exists test
+                               $good, $good, $good
+                       ) );
+               $mocks['primary']->expects( $this->exactly( 3 ) )->method( 'autoCreatedAccount' )
+                       ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) );
+
+               $mocks['secondary']->expects( $this->exactly( 8 ) )->method( 'testUserForCreation' )
+                       ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
+                       ->will( $this->onConsecutiveCalls(
+                               StatusValue::newFatal( 'fail-in-secondary' ),
+                               $good, // backoff test
+                               $good, // addToDatabase fails test
+                               $good, // addToDatabase throws test
+                               $good, // addToDatabase exists test
+                               $good, $good, $good
+                       ) );
+               $mocks['secondary']->expects( $this->exactly( 3 ) )->method( 'autoCreatedAccount' )
+                       ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) );
+
+               $this->preauthMocks = [ $mocks['pre'] ];
+               $this->primaryauthMocks = [ $mocks['primary'] ];
+               $this->secondaryauthMocks = [ $mocks['secondary'] ];
+               $this->initializeManager( true );
+               $session = $this->request->getSession();
+
+               $logger = new \TestLogger( true, function ( $m ) {
+                       $m = str_replace( 'MediaWiki\\Auth\\AuthManager::autoCreateUser: ', '', $m );
+                       return $m;
+               } );
+               $this->manager->setLogger( $logger );
+
+               try {
+                       $user = \User::newFromName( 'UTSysop' );
+                       $this->manager->autoCreateUser( $user, 'InvalidSource', true );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \InvalidArgumentException $ex ) {
+                       $this->assertSame( 'Unknown auto-creation source: InvalidSource', $ex->getMessage() );
+               }
+
+               // First, check an existing user
+               $session->clear();
+               $user = \User::newFromName( 'UTSysop' );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $expect = \Status::newGood();
+               $expect->warning( 'userexists' );
+               $this->assertEquals( $expect, $ret );
+               $this->assertNotEquals( 0, $user->getId() );
+               $this->assertSame( 'UTSysop', $user->getName() );
+               $this->assertEquals( $user->getId(), $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, '{username} already exists locally' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               $session->clear();
+               $user = \User::newFromName( 'UTSysop' );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
+               $this->unhook( 'LocalUserCreated' );
+               $expect = \Status::newGood();
+               $expect->warning( 'userexists' );
+               $this->assertEquals( $expect, $ret );
+               $this->assertNotEquals( 0, $user->getId() );
+               $this->assertSame( 'UTSysop', $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, '{username} already exists locally' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               // Wiki is read-only
+               $session->clear();
+               $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'readonlytext', 'Because' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'denied by wfReadOnly(): {reason}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->setMwGlobals( [ 'wgReadOnly' => false ] );
+
+               // Session blacklisted
+               $session->clear();
+               $session->set( 'AuthManager::AutoCreateBlacklist', 'test' );
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'test' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'blacklisted in session {sessionid}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               $session->clear();
+               $session->set( 'AuthManager::AutoCreateBlacklist', StatusValue::newFatal( 'test2' ) );
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'test2' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'blacklisted in session {sessionid}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               // Uncreatable name
+               $session->clear();
+               $user = \User::newFromName( $username . '@' );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'noname' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username . '@', $user->getId() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'name "{username}" is not creatable' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame( 'noname', $session->get( 'AuthManager::AutoCreateBlacklist' ) );
+
+               // IP unable to create accounts
+               $wgGroupPermissions['*']['createaccount'] = false;
+               $wgGroupPermissions['*']['autocreateaccount'] = false;
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'authmanager-autocreate-noperm' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'IP lacks the ability to create or autocreate accounts' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame(
+                       'authmanager-autocreate-noperm', $session->get( 'AuthManager::AutoCreateBlacklist' )
+               );
+
+               // Test that both permutations of permissions are allowed
+               // (this hits the two "ok" entries in $mocks['pre'])
+               $wgGroupPermissions['*']['createaccount'] = false;
+               $wgGroupPermissions['*']['autocreateaccount'] = true;
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'ok' ), $ret );
+
+               $wgGroupPermissions['*']['createaccount'] = true;
+               $wgGroupPermissions['*']['autocreateaccount'] = false;
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'ok' ), $ret );
+               $logger->clearBuffer();
+
+               // Test lock fail
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $cache = \ObjectCache::getLocalClusterInstance();
+               $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               unset( $lock );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'usernameinprogress' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'Could not acquire account creation lock' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               // Test pre-authentication provider fail
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'fail-in-pre' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertEquals(
+                       StatusValue::newFatal( 'fail-in-pre' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
+               );
+
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'fail-in-primary' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertEquals(
+                       StatusValue::newFatal( 'fail-in-primary' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
+               );
+
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'fail-in-secondary' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertEquals(
+                       StatusValue::newFatal( 'fail-in-secondary' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
+               );
+
+               // Test backoff
+               $cache = \ObjectCache::getLocalClusterInstance();
+               $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
+               $cache->set( $backoffKey, true );
+               $session->clear();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newFatal( 'authmanager-autocreate-exception' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::DEBUG, '{username} denied by prior creation attempt failures' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
+               $cache->delete( $backoffKey );
+
+               // Test addToDatabase fails
+               $session->clear();
+               $user = $this->getMock( 'User', [ 'addToDatabase' ] );
+               $user->expects( $this->once() )->method( 'addToDatabase' )
+                       ->will( $this->returnValue( \Status::newFatal( 'because' ) ) );
+               $user->setName( $username );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->assertEquals( \Status::newFatal( 'because' ), $ret );
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertNotEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
+                       [ LogLevel::ERROR, '{username} failed with message {message}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
+
+               // Test addToDatabase throws an exception
+               $cache = \ObjectCache::getLocalClusterInstance();
+               $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
+               $this->assertFalse( $cache->get( $backoffKey ), 'sanity check' );
+               $session->clear();
+               $user = $this->getMock( 'User', [ 'addToDatabase' ] );
+               $user->expects( $this->once() )->method( 'addToDatabase' )
+                       ->will( $this->throwException( new \Exception( 'Excepted' ) ) );
+               $user->setName( $username );
+               try {
+                       $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \Exception $ex ) {
+                       $this->assertSame( 'Excepted', $ex->getMessage() );
+               }
+               $this->assertEquals( 0, $user->getId() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
+                       [ LogLevel::ERROR, '{username} failed with exception {exception}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
+               $this->assertNotEquals( false, $cache->get( $backoffKey ) );
+               $cache->delete( $backoffKey );
+
+               // Test addToDatabase fails because the user already exists.
+               $session->clear();
+               $user = $this->getMock( 'User', [ 'addToDatabase' ] );
+               $user->expects( $this->once() )->method( 'addToDatabase' )
+                       ->will( $this->returnCallback( function () use ( $username ) {
+                               $status = \User::newFromName( $username )->addToDatabase();
+                               $this->assertTrue( $status->isOK(), 'sanity check' );
+                               return \Status::newFatal( 'userexists' );
+                       } ) );
+               $user->setName( $username );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $expect = \Status::newGood();
+               $expect->warning( 'userexists' );
+               $this->assertEquals( $expect, $ret );
+               $this->assertNotEquals( 0, $user->getId() );
+               $this->assertEquals( $username, $user->getName() );
+               $this->assertEquals( $user->getId(), $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
+                       [ LogLevel::INFO, '{username} already exists locally (race)' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
+
+               // Success!
+               $session->clear();
+               $username = self::usernameForCreation();
+               $user = \User::newFromName( $username );
+               $this->hook( 'AuthPluginAutoCreate', $this->once() )
+                       ->with( $callback );
+               $this->hideDeprecated( 'AuthPluginAutoCreate hook (used in ' .
+                               get_class( $wgHooks['AuthPluginAutoCreate'][0] ) . '::onAuthPluginAutoCreate)' );
+               $this->hook( 'LocalUserCreated', $this->once() )
+                       ->with( $callback, $this->equalTo( true ) );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
+               $this->unhook( 'LocalUserCreated' );
+               $this->unhook( 'AuthPluginAutoCreate' );
+               $this->assertEquals( \Status::newGood(), $ret );
+               $this->assertNotEquals( 0, $user->getId() );
+               $this->assertEquals( $username, $user->getName() );
+               $this->assertEquals( $user->getId(), $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               $dbw = wfGetDB( DB_MASTER );
+               $maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
+               $session->clear();
+               $username = self::usernameForCreation();
+               $user = \User::newFromName( $username );
+               $this->hook( 'LocalUserCreated', $this->once() )
+                       ->with( $callback, $this->equalTo( true ) );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
+               $this->unhook( 'LocalUserCreated' );
+               $this->assertEquals( \Status::newGood(), $ret );
+               $this->assertNotEquals( 0, $user->getId() );
+               $this->assertEquals( $username, $user->getName() );
+               $this->assertEquals( 0, $session->getUser()->getId() );
+               $this->assertSame( [
+                       [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+               $this->assertSame(
+                       $maxLogId,
+                       $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] )
+               );
+
+               $this->config->set( 'NewUserLog', true );
+               $session->clear();
+               $username = self::usernameForCreation();
+               $user = \User::newFromName( $username );
+               $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
+               $this->assertEquals( \Status::newGood(), $ret );
+               $logger->clearBuffer();
+
+               $data = \DatabaseLogEntry::getSelectQueryData();
+               $rows = iterator_to_array( $dbw->select(
+                       $data['tables'],
+                       $data['fields'],
+                       [
+                               'log_id > ' . (int)$maxLogId,
+                               'log_type' => 'newusers'
+                       ] + $data['conds'],
+                       __METHOD__,
+                       $data['options'],
+                       $data['join_conds']
+               ) );
+               $this->assertCount( 1, $rows );
+               $entry = \DatabaseLogEntry::newFromRow( reset( $rows ) );
+
+               $this->assertSame( 'autocreate', $entry->getSubtype() );
+               $this->assertSame( $user->getId(), $entry->getPerformer()->getId() );
+               $this->assertSame( $user->getName(), $entry->getPerformer()->getName() );
+               $this->assertSame( $user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
+               $this->assertSame( [ '4::userid' => $user->getId() ], $entry->getParameters() );
+
+               $workaroundPHPUnitBug = true;
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $expect
+        * @param array $state
+        */
+       public function testGetAuthenticationRequests( $action, $expect, $state = [] ) {
+               $makeReq = function ( $key ) use ( $action ) {
+                       $req = $this->getMock( AuthenticationRequest::class );
+                       $req->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+                       $req->action = $action === AuthManager::ACTION_UNLINK ? AuthManager::ACTION_REMOVE : $action;
+                       $req->key = $key;
+                       return $req;
+               };
+               $cmpReqs = function ( $a, $b ) {
+                       $ret = strcmp( get_class( $a ), get_class( $b ) );
+                       if ( !$ret ) {
+                               $ret = strcmp( $a->key, $b->key );
+                       }
+                       return $ret;
+               };
+
+               $good = StatusValue::newGood();
+
+               $mocks = [];
+               foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
+                       $class = ucfirst( $key ) . 'AuthenticationProvider';
+                       $mocks[$key] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+                       $mocks[$key]->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                               ->will( $this->returnCallback( function ( $action ) use ( $key, $makeReq ) {
+                                       return [ $makeReq( "$key-$action" ), $makeReq( 'generic' ) ];
+                               } ) );
+                       $mocks[$key]->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
+                               ->will( $this->returnValue( $good ) );
+               }
+
+               $primaries = [];
+               foreach ( [
+                       PrimaryAuthenticationProvider::TYPE_NONE,
+                       PrimaryAuthenticationProvider::TYPE_CREATE,
+                       PrimaryAuthenticationProvider::TYPE_LINK
+               ] as $type ) {
+                       $class = 'PrimaryAuthenticationProvider';
+                       $mocks["primary-$type"] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks["primary-$type"]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( "primary-$type" ) );
+                       $mocks["primary-$type"]->expects( $this->any() )->method( 'accountCreationType' )
+                               ->will( $this->returnValue( $type ) );
+                       $mocks["primary-$type"]->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                               ->will( $this->returnCallback( function ( $action ) use ( $type, $makeReq ) {
+                                       return [ $makeReq( "primary-$type-$action" ), $makeReq( 'generic' ) ];
+                               } ) );
+                       $mocks["primary-$type"]->expects( $this->any() )
+                               ->method( 'providerAllowsAuthenticationDataChange' )
+                               ->will( $this->returnValue( $good ) );
+                       $this->primaryauthMocks[] = $mocks["primary-$type"];
+               }
+
+               $mocks['primary2'] = $this->getMockForAbstractClass(
+                       PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider"
+               );
+               $mocks['primary2']->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary2' ) );
+               $mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               $mocks['primary2']->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                       ->will( $this->returnValue( [] ) );
+               $mocks['primary2']->expects( $this->any() )
+                       ->method( 'providerAllowsAuthenticationDataChange' )
+                       ->will( $this->returnCallback( function ( $req ) use ( $good ) {
+                               return $req->key === 'generic' ? StatusValue::newFatal( 'no' ) : $good;
+                       } ) );
+               $this->primaryauthMocks[] = $mocks['primary2'];
+
+               $this->preauthMocks = [ $mocks['pre'] ];
+               $this->secondaryauthMocks = [ $mocks['secondary'] ];
+               $this->initializeManager( true );
+
+               if ( $state ) {
+                       if ( isset( $state['continueRequests'] ) ) {
+                               $state['continueRequests'] = array_map( $makeReq, $state['continueRequests'] );
+                       }
+                       if ( $action === AuthManager::ACTION_LOGIN_CONTINUE ) {
+                               $this->request->getSession()->setSecret( 'AuthManager::authnState', $state );
+                       } elseif ( $action === AuthManager::ACTION_CREATE_CONTINUE ) {
+                               $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', $state );
+                       } elseif ( $action === AuthManager::ACTION_LINK_CONTINUE ) {
+                               $this->request->getSession()->setSecret( 'AuthManager::accountLinkState', $state );
+                       }
+               }
+
+               $expectReqs = array_map( $makeReq, $expect );
+               if ( $action === AuthManager::ACTION_LOGIN ) {
+                       $req = new RememberMeAuthenticationRequest;
+                       $req->action = $action;
+                       $req->required = AuthenticationRequest::REQUIRED;
+                       $expectReqs[] = $req;
+               } elseif ( $action === AuthManager::ACTION_CREATE ) {
+                       $req = new UsernameAuthenticationRequest;
+                       $req->action = $action;
+                       $expectReqs[] = $req;
+                       $req = new UserDataAuthenticationRequest;
+                       $req->action = $action;
+                       $req->required = AuthenticationRequest::REQUIRED;
+                       $expectReqs[] = $req;
+               }
+               usort( $expectReqs, $cmpReqs );
+
+               $actual = $this->manager->getAuthenticationRequests( $action );
+               foreach ( $actual as $req ) {
+                       // Don't test this here.
+                       $req->required = AuthenticationRequest::REQUIRED;
+               }
+               usort( $actual, $cmpReqs );
+
+               $this->assertEquals( $expectReqs, $actual );
+
+               // Test CreationReasonAuthenticationRequest gets returned
+               if ( $action === AuthManager::ACTION_CREATE ) {
+                       $req = new CreationReasonAuthenticationRequest;
+                       $req->action = $action;
+                       $req->required = AuthenticationRequest::REQUIRED;
+                       $expectReqs[] = $req;
+                       usort( $expectReqs, $cmpReqs );
+
+                       $actual = $this->manager->getAuthenticationRequests( $action, \User::newFromName( 'UTSysop' ) );
+                       foreach ( $actual as $req ) {
+                               // Don't test this here.
+                               $req->required = AuthenticationRequest::REQUIRED;
+                       }
+                       usort( $actual, $cmpReqs );
+
+                       $this->assertEquals( $expectReqs, $actual );
+               }
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               return [
+                       [
+                               AuthManager::ACTION_LOGIN,
+                               [ 'pre-login', 'primary-none-login', 'primary-create-login',
+                                       'primary-link-login', 'secondary-login', 'generic' ],
+                       ],
+                       [
+                               AuthManager::ACTION_CREATE,
+                               [ 'pre-create', 'primary-none-create', 'primary-create-create',
+                                       'primary-link-create', 'secondary-create', 'generic' ],
+                       ],
+                       [
+                               AuthManager::ACTION_LINK,
+                               [ 'primary-link-link', 'generic' ],
+                       ],
+                       [
+                               AuthManager::ACTION_CHANGE,
+                               [ 'primary-none-change', 'primary-create-change', 'primary-link-change',
+                                       'secondary-change' ],
+                       ],
+                       [
+                               AuthManager::ACTION_REMOVE,
+                               [ 'primary-none-remove', 'primary-create-remove', 'primary-link-remove',
+                                       'secondary-remove' ],
+                       ],
+                       [
+                               AuthManager::ACTION_UNLINK,
+                               [ 'primary-link-remove' ],
+                       ],
+                       [
+                               AuthManager::ACTION_LOGIN_CONTINUE,
+                               [],
+                       ],
+                       [
+                               AuthManager::ACTION_LOGIN_CONTINUE,
+                               $reqs = [ 'continue-login', 'foo', 'bar' ],
+                               [
+                                       'continueRequests' => $reqs,
+                               ],
+                       ],
+                       [
+                               AuthManager::ACTION_CREATE_CONTINUE,
+                               [],
+                       ],
+                       [
+                               AuthManager::ACTION_CREATE_CONTINUE,
+                               $reqs = [ 'continue-create', 'foo', 'bar' ],
+                               [
+                                       'continueRequests' => $reqs,
+                               ],
+                       ],
+                       [
+                               AuthManager::ACTION_LINK_CONTINUE,
+                               [],
+                       ],
+                       [
+                               AuthManager::ACTION_LINK_CONTINUE,
+                               $reqs = [ 'continue-link', 'foo', 'bar' ],
+                               [
+                                       'continueRequests' => $reqs,
+                               ],
+                       ],
+               ];
+       }
+
+       public function testGetAuthenticationRequestsRequired() {
+               $makeReq = function ( $key, $required ) {
+                       $req = $this->getMock( AuthenticationRequest::class );
+                       $req->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+                       $req->action = AuthManager::ACTION_LOGIN;
+                       $req->key = $key;
+                       $req->required = $required;
+                       return $req;
+               };
+               $cmpReqs = function ( $a, $b ) {
+                       $ret = strcmp( get_class( $a ), get_class( $b ) );
+                       if ( !$ret ) {
+                               $ret = strcmp( $a->key, $b->key );
+                       }
+                       return $ret;
+               };
+
+               $good = StatusValue::newGood();
+
+               $primary1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $primary1->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary1' ) );
+               $primary1->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $primary1->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                       ->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
+                               return [
+                                       $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "required", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
+                                       $makeReq( "foo", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "bar", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "baz", AuthenticationRequest::OPTIONAL ),
+                               ];
+                       } ) );
+
+               $primary2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $primary2->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'primary2' ) );
+               $primary2->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $primary2->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                       ->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
+                               return [
+                                       $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "required2", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "optional2", AuthenticationRequest::OPTIONAL ),
+                               ];
+                       } ) );
+
+               $secondary = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
+               $secondary->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'secondary' ) );
+               $secondary->expects( $this->any() )->method( 'getAuthenticationRequests' )
+                       ->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
+                               return [
+                                       $makeReq( "foo", AuthenticationRequest::OPTIONAL ),
+                                       $makeReq( "bar", AuthenticationRequest::REQUIRED ),
+                                       $makeReq( "baz", AuthenticationRequest::REQUIRED ),
+                               ];
+                       } ) );
+
+               $rememberReq = new RememberMeAuthenticationRequest;
+               $rememberReq->action = AuthManager::ACTION_LOGIN;
+
+               $this->primaryauthMocks = [ $primary1, $primary2 ];
+               $this->secondaryauthMocks = [ $secondary ];
+               $this->initializeManager( true );
+
+               $actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
+               $expected = [
+                       $rememberReq,
+                       $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
+                       $makeReq( "required2", AuthenticationRequest::PRIMARY_REQUIRED ),
+                       $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
+                       $makeReq( "optional2", AuthenticationRequest::OPTIONAL ),
+                       $makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
+                       $makeReq( "bar", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "baz", AuthenticationRequest::REQUIRED ),
+               ];
+               usort( $actual, $cmpReqs );
+               usort( $expected, $cmpReqs );
+               $this->assertEquals( $expected, $actual );
+
+               $this->primaryauthMocks = [ $primary1 ];
+               $this->secondaryauthMocks = [ $secondary ];
+               $this->initializeManager( true );
+
+               $actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
+               $expected = [
+                       $rememberReq,
+                       $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "required", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
+                       $makeReq( "foo", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "bar", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "baz", AuthenticationRequest::REQUIRED ),
+               ];
+               usort( $actual, $cmpReqs );
+               usort( $expected, $cmpReqs );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testAllowsPropertyChange() {
+               $mocks = [];
+               foreach ( [ 'primary', 'secondary' ] as $key ) {
+                       $class = ucfirst( $key ) . 'AuthenticationProvider';
+                       $mocks[$key] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+                       $mocks[$key]->expects( $this->any() )->method( 'providerAllowsPropertyChange' )
+                               ->will( $this->returnCallback( function ( $prop ) use ( $key ) {
+                                       return $prop !== $key;
+                               } ) );
+               }
+
+               $this->primaryauthMocks = [ $mocks['primary'] ];
+               $this->secondaryauthMocks = [ $mocks['secondary'] ];
+               $this->initializeManager( true );
+
+               $this->assertTrue( $this->manager->allowsPropertyChange( 'foo' ) );
+               $this->assertFalse( $this->manager->allowsPropertyChange( 'primary' ) );
+               $this->assertFalse( $this->manager->allowsPropertyChange( 'secondary' ) );
+       }
+
+       public function testAutoCreateOnLogin() {
+               $username = self::usernameForCreation();
+
+               $req = $this->getMock( AuthenticationRequest::class );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
+               $mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
+                       ->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+
+               $mock2 = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
+               $mock2->expects( $this->any() )->method( 'getUniqueId' )
+                       ->will( $this->returnValue( 'secondary' ) );
+               $mock2->expects( $this->any() )->method( 'beginSecondaryAuthentication' )->will(
+                       $this->returnValue(
+                               AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) )
+                       )
+               );
+               $mock2->expects( $this->any() )->method( 'continueSecondaryAuthentication' )
+                       ->will( $this->returnValue( AuthenticationResponse::newAbstain() ) );
+               $mock2->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+
+               $this->primaryauthMocks = [ $mock ];
+               $this->secondaryauthMocks = [ $mock2 ];
+               $this->initializeManager( true );
+               $this->manager->setLogger( new \Psr\Log\NullLogger() );
+               $session = $this->request->getSession();
+               $session->clear();
+
+               $this->assertSame( 0, \User::newFromName( $username )->getId(),
+                       'sanity check' );
+
+               $callback = $this->callback( function ( $user ) use ( $username ) {
+                       return $user->getName() === $username;
+               } );
+
+               $this->hook( 'UserLoggedIn', $this->never() );
+               $this->hook( 'LocalUserCreated', $this->once() )->with( $callback, $this->equalTo( true ) );
+               $ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->unhook( 'UserLoggedIn' );
+               $this->assertSame( AuthenticationResponse::UI, $ret->status );
+
+               $id = (int)\User::newFromName( $username )->getId();
+               $this->assertNotSame( 0, \User::newFromName( $username )->getId() );
+               $this->assertSame( 0, $session->getUser()->getId() );
+
+               $this->hook( 'UserLoggedIn', $this->once() )->with( $callback );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->continueAuthentication( [] );
+               $this->unhook( 'LocalUserCreated' );
+               $this->unhook( 'UserLoggedIn' );
+               $this->assertSame( AuthenticationResponse::PASS, $ret->status );
+               $this->assertSame( $username, $ret->username );
+               $this->assertSame( $id, $session->getUser()->getId() );
+       }
+
+       public function testAutoCreateFailOnLogin() {
+               $username = self::usernameForCreation();
+
+               $mock = $this->getMockForAbstractClass(
+                       PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider" );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
+               $mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
+                       ->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
+               $mock->expects( $this->any() )->method( 'testUserForCreation' )
+                       ->will( $this->returnValue( StatusValue::newFatal( 'fail-from-primary' ) ) );
+
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+               $this->manager->setLogger( new \Psr\Log\NullLogger() );
+               $session = $this->request->getSession();
+               $session->clear();
+
+               $this->assertSame( 0, $session->getUser()->getId(),
+                       'sanity check' );
+               $this->assertSame( 0, \User::newFromName( $username )->getId(),
+                       'sanity check' );
+
+               $this->hook( 'UserLoggedIn', $this->never() );
+               $this->hook( 'LocalUserCreated', $this->never() );
+               $ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
+               $this->unhook( 'LocalUserCreated' );
+               $this->unhook( 'UserLoggedIn' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'authmanager-authn-autocreate-failed', $ret->message->getKey() );
+
+               $this->assertSame( 0, \User::newFromName( $username )->getId() );
+               $this->assertSame( 0, $session->getUser()->getId() );
+       }
+
+       public function testAuthenticationSessionData() {
+               $this->initializeManager( true );
+
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
+               $this->manager->setAuthenticationSessionData( 'foo', 'foo!' );
+               $this->manager->setAuthenticationSessionData( 'bar', 'bar!' );
+               $this->assertSame( 'foo!', $this->manager->getAuthenticationSessionData( 'foo' ) );
+               $this->assertSame( 'bar!', $this->manager->getAuthenticationSessionData( 'bar' ) );
+               $this->manager->removeAuthenticationSessionData( 'foo' );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
+               $this->assertSame( 'bar!', $this->manager->getAuthenticationSessionData( 'bar' ) );
+               $this->manager->removeAuthenticationSessionData( 'bar' );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) );
+
+               $this->manager->setAuthenticationSessionData( 'foo', 'foo!' );
+               $this->manager->setAuthenticationSessionData( 'bar', 'bar!' );
+               $this->manager->removeAuthenticationSessionData( null );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) );
+
+       }
+
+       public function testCanLinkAccounts() {
+               $types = [
+                       PrimaryAuthenticationProvider::TYPE_CREATE => true,
+                       PrimaryAuthenticationProvider::TYPE_LINK => true,
+                       PrimaryAuthenticationProvider::TYPE_NONE => false,
+               ];
+
+               foreach ( $types as $type => $can ) {
+                       $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+                       $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $type ) );
+                       $mock->expects( $this->any() )->method( 'accountCreationType' )
+                               ->will( $this->returnValue( $type ) );
+                       $this->primaryauthMocks = [ $mock ];
+                       $this->initializeManager( true );
+                       $this->assertSame( $can, $this->manager->canCreateAccounts(), $type );
+               }
+       }
+
+       public function testBeginAccountLink() {
+               $user = \User::newFromName( 'UTSysop' );
+               $this->initializeManager();
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountLinkState', 'test' );
+               try {
+                       $this->manager->beginAccountLink( $user, [], 'http://localhost/' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \LogicException $ex ) {
+                       $this->assertEquals( 'Account linking is not possible', $ex->getMessage() );
+               }
+               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $ret = $this->manager->beginAccountLink( new \User, [], 'http://localhost/' );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'noname', $ret->message->getKey() );
+
+               $ret = $this->manager->beginAccountLink(
+                       \User::newFromName( 'UTDoesNotExist' ), [], 'http://localhost/'
+               );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'authmanager-userdoesnotexist', $ret->message->getKey() );
+       }
+
+       public function testContinueAccountLink() {
+               $user = \User::newFromName( 'UTSysop' );
+               $this->initializeManager();
+
+               $session = [
+                       'userid' => $user->getId(),
+                       'username' => $user->getName(),
+                       'primary' => 'X',
+               ];
+
+               try {
+                       $this->manager->continueAccountLink( [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \LogicException $ex ) {
+                       $this->assertEquals( 'Account linking is not possible', $ex->getMessage() );
+               }
+
+               $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
+               $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
+               $mock->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               $mock->expects( $this->any() )->method( 'beginPrimaryAccountLink' )->will(
+                       $this->returnValue( AuthenticationResponse::newFail( $this->message( 'fail' ) ) )
+               );
+               $this->primaryauthMocks = [ $mock ];
+               $this->initializeManager( true );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountLinkState', null );
+               $ret = $this->manager->continueAccountLink( [] );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'authmanager-link-not-in-progress', $ret->message->getKey() );
+
+               $this->request->getSession()->setSecret( 'AuthManager::accountLinkState',
+                       [ 'username' => $user->getName() . '<>' ] + $session );
+               $ret = $this->manager->continueAccountLink( [] );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'noname', $ret->message->getKey() );
+               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
+
+               $id = $user->getId();
+               $this->request->getSession()->setSecret( 'AuthManager::accountLinkState',
+                       [ 'userid' => $id + 1 ] + $session );
+               try {
+                       $ret = $this->manager->continueAccountLink( [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertEquals(
+                               "User \"{$user->getName()}\" is valid, but ID $id != " . ( $id + 1 ) . '!',
+                               $ex->getMessage()
+                       );
+               }
+               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
+       }
+
+       /**
+        * @dataProvider provideAccountLink
+        * @param StatusValue $preTest
+        * @param array $primaryResponses
+        * @param array $managerResponses
+        */
+       public function testAccountLink(
+               StatusValue $preTest, array $primaryResponses, array $managerResponses
+       ) {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $this->initializeManager();
+
+               // Set up lots of mocks...
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $req->primary = $primaryResponses;
+               $mocks = [];
+
+               foreach ( [ 'pre', 'primary' ] as $key ) {
+                       $class = ucfirst( $key ) . 'AuthenticationProvider';
+                       $mocks[$key] = $this->getMockForAbstractClass(
+                               "MediaWiki\\Auth\\$class", [], "Mock$class"
+                       );
+                       $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( $key ) );
+
+                       for ( $i = 2; $i <= 3; $i++ ) {
+                               $mocks[$key . $i] = $this->getMockForAbstractClass(
+                                       "MediaWiki\\Auth\\$class", [], "Mock$class"
+                               );
+                               $mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
+                                       ->will( $this->returnValue( $key . $i ) );
+                       }
+               }
+
+               $mocks['pre']->expects( $this->any() )->method( 'testForAccountLink' )
+                       ->will( $this->returnCallback(
+                               function ( $u )
+                                       use ( $user, $preTest )
+                               {
+                                       $this->assertSame( $user->getId(), $u->getId() );
+                                       $this->assertSame( $user->getName(), $u->getName() );
+                                       return $preTest;
+                               }
+                       ) );
+
+               $mocks['pre2']->expects( $this->atMost( 1 ) )->method( 'testForAccountLink' )
+                       ->will( $this->returnValue( StatusValue::newGood() ) );
+
+               $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               $ct = count( $req->primary );
+               $callback = $this->returnCallback( function ( $u, $reqs ) use ( $user, $req ) {
+                       $this->assertSame( $user->getId(), $u->getId() );
+                       $this->assertSame( $user->getName(), $u->getName() );
+                       $foundReq = false;
+                       foreach ( $reqs as $r ) {
+                               $this->assertSame( $user->getName(), $r->username );
+                               $foundReq = $foundReq || get_class( $r ) === get_class( $req );
+                       }
+                       $this->assertTrue( $foundReq, '$reqs contains $req' );
+                       return array_shift( $req->primary );
+               } );
+               $mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
+                       ->method( 'beginPrimaryAccountLink' )
+                       ->will( $callback );
+               $mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
+                       ->method( 'continuePrimaryAccountLink' )
+                       ->will( $callback );
+
+               $abstain = AuthenticationResponse::newAbstain();
+               $mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
+               $mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAccountLink' )
+                       ->will( $this->returnValue( $abstain ) );
+               $mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAccountLink' );
+               $mocks['primary3']->expects( $this->any() )->method( 'accountCreationType' )
+                       ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
+               $mocks['primary3']->expects( $this->never() )->method( 'beginPrimaryAccountLink' );
+               $mocks['primary3']->expects( $this->never() )->method( 'continuePrimaryAccountLink' );
+
+               $this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
+               $this->primaryauthMocks = [ $mocks['primary3'], $mocks['primary2'], $mocks['primary'] ];
+               $this->logger = new \TestLogger( true, function ( $message, $level ) {
+                       return $level === LogLevel::DEBUG ? null : $message;
+               } );
+               $this->initializeManager( true );
+
+               $constraint = \PHPUnit_Framework_Assert::logicalOr(
+                       $this->equalTo( AuthenticationResponse::PASS ),
+                       $this->equalTo( AuthenticationResponse::FAIL )
+               );
+               $providers = array_merge( $this->preauthMocks, $this->primaryauthMocks );
+               foreach ( $providers as $p ) {
+                       $p->postCalled = false;
+                       $p->expects( $this->atMost( 1 ) )->method( 'postAccountLink' )
+                               ->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
+                                       $this->assertInstanceOf( 'User', $user );
+                                       $this->assertSame( 'UTSysop', $user->getName() );
+                                       $this->assertInstanceOf( AuthenticationResponse::class, $response );
+                                       $this->assertThat( $response->status, $constraint );
+                                       $p->postCalled = $response->status;
+                               } );
+               }
+
+               $first = true;
+               $created = false;
+               $expectLog = [];
+               foreach ( $managerResponses as $i => $response ) {
+                       if ( $response instanceof AuthenticationResponse &&
+                               $response->status === AuthenticationResponse::PASS
+                       ) {
+                               $expectLog[] = [ LogLevel::INFO, 'Account linked to {user} by primary' ];
+                       }
+
+                       $ex = null;
+                       try {
+                               if ( $first ) {
+                                       $ret = $this->manager->beginAccountLink( $user, [ $req ], 'http://localhost/' );
+                               } else {
+                                       $ret = $this->manager->continueAccountLink( [ $req ] );
+                               }
+                               if ( $response instanceof \Exception ) {
+                                       $this->fail( 'Expected exception not thrown', "Response $i" );
+                               }
+                       } catch ( \Exception $ex ) {
+                               if ( !$response instanceof \Exception ) {
+                                       throw $ex;
+                               }
+                               $this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
+                               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
+                                       "Response $i, exception, session state" );
+                               return;
+                       }
+
+                       $this->assertSame( 'http://localhost/', $req->returnToUrl );
+
+                       $ret->message = $this->message( $ret->message );
+                       $this->assertEquals( $response, $ret, "Response $i, response" );
+                       if ( $response->status === AuthenticationResponse::PASS ||
+                               $response->status === AuthenticationResponse::FAIL
+                       ) {
+                               $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
+                                       "Response $i, session state" );
+                               foreach ( $providers as $p ) {
+                                       $this->assertSame( $response->status, $p->postCalled,
+                                               "Response $i, post-auth callback called" );
+                               }
+                       } else {
+                               $this->assertNotNull(
+                                       $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
+                                       "Response $i, session state"
+                               );
+                               $this->assertEquals(
+                                       $ret->neededRequests,
+                                       $this->manager->getAuthenticationRequests( AuthManager::ACTION_LINK_CONTINUE ),
+                                       "Response $i, continuation check"
+                               );
+                               foreach ( $providers as $p ) {
+                                       $this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
+                               }
+                       }
+
+                       $first = false;
+               }
+
+               $this->assertSame( $expectLog, $this->logger->getBuffer() );
+       }
+
+       public function provideAccountLink() {
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $good = StatusValue::newGood();
+
+               return [
+                       'Pre-link test fail in pre' => [
+                               StatusValue::newFatal( 'fail-from-pre' ),
+                               [],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
+                               ]
+                       ],
+                       'Failure in primary' => [
+                               $good,
+                               $tmp = [
+                                       AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
+                               ],
+                               $tmp
+                       ],
+                       'All primary abstain' => [
+                               $good,
+                               [
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [
+                                       AuthenticationResponse::newFail( $this->message( 'authmanager-link-no-primary' ) )
+                               ]
+                       ],
+                       'Primary UI, then redirect, then fail' => [
+                               $good,
+                               $tmp = [
+                                       AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
+                                       AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
+                               ],
+                               $tmp
+                       ],
+                       'Primary redirect, then abstain' => [
+                               $good,
+                               [
+                                       $tmp = AuthenticationResponse::newRedirect(
+                                               [ $req ], '/foo.html', [ 'foo' => 'bar' ]
+                                       ),
+                                       AuthenticationResponse::newAbstain(),
+                               ],
+                               [
+                                       $tmp,
+                                       new \DomainException(
+                                               'MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN'
+                                       )
+                               ]
+                       ],
+                       'Primary UI, then pass' => [
+                               $good,
+                               [
+                                       $tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
+                                       AuthenticationResponse::newPass(),
+                               ],
+                               [
+                                       $tmp1,
+                                       AuthenticationResponse::newPass( '' ),
+                               ]
+                       ],
+                       'Primary pass' => [
+                               $good,
+                               [
+                                       AuthenticationResponse::newPass( '' ),
+                               ],
+                               [
+                                       AuthenticationResponse::newPass( '' ),
+                               ]
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/AuthPluginPrimaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..b676d69
--- /dev/null
@@ -0,0 +1,706 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AuthPluginPrimaryAuthenticationProvider
+ */
+class AuthPluginPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testConstruction() {
+               $plugin = new AuthManagerAuthPlugin();
+               try {
+                       $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Trying to wrap AuthManagerAuthPlugin in AuthPluginPrimaryAuthenticationProvider ' .
+                                       'makes no sense.',
+                               $ex->getMessage()
+                       );
+               }
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       [ new PasswordAuthenticationRequest ],
+                       $provider->getAuthenticationRequests( AuthManager::ACTION_LOGIN, [] )
+               );
+
+               $req = $this->getMock( PasswordAuthenticationRequest::class );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin, get_class( $req ) );
+               $this->assertEquals(
+                       [ $req ],
+                       $provider->getAuthenticationRequests( AuthManager::ACTION_LOGIN, [] )
+               );
+
+               $reqType = get_class( $this->getMock( AuthenticationRequest::class ) );
+               try {
+                       $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin, $reqType );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               "$reqType is not a MediaWiki\\Auth\\PasswordAuthenticationRequest",
+                               $ex->getMessage()
+                       );
+               }
+       }
+
+       public function testOnUserSaveSettings() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'updateExternalDB' )
+                       ->with( $this->identicalTo( $user ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               \Hooks::run( 'UserSaveSettings', [ $user ] );
+       }
+
+       public function testOnUserGroupsChanged() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'updateExternalDBGroups' )
+                       ->with(
+                               $this->identicalTo( $user ),
+                               $this->identicalTo( [ 'added' ] ),
+                               $this->identicalTo( [ 'removed' ] )
+                       );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               \Hooks::run( 'UserGroupsChanged', [ $user, [ 'added' ], [ 'removed' ] ] );
+       }
+
+       public function testOnUserLoggedIn() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->exactly( 2 ) )->method( 'updateUser' )
+                       ->with( $this->identicalTo( $user ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               \Hooks::run( 'UserLoggedIn', [ $user ] );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'updateUser' )
+                       ->will( $this->returnCallback( function ( &$user ) {
+                               $user = \User::newFromName( 'UTSysop' );
+                       } ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               try {
+                       \Hooks::run( 'UserLoggedIn', [ $user ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               get_class( $plugin ) . '::updateUser() tried to replace $user!',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
+       public function testOnLocalUserCreated() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->exactly( 2 ) )->method( 'initUser' )
+                       ->with( $this->identicalTo( $user ), $this->identicalTo( false ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               \Hooks::run( 'LocalUserCreated', [ $user, false ] );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'initUser' )
+                       ->will( $this->returnCallback( function ( &$user ) {
+                               $user = \User::newFromName( 'UTSysop' );
+                       } ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               try {
+                       \Hooks::run( 'LocalUserCreated', [ $user, false ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               get_class( $plugin ) . '::initUser() tried to replace $user!',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
+       public function testGetUniqueId() {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertSame(
+                       'MediaWiki\\Auth\\AuthPluginPrimaryAuthenticationProvider:' . get_class( $plugin ),
+                       $provider->getUniqueId()
+               );
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $response
+        * @param bool $allowPasswordChange
+        */
+       public function testGetAuthenticationRequests( $action, $response, $allowPasswordChange ) {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->any() )->method( 'allowPasswordChange' )
+                       ->will( $this->returnValue( $allowPasswordChange ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) );
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               $arr = [ new PasswordAuthenticationRequest() ];
+               return [
+                       [ AuthManager::ACTION_LOGIN, $arr, true ],
+                       [ AuthManager::ACTION_LOGIN, $arr, false ],
+                       [ AuthManager::ACTION_CREATE, $arr, true ],
+                       [ AuthManager::ACTION_CREATE, $arr, false ],
+                       [ AuthManager::ACTION_LINK, [], true ],
+                       [ AuthManager::ACTION_LINK, [], false ],
+                       [ AuthManager::ACTION_CHANGE, $arr, true ],
+                       [ AuthManager::ACTION_CHANGE, [], false ],
+                       [ AuthManager::ACTION_REMOVE, $arr, true ],
+                       [ AuthManager::ACTION_REMOVE, [], false ],
+               ];
+       }
+
+       public function testAuthentication() {
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_LOGIN;
+               $reqs = [ PasswordAuthenticationRequest::class => $req ];
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'authenticate' ] )
+                       ->getMock();
+               $plugin->expects( $this->never() )->method( 'authenticate' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( [] )
+               );
+
+               $req->username = 'foo';
+               $req->password = null;
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = null;
+               $req->password = 'bar';
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = 'foo';
+               $req->password = 'bar';
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'authenticate' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'authenticate' )
+                       ->with( $this->equalTo( 'Foo' ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'Foo', $req ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'authenticate' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( false ) );
+               $plugin->expects( $this->never() )->method( 'authenticate' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $pluginUser = $this->getMockBuilder( 'AuthPluginUser' )
+                       ->setMethods( [ 'isLocked' ] )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $pluginUser->expects( $this->once() )->method( 'isLocked' )
+                       ->will( $this->returnValue( true ) );
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'getUserInstance', 'authenticate' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'getUserInstance' )
+                       ->will( $this->returnValue( $pluginUser ) );
+               $plugin->expects( $this->never() )->method( 'authenticate' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'authenticate' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'authenticate' )
+                       ->with( $this->equalTo( 'Foo' ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( false ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'authenticate', 'strict' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'authenticate' )
+                       ->with( $this->equalTo( 'Foo' ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( false ) );
+               $plugin->expects( $this->any() )->method( 'strict' )->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'wrongpassword', $ret->message->getKey() );
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'authenticate', 'strictUserAuth' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'authenticate' )
+                       ->with( $this->equalTo( 'Foo' ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( false ) );
+               $plugin->expects( $this->any() )->method( 'strictUserAuth' )
+                       ->with( $this->equalTo( 'Foo' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'wrongpassword', $ret->message->getKey() );
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'domainList', 'validDomain', 'setDomain', 'userExists', 'authenticate' ] )
+                       ->getMock();
+               $plugin->expects( $this->any() )->method( 'domainList' )
+                       ->will( $this->returnValue( [ 'Domain1', 'Domain2' ] ) );
+               $plugin->expects( $this->any() )->method( 'validDomain' )
+                       ->will( $this->returnCallback( function ( $domain ) {
+                               return in_array( $domain, [ 'Domain1', 'Domain2' ] );
+                       } ) );
+               $plugin->expects( $this->once() )->method( 'setDomain' )
+                       ->with( $this->equalTo( 'Domain2' ) );
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'authenticate' )
+                       ->with( $this->equalTo( 'Foo' ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               list( $req ) = $provider->getAuthenticationRequests( AuthManager::ACTION_LOGIN, [] );
+               $req->username = 'foo';
+               $req->password = 'bar';
+               $req->domain = 'Domain2';
+               $provider->beginPrimaryAuthentication( [ $req ] );
+       }
+
+       public function testTestUserExists() {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->with( $this->equalTo( 'Foo' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               $this->assertTrue( $provider->testUserExists( 'foo' ) );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->with( $this->equalTo( 'Foo' ) )
+                       ->will( $this->returnValue( false ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               $this->assertFalse( $provider->testUserExists( 'foo' ) );
+       }
+
+       public function testTestUserCanAuthenticate() {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->with( $this->equalTo( 'Foo' ) )
+                       ->will( $this->returnValue( false ) );
+               $plugin->expects( $this->never() )->method( 'getUserInstance' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'foo' ) );
+
+               $pluginUser = $this->getMockBuilder( 'AuthPluginUser' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $pluginUser->expects( $this->once() )->method( 'isLocked' )
+                       ->will( $this->returnValue( true ) );
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->with( $this->equalTo( 'Foo' ) )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'getUserInstance' )
+                       ->with( $this->callback( function ( $user ) {
+                               $this->assertInstanceOf( 'User', $user );
+                               $this->assertEquals( 'Foo', $user->getName() );
+                               return true;
+                       } ) )
+                       ->will( $this->returnValue( $pluginUser ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'foo' ) );
+
+               $pluginUser = $this->getMockBuilder( 'AuthPluginUser' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $pluginUser->expects( $this->once() )->method( 'isLocked' )
+                       ->will( $this->returnValue( false ) );
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'userExists' )
+                       ->with( $this->equalTo( 'Foo' ) )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'getUserInstance' )
+                       ->with( $this->callback( function ( $user ) {
+                               $this->assertInstanceOf( 'User', $user );
+                               $this->assertEquals( 'Foo', $user->getName() );
+                               return true;
+                       } ) )
+                       ->will( $this->returnValue( $pluginUser ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'foo' ) );
+       }
+
+       public function testProviderRevokeAccessForUser() {
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'userExists', 'setPassword' ] )
+                       ->getMock();
+               $plugin->expects( $this->once() )->method( 'userExists' )->willReturn( true );
+               $plugin->expects( $this->once() )->method( 'setPassword' )
+                       ->with( $this->callback( function ( $u ) {
+                               return $u instanceof \User && $u->getName() === 'Foo';
+                       } ), $this->identicalTo( null ) )
+                       ->willReturn( true );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $provider->providerRevokeAccessForUser( 'foo' );
+
+               $plugin = $this->getMockBuilder( 'AuthPlugin' )
+                       ->setMethods( [ 'domainList', 'userExists', 'setPassword' ] )
+                       ->getMock();
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [ 'D1', 'D2', 'D3' ] );
+               $plugin->expects( $this->exactly( 3 ) )->method( 'userExists' )
+                       ->willReturnCallback( function () use ( $plugin ) {
+                               return $plugin->getDomain() !== 'D2';
+                       } );
+               $plugin->expects( $this->exactly( 2 ) )->method( 'setPassword' )
+                       ->with( $this->callback( function ( $u ) {
+                               return $u instanceof \User && $u->getName() === 'Foo';
+                       } ), $this->identicalTo( null ) )
+                       ->willReturnCallback( function () use ( $plugin ) {
+                               $this->assertNotEquals( 'D2', $plugin->getDomain() );
+                               return $plugin->getDomain() !== 'D1';
+                       } );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               try {
+                       $provider->providerRevokeAccessForUser( 'foo' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'AuthPlugin failed to reset password for Foo in the following domains: D1',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
+       public function testProviderAllowsPropertyChange() {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->any() )->method( 'allowPropChange' )
+                       ->will( $this->returnCallback( function ( $prop ) {
+                               return $prop === 'allow';
+                       } ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               $this->assertTrue( $provider->providerAllowsPropertyChange( 'allow' ) );
+               $this->assertFalse( $provider->providerAllowsPropertyChange( 'deny' ) );
+       }
+
+       /**
+        * @dataProvider provideProviderAllowsAuthenticationDataChange
+        * @param string $type
+        * @param bool|null $allow
+        * @param StatusValue $expect
+        */
+       public function testProviderAllowsAuthenticationDataChange( $type, $allow, $expect ) {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $allow === null ? $this->never() : $this->once() )
+                       ->method( 'allowPasswordChange' )->will( $this->returnValue( $allow ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               if ( $type === PasswordAuthenticationRequest::class ) {
+                       $req = new $type();
+               } else {
+                       $req = $this->getMock( $type );
+               }
+               $req->action = AuthManager::ACTION_CHANGE;
+               $req->username = 'UTSysop';
+               $req->password = 'Pa$$w0Rd!!!';
+               $req->retype = 'Pa$$w0Rd!!!';
+               $this->assertEquals( $expect, $provider->providerAllowsAuthenticationDataChange( $req ) );
+       }
+
+       public static function provideProviderAllowsAuthenticationDataChange() {
+               return [
+                       [ AuthenticationRequest::class, null, \StatusValue::newGood( 'ignored' ) ],
+                       [ PasswordAuthenticationRequest::class, true, \StatusValue::newGood() ],
+                       [
+                               PasswordAuthenticationRequest::class,
+                               false,
+                               \StatusValue::newFatal( 'authmanager-authplugin-setpass-denied' )
+                       ],
+               ];
+       }
+
+       public function testProviderChangeAuthenticationData() {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->never() )->method( 'setPassword' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $provider->providerChangeAuthenticationData(
+                       $this->getMock( AuthenticationRequest::class )
+               );
+
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_CHANGE;
+               $req->username = 'foo';
+               $req->password = 'bar';
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'setPassword' )
+                       ->with( $this->callback( function ( $u ) {
+                               return $u instanceof \User && $u->getName() === 'Foo';
+                       } ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $provider->providerChangeAuthenticationData( $req );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )->method( 'setPassword' )
+                       ->with( $this->callback( function ( $u ) {
+                               return $u instanceof \User && $u->getName() === 'Foo';
+                       } ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( false ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               try {
+                       $provider->providerChangeAuthenticationData( $req );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \ErrorPageError $e ) {
+                       $this->assertSame( 'authmanager-authplugin-setpass-failed-title', $e->title );
+                       $this->assertSame( 'authmanager-authplugin-setpass-failed-message', $e->msg );
+               }
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )
+                       ->will( $this->returnValue( [ 'Domain1', 'Domain2' ] ) );
+               $plugin->expects( $this->any() )->method( 'validDomain' )
+                       ->will( $this->returnCallback( function ( $domain ) {
+                               return in_array( $domain, [ 'Domain1', 'Domain2' ] );
+                       } ) );
+               $plugin->expects( $this->once() )->method( 'setDomain' )
+                       ->with( $this->equalTo( 'Domain2' ) );
+               $plugin->expects( $this->once() )->method( 'setPassword' )
+                       ->with( $this->callback( function ( $u ) {
+                               return $u instanceof \User && $u->getName() === 'Foo';
+                       } ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               list( $req ) = $provider->getAuthenticationRequests( AuthManager::ACTION_CREATE, [] );
+               $req->username = 'foo';
+               $req->password = 'bar';
+               $req->domain = 'Domain2';
+               $provider->providerChangeAuthenticationData( $req );
+       }
+
+       /**
+        * @dataProvider provideAccountCreationType
+        * @param bool $can
+        * @param string $expect
+        */
+       public function testAccountCreationType( $can, $expect ) {
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->once() )
+                       ->method( 'canCreateAccounts' )->will( $this->returnValue( $can ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               $this->assertSame( $expect, $provider->accountCreationType() );
+       }
+
+       public static function provideAccountCreationType() {
+               return [
+                       [ true, PrimaryAuthenticationProvider::TYPE_CREATE ],
+                       [ false, PrimaryAuthenticationProvider::TYPE_NONE ],
+               ];
+       }
+
+       public function testTestForAccountCreation() {
+               $user = \User::newFromName( 'foo' );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, [] )
+               );
+       }
+
+       public function testAccountCreation() {
+               $user = \User::newFromName( 'foo' );
+               $user->setEmail( 'email' );
+               $user->setRealName( 'realname' );
+
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_CREATE;
+               $reqs = [ PasswordAuthenticationRequest::class => $req ];
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->any() )->method( 'canCreateAccounts' )
+                       ->will( $this->returnValue( false ) );
+               $plugin->expects( $this->never() )->method( 'addUser' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               try {
+                       $provider->beginPrimaryAccountCreation( $user, $user, [] );
+                       $this->fail( 'Expected exception was not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+                       $this->assertSame(
+                               'Shouldn\'t call this when accountCreationType() is NONE', $ex->getMessage()
+                       );
+               }
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->any() )->method( 'canCreateAccounts' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->never() )->method( 'addUser' );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, [] )
+               );
+
+               $req->username = 'foo';
+               $req->password = null;
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $req->username = null;
+               $req->password = 'bar';
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $req->username = 'foo';
+               $req->password = 'bar';
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->any() )->method( 'canCreateAccounts' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'addUser' )
+                       ->with(
+                               $this->callback( function ( $u ) {
+                                       return $u instanceof \User && $u->getName() === 'Foo';
+                               } ),
+                               $this->equalTo( 'bar' ),
+                               $this->equalTo( 'email' ),
+                               $this->equalTo( 'realname' )
+                       )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $this->assertEquals(
+                       AuthenticationResponse::newPass(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'domainList' )->willReturn( [] );
+               $plugin->expects( $this->any() )->method( 'canCreateAccounts' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->once() )->method( 'addUser' )
+                       ->with(
+                               $this->callback( function ( $u ) {
+                                       return $u instanceof \User && $u->getName() === 'Foo';
+                               } ),
+                               $this->equalTo( 'bar' ),
+                               $this->equalTo( 'email' ),
+                               $this->equalTo( 'realname' )
+                       )
+                       ->will( $this->returnValue( false ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               $ret = $provider->beginPrimaryAccountCreation( $user, $user, $reqs );
+               $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
+               $this->assertSame( 'authmanager-authplugin-create-fail', $ret->message->getKey() );
+
+               $plugin = $this->getMock( 'AuthPlugin' );
+               $plugin->expects( $this->any() )->method( 'canCreateAccounts' )
+                       ->will( $this->returnValue( true ) );
+               $plugin->expects( $this->any() )->method( 'domainList' )
+                       ->will( $this->returnValue( [ 'Domain1', 'Domain2' ] ) );
+               $plugin->expects( $this->any() )->method( 'validDomain' )
+                       ->will( $this->returnCallback( function ( $domain ) {
+                               return in_array( $domain, [ 'Domain1', 'Domain2' ] );
+                       } ) );
+               $plugin->expects( $this->once() )->method( 'setDomain' )
+                       ->with( $this->equalTo( 'Domain2' ) );
+               $plugin->expects( $this->once() )->method( 'addUser' )
+                       ->with( $this->callback( function ( $u ) {
+                               return $u instanceof \User && $u->getName() === 'Foo';
+                       } ), $this->equalTo( 'bar' ) )
+                       ->will( $this->returnValue( true ) );
+               $provider = new AuthPluginPrimaryAuthenticationProvider( $plugin );
+               list( $req ) = $provider->getAuthenticationRequests( AuthManager::ACTION_CREATE, [] );
+               $req->username = 'foo';
+               $req->password = 'bar';
+               $req->domain = 'Domain2';
+               $provider->beginPrimaryAccountCreation( $user, $user, [ $req ] );
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/AuthenticationRequestTest.php b/tests/phpunit/includes/auth/AuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..84a0ea6
--- /dev/null
@@ -0,0 +1,514 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AuthenticationRequest
+ */
+class AuthenticationRequestTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testBasics() {
+               $mock = $this->getMockForAbstractClass( AuthenticationRequest::class );
+
+               $this->assertSame( get_class( $mock ), $mock->getUniqueId() );
+
+               $this->assertType( 'array', $mock->getMetadata() );
+
+               $ret = $mock->describeCredentials();
+               $this->assertInternalType( 'array', $ret );
+               $this->assertArrayHasKey( 'provider', $ret );
+               $this->assertInstanceOf( 'Message', $ret['provider'] );
+               $this->assertArrayHasKey( 'account', $ret );
+               $this->assertInstanceOf( 'Message', $ret['account'] );
+       }
+
+       public function testLoadRequestsFromSubmission() {
+               $mb = $this->getMockBuilder( AuthenticationRequest::class )
+                       ->setMethods( [ 'loadFromSubmission' ] );
+
+               $data = [ 'foo', 'bar' ];
+
+               $req1 = $mb->getMockForAbstractClass();
+               $req1->expects( $this->once() )->method( 'loadFromSubmission' )
+                       ->with( $this->identicalTo( $data ) )
+                       ->will( $this->returnValue( false ) );
+
+               $req2 = $mb->getMockForAbstractClass();
+               $req2->expects( $this->once() )->method( 'loadFromSubmission' )
+                       ->with( $this->identicalTo( $data ) )
+                       ->will( $this->returnValue( true ) );
+
+               $this->assertSame(
+                       [ $req2 ],
+                       AuthenticationRequest::loadRequestsFromSubmission( [ $req1, $req2 ], $data )
+               );
+       }
+
+       public function testGetRequestByClass() {
+               $mb = $this->getMockBuilder(
+                       AuthenticationRequest::class, 'AuthenticationRequestTest_AuthenticationRequest2'
+               );
+
+               $reqs = [
+                       $this->getMockForAbstractClass(
+                               AuthenticationRequest::class, [], 'AuthenticationRequestTest_AuthenticationRequest1'
+                       ),
+                       $mb->getMockForAbstractClass(),
+                       $mb->getMockForAbstractClass(),
+                       $this->getMockForAbstractClass(
+                               PasswordAuthenticationRequest::class, [],
+                               'AuthenticationRequestTest_PasswordAuthenticationRequest'
+                       ),
+               ];
+
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, 'AuthenticationRequestTest_AuthenticationRequest0'
+               ) );
+               $this->assertSame( $reqs[0], AuthenticationRequest::getRequestByClass(
+                       $reqs, 'AuthenticationRequestTest_AuthenticationRequest1'
+               ) );
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, 'AuthenticationRequestTest_AuthenticationRequest2'
+               ) );
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, PasswordAuthenticationRequest::class
+               ) );
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, 'ClassThatDoesNotExist'
+               ) );
+
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, 'AuthenticationRequestTest_AuthenticationRequest0', true
+               ) );
+               $this->assertSame( $reqs[0], AuthenticationRequest::getRequestByClass(
+                       $reqs, 'AuthenticationRequestTest_AuthenticationRequest1', true
+               ) );
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, 'AuthenticationRequestTest_AuthenticationRequest2', true
+               ) );
+               $this->assertSame( $reqs[3], AuthenticationRequest::getRequestByClass(
+                       $reqs, PasswordAuthenticationRequest::class, true
+               ) );
+               $this->assertNull( AuthenticationRequest::getRequestByClass(
+                       $reqs, 'ClassThatDoesNotExist', true
+               ) );
+       }
+
+       public function testGetUsernameFromRequests() {
+               $mb = $this->getMockBuilder( AuthenticationRequest::class );
+
+               for ( $i = 0; $i < 3; $i++ ) {
+                       $req = $mb->getMockForAbstractClass();
+                       $req->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
+                               'username' => [
+                                       'type' => 'string',
+                               ],
+                       ] ) );
+                       $reqs[] = $req;
+               }
+
+               $req = $mb->getMockForAbstractClass();
+               $req->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [] ) );
+               $req->username = 'baz';
+               $reqs[] = $req;
+
+               $this->assertNull( AuthenticationRequest::getUsernameFromRequests( $reqs ) );
+
+               $reqs[1]->username = 'foo';
+               $this->assertSame( 'foo', AuthenticationRequest::getUsernameFromRequests( $reqs ) );
+
+               $reqs[0]->username = 'foo';
+               $reqs[2]->username = 'foo';
+               $this->assertSame( 'foo', AuthenticationRequest::getUsernameFromRequests( $reqs ) );
+
+               $reqs[1]->username = 'bar';
+               try {
+                       AuthenticationRequest::getUsernameFromRequests( $reqs );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'Conflicting username fields: "bar" from ' .
+                                       get_class( $reqs[1] ) . '::$username vs. "foo" from ' .
+                                       get_class( $reqs[0] ) . '::$username',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
+       public function testMergeFieldInfo() {
+               $msg = wfMessage( 'foo' );
+
+               $req1 = $this->getMock( AuthenticationRequest::class );
+               $req1->required = AuthenticationRequest::REQUIRED;
+               $req1->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
+                       'string1' => [
+                               'type' => 'string',
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+                       'string2' => [
+                               'type' => 'string',
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+                       'optional' => [
+                               'type' => 'string',
+                               'label' => $msg,
+                               'help' => $msg,
+                               'optional' => true,
+                       ],
+                       'select' => [
+                               'type' => 'select',
+                               'options' => [ 'foo' => $msg, 'baz' => $msg ],
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+               ] ) );
+
+               $req2 = $this->getMock( AuthenticationRequest::class );
+               $req2->required = AuthenticationRequest::REQUIRED;
+               $req2->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
+                       'string1' => [
+                               'type' => 'string',
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+                       'string3' => [
+                               'type' => 'string',
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+                       'select' => [
+                               'type' => 'select',
+                               'options' => [ 'bar' => $msg, 'baz' => $msg ],
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+               ] ) );
+
+               $req3 = $this->getMock( AuthenticationRequest::class );
+               $req3->required = AuthenticationRequest::REQUIRED;
+               $req3->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
+                       'string1' => [
+                               'type' => 'checkbox',
+                               'label' => $msg,
+                               'help' => $msg,
+                       ],
+               ] ) );
+
+               $req4 = $this->getMock( AuthenticationRequest::class );
+               $req4->required = AuthenticationRequest::REQUIRED;
+               $req4->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [] ) );
+
+               // Basic combining
+
+               $fields = AuthenticationRequest::mergeFieldInfo( [ $req1 ] );
+               $expect = $req1->getFieldInfo();
+               foreach ( $expect as $name => &$options ) {
+                       $options['optional'] = !empty( $options['optional'] );
+               }
+               unset( $options );
+               $this->assertEquals( $expect, $fields );
+
+               $fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req4 ] );
+               $this->assertEquals( $expect, $fields );
+
+               try {
+                       AuthenticationRequest::mergeFieldInfo( [ $req1, $req3 ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'Field type conflict for "string1", "string" vs "checkbox"',
+                               $ex->getMessage()
+                       );
+               }
+
+               $fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
+               $expect += $req2->getFieldInfo();
+               $expect['string2']['optional'] = false;
+               $expect['string3']['optional'] = false;
+               $expect['select']['options']['bar'] = $msg;
+               $this->assertEquals( $expect, $fields );
+
+               // Combining with something not required
+
+               $req1->required = AuthenticationRequest::PRIMARY_REQUIRED;
+
+               $fields = AuthenticationRequest::mergeFieldInfo( [ $req1 ] );
+               $expect = $req1->getFieldInfo();
+               foreach ( $expect as $name => &$options ) {
+                       $options['optional'] = true;
+               }
+               unset( $options );
+               $this->assertEquals( $expect, $fields );
+
+               $fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
+               $expect += $req2->getFieldInfo();
+               $expect['string1']['optional'] = false;
+               $expect['string3']['optional'] = false;
+               $expect['select']['optional'] = false;
+               $expect['select']['options']['bar'] = $msg;
+               $this->assertEquals( $expect, $fields );
+       }
+
+       /**
+        * @dataProvider provideLoadFromSubmission
+        * @param array $fieldInfo
+        * @param array $data
+        * @param array|bool $expectState
+        */
+       public function testLoadFromSubmission( $fieldInfo, $data, $expectState ) {
+               $mock = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $mock->expects( $this->any() )->method( 'getFieldInfo' )
+                       ->will( $this->returnValue( $fieldInfo ) );
+
+               $ret = $mock->loadFromSubmission( $data );
+               if ( is_array( $expectState ) ) {
+                       $this->assertTrue( $ret );
+                       $expect = call_user_func( [ get_class( $mock ), '__set_state' ], $expectState );
+                       $this->assertEquals( $expect, $mock );
+               } else {
+                       $this->assertFalse( $ret );
+               }
+       }
+
+       public static function provideLoadFromSubmission() {
+               return [
+                       'No fields' => [
+                               [],
+                               $data = [ 'foo' => 'bar' ],
+                               false
+                       ],
+
+                       'Simple field' => [
+                               [
+                                       'field' => [
+                                               'type' => 'string',
+                                       ],
+                               ],
+                               $data = [ 'field' => 'string!' ],
+                               $data
+                       ],
+                       'Simple field, not supplied' => [
+                               [
+                                       'field' => [
+                                               'type' => 'string',
+                                       ],
+                               ],
+                               [],
+                               false
+                       ],
+                       'Simple field, empty' => [
+                               [
+                                       'field' => [
+                                               'type' => 'string',
+                                       ],
+                               ],
+                               [ 'field' => '' ],
+                               false
+                       ],
+                       'Simple field, optional, not supplied' => [
+                               [
+                                       'field' => [
+                                               'type' => 'string',
+                                               'optional' => true,
+                                       ],
+                               ],
+                               [],
+                               false
+                       ],
+                       'Simple field, optional, empty' => [
+                               [
+                                       'field' => [
+                                               'type' => 'string',
+                                               'optional' => true,
+                                       ],
+                               ],
+                               $data = [ 'field' => '' ],
+                               $data
+                       ],
+
+                       'Checkbox, checked' => [
+                               [
+                                       'check' => [
+                                               'type' => 'checkbox',
+                                       ],
+                               ],
+                               [ 'check' => '' ],
+                               [ 'check' => true ]
+                       ],
+                       'Checkbox, unchecked' => [
+                               [
+                                       'check' => [
+                                               'type' => 'checkbox',
+                                       ],
+                               ],
+                               [],
+                               false
+                       ],
+                       'Checkbox, optional, unchecked' => [
+                               [
+                                       'check' => [
+                                               'type' => 'checkbox',
+                                               'optional' => true,
+                                       ],
+                               ],
+                               [],
+                               [ 'check' => false ]
+                       ],
+
+                       'Button, used' => [
+                               [
+                                       'push' => [
+                                               'type' => 'button',
+                                       ],
+                               ],
+                               [ 'push' => '' ],
+                               [ 'push' => true ]
+                       ],
+                       'Button, unused' => [
+                               [
+                                       'push' => [
+                                               'type' => 'button',
+                                       ],
+                               ],
+                               [],
+                               false
+                       ],
+                       'Button, optional, unused' => [
+                               [
+                                       'push' => [
+                                               'type' => 'button',
+                                               'optional' => true,
+                                       ],
+                               ],
+                               [],
+                               [ 'push' => false ]
+                       ],
+                       'Button, image-style' => [
+                               [
+                                       'push' => [
+                                               'type' => 'button',
+                                       ],
+                               ],
+                               [ 'push_x' => 0, 'push_y' => 0 ],
+                               [ 'push' => true ]
+                       ],
+
+                       'Select' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'select',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               $data = [ 'choose' => 'foo' ],
+                               $data
+                       ],
+                       'Select, invalid choice' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'select',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               $data = [ 'choose' => 'baz' ],
+                               false
+                       ],
+                       'Multiselect (2)' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'multiselect',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               $data = [ 'choose' => [ 'foo', 'bar' ] ],
+                               $data
+                       ],
+                       'Multiselect (1)' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'multiselect',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               $data = [ 'choose' => [ 'bar' ] ],
+                               $data
+                       ],
+                       'Multiselect, string for some reason' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'multiselect',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               [ 'choose' => 'foo' ],
+                               [ 'choose' => [ 'foo' ] ]
+                       ],
+                       'Multiselect, invalid choice' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'multiselect',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               [ 'choose' => [ 'foo', 'baz' ] ],
+                               false
+                       ],
+                       'Multiselect, empty' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'multiselect',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                       ],
+                               ],
+                               [ 'choose' => [] ],
+                               false
+                       ],
+                       'Multiselect, optional, nothing submitted' => [
+                               [
+                                       'choose' => [
+                                               'type' => 'multiselect',
+                                               'options' => [
+                                                       'foo' => wfMessage( 'mainpage' ),
+                                                       'bar' => wfMessage( 'mainpage' ),
+                                               ],
+                                               'optional' => true,
+                                       ],
+                               ],
+                               [],
+                               [ 'choose' => [] ]
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php b/tests/phpunit/includes/auth/AuthenticationRequestTestCase.php
new file mode 100644 (file)
index 0000000..aafcd09
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ */
+abstract class AuthenticationRequestTestCase extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       abstract protected function getInstance( array $args = [] );
+
+       /**
+        * @dataProvider provideGetFieldInfo
+        */
+       public function testGetFieldInfo( array $args ) {
+               $info = $this->getInstance( $args )->getFieldInfo();
+               $this->assertType( 'array', $info );
+
+               foreach ( $info as $field => $data ) {
+                       $this->assertType( 'array', $data, "Field $field" );
+                       $this->assertArrayHasKey( 'type', $data, "Field $field" );
+                       $this->assertArrayHasKey( 'label', $data, "Field $field" );
+                       $this->assertInstanceOf( 'Message', $data['label'], "Field $field, label" );
+
+                       if ( $data['type'] !== 'null' ) {
+                               $this->assertArrayHasKey( 'help', $data, "Field $field" );
+                               $this->assertInstanceOf( 'Message', $data['help'], "Field $field, help" );
+                       }
+
+                       if ( isset( $data['optional'] ) ) {
+                               $this->assertType( 'bool', $data['optional'], "Field $field, optional" );
+                       }
+                       if ( isset( $data['image'] ) ) {
+                               $this->assertType( 'string', $data['image'], "Field $field, image" );
+                       }
+
+                       switch ( $data['type'] ) {
+                               case 'string':
+                               case 'password':
+                               case 'hidden':
+                                       break;
+                               case 'select':
+                               case 'multiselect':
+                                       $this->assertArrayHasKey( 'options', $data, "Field $field" );
+                                       $this->assertType( 'array', $data['options'], "Field $field, options" );
+                                       foreach ( $data['options'] as $val => $msg ) {
+                                               $this->assertInstanceOf( 'Message', $msg, "Field $field, option $val" );
+                                       }
+                                       break;
+                               case 'checkbox':
+                                       break;
+                               case 'button':
+                                       break;
+                               case 'null':
+                                       break;
+                               default:
+                                       $this->fail( "Field $field, unknown type " . $data['type'] );
+                                       break;
+                       }
+               }
+       }
+
+       public static function provideGetFieldInfo() {
+               return [
+                       [ [] ]
+               ];
+       }
+
+       /**
+        * @dataProvider provideLoadFromSubmission
+        * @param array $args
+        * @param array $data
+        * @param array|bool $expectState
+        */
+       public function testLoadFromSubmission( array $args, array $data, $expectState ) {
+               $instance = $this->getInstance( $args );
+               $ret = $instance->loadFromSubmission( $data );
+               if ( is_array( $expectState ) ) {
+                       $this->assertTrue( $ret );
+                       $expect = call_user_func( [ get_class( $instance ), '__set_state' ], $expectState );
+                       $this->assertEquals( $expect, $instance );
+               } else {
+                       $this->assertFalse( $ret );
+               }
+       }
+
+       abstract public function provideLoadFromSubmission();
+}
diff --git a/tests/phpunit/includes/auth/AuthenticationResponseTest.php b/tests/phpunit/includes/auth/AuthenticationResponseTest.php
new file mode 100644 (file)
index 0000000..58ff8b6
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\AuthenticationResponse
+ */
+class AuthenticationResponseTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       /**
+        * @dataProvider provideConstructors
+        * @param string $constructor
+        * @param array $args
+        * @param array|Exception $expect
+        */
+       public function testConstructors( $constructor, $args, $expect ) {
+               if ( is_array( $expect ) ) {
+                       $res = new AuthenticationResponse();
+                       foreach ( $expect as $field => $value ) {
+                               $res->$field = $value;
+                       }
+                       $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
+                       $this->assertEquals( $res, $ret );
+               } else {
+                       try {
+                               call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
+                               $this->fail( 'Expected exception not thrown' );
+                       } catch ( \Exception $ex ) {
+                               $this->assertEquals( $expect, $ex );
+                       }
+               }
+       }
+
+       public function provideConstructors() {
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $msg = new \Message( 'mainpage' );
+
+               return [
+                       [ 'newPass', [], [
+                               'status' => AuthenticationResponse::PASS,
+                       ] ],
+                       [ 'newPass', [ 'name' ], [
+                               'status' => AuthenticationResponse::PASS,
+                               'username' => 'name',
+                       ] ],
+                       [ 'newPass', [ 'name', null ], [
+                               'status' => AuthenticationResponse::PASS,
+                               'username' => 'name',
+                       ] ],
+
+                       [ 'newFail', [ $msg ], [
+                               'status' => AuthenticationResponse::FAIL,
+                               'message' => $msg,
+                       ] ],
+
+                       [ 'newRestart', [ $msg ], [
+                               'status' => AuthenticationResponse::RESTART,
+                               'message' => $msg,
+                       ] ],
+
+                       [ 'newAbstain', [], [
+                               'status' => AuthenticationResponse::ABSTAIN,
+                       ] ],
+
+                       [ 'newUI', [ [ $req ], $msg ], [
+                               'status' => AuthenticationResponse::UI,
+                               'neededRequests' => [ $req ],
+                               'message' => $msg,
+                       ] ],
+                       [ 'newUI', [ [], $msg ],
+                               new \InvalidArgumentException( '$reqs may not be empty' )
+                       ],
+
+                       [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [
+                               'status' => AuthenticationResponse::REDIRECT,
+                               'neededRequests' => [ $req ],
+                               'redirectTarget' => 'http://example.org/redir',
+                       ] ],
+                       [
+                               'newRedirect',
+                               [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ],
+                               [
+                                       'status' => AuthenticationResponse::REDIRECT,
+                                       'neededRequests' => [ $req ],
+                                       'redirectTarget' => 'http://example.org/redir',
+                                       'redirectApiData' => [ 'foo' => 'bar' ],
+                               ]
+                       ],
+                       [ 'newRedirect', [ [], 'http://example.org/redir' ],
+                               new \InvalidArgumentException( '$reqs may not be empty' )
+                       ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/ButtonAuthenticationRequestTest.php b/tests/phpunit/includes/auth/ButtonAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..3bc077c
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\ButtonAuthenticationRequest
+ */
+class ButtonAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               $data = array_intersect_key( $args, [ 'name' => 1, 'label' => 1, 'help' => 1 ] );
+               return ButtonAuthenticationRequest::__set_state( $data );
+       }
+
+       public static function provideGetFieldInfo() {
+               return [
+                       [ [ 'name' => 'foo', 'label' => 'bar', 'help' => 'baz' ] ]
+               ];
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [ 'name' => 'foo', 'label' => 'bar', 'help' => 'baz' ],
+                               [],
+                               false
+                       ],
+                       'Button present' => [
+                               [ 'name' => 'foo', 'label' => 'bar', 'help' => 'baz' ],
+                               [ 'foo' => 'Foobar' ],
+                               [ 'name' => 'foo', 'label' => 'bar', 'help' => 'baz', 'foo' => true ]
+                       ],
+               ];
+       }
+
+       public function testGetUniqueId() {
+               $req = new ButtonAuthenticationRequest( 'foo', wfMessage( 'bar' ), wfMessage( 'baz' ) );
+               $this->assertSame(
+                       'MediaWiki\\Auth\\ButtonAuthenticationRequest:foo', $req->getUniqueId()
+               );
+       }
+
+       public function testGetRequestByName() {
+               $reqs = [];
+               $reqs['testOne'] = new ButtonAuthenticationRequest(
+                       'foo', wfMessage( 'msg' ), wfMessage( 'help' )
+               );
+               $reqs[] = new ButtonAuthenticationRequest( 'bar', wfMessage( 'msg1' ), wfMessage( 'help1' ) );
+               $reqs[] = new ButtonAuthenticationRequest( 'bar', wfMessage( 'msg2' ), wfMessage( 'help2' ) );
+               $reqs['testSub'] = $this->getMockBuilder( ButtonAuthenticationRequest::class )
+                       ->setConstructorArgs( [ 'subclass', wfMessage( 'msg3' ), wfMessage( 'help3' ) ] )
+                       ->getMock();
+
+               $this->assertNull( ButtonAuthenticationRequest::getRequestByName( $reqs, 'missing' ) );
+               $this->assertSame(
+                       $reqs['testOne'], ButtonAuthenticationRequest::getRequestByName( $reqs, 'foo' )
+               );
+               $this->assertNull( ButtonAuthenticationRequest::getRequestByName( $reqs, 'bar' ) );
+               $this->assertSame(
+                       $reqs['testSub'], ButtonAuthenticationRequest::getRequestByName( $reqs, 'subclass' )
+               );
+       }
+}
diff --git a/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..f2341bc
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @group Database
+ * @covers MediaWiki\Auth\CheckBlocksSecondaryAuthenticationProvider
+ */
+class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testConstructor() {
+               $provider = new CheckBlocksSecondaryAuthenticationProvider();
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $config = new \HashConfig( [
+                       'BlockDisablesLogin' => false
+               ] );
+               $provider->setConfig( $config );
+               $this->assertSame( false, $providerPriv->blockDisablesLogin );
+
+               $provider = new CheckBlocksSecondaryAuthenticationProvider(
+                       [ 'blockDisablesLogin' => true ]
+               );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $config = new \HashConfig( [
+                       'BlockDisablesLogin' => false
+               ] );
+               $provider->setConfig( $config );
+               $this->assertSame( true, $providerPriv->blockDisablesLogin );
+       }
+
+       public function testBasics() {
+               $provider = new CheckBlocksSecondaryAuthenticationProvider();
+               $user = \User::newFromName( 'UTSysop' );
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginSecondaryAccountCreation( $user, $user, [] )
+               );
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $response
+        */
+       public function testGetAuthenticationRequests( $action, $response ) {
+               $provider = new CheckBlocksSecondaryAuthenticationProvider();
+
+               $this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) );
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               return [
+                       [ AuthManager::ACTION_LOGIN, [] ],
+                       [ AuthManager::ACTION_CREATE, [] ],
+                       [ AuthManager::ACTION_LINK, [] ],
+                       [ AuthManager::ACTION_CHANGE, [] ],
+                       [ AuthManager::ACTION_REMOVE, [] ],
+               ];
+       }
+
+       private function getBlockedUser() {
+               $user = \User::newFromName( 'UTBlockee' );
+               if ( $user->getID() == 0 ) {
+                       $user->addToDatabase();
+                       \TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
+                       $user->saveSettings();
+               }
+               $oldBlock = \Block::newFromTarget( 'UTBlockee' );
+               if ( $oldBlock ) {
+                       // An old block will prevent our new one from saving.
+                       $oldBlock->delete();
+               }
+               $blockOptions = [
+                       'address' => 'UTBlockee',
+                       'user' => $user->getID(),
+                       'reason' => __METHOD__,
+                       'expiry' => time() + 100500,
+                       'createAccount' => true,
+               ];
+               $block = new \Block( $blockOptions );
+               $block->insert();
+               return $user;
+       }
+
+       public function testBeginSecondaryAuthentication() {
+               $unblockedUser = \User::newFromName( 'UTSysop' );
+               $blockedUser = $this->getBlockedUser();
+
+               $provider = new CheckBlocksSecondaryAuthenticationProvider(
+                       [ 'blockDisablesLogin' => false ]
+               );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginSecondaryAuthentication( $unblockedUser, [] )
+               );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginSecondaryAuthentication( $blockedUser, [] )
+               );
+
+               $provider = new CheckBlocksSecondaryAuthenticationProvider(
+                       [ 'blockDisablesLogin' => true ]
+               );
+               $this->assertEquals(
+                       AuthenticationResponse::newPass(),
+                       $provider->beginSecondaryAuthentication( $unblockedUser, [] )
+               );
+               $ret = $provider->beginSecondaryAuthentication( $blockedUser, [] );
+               $this->assertEquals( AuthenticationResponse::FAIL, $ret->status );
+       }
+
+       public function testTestUserForCreation() {
+               $provider = new CheckBlocksSecondaryAuthenticationProvider(
+                       [ 'blockDisablesLogin' => false ]
+               );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( new \HashConfig() );
+               $provider->setManager( AuthManager::singleton() );
+
+               $unblockedUser = \User::newFromName( 'UTSysop' );
+               $blockedUser = $this->getBlockedUser();
+
+               $user = \User::newFromName( 'RandomUser' );
+
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $unblockedUser, AuthManager::AUTOCREATE_SOURCE_SESSION )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testUserForCreation( $unblockedUser, false )
+               );
+
+               $status = $provider->testUserForCreation( $blockedUser, AuthManager::AUTOCREATE_SOURCE_SESSION );
+               $this->assertInstanceOf( 'StatusValue', $status );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
+
+               $status = $provider->testUserForCreation( $blockedUser, false );
+               $this->assertInstanceOf( 'StatusValue', $status );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
+       }
+
+       public function testRangeBlock() {
+               $blockOptions = [
+                       'address' => '127.0.0.0/24',
+                       'reason' => __METHOD__,
+                       'expiry' => time() + 100500,
+                       'createAccount' => true,
+               ];
+               $block = new \Block( $blockOptions );
+               $block->insert();
+               $scopeVariable = new \ScopedCallback( [ $block, 'delete' ] );
+
+               $user = \User::newFromName( 'UTNormalUser' );
+               if ( $user->getID() == 0 ) {
+                       $user->addToDatabase();
+                       \TestUser::setPasswordForUser( $user, 'UTNormalUserPassword' );
+                       $user->saveSettings();
+               }
+               $this->setMwGlobals( [ 'wgUser' => $user ] );
+               $newuser = \User::newFromName( 'RandomUser' );
+
+               $provider = new CheckBlocksSecondaryAuthenticationProvider(
+                       [ 'blockDisablesLogin' => true ]
+               );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( new \HashConfig() );
+               $provider->setManager( AuthManager::singleton() );
+
+               $ret = $provider->beginSecondaryAuthentication( $user, [] );
+               $this->assertEquals( AuthenticationResponse::FAIL, $ret->status );
+
+               $status = $provider->testUserForCreation( $newuser, AuthManager::AUTOCREATE_SOURCE_SESSION );
+               $this->assertInstanceOf( 'StatusValue', $status );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
+
+               $status = $provider->testUserForCreation( $newuser, false );
+               $this->assertInstanceOf( 'StatusValue', $status );
+               $this->assertFalse( $status->isOK() );
+               $this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
+       }
+}
diff --git a/tests/phpunit/includes/auth/ConfirmLinkAuthenticationRequestTest.php b/tests/phpunit/includes/auth/ConfirmLinkAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..f208cc4
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+use InvalidArgumentException;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\ConfirmLinkAuthenticationRequest
+ */
+class ConfirmLinkAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               return new ConfirmLinkAuthenticationRequest( $this->getLinkRequests() );
+       }
+
+       /**
+        * @expectedException InvalidArgumentException
+        * @expectedExceptionMessage $linkRequests must not be empty
+        */
+       public function testConstructorException() {
+               new ConfirmLinkAuthenticationRequest( [] );
+       }
+
+       /**
+        * Get requests for testing
+        * @return AuthenticationRequest[]
+        */
+       private function getLinkRequests() {
+               $reqs = [];
+
+               $mb = $this->getMockBuilder( AuthenticationRequest::class )
+                       ->setMethods( [ 'getUniqueId' ] );
+               for ( $i = 1; $i <= 3; $i++ ) {
+                       $req = $mb->getMockForAbstractClass();
+                       $req->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( "Request$i" ) );
+                       $reqs[$req->getUniqueId()] = $req;
+               }
+
+               return $reqs;
+       }
+
+       public function provideLoadFromSubmission() {
+               $reqs = $this->getLinkRequests();
+
+               return [
+                       'Empty request' => [
+                               [],
+                               [],
+                               [ 'linkRequests' => $reqs ],
+                       ],
+                       'Some confirmed' => [
+                               [],
+                               [ 'confirmedLinkIDs' => [ 'Request1', 'Request3' ] ],
+                               [ 'confirmedLinkIDs' => [ 'Request1', 'Request3' ], 'linkRequests' => $reqs ],
+                       ],
+               ];
+       }
+
+       public function testGetUniqueId() {
+               $req = new ConfirmLinkAuthenticationRequest( $this->getLinkRequests() );
+               $this->assertSame(
+                       get_class( $req ) . ':Request1|Request2|Request3',
+                       $req->getUniqueId()
+               );
+       }
+}
diff --git a/tests/phpunit/includes/auth/ConfirmLinkSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/ConfirmLinkSecondaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..09d046c
--- /dev/null
@@ -0,0 +1,276 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\ConfirmLinkSecondaryAuthenticationProvider
+ */
+class ConfirmLinkSecondaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $response
+        */
+       public function testGetAuthenticationRequests( $action, $response ) {
+               $provider = new ConfirmLinkSecondaryAuthenticationProvider();
+
+               $this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) );
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               return [
+                       [ AuthManager::ACTION_LOGIN, [] ],
+                       [ AuthManager::ACTION_CREATE, [] ],
+                       [ AuthManager::ACTION_LINK, [] ],
+                       [ AuthManager::ACTION_CHANGE, [] ],
+                       [ AuthManager::ACTION_REMOVE, [] ],
+               ];
+       }
+
+       public function testBeginSecondaryAuthentication() {
+               $user = \User::newFromName( 'UTSysop' );
+               $obj = new \stdClass;
+
+               $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
+                       ->getMock();
+               $mock->expects( $this->once() )->method( 'beginLinkAttempt' )
+                       ->with( $this->identicalTo( $user ), $this->identicalTo( 'AuthManager::authnState' ) )
+                       ->will( $this->returnValue( $obj ) );
+               $mock->expects( $this->never() )->method( 'continueLinkAttempt' );
+
+               $this->assertSame( $obj, $mock->beginSecondaryAuthentication( $user, [] ) );
+       }
+
+       public function testContinueSecondaryAuthentication() {
+               $user = \User::newFromName( 'UTSysop' );
+               $obj = new \stdClass;
+               $reqs = [ new \stdClass ];
+
+               $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
+                       ->getMock();
+               $mock->expects( $this->never() )->method( 'beginLinkAttempt' );
+               $mock->expects( $this->once() )->method( 'continueLinkAttempt' )
+                       ->with(
+                               $this->identicalTo( $user ),
+                               $this->identicalTo( 'AuthManager::authnState' ),
+                               $this->identicalTo( $reqs )
+                       )
+                       ->will( $this->returnValue( $obj ) );
+
+               $this->assertSame( $obj, $mock->continueSecondaryAuthentication( $user, $reqs ) );
+       }
+
+       public function testBeginSecondaryAccountCreation() {
+               $user = \User::newFromName( 'UTSysop' );
+               $obj = new \stdClass;
+
+               $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
+                       ->getMock();
+               $mock->expects( $this->once() )->method( 'beginLinkAttempt' )
+                       ->with( $this->identicalTo( $user ), $this->identicalTo( 'AuthManager::accountCreationState' ) )
+                       ->will( $this->returnValue( $obj ) );
+               $mock->expects( $this->never() )->method( 'continueLinkAttempt' );
+
+               $this->assertSame( $obj, $mock->beginSecondaryAccountCreation( $user, $user, [] ) );
+       }
+
+       public function testContinueSecondaryAccountCreation() {
+               $user = \User::newFromName( 'UTSysop' );
+               $obj = new \stdClass;
+               $reqs = [ new \stdClass ];
+
+               $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
+                       ->getMock();
+               $mock->expects( $this->never() )->method( 'beginLinkAttempt' );
+               $mock->expects( $this->once() )->method( 'continueLinkAttempt' )
+                       ->with(
+                               $this->identicalTo( $user ),
+                               $this->identicalTo( 'AuthManager::accountCreationState' ),
+                               $this->identicalTo( $reqs )
+                       )
+                       ->will( $this->returnValue( $obj ) );
+
+               $this->assertSame( $obj, $mock->continueSecondaryAccountCreation( $user, $user, $reqs ) );
+       }
+
+       /**
+        * Get requests for testing
+        * @return AuthenticationRequest[]
+        */
+       private function getLinkRequests() {
+               $reqs = [];
+
+               $mb = $this->getMockBuilder( AuthenticationRequest::class )
+                       ->setMethods( [ 'getUniqueId' ] );
+               for ( $i = 1; $i <= 3; $i++ ) {
+                       $req = $mb->getMockForAbstractClass();
+                       $req->expects( $this->any() )->method( 'getUniqueId' )
+                               ->will( $this->returnValue( "Request$i" ) );
+                       $req->id = $i - 1;
+                       $reqs[$req->getUniqueId()] = $req;
+               }
+
+               return $reqs;
+       }
+
+       public function testBeginLinkAttempt() {
+               $user = \User::newFromName( 'UTSysop' );
+               $provider = \TestingAccessWrapper::newFromObject(
+                       new ConfirmLinkSecondaryAuthenticationProvider
+               );
+               $request = new \FauxRequest();
+               $manager = new AuthManager( $request, \RequestContext::getMain()->getConfig() );
+               $provider->setManager( $manager );
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginLinkAttempt( $user, 'state' )
+               );
+
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => [],
+               ] );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginLinkAttempt( $user, 'state' )
+               );
+
+               $reqs = $this->getLinkRequests();
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => $reqs
+               ] );
+               $res = $provider->beginLinkAttempt( $user, 'state' );
+               $this->assertInstanceOf( AuthenticationResponse::class, $res );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertSame( 'authprovider-confirmlink-message', $res->message->getKey() );
+               $this->assertCount( 1, $res->neededRequests );
+               $req = $res->neededRequests[0];
+               $this->assertInstanceOf( ConfirmLinkAuthenticationRequest::class, $req );
+               $this->assertEquals( $reqs, \TestingAccessWrapper::newFromObject( $req )->linkRequests );
+       }
+
+       public function testContinueLinkAttempt() {
+               $user = \User::newFromName( 'UTSysop' );
+               $obj = new \stdClass;
+               $reqs = $this->getLinkRequests();
+
+               $done = [ false, false, false ];
+
+               // First, test the pass-through for not containing the ConfirmLinkAuthenticationRequest
+               $mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'beginLinkAttempt' ] )
+                       ->getMock();
+               $mock->expects( $this->once() )->method( 'beginLinkAttempt' )
+                       ->with( $this->identicalTo( $user ), $this->identicalTo( 'state' ) )
+                       ->will( $this->returnValue( $obj ) );
+               $this->assertSame(
+                       $obj,
+                       \TestingAccessWrapper::newFromObject( $mock )->continueLinkAttempt( $user, 'state', $reqs )
+               );
+
+               // Now test the actual functioning
+               $provider = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
+                       ->setMethods( [
+                               'beginLinkAttempt', 'providerAllowsAuthenticationDataChange',
+                               'providerChangeAuthenticationData'
+                       ] )
+                       ->getMock();
+               $provider->expects( $this->never() )->method( 'beginLinkAttempt' );
+               $provider->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
+                       ->will( $this->returnCallback( function ( $req ) use ( $reqs ) {
+                               return $req->getUniqueId() === 'Request3'
+                                       ? \StatusValue::newFatal( 'foo' ) : \StatusValue::newGood();
+                       } ) );
+               $provider->expects( $this->any() )->method( 'providerChangeAuthenticationData' )
+                       ->will( $this->returnCallback( function ( $req ) use ( &$done ) {
+                               $done[$req->id] = true;
+                       } ) );
+               $config = new \HashConfig( [
+                       'AuthManagerConfig' => [
+                               'preauth' => [],
+                               'primaryauth' => [],
+                               'secondaryauth' => [
+                                       [ 'factory' => function () use ( $provider ) {
+                                               return $provider;
+                                       } ],
+                               ],
+                       ],
+               ] );
+               $request = new \FauxRequest();
+               $manager = new AuthManager( $request, $config );
+               $provider->setManager( $manager );
+               $provider = \TestingAccessWrapper::newFromObject( $provider );
+
+               $req = new ConfirmLinkAuthenticationRequest( $reqs );
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->continueLinkAttempt( $user, 'state', [ $req ] )
+               );
+
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => [],
+               ] );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->continueLinkAttempt( $user, 'state', [ $req ] )
+               );
+
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => $reqs
+               ] );
+               $this->assertEquals(
+                       AuthenticationResponse::newPass(),
+                       $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] )
+               );
+               $this->assertSame( [ false, false, false ], $done );
+
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => [ $reqs['Request2'] ],
+               ] );
+               $req->confirmedLinkIDs = [ 'Request1', 'Request2' ];
+               $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] );
+               $this->assertEquals( AuthenticationResponse::newPass(), $res );
+               $this->assertSame( [ false, true, false ], $done );
+               $done = [ false, false, false ];
+
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => $reqs,
+               ] );
+               $req->confirmedLinkIDs = [ 'Request1', 'Request2' ];
+               $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] );
+               $this->assertEquals( AuthenticationResponse::newPass(), $res );
+               $this->assertSame( [ true, true, false ], $done );
+               $done = [ false, false, false ];
+
+               $request->getSession()->setSecret( 'state', [
+                       'maybeLink' => $reqs,
+               ] );
+               $req->confirmedLinkIDs = [ 'Request1', 'Request3' ];
+               $res = $provider->continueLinkAttempt( $user, 'state', [ $req ] );
+               $this->assertEquals( AuthenticationResponse::UI, $res->status );
+               $this->assertCount( 1, $res->neededRequests );
+               $this->assertInstanceOf( ButtonAuthenticationRequest::class, $res->neededRequests[0] );
+               $this->assertSame( [ true, false, false ], $done );
+               $done = [ false, false, false ];
+
+               $res = $provider->continueLinkAttempt( $user, 'state', [ $res->neededRequests[0] ] );
+               $this->assertEquals( AuthenticationResponse::newPass(), $res );
+               $this->assertSame( [ false, false, false ], $done );
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/CreateFromLoginAuthenticationRequestTest.php b/tests/phpunit/includes/auth/CreateFromLoginAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..fb0613d
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\CreateFromLoginAuthenticationRequest
+ */
+class CreateFromLoginAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               return new CreateFromLoginAuthenticationRequest(
+                       null, []
+               );
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [],
+                               [],
+                               [],
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/CreatedAccountAuthenticationRequestTest.php b/tests/phpunit/includes/auth/CreatedAccountAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..fc1e6f1
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\CreatedAccountAuthenticationRequest
+ */
+class CreatedAccountAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               return new CreatedAccountAuthenticationRequest( 42, 'Test' );
+       }
+
+       public function testConstructor() {
+               $ret = new CreatedAccountAuthenticationRequest( 42, 'Test' );
+               $this->assertSame( 42, $ret->id );
+               $this->assertSame( 'Test', $ret->username );
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [],
+                               [],
+                               false
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/CreationReasonAuthenticationRequestTest.php b/tests/phpunit/includes/auth/CreationReasonAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..cce1e8c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\CreationReasonAuthenticationRequest
+ */
+class CreationReasonAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               return new CreationReasonAuthenticationRequest();
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [],
+                               [],
+                               false
+                       ],
+                       'Reason given' => [
+                               [],
+                               $data = [ 'reason' => 'Because' ],
+                               $data,
+                       ],
+                       'Reason empty' => [
+                               [],
+                               [ 'reason' => '' ],
+                               false
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..18c46f7
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+use Psr\Log\LoggerInterface;
+
+class EmailNotificationSecondaryAuthenticationProviderTest extends \PHPUnit_Framework_TestCase {
+       public function testConstructor() {
+               $config = new \HashConfig( [
+                       'EnableEmail' => true,
+                       'EmailAuthentication' => true,
+               ] );
+
+               $provider = new EmailNotificationSecondaryAuthenticationProvider();
+               $provider->setConfig( $config );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $this->assertTrue( $providerPriv->sendConfirmationEmail );
+
+               $provider = new EmailNotificationSecondaryAuthenticationProvider( [
+                       'sendConfirmationEmail' => false,
+               ] );
+               $provider->setConfig( $config );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $this->assertFalse( $providerPriv->sendConfirmationEmail );
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param AuthenticationRequest[] $expected
+        */
+       public function testGetAuthenticationRequests( $action, $expected ) {
+               $provider = new EmailNotificationSecondaryAuthenticationProvider( [
+                       'sendConfirmationEmail' => true,
+               ] );
+               $this->assertSame( $expected, $provider->getAuthenticationRequests( $action, [] ) );
+       }
+
+       public function provideGetAuthenticationRequests() {
+               return [
+                       [ AuthManager::ACTION_LOGIN, [] ],
+                       [ AuthManager::ACTION_CREATE, [] ],
+                       [ AuthManager::ACTION_LINK, [] ],
+                       [ AuthManager::ACTION_CHANGE, [] ],
+                       [ AuthManager::ACTION_REMOVE, [] ],
+               ];
+       }
+
+       public function testBeginSecondaryAuthentication() {
+               $provider = new EmailNotificationSecondaryAuthenticationProvider( [
+                       'sendConfirmationEmail' => true,
+               ] );
+               $this->assertEquals( AuthenticationResponse::newAbstain(),
+                       $provider->beginSecondaryAuthentication( \User::newFromName( 'Foo' ), [] ) );
+       }
+
+       public function testBeginSecondaryAccountCreation() {
+               $authManager = new AuthManager( new \FauxRequest(), new \HashConfig() );
+
+               $creator = $this->getMock( 'User' );
+               $userWithoutEmail = $this->getMock( 'User' );
+               $userWithoutEmail->expects( $this->any() )->method( 'getEmail' )->willReturn( '' );
+               $userWithoutEmail->expects( $this->never() )->method( 'sendConfirmationMail' );
+               $userWithEmailError = $this->getMock( 'User' );
+               $userWithEmailError->expects( $this->any() )->method( 'getEmail' )->willReturn( 'foo@bar.baz' );
+               $userWithEmailError->expects( $this->any() )->method( 'sendConfirmationMail' )
+                       ->willReturn( \Status::newFatal( 'fail' ) );
+               $userExpectsConfirmation = $this->getMock( 'User' );
+               $userExpectsConfirmation->expects( $this->any() )->method( 'getEmail' )
+                       ->willReturn( 'foo@bar.baz' );
+               $userExpectsConfirmation->expects( $this->once() )->method( 'sendConfirmationMail' )
+                       ->willReturn( \Status::newGood() );
+               $userNotExpectsConfirmation = $this->getMock( 'User' );
+               $userNotExpectsConfirmation->expects( $this->any() )->method( 'getEmail' )
+                       ->willReturn( 'foo@bar.baz' );
+               $userNotExpectsConfirmation->expects( $this->never() )->method( 'sendConfirmationMail' );
+
+               $provider = new EmailNotificationSecondaryAuthenticationProvider( [
+                       'sendConfirmationEmail' => false,
+               ] );
+               $provider->setManager( $authManager );
+               $provider->beginSecondaryAccountCreation( $userNotExpectsConfirmation, $creator, [] );
+
+               $provider = new EmailNotificationSecondaryAuthenticationProvider( [
+                       'sendConfirmationEmail' => true,
+               ] );
+               $provider->setManager( $authManager );
+               $provider->beginSecondaryAccountCreation( $userWithoutEmail, $creator, [] );
+               $provider->beginSecondaryAccountCreation( $userExpectsConfirmation, $creator, [] );
+
+               // test logging of email errors
+               $logger = $this->getMockForAbstractClass( LoggerInterface::class );
+               $logger->expects( $this->once() )->method( 'warning' );
+               $provider->setLogger( $logger );
+               $provider->beginSecondaryAccountCreation( $userWithEmailError, $creator, [] );
+
+               // test disable flag used by other providers
+               $authManager->setAuthenticationSessionData( 'no-email', true );
+               $provider->setManager( $authManager );
+               $provider->beginSecondaryAccountCreation( $userNotExpectsConfirmation, $creator, [] );
+
+       }
+}
diff --git a/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php b/tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..edee6fc
--- /dev/null
@@ -0,0 +1,420 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @group Database
+ * @covers MediaWiki\Auth\LegacyHookPreAuthenticationProvider
+ */
+class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       /**
+        * Get an instance of the provider
+        * @return LegacyHookPreAuthenticationProvider
+        */
+       protected function getProvider() {
+               $request = $this->getMock( 'FauxRequest', [ 'getIP' ] );
+               $request->expects( $this->any() )->method( 'getIP' )->will( $this->returnValue( '127.0.0.42' ) );
+
+               $manager = new AuthManager(
+                       $request, \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               );
+
+               $provider = new LegacyHookPreAuthenticationProvider();
+               $provider->setManager( $manager );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( new \HashConfig( [
+                       'PasswordAttemptThrottle' => [ 'count' => 23, 'seconds' => 42 ],
+               ] ) );
+               return $provider;
+       }
+
+       /**
+        * Sets a mock on a hook
+        * @param string $hook
+        * @param object $expect From $this->once(), $this->never(), etc.
+        * @return object $mock->expects( $expect )->method( ... ).
+        */
+       protected function hook( $hook, $expect ) {
+               $mock = $this->getMock( __CLASS__, [ "on$hook" ] );
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       $hook => [ $mock ],
+               ] );
+               return $mock->expects( $expect )->method( "on$hook" );
+       }
+
+       /**
+        * Unsets a hook
+        * @param string $hook
+        */
+       protected function unhook( $hook ) {
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       $hook => [],
+               ] );
+       }
+
+       // Stubs for hooks taking reference parameters
+       public function onLoginUserMigrated( $user, &$msg ) {
+       }
+       public function onAbortLogin( $user, $password, &$abort, &$msg ) {
+       }
+       public function onAbortNewAccount( $user, &$abortError, &$abortStatus ) {
+       }
+       public function onAbortAutoAccount( $user, &$abortError ) {
+       }
+
+       /**
+        * @dataProvider provideTestForAuthentication
+        * @param string|null $username
+        * @param string|null $password
+        * @param string|null $msgForLoginUserMigrated
+        * @param int|null $abortForAbortLogin
+        * @param string|null $msgForAbortLogin
+        * @param string|null $failMsg
+        * @param array $failParams
+        */
+       public function testTestForAuthentication(
+               $username, $password,
+               $msgForLoginUserMigrated, $abortForAbortLogin, $msgForAbortLogin,
+               $failMsg, $failParams = []
+       ) {
+               $reqs = [];
+               if ( $username === null ) {
+                       $this->hook( 'LoginUserMigrated', $this->never() );
+                       $this->hook( 'AbortLogin', $this->never() );
+               } else {
+                       if ( $password === null ) {
+                               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+                       } else {
+                               $req = new PasswordAuthenticationRequest();
+                               $req->action = AuthManager::ACTION_LOGIN;
+                               $req->password = $password;
+                       }
+                       $req->username = $username;
+                       $reqs[get_class( $req )] = $req;
+
+                       $h = $this->hook( 'LoginUserMigrated', $this->once() );
+                       if ( $msgForLoginUserMigrated !== null ) {
+                               $h->will( $this->returnCallback(
+                                       function ( $user, &$msg ) use ( $username, $msgForLoginUserMigrated ) {
+                                               $this->assertInstanceOf( 'User', $user );
+                                               $this->assertSame( $username, $user->getName() );
+                                               $msg = $msgForLoginUserMigrated;
+                                               return false;
+                                       }
+                               ) );
+                               $this->hook( 'AbortLogin', $this->never() );
+                       } else {
+                               $h->will( $this->returnCallback(
+                                       function ( $user, &$msg ) use ( $username ) {
+                                               $this->assertInstanceOf( 'User', $user );
+                                               $this->assertSame( $username, $user->getName() );
+                                               return true;
+                                       }
+                               ) );
+                               $h2 = $this->hook( 'AbortLogin', $this->once() );
+                               if ( $abortForAbortLogin !== null ) {
+                                       $h2->will( $this->returnCallback(
+                                               function ( $user, $pass, &$abort, &$msg )
+                                                       use ( $username, $password, $abortForAbortLogin, $msgForAbortLogin )
+                                               {
+                                                       $this->assertInstanceOf( 'User', $user );
+                                                       $this->assertSame( $username, $user->getName() );
+                                                       if ( $password !== null ) {
+                                                               $this->assertSame( $password, $pass );
+                                                       } else {
+                                                               $this->assertInternalType( 'string', $pass );
+                                                       }
+                                                       $abort = $abortForAbortLogin;
+                                                       $msg = $msgForAbortLogin;
+                                                       return false;
+                                               }
+                                       ) );
+                               } else {
+                                       $h2->will( $this->returnCallback(
+                                               function ( $user, $pass, &$abort, &$msg ) use ( $username, $password ) {
+                                                       $this->assertInstanceOf( 'User', $user );
+                                                       $this->assertSame( $username, $user->getName() );
+                                                       if ( $password !== null ) {
+                                                               $this->assertSame( $password, $pass );
+                                                       } else {
+                                                               $this->assertInternalType( 'string', $pass );
+                                                       }
+                                                       return true;
+                                               }
+                                       ) );
+                               }
+                       }
+               }
+               unset( $h, $h2 );
+
+               $status = $this->getProvider()->testForAuthentication( $reqs );
+
+               $this->unhook( 'LoginUserMigrated' );
+               $this->unhook( 'AbortLogin' );
+
+               if ( $failMsg === null ) {
+                       $this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
+               } else {
+                       $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
+                       $this->assertFalse( $status->isOk(), 'should fail (ok)' );
+                       $errors = $status->getErrors();
+                       $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
+                       $this->assertEquals( $failParams, $errors[0]['params'], 'should fail (params)' );
+               }
+       }
+
+       public static function provideTestForAuthentication() {
+               return [
+                       'No valid requests' => [
+                               null, null, null, null, null, null
+                       ],
+                       'No hook errors' => [
+                               'User', 'PaSsWoRd', null, null, null, null
+                       ],
+                       'No hook errors, no password' => [
+                               'User', null, null, null, null, null
+                       ],
+                       'LoginUserMigrated no message' => [
+                               'User', 'PaSsWoRd', false, null, null, 'login-migrated-generic'
+                       ],
+                       'LoginUserMigrated with message' => [
+                               'User', 'PaSsWoRd', 'LUM-abort', null, null, 'LUM-abort'
+                       ],
+                       'LoginUserMigrated with message and params' => [
+                               'User', 'PaSsWoRd', [ 'LUM-abort', 'foo' ], null, null, 'LUM-abort', [ 'foo' ]
+                       ],
+                       'AbortLogin, SUCCESS' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::SUCCESS, null, null
+                       ],
+                       'AbortLogin, NEED_TOKEN, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::NEED_TOKEN, null, 'nocookiesforlogin'
+                       ],
+                       'AbortLogin, NEED_TOKEN, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::NEED_TOKEN, 'needtoken', 'needtoken'
+                       ],
+                       'AbortLogin, WRONG_TOKEN, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::WRONG_TOKEN, null, 'sessionfailure'
+                       ],
+                       'AbortLogin, WRONG_TOKEN, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::WRONG_TOKEN, 'wrongtoken', 'wrongtoken'
+                       ],
+                       'AbortLogin, ILLEGAL, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::ILLEGAL, null, 'noname'
+                       ],
+                       'AbortLogin, ILLEGAL, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::ILLEGAL, 'badname', 'badname'
+                       ],
+                       'AbortLogin, NO_NAME, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::NO_NAME, null, 'noname'
+                       ],
+                       'AbortLogin, NO_NAME, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::NO_NAME, 'badname', 'badname'
+                       ],
+                       'AbortLogin, WRONG_PASS, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::WRONG_PASS, null, 'wrongpassword'
+                       ],
+                       'AbortLogin, WRONG_PASS, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::WRONG_PASS, 'badpass', 'badpass'
+                       ],
+                       'AbortLogin, WRONG_PLUGIN_PASS, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::WRONG_PLUGIN_PASS, null, 'wrongpassword'
+                       ],
+                       'AbortLogin, WRONG_PLUGIN_PASS, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::WRONG_PLUGIN_PASS, 'badpass', 'badpass'
+                       ],
+                       'AbortLogin, NOT_EXISTS, no message' => [
+                               "User'", 'A', null, \LoginForm::NOT_EXISTS, null, 'nosuchusershort', [ 'User&#39;' ]
+                       ],
+                       'AbortLogin, NOT_EXISTS, with message' => [
+                               "User'", 'A', null, \LoginForm::NOT_EXISTS, 'badname', 'badname', [ 'User&#39;' ]
+                       ],
+                       'AbortLogin, EMPTY_PASS, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::EMPTY_PASS, null, 'wrongpasswordempty'
+                       ],
+                       'AbortLogin, EMPTY_PASS, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::EMPTY_PASS, 'badpass', 'badpass'
+                       ],
+                       'AbortLogin, RESET_PASS, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::RESET_PASS, null, 'resetpass_announce'
+                       ],
+                       'AbortLogin, RESET_PASS, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::RESET_PASS, 'resetpass', 'resetpass'
+                       ],
+                       'AbortLogin, THROTTLED, no message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::THROTTLED, null, 'login-throttled',
+                               [ \Message::durationParam( 42 ) ]
+                       ],
+                       'AbortLogin, THROTTLED, with message' => [
+                               'User', 'PaSsWoRd', null, \LoginForm::THROTTLED, 't', 't',
+                               [ \Message::durationParam( 42 ) ]
+                       ],
+                       'AbortLogin, USER_BLOCKED, no message' => [
+                               "User'", 'P', null, \LoginForm::USER_BLOCKED, null, 'login-userblocked', [ 'User&#39;' ]
+                       ],
+                       'AbortLogin, USER_BLOCKED, with message' => [
+                               "User'", 'P', null, \LoginForm::USER_BLOCKED, 'blocked', 'blocked', [ 'User&#39;' ]
+                       ],
+                       'AbortLogin, ABORTED, no message' => [
+                               "User'", 'P', null, \LoginForm::ABORTED, null, 'login-abort-generic', [ 'User&#39;' ]
+                       ],
+                       'AbortLogin, ABORTED, with message' => [
+                               "User'", 'P', null, \LoginForm::ABORTED, 'aborted', 'aborted', [ 'User&#39;' ]
+                       ],
+                       'AbortLogin, USER_MIGRATED, no message' => [
+                               'User', 'P', null, \LoginForm::USER_MIGRATED, null, 'login-migrated-generic'
+                       ],
+                       'AbortLogin, USER_MIGRATED, with message' => [
+                               'User', 'P', null, \LoginForm::USER_MIGRATED, 'migrated', 'migrated'
+                       ],
+                       'AbortLogin, USER_MIGRATED, with message and params' => [
+                               'User', 'P', null, \LoginForm::USER_MIGRATED, [ 'migrated', 'foo' ],
+                               'migrated', [ 'foo' ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestForAccountCreation
+        * @param string $msg
+        * @param Status|null $status
+        * @param StatusValue Result
+        */
+       public function testTestForAccountCreation( $msg, $status, $result ) {
+               $this->hook( 'AbortNewAccount', $this->once() )
+                       ->will( $this->returnCallback( function ( $user, &$error, &$abortStatus )
+                               use ( $msg, $status )
+                       {
+                               $this->assertInstanceOf( 'User', $user );
+                               $this->assertSame( 'User', $user->getName() );
+                               $error = $msg;
+                               $abortStatus = $status;
+                               return $error === null && $status === null;
+                       } ) );
+
+               $user = \User::newFromName( 'User' );
+               $creator = \User::newFromName( 'UTSysop' );
+               $ret = $this->getProvider()->testForAccountCreation( $user, $creator, [] );
+
+               $this->unhook( 'AbortNewAccount' );
+
+               $this->assertEquals( $result, $ret );
+       }
+
+       public static function provideTestForAccountCreation() {
+               return [
+                       'No hook errors' => [
+                               null, null, \StatusValue::newGood()
+                       ],
+                       'AbortNewAccount, old style' => [
+                               'foobar', null, \StatusValue::newFatal(
+                                       \Message::newFromKey( 'createaccount-hook-aborted' )->rawParams( 'foobar' )
+                               )
+                       ],
+                       'AbortNewAccount, new style' => [
+                               'foobar',
+                               \Status::newFatal( 'aborted!', 'param' ),
+                               \StatusValue::newFatal( 'aborted!', 'param' )
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestUserForCreation
+        * @param string|null $error
+        * @param string|null $failMsg
+        */
+       public function testTestUserForCreation( $error, $failMsg ) {
+               $this->hook( 'AbortNewAccount', $this->never() );
+               $this->hook( 'AbortAutoAccount', $this->once() )
+                       ->will( $this->returnCallback( function ( $user, &$abortError ) use ( $error ) {
+                               $this->assertInstanceOf( 'User', $user );
+                               $this->assertSame( 'UTSysop', $user->getName() );
+                               $abortError = $error;
+                               return $error === null;
+                       } ) );
+
+               $status = $this->getProvider()->testUserForCreation(
+                       \User::newFromName( 'UTSysop' ), AuthManager::AUTOCREATE_SOURCE_SESSION
+               );
+
+               $this->unhook( 'AbortNewAccount' );
+               $this->unhook( 'AbortAutoAccount' );
+
+               if ( $failMsg === null ) {
+                       $this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
+               } else {
+                       $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
+                       $this->assertFalse( $status->isOk(), 'should fail (ok)' );
+                       $errors = $status->getErrors();
+                       $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
+               }
+
+               $this->hook( 'AbortAutoAccount', $this->never() );
+               $this->hook( 'AbortNewAccount', $this->once() )
+                       ->will( $this->returnCallback(
+                               function ( $user, &$abortError, &$abortStatus ) use ( $error ) {
+                                       $this->assertInstanceOf( 'User', $user );
+                                       $this->assertSame( 'UTSysop', $user->getName() );
+                                       $abortError = $error;
+                                       return $error === null;
+                               }
+                       ) );
+               $status = $this->getProvider()->testUserForCreation( \User::newFromName( 'UTSysop' ), false );
+               $this->unhook( 'AbortNewAccount' );
+               $this->unhook( 'AbortAutoAccount' );
+               if ( $failMsg === null ) {
+                       $this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
+               } else {
+                       $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
+                       $this->assertFalse( $status->isOk(), 'should fail (ok)' );
+                       $errors = $status->getErrors();
+                       $msg = $errors[0]['message'];
+                       $this->assertInstanceOf( \Message::class, $msg );
+                       $this->assertEquals(
+                               'createaccount-hook-aborted', $msg->getKey(), 'should fail (message)'
+                       );
+               }
+
+               if ( $error !== false ) {
+                       $this->hook( 'AbortAutoAccount', $this->never() );
+                       $this->hook( 'AbortNewAccount', $this->once() )
+                               ->will( $this->returnCallback(
+                                       function ( $user, &$abortError, &$abortStatus ) use ( $error ) {
+                                               $this->assertInstanceOf( 'User', $user );
+                                               $this->assertSame( 'UTSysop', $user->getName() );
+                                               $abortStatus = $error ? \Status::newFatal( $error ) : \Status::newGood();
+                                               return $error === null;
+                                       }
+                       ) );
+                       $status = $this->getProvider()->testUserForCreation( \User::newFromName( 'UTSysop' ), false );
+                       $this->unhook( 'AbortNewAccount' );
+                       $this->unhook( 'AbortAutoAccount' );
+                       if ( $failMsg === null ) {
+                               $this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
+                       } else {
+                               $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
+                               $this->assertFalse( $status->isOk(), 'should fail (ok)' );
+                               $errors = $status->getErrors();
+                               $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
+                       }
+               }
+       }
+
+       public static function provideTestUserForCreation() {
+               return [
+                       'Success' => [ null, null ],
+                       'Fail, no message' => [ false, 'login-abort-generic' ],
+                       'Fail, with message' => [ 'fail', 'fail' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..fa68dee
--- /dev/null
@@ -0,0 +1,666 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @group Database
+ * @covers MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider
+ */
+class LocalPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
+
+       private $manager = null;
+       private $config = null;
+       private $validity = null;
+
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       /**
+        * Get an instance of the provider
+        *
+        * $provider->checkPasswordValidity is mocked to return $this->validity,
+        * because we don't need to test that here.
+        *
+        * @param bool $loginOnly
+        * @return LocalPasswordPrimaryAuthenticationProvider
+        */
+       protected function getProvider( $loginOnly = false ) {
+               if ( !$this->config ) {
+                       $this->config = new \HashConfig();
+               }
+               $config = new \MultiConfig( [
+                       $this->config,
+                       \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               ] );
+
+               if ( !$this->manager ) {
+                       $this->manager = new AuthManager( new \FauxRequest(), $config );
+               }
+               $this->validity = \Status::newGood();
+
+               $provider = $this->getMock(
+                       LocalPasswordPrimaryAuthenticationProvider::class,
+                       [ 'checkPasswordValidity' ],
+                       [ [ 'loginOnly' => $loginOnly ] ]
+               );
+               $provider->expects( $this->any() )->method( 'checkPasswordValidity' )
+                       ->will( $this->returnCallback( function () {
+                               return $this->validity;
+                       } ) );
+               $provider->setConfig( $config );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setManager( $this->manager );
+
+               return $provider;
+       }
+
+       public function testBasics() {
+               $provider = new LocalPasswordPrimaryAuthenticationProvider();
+
+               $this->assertSame(
+                       PrimaryAuthenticationProvider::TYPE_CREATE,
+                       $provider->accountCreationType()
+               );
+
+               $this->assertTrue( $provider->testUserExists( 'UTSysop' ) );
+               $this->assertTrue( $provider->testUserExists( 'uTSysop' ) );
+               $this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) );
+               $this->assertFalse( $provider->testUserExists( '<invalid>' ) );
+
+               $provider = new LocalPasswordPrimaryAuthenticationProvider( [ 'loginOnly' => true ] );
+
+               $this->assertSame(
+                       PrimaryAuthenticationProvider::TYPE_NONE,
+                       $provider->accountCreationType()
+               );
+
+               $this->assertTrue( $provider->testUserExists( 'UTSysop' ) );
+               $this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) );
+
+               $req = new PasswordAuthenticationRequest;
+               $req->action = AuthManager::ACTION_CHANGE;
+               $req->username = '<invalid>';
+               $provider->providerChangeAuthenticationData( $req );
+       }
+
+       public function testTestUserCanAuthenticate() {
+               $dbw = wfGetDB( DB_MASTER );
+               $oldHash = $dbw->selectField( 'user', 'user_password', [ 'user_name' => 'UTSysop' ] );
+               $cb = new \ScopedCallback( function () use ( $dbw, $oldHash ) {
+                       $dbw->update( 'user', [ 'user_password' => $oldHash ], [ 'user_name' => 'UTSysop' ] );
+               } );
+               $id = \User::idFromName( 'UTSysop' );
+
+               $provider = $this->getProvider();
+
+               $this->assertFalse( $provider->testUserCanAuthenticate( '<invalid>' ) );
+
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'DoesNotExist' ) );
+
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'uTSysop' ) );
+
+               $dbw->update(
+                       'user',
+                       [ 'user_password' => \PasswordFactory::newInvalidPassword()->toString() ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+
+               // Really old format
+               $dbw->update(
+                       'user',
+                       [ 'user_password' => '0123456789abcdef0123456789abcdef' ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+       }
+
+       public function testSetPasswordResetFlag() {
+               // Set instance vars
+               $this->getProvider();
+
+               /// @todo: Because we're currently using User, which uses the global config...
+               $this->setMwGlobals( [ 'wgPasswordExpireGrace' => 100 ] );
+
+               $this->config->set( 'PasswordExpireGrace', 100 );
+               $this->config->set( 'InvalidPasswordReset', true );
+
+               $provider = new LocalPasswordPrimaryAuthenticationProvider();
+               $provider->setConfig( $this->config );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setManager( $this->manager );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
+                       'user',
+                       '*',
+                       [ 'user_name' => 'UTSysop' ],
+                       __METHOD__
+               );
+
+               $this->manager->removeAuthenticationSessionData( null );
+               $row->user_password_expires = wfTimestamp( TS_MW, time() + 200 );
+               $providerPriv->setPasswordResetFlag( 'UTSysop', \Status::newGood(), $row );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
+
+               $this->manager->removeAuthenticationSessionData( null );
+               $row->user_password_expires = wfTimestamp( TS_MW, time() - 200 );
+               $providerPriv->setPasswordResetFlag( 'UTSysop', \Status::newGood(), $row );
+               $ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
+               $this->assertNotNull( $ret );
+               $this->assertSame( 'resetpass-expired', $ret->msg->getKey() );
+               $this->assertTrue( $ret->hard );
+
+               $this->manager->removeAuthenticationSessionData( null );
+               $row->user_password_expires = wfTimestamp( TS_MW, time() - 1 );
+               $providerPriv->setPasswordResetFlag( 'UTSysop', \Status::newGood(), $row );
+               $ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
+               $this->assertNotNull( $ret );
+               $this->assertSame( 'resetpass-expired-soft', $ret->msg->getKey() );
+               $this->assertFalse( $ret->hard );
+
+               $this->manager->removeAuthenticationSessionData( null );
+               $row->user_password_expires = null;
+               $status = \Status::newGood();
+               $status->error( 'testing' );
+               $providerPriv->setPasswordResetFlag( 'UTSysop', $status, $row );
+               $ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
+               $this->assertNotNull( $ret );
+               $this->assertSame( 'resetpass-validity-soft', $ret->msg->getKey() );
+               $this->assertFalse( $ret->hard );
+       }
+
+       public function testAuthentication() {
+               $dbw = wfGetDB( DB_MASTER );
+               $oldHash = $dbw->selectField( 'user', 'user_password', [ 'user_name' => 'UTSysop' ] );
+               $cb = new \ScopedCallback( function () use ( $dbw, $oldHash ) {
+                       $dbw->update( 'user', [ 'user_password' => $oldHash ], [ 'user_name' => 'UTSysop' ] );
+               } );
+               $id = \User::idFromName( 'UTSysop' );
+
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_LOGIN;
+               $reqs = [ PasswordAuthenticationRequest::class => $req ];
+
+               $provider = $this->getProvider();
+
+               // General failures
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( [] )
+               );
+
+               $req->username = 'foo';
+               $req->password = null;
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = null;
+               $req->password = 'bar';
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = '<invalid>';
+               $req->password = 'WhoCares';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = 'DoesNotExist';
+               $req->password = 'DoesNotExist';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               // Validation failure
+               $req->username = 'UTSysop';
+               $req->password = 'UTSysopPassword';
+               $this->validity = \Status::newFatal( 'arbitrary-failure' );
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'arbitrary-failure',
+                       $ret->message->getKey()
+               );
+
+               // Successful auth
+               $this->manager->removeAuthenticationSessionData( null );
+               $this->validity = \Status::newGood();
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
+
+               // Successful auth after normalizing name
+               $this->manager->removeAuthenticationSessionData( null );
+               $this->validity = \Status::newGood();
+               $req->username = 'uTSysop';
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $req->username = 'UTSysop';
+
+               // Successful auth with reset
+               $this->manager->removeAuthenticationSessionData( null );
+               $this->validity->error( 'arbitrary-warning' );
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+               $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
+
+               // Wrong password
+               $this->validity = \Status::newGood();
+               $req->password = 'Wrong';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'wrongpassword',
+                       $ret->message->getKey()
+               );
+
+               // Correct handling of legacy encodings
+               $password = ':B:salt:' . md5( 'salt-' . md5( "\xe1\xe9\xed\xf3\xfa" ) );
+               $dbw->update( 'user', [ 'user_password' => $password ], [ 'user_name' => 'UTSysop' ] );
+               $req->password = 'áéíóú';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'wrongpassword',
+                       $ret->message->getKey()
+               );
+
+               $this->config->set( 'LegacyEncoding', true );
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->password = 'áéíóú Wrong';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'wrongpassword',
+                       $ret->message->getKey()
+               );
+
+               // Correct handling of really old password hashes
+               $this->config->set( 'PasswordSalt', false );
+               $password = md5( 'FooBar' );
+               $dbw->update( 'user', [ 'user_password' => $password ], [ 'user_name' => 'UTSysop' ] );
+               $req->password = 'FooBar';
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $this->config->set( 'PasswordSalt', true );
+               $password = md5( "$id-" . md5( 'FooBar' ) );
+               $dbw->update( 'user', [ 'user_password' => $password ], [ 'user_name' => 'UTSysop' ] );
+               $req->password = 'FooBar';
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+       }
+
+       /**
+        * @dataProvider provideProviderAllowsAuthenticationDataChange
+        * @param string $type
+        * @param string $user
+        * @param \Status $validity Result of the password validity check
+        * @param \StatusValue $expect1 Expected result with $checkData = false
+        * @param \StatusValue $expect2 Expected result with $checkData = true
+        */
+       public function testProviderAllowsAuthenticationDataChange( $type, $user, \Status $validity,
+               \StatusValue $expect1, \StatusValue $expect2
+       ) {
+               if ( $type === PasswordAuthenticationRequest::class ) {
+                       $req = new $type();
+               } elseif ( $type === PasswordDomainAuthenticationRequest::class ) {
+                       $req = new $type( [] );
+               } else {
+                       $req = $this->getMock( $type );
+               }
+               $req->action = AuthManager::ACTION_CHANGE;
+               $req->username = $user;
+               $req->password = 'NewPassword';
+               $req->retype = 'NewPassword';
+
+               $provider = $this->getProvider();
+               $this->validity = $validity;
+               $this->assertEquals( $expect1, $provider->providerAllowsAuthenticationDataChange( $req, false ) );
+               $this->assertEquals( $expect2, $provider->providerAllowsAuthenticationDataChange( $req, true ) );
+
+               $req->retype = 'BadRetype';
+               $this->assertEquals(
+                       $expect1,
+                       $provider->providerAllowsAuthenticationDataChange( $req, false )
+               );
+               $this->assertEquals(
+                       $expect2->getValue() === 'ignored' ? $expect2 : \StatusValue::newFatal( 'badretype' ),
+                       $provider->providerAllowsAuthenticationDataChange( $req, true )
+               );
+
+               $provider = $this->getProvider( true );
+               $this->assertEquals(
+                       \StatusValue::newGood( 'ignored' ),
+                       $provider->providerAllowsAuthenticationDataChange( $req, true ),
+                       'loginOnly mode should claim to ignore all changes'
+               );
+       }
+
+       public static function provideProviderAllowsAuthenticationDataChange() {
+               $err = \StatusValue::newGood();
+               $err->error( 'arbitrary-warning' );
+
+               return [
+                       [ AuthenticationRequest::class, 'UTSysop', \Status::newGood(),
+                               \StatusValue::newGood( 'ignored' ), \StatusValue::newGood( 'ignored' ) ],
+                       [ PasswordAuthenticationRequest::class, 'UTSysop', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood() ],
+                       [ PasswordAuthenticationRequest::class, 'uTSysop', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood() ],
+                       [ PasswordAuthenticationRequest::class, 'UTSysop', \Status::wrap( $err ),
+                               \StatusValue::newGood(), $err ],
+                       [ PasswordAuthenticationRequest::class, 'UTSysop', \Status::newFatal( 'arbitrary-error' ),
+                               \StatusValue::newGood(), \StatusValue::newFatal( 'arbitrary-error' ) ],
+                       [ PasswordAuthenticationRequest::class, 'DoesNotExist', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood( 'ignored' ) ],
+                       [ PasswordDomainAuthenticationRequest::class, 'UTSysop', \Status::newGood(),
+                               \StatusValue::newGood( 'ignored' ), \StatusValue::newGood( 'ignored' ) ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideProviderChangeAuthenticationData
+        * @param string $user
+        * @param string $type
+        * @param bool $loginOnly
+        * @param bool $changed
+        */
+       public function testProviderChangeAuthenticationData( $user, $type, $loginOnly, $changed ) {
+               $cuser = ucfirst( $user );
+               $oldpass = 'UTSysopPassword';
+               $newpass = 'NewPassword';
+
+               $dbw = wfGetDB( DB_MASTER );
+               $oldHash = $dbw->selectField( 'user', 'user_password', [ 'user_name' => $cuser ] );
+               $oldExpiry = $dbw->selectField( 'user', 'user_password_expires', [ 'user_name' => $cuser ] );
+               $cb = new \ScopedCallback( function () use ( $dbw, $cuser, $oldHash, $oldExpiry ) {
+                       $dbw->update(
+                               'user',
+                               [
+                                       'user_password' => $oldHash,
+                                       'user_password_expires' => $oldExpiry,
+                               ],
+                               [ 'user_name' => $cuser ]
+                       );
+               } );
+
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'ResetPasswordExpiration' => [ function ( $user, &$expires ) {
+                               $expires = '30001231235959';
+                       } ]
+               ] );
+
+               $provider = $this->getProvider( $loginOnly );
+
+               // Sanity check
+               $loginReq = new PasswordAuthenticationRequest();
+               $loginReq->action = AuthManager::ACTION_LOGIN;
+               $loginReq->username = $user;
+               $loginReq->password = $oldpass;
+               $loginReqs = [ PasswordAuthenticationRequest::class => $loginReq ];
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( $cuser ),
+                       $provider->beginPrimaryAuthentication( $loginReqs ),
+                       'Sanity check'
+               );
+
+               if ( $type === PasswordAuthenticationRequest::class ) {
+                       $changeReq = new $type();
+               } else {
+                       $changeReq = $this->getMock( $type );
+               }
+               $changeReq->action = AuthManager::ACTION_CHANGE;
+               $changeReq->username = $user;
+               $changeReq->password = $newpass;
+               $provider->providerChangeAuthenticationData( $changeReq );
+
+               if ( $loginOnly ) {
+                       $old = 'fail';
+                       $new = 'fail';
+                       $expectExpiry = null;
+               } elseif ( $changed ) {
+                       $old = 'fail';
+                       $new = 'pass';
+                       $expectExpiry = '30001231235959';
+               } else {
+                       $old = 'pass';
+                       $new = 'fail';
+                       $expectExpiry = $oldExpiry;
+               }
+
+               $loginReq->password = $oldpass;
+               $ret = $provider->beginPrimaryAuthentication( $loginReqs );
+               if ( $old === 'pass' ) {
+                       $this->assertEquals(
+                               AuthenticationResponse::newPass( $cuser ),
+                               $ret,
+                               'old password should pass'
+                       );
+               } else {
+                       $this->assertEquals(
+                               AuthenticationResponse::FAIL,
+                               $ret->status,
+                               'old password should fail'
+                       );
+                       $this->assertEquals(
+                               'wrongpassword',
+                               $ret->message->getKey(),
+                               'old password should fail'
+                       );
+               }
+
+               $loginReq->password = $newpass;
+               $ret = $provider->beginPrimaryAuthentication( $loginReqs );
+               if ( $new === 'pass' ) {
+                       $this->assertEquals(
+                               AuthenticationResponse::newPass( $cuser ),
+                               $ret,
+                               'new password should pass'
+                       );
+               } else {
+                       $this->assertEquals(
+                               AuthenticationResponse::FAIL,
+                               $ret->status,
+                               'new password should fail'
+                       );
+                       $this->assertEquals(
+                               'wrongpassword',
+                               $ret->message->getKey(),
+                               'new password should fail'
+                       );
+               }
+
+               $this->assertSame(
+                       $expectExpiry,
+                       $dbw->selectField( 'user', 'user_password_expires', [ 'user_name' => $cuser ] )
+               );
+       }
+
+       public static function provideProviderChangeAuthenticationData() {
+               return [
+                       [ 'UTSysop', AuthenticationRequest::class, false, false ],
+                       [ 'UTSysop', PasswordAuthenticationRequest::class, false, true ],
+                       [ 'UTSysop', AuthenticationRequest::class, true, false ],
+                       [ 'UTSysop', PasswordAuthenticationRequest::class, true, true ],
+                       [ 'uTSysop', PasswordAuthenticationRequest::class, false, true ],
+                       [ 'uTSysop', PasswordAuthenticationRequest::class, true, true ],
+               ];
+       }
+
+       public function testTestForAccountCreation() {
+               $user = \User::newFromName( 'foo' );
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_CREATE;
+               $req->username = 'Foo';
+               $req->password = 'Bar';
+               $req->retype = 'Bar';
+               $reqs = [ PasswordAuthenticationRequest::class => $req ];
+
+               $provider = $this->getProvider();
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, [] ),
+                       'No password request'
+               );
+
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, $reqs ),
+                       'Password request, validated'
+               );
+
+               $req->retype = 'Baz';
+               $this->assertEquals(
+                       \StatusValue::newFatal( 'badretype' ),
+                       $provider->testForAccountCreation( $user, $user, $reqs ),
+                       'Password request, bad retype'
+               );
+               $req->retype = 'Bar';
+
+               $this->validity->error( 'arbitrary warning' );
+               $expect = \StatusValue::newGood();
+               $expect->error( 'arbitrary warning' );
+               $this->assertEquals(
+                       $expect,
+                       $provider->testForAccountCreation( $user, $user, $reqs ),
+                       'Password request, not validated'
+               );
+
+               $provider = $this->getProvider( true );
+               $this->validity->error( 'arbitrary warning' );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, $reqs ),
+                       'Password request, not validated, loginOnly'
+               );
+       }
+
+       public function testAccountCreation() {
+               $user = \User::newFromName( 'Foo' );
+
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_CREATE;
+               $reqs = [ PasswordAuthenticationRequest::class => $req ];
+
+               $provider = $this->getProvider( true );
+               try {
+                       $provider->beginPrimaryAccountCreation( $user, $user, [] );
+                       $this->fail( 'Expected exception was not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+                       $this->assertSame(
+                               'Shouldn\'t call this when accountCreationType() is NONE', $ex->getMessage()
+                       );
+               }
+
+               try {
+                       $provider->finishAccountCreation( $user, $user, AuthenticationResponse::newPass() );
+                       $this->fail( 'Expected exception was not thrown' );
+               } catch ( \BadMethodCallException $ex ) {
+                       $this->assertSame(
+                               'Shouldn\'t call this when accountCreationType() is NONE', $ex->getMessage()
+                       );
+               }
+
+               $provider = $this->getProvider( false );
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, [] )
+               );
+
+               $req->username = 'foo';
+               $req->password = null;
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $req->username = null;
+               $req->password = 'bar';
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $req->username = 'foo';
+               $req->password = 'bar';
+
+               $expect = AuthenticationResponse::newPass( 'Foo' );
+               $expect->createRequest = clone( $req );
+               $expect->createRequest->username = 'Foo';
+               $this->assertEquals( $expect, $provider->beginPrimaryAccountCreation( $user, $user, $reqs ) );
+
+               // We have to cheat a bit to avoid having to add a new user to
+               // the database to test the actual setting of the password works right
+               $dbw = wfGetDB( DB_MASTER );
+               $oldHash = $dbw->selectField( 'user', 'user_password', [ 'user_name' => $user ] );
+               $cb = new \ScopedCallback( function () use ( $dbw, $user, $oldHash ) {
+                       $dbw->update( 'user', [ 'user_password' => $oldHash ], [ 'user_name' => $user ] );
+               } );
+
+               $user = \User::newFromName( 'UTSysop' );
+               $req->username = $user->getName();
+               $req->password = 'NewPassword';
+               $expect = AuthenticationResponse::newPass( 'UTSysop' );
+               $expect->createRequest = $req;
+
+               $res2 = $provider->beginPrimaryAccountCreation( $user, $user, $reqs );
+               $this->assertEquals( $expect, $res2, 'Sanity check' );
+
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals( AuthenticationResponse::FAIL, $ret->status, 'sanity check' );
+
+               $this->assertNull( $provider->finishAccountCreation( $user, $user, $res2 ) );
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals( AuthenticationResponse::PASS, $ret->status, 'new password is set' );
+
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php b/tests/phpunit/includes/auth/PasswordAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..3387e7c
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\PasswordAuthenticationRequest
+ */
+class PasswordAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               $ret = new PasswordAuthenticationRequest();
+               $ret->action = $args[0];
+               return $ret;
+       }
+
+       public static function provideGetFieldInfo() {
+               return [
+                       [ [ AuthManager::ACTION_LOGIN ] ],
+                       [ [ AuthManager::ACTION_CREATE ] ],
+                       [ [ AuthManager::ACTION_CHANGE ] ],
+                       [ [ AuthManager::ACTION_REMOVE ] ],
+               ];
+       }
+
+       public function testGetFieldInfo2() {
+               $info = [];
+               foreach ( [
+                       AuthManager::ACTION_LOGIN,
+                       AuthManager::ACTION_CREATE,
+                       AuthManager::ACTION_CHANGE,
+                       AuthManager::ACTION_REMOVE,
+               ] as $action ) {
+                       $req = new PasswordAuthenticationRequest();
+                       $req->action = $action;
+                       $info[$action] = $req->getFieldInfo();
+               }
+
+               $this->assertSame( [], $info[AuthManager::ACTION_REMOVE], 'No data needed to remove' );
+
+               $this->assertArrayNotHasKey( 'retype', $info[AuthManager::ACTION_LOGIN],
+                       'No need to retype password on login' );
+               $this->assertArrayHasKey( 'retype', $info[AuthManager::ACTION_CREATE],
+                       'Need to retype when creating new password' );
+               $this->assertArrayHasKey( 'retype', $info[AuthManager::ACTION_CHANGE],
+                       'Need to retype when changing password' );
+
+               $this->assertNotEquals(
+                       $info[AuthManager::ACTION_LOGIN]['password']['label'],
+                       $info[AuthManager::ACTION_CHANGE]['password']['label'],
+                       'Password field for change is differentiated from login'
+               );
+               $this->assertNotEquals(
+                       $info[AuthManager::ACTION_CREATE]['password']['label'],
+                       $info[AuthManager::ACTION_CHANGE]['password']['label'],
+                       'Password field for change is differentiated from create'
+               );
+               $this->assertNotEquals(
+                       $info[AuthManager::ACTION_CREATE]['retype']['label'],
+                       $info[AuthManager::ACTION_CHANGE]['retype']['label'],
+                       'Retype field for change is differentiated from create'
+               );
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [],
+                               false,
+                       ],
+                       'Empty request, change' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [],
+                               false,
+                       ],
+                       'Empty request, remove' => [
+                               [ AuthManager::ACTION_REMOVE ],
+                               [],
+                               false,
+                       ],
+                       'Username + password, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               $data = [ 'username' => 'User', 'password' => 'Bar' ],
+                               $data + [ 'action' => AuthManager::ACTION_LOGIN ],
+                       ],
+                       'Username + password, change' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => 'User', 'password' => 'Bar' ],
+                               false,
+                       ],
+                       'Username + password + retype' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => 'User', 'password' => 'Bar', 'retype' => 'baz' ],
+                               [ 'password' => 'Bar', 'retype' => 'baz', 'action' => AuthManager::ACTION_CHANGE ],
+                       ],
+                       'Username empty, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [ 'username' => '', 'password' => 'Bar' ],
+                               false,
+                       ],
+                       'Username empty, change' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => '', 'password' => 'Bar', 'retype' => 'baz' ],
+                               [ 'password' => 'Bar', 'retype' => 'baz', 'action' => AuthManager::ACTION_CHANGE ],
+                       ],
+                       'Password empty, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [ 'username' => 'User', 'password' => '' ],
+                               false,
+                       ],
+                       'Password empty, login, with retype' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [ 'username' => 'User', 'password' => '', 'retype' => 'baz' ],
+                               false,
+                       ],
+                       'Retype empty' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => 'User', 'password' => 'Bar', 'retype' => '' ],
+                               false,
+                       ],
+               ];
+       }
+
+       public function testDescribeCredentials() {
+               $req = new PasswordAuthenticationRequest;
+               $req->action = AuthManager::ACTION_LOGIN;
+               $req->username = 'UTSysop';
+               $ret = $req->describeCredentials();
+               $this->assertInternalType( 'array', $ret );
+               $this->assertArrayHasKey( 'provider', $ret );
+               $this->assertInstanceOf( 'Message', $ret['provider'] );
+               $this->assertSame( 'authmanager-provider-password', $ret['provider']->getKey() );
+               $this->assertArrayHasKey( 'account', $ret );
+               $this->assertInstanceOf( 'Message', $ret['account'] );
+               $this->assertSame( [ 'UTSysop' ], $ret['account']->getParams() );
+       }
+}
diff --git a/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php b/tests/phpunit/includes/auth/PasswordDomainAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..f746515
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\PasswordDomainAuthenticationRequest
+ */
+class PasswordDomainAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               $ret = new PasswordDomainAuthenticationRequest( [ 'd1', 'd2' ] );
+               $ret->action = $args[0];
+               return $ret;
+       }
+
+       public static function provideGetFieldInfo() {
+               return [
+                       [ [ AuthManager::ACTION_LOGIN ] ],
+                       [ [ AuthManager::ACTION_CREATE ] ],
+                       [ [ AuthManager::ACTION_CHANGE ] ],
+                       [ [ AuthManager::ACTION_REMOVE ] ],
+               ];
+       }
+
+       public function testGetFieldInfo2() {
+               $info = [];
+               foreach ( [
+                       AuthManager::ACTION_LOGIN,
+                       AuthManager::ACTION_CREATE,
+                       AuthManager::ACTION_CHANGE,
+                       AuthManager::ACTION_REMOVE,
+               ] as $action ) {
+                       $req = new PasswordDomainAuthenticationRequest( [ 'd1', 'd2' ] );
+                       $req->action = $action;
+                       $info[$action] = $req->getFieldInfo();
+               }
+
+               $this->assertSame( [], $info[AuthManager::ACTION_REMOVE], 'No data needed to remove' );
+
+               $this->assertArrayNotHasKey( 'retype', $info[AuthManager::ACTION_LOGIN],
+                       'No need to retype password on login' );
+               $this->assertArrayHasKey( 'domain', $info[AuthManager::ACTION_LOGIN],
+                       'Domain needed on login' );
+               $this->assertArrayHasKey( 'retype', $info[AuthManager::ACTION_CREATE],
+                       'Need to retype when creating new password' );
+               $this->assertArrayHasKey( 'domain', $info[AuthManager::ACTION_CREATE],
+                       'Domain needed on account creation' );
+               $this->assertArrayHasKey( 'retype', $info[AuthManager::ACTION_CHANGE],
+                       'Need to retype when changing password' );
+               $this->assertArrayNotHasKey( 'domain', $info[AuthManager::ACTION_CHANGE],
+                       'Domain not needed on account creation' );
+
+               $this->assertNotEquals(
+                       $info[AuthManager::ACTION_LOGIN]['password']['label'],
+                       $info[AuthManager::ACTION_CHANGE]['password']['label'],
+                       'Password field for change is differentiated from login'
+               );
+               $this->assertNotEquals(
+                       $info[AuthManager::ACTION_CREATE]['password']['label'],
+                       $info[AuthManager::ACTION_CHANGE]['password']['label'],
+                       'Password field for change is differentiated from create'
+               );
+               $this->assertNotEquals(
+                       $info[AuthManager::ACTION_CREATE]['retype']['label'],
+                       $info[AuthManager::ACTION_CHANGE]['retype']['label'],
+                       'Retype field for change is differentiated from create'
+               );
+       }
+
+       public function provideLoadFromSubmission() {
+               $domainList = [ 'domainList' => [ 'd1', 'd2' ] ];
+               return [
+                       'Empty request, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [],
+                               false,
+                       ],
+                       'Empty request, change' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [],
+                               false,
+                       ],
+                       'Empty request, remove' => [
+                               [ AuthManager::ACTION_REMOVE ],
+                               [],
+                               false,
+                       ],
+                       'Username + password, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               $data = [ 'username' => 'User', 'password' => 'Bar' ],
+                               false,
+                       ],
+                       'Username + password + domain, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               $data = [ 'username' => 'User', 'password' => 'Bar', 'domain' => 'd1' ],
+                               $data + [ 'action' => AuthManager::ACTION_LOGIN ] + $domainList,
+                       ],
+                       'Username + password + bad domain, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               $data = [ 'username' => 'User', 'password' => 'Bar', 'domain' => 'd5' ],
+                               false,
+                       ],
+                       'Username + password + domain, change' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => 'User', 'password' => 'Bar', 'domain' => 'd1' ],
+                               false,
+                       ],
+                       'Username + password + domain + retype' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => 'User', 'password' => 'Bar', 'retype' => 'baz', 'domain' => 'd1' ],
+                               [ 'password' => 'Bar', 'retype' => 'baz', 'action' => AuthManager::ACTION_CHANGE ] +
+                                       $domainList,
+                       ],
+                       'Username empty, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [ 'username' => '', 'password' => 'Bar', 'domain' => 'd1' ],
+                               false,
+                       ],
+                       'Username empty, change' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => '', 'password' => 'Bar', 'retype' => 'baz', 'domain' => 'd1' ],
+                               [ 'password' => 'Bar', 'retype' => 'baz', 'action' => AuthManager::ACTION_CHANGE ] +
+                                       $domainList,
+                       ],
+                       'Password empty, login' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [ 'username' => 'User', 'password' => '', 'domain' => 'd1' ],
+                               false,
+                       ],
+                       'Password empty, login, with retype' => [
+                               [ AuthManager::ACTION_LOGIN ],
+                               [ 'username' => 'User', 'password' => '', 'retype' => 'baz', 'domain' => 'd1' ],
+                               false,
+                       ],
+                       'Retype empty' => [
+                               [ AuthManager::ACTION_CHANGE ],
+                               [ 'username' => 'User', 'password' => 'Bar', 'retype' => '', 'domain' => 'd1' ],
+                               false,
+                       ],
+               ];
+       }
+
+       public function testDescribeCredentials() {
+               $req = new PasswordDomainAuthenticationRequest( [ 'd1', 'd2' ] );
+               $req->action = AuthManager::ACTION_LOGIN;
+               $req->username = 'UTSysop';
+               $req->domain = 'd2';
+               $ret = $req->describeCredentials();
+               $this->assertInternalType( 'array', $ret );
+               $this->assertArrayHasKey( 'provider', $ret );
+               $this->assertInstanceOf( 'Message', $ret['provider'] );
+               $this->assertSame( 'authmanager-provider-password-domain', $ret['provider']->getKey() );
+               $this->assertArrayHasKey( 'account', $ret );
+               $this->assertInstanceOf( 'Message', $ret['account'] );
+               $this->assertSame( 'authmanager-account-password-domain', $ret['account']->getKey() );
+               $this->assertSame( [ 'UTSysop', 'd2' ], $ret['account']->getParams() );
+       }
+}
diff --git a/tests/phpunit/includes/auth/RememberMeAuthenticationRequestTest.php b/tests/phpunit/includes/auth/RememberMeAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..3f90169
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\RememberMeAuthenticationRequest
+ */
+class RememberMeAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       public static function provideGetFieldInfo() {
+               return [
+                       [ [ 1 ] ],
+                       [ [ null ] ],
+               ];
+       }
+
+       public function testGetFieldInfo_2() {
+               $req = new RememberMeAuthenticationRequest();
+               $reqWrapper = \TestingAccessWrapper::newFromObject( $req );
+
+               $reqWrapper->expiration = 30 * 24 * 3600;
+               $this->assertNotEmpty( $req->getFieldInfo() );
+
+               $reqWrapper->expiration = null;
+               $this->assertEmpty( $req->getFieldInfo() );
+       }
+
+       protected function getInstance( array $args = [] ) {
+               $req = new RememberMeAuthenticationRequest();
+               $reqWrapper = \TestingAccessWrapper::newFromObject( $req );
+               $reqWrapper->expiration = $args[0];
+               return $req;
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [ 30 * 24 * 3600 ],
+                               [],
+                               [ 'expiration' => 30 * 24 * 3600, 'rememberMe' => false ]
+                       ],
+                       'RememberMe present' => [
+                               [ 30 * 24 * 3600 ],
+                               [ 'rememberMe' => '' ],
+                               [ 'expiration' => 30 * 24 * 3600, 'rememberMe' => true ]
+                       ],
+                       'RememberMe present but session provider cannot remember' => [
+                               [ null ],
+                               [ 'rememberMe' => '' ],
+                               false
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/ResetPasswordSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/ResetPasswordSecondaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..59edede
--- /dev/null
@@ -0,0 +1,313 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider
+ */
+class ResetPasswordSecondaryAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $response
+        */
+       public function testGetAuthenticationRequests( $action, $response ) {
+               $provider = new ResetPasswordSecondaryAuthenticationProvider();
+
+               $this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) );
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               return [
+                       [ AuthManager::ACTION_LOGIN, [] ],
+                       [ AuthManager::ACTION_CREATE, [] ],
+                       [ AuthManager::ACTION_LINK, [] ],
+                       [ AuthManager::ACTION_CHANGE, [] ],
+                       [ AuthManager::ACTION_REMOVE, [] ],
+               ];
+       }
+
+       public function testBasics() {
+               $user = \User::newFromName( 'UTSysop' );
+               $user2 = new \User;
+               $obj = new \stdClass;
+               $reqs = [ new \stdClass ];
+
+               $mb = $this->getMockBuilder( ResetPasswordSecondaryAuthenticationProvider::class )
+                       ->setMethods( [ 'tryReset' ] );
+
+               $methods = [
+                       'beginSecondaryAuthentication' => [ $user, $reqs ],
+                       'continueSecondaryAuthentication' => [ $user, $reqs ],
+                       'beginSecondaryAccountCreation' => [ $user, $user2, $reqs ],
+                       'continueSecondaryAccountCreation' => [ $user, $user2, $reqs ],
+               ];
+               foreach ( $methods as $method => $args ) {
+                       $mock = $mb->getMock();
+                       $mock->expects( $this->once() )->method( 'tryReset' )
+                               ->with( $this->identicalTo( $user ), $this->identicalTo( $reqs ) )
+                               ->will( $this->returnValue( $obj ) );
+                       $this->assertSame( $obj, call_user_func_array( [ $mock, $method ], $args ) );
+               }
+       }
+
+       public function testTryReset() {
+               $user = \User::newFromName( 'UTSysop' );
+
+               $provider = $this->getMockBuilder(
+                       ResetPasswordSecondaryAuthenticationProvider::class
+               )
+                       ->setMethods( [
+                               'providerAllowsAuthenticationDataChange', 'providerChangeAuthenticationData'
+                       ] )
+                       ->getMock();
+               $provider->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
+                       ->will( $this->returnCallback( function ( $req ) {
+                               $this->assertSame( 'UTSysop', $req->username );
+                               return $req->allow;
+                       } ) );
+               $provider->expects( $this->any() )->method( 'providerChangeAuthenticationData' )
+                       ->will( $this->returnCallback( function ( $req ) {
+                               $this->assertSame( 'UTSysop', $req->username );
+                               $req->done = true;
+                       } ) );
+               $config = new \HashConfig( [
+                       'AuthManagerConfig' => [
+                               'preauth' => [],
+                               'primaryauth' => [],
+                               'secondaryauth' => [
+                                       [ 'factory' => function () use ( $provider ) {
+                                               return $provider;
+                                       } ],
+                               ],
+                       ],
+               ] );
+               $manager = new AuthManager( new \FauxRequest, $config );
+               $provider->setManager( $manager );
+               $provider = \TestingAccessWrapper::newFromObject( $provider );
+
+               $msg = wfMessage( 'foo' );
+               $skipReq = new ButtonAuthenticationRequest(
+                       'skipReset',
+                       wfMessage( 'authprovider-resetpass-skip-label' ),
+                       wfMessage( 'authprovider-resetpass-skip-help' )
+               );
+               $passReq = new PasswordAuthenticationRequest();
+               $passReq->action = AuthManager::ACTION_CHANGE;
+               $passReq->password = 'Foo';
+               $passReq->retype = 'Bar';
+               $passReq->allow = \StatusValue::newGood();
+               $passReq->done = false;
+
+               $passReq2 = $this->getMockBuilder( PasswordAuthenticationRequest::class )
+                       ->enableProxyingToOriginalMethods()
+                       ->getMock();
+               $passReq2->action = AuthManager::ACTION_CHANGE;
+               $passReq2->password = 'Foo';
+               $passReq2->retype = 'Foo';
+               $passReq2->allow = \StatusValue::newGood();
+               $passReq2->done = false;
+
+               $passReq3 = new PasswordAuthenticationRequest();
+               $passReq3->action = AuthManager::ACTION_LOGIN;
+               $passReq3->password = 'Foo';
+               $passReq3->retype = 'Foo';
+               $passReq3->allow = \StatusValue::newGood();
+               $passReq3->done = false;
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->tryReset( $user, [] )
+               );
+
+               $manager->setAuthenticationSessionData( 'reset-pass', 'foo' );
+               try {
+                       $provider->tryReset( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame( 'reset-pass is not valid', $ex->getMessage() );
+               }
+
+               $manager->setAuthenticationSessionData( 'reset-pass', (object)[] );
+               try {
+                       $provider->tryReset( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame( 'reset-pass msg is missing', $ex->getMessage() );
+               }
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => 'foo',
+               ] );
+               try {
+                       $provider->tryReset( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame( 'reset-pass msg is not valid', $ex->getMessage() );
+               }
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+               ] );
+               try {
+                       $provider->tryReset( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame( 'reset-pass hard is missing', $ex->getMessage() );
+               }
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => true,
+                       'req' => 'foo',
+               ] );
+               try {
+                       $provider->tryReset( $user, [] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame( 'reset-pass req is not valid', $ex->getMessage() );
+               }
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => false,
+                       'req' => $passReq3,
+               ] );
+               try {
+                       $provider->tryReset( $user, [ $passReq ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \UnexpectedValueException $ex ) {
+                       $this->assertSame( 'reset-pass req is not valid', $ex->getMessage() );
+               }
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => true,
+               ] );
+               $res = $provider->tryReset( $user, [] );
+               $this->assertInstanceOf( AuthenticationResponse::class, $res );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertEquals( $msg, $res->message );
+               $this->assertCount( 1, $res->neededRequests );
+               $this->assertInstanceOf(
+                       PasswordAuthenticationRequest::class,
+                       $res->neededRequests[0]
+               );
+               $this->assertNotNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => false,
+                       'req' => $passReq,
+               ] );
+               $res = $provider->tryReset( $user, [] );
+               $this->assertInstanceOf( AuthenticationResponse::class, $res );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertEquals( $msg, $res->message );
+               $this->assertCount( 2, $res->neededRequests );
+               $this->assertEquals( $passReq, $res->neededRequests[0] );
+               $this->assertEquals( $skipReq, $res->neededRequests[1] );
+               $this->assertNotNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+
+               $passReq->retype = 'Bad';
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => false,
+                       'req' => $passReq,
+               ] );
+               $res = $provider->tryReset( $user, [ $skipReq, $passReq ] );
+               $this->assertEquals( AuthenticationResponse::newPass(), $res );
+               $this->assertNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+
+               $passReq->retype = 'Bad';
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => true,
+               ] );
+               $res = $provider->tryReset( $user, [ $skipReq, $passReq ] );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertSame( 'badretype', $res->message->getKey() );
+               $this->assertCount( 1, $res->neededRequests );
+               $this->assertInstanceOf(
+                       PasswordAuthenticationRequest::class,
+                       $res->neededRequests[0]
+               );
+               $this->assertNotNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => true,
+               ] );
+               $res = $provider->tryReset( $user, [ $skipReq, $passReq3 ] );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertEquals( $msg, $res->message );
+               $this->assertCount( 1, $res->neededRequests );
+               $this->assertInstanceOf(
+                       PasswordAuthenticationRequest::class,
+                       $res->neededRequests[0]
+               );
+               $this->assertNotNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+
+               $passReq->retype = $passReq->password;
+               $passReq->allow = \StatusValue::newFatal( 'arbitrary-fail' );
+               $res = $provider->tryReset( $user, [ $skipReq, $passReq ] );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertSame( 'arbitrary-fail', $res->message->getKey() );
+               $this->assertCount( 1, $res->neededRequests );
+               $this->assertInstanceOf(
+                       PasswordAuthenticationRequest::class,
+                       $res->neededRequests[0]
+               );
+               $this->assertNotNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+
+               $passReq->allow = \StatusValue::newGood();
+               $res = $provider->tryReset( $user, [ $skipReq, $passReq ] );
+               $this->assertEquals( AuthenticationResponse::newPass(), $res );
+               $this->assertNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertTrue( $passReq->done );
+
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => false,
+                       'req' => $passReq2,
+               ] );
+               $res = $provider->tryReset( $user, [ $passReq2 ] );
+               $this->assertEquals( AuthenticationResponse::newPass(), $res );
+               $this->assertNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertTrue( $passReq2->done );
+
+               $passReq->done = false;
+               $passReq2->done = false;
+               $manager->setAuthenticationSessionData( 'reset-pass', [
+                       'msg' => $msg,
+                       'hard' => false,
+                       'req' => $passReq2,
+               ] );
+               $res = $provider->tryReset( $user, [ $passReq ] );
+               $this->assertInstanceOf( AuthenticationResponse::class, $res );
+               $this->assertSame( AuthenticationResponse::UI, $res->status );
+               $this->assertEquals( $msg, $res->message );
+               $this->assertCount( 2, $res->neededRequests );
+               $this->assertEquals( $passReq2, $res->neededRequests[0] );
+               $this->assertEquals( $skipReq, $res->neededRequests[1] );
+               $this->assertNotNull( $manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $this->assertFalse( $passReq->done );
+               $this->assertFalse( $passReq2->done );
+       }
+}
diff --git a/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php b/tests/phpunit/includes/auth/TemporaryPasswordAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..05c5165
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\TemporaryPasswordAuthenticationRequest
+ */
+class TemporaryPasswordAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               $ret = new TemporaryPasswordAuthenticationRequest;
+               $ret->action = $args[0];
+               return $ret;
+       }
+
+       public static function provideGetFieldInfo() {
+               return [
+                       [ [ AuthManager::ACTION_CREATE ] ],
+                       [ [ AuthManager::ACTION_CHANGE ] ],
+                       [ [ AuthManager::ACTION_REMOVE ] ],
+               ];
+       }
+
+       public function testNewRandom() {
+               global $wgPasswordPolicy;
+
+               $this->stashMwGlobals( 'wgPasswordPolicy' );
+               $wgPasswordPolicy['policies']['default'] += [
+                       'MinimalPasswordLength' => 1,
+                       'MinimalPasswordLengthToLogin' => 1,
+               ];
+
+               $ret1 = TemporaryPasswordAuthenticationRequest::newRandom();
+               $ret2 = TemporaryPasswordAuthenticationRequest::newRandom();
+               $this->assertNotSame( '', $ret1->password );
+               $this->assertNotSame( '', $ret2->password );
+               $this->assertNotSame( $ret1->password, $ret2->password );
+       }
+
+       public function testNewInvalid() {
+               $ret = TemporaryPasswordAuthenticationRequest::newInvalid();
+               $this->assertNull( $ret->password );
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [ AuthManager::ACTION_REMOVE ],
+                               [],
+                               false,
+                       ],
+                       'Create, empty request' => [
+                               [ AuthManager::ACTION_CREATE ],
+                               [],
+                               false,
+                       ],
+                       'Create, mailpassword set' => [
+                               [ AuthManager::ACTION_CREATE ],
+                               [ 'mailpassword' => 1 ],
+                               [ 'mailpassword' => true, 'action' => AuthManager::ACTION_CREATE ],
+                       ],
+               ];
+       }
+
+       public function testDescribeCredentials() {
+               $req = new TemporaryPasswordAuthenticationRequest;
+               $req->action = AuthManager::ACTION_LOGIN;
+               $req->username = 'UTSysop';
+               $ret = $req->describeCredentials();
+               $this->assertInternalType( 'array', $ret );
+               $this->assertArrayHasKey( 'provider', $ret );
+               $this->assertInstanceOf( 'Message', $ret['provider'] );
+               $this->assertSame( 'authmanager-provider-temporarypassword', $ret['provider']->getKey() );
+               $this->assertArrayHasKey( 'account', $ret );
+               $this->assertInstanceOf( 'Message', $ret['account'] );
+               $this->assertSame( [ 'UTSysop' ], $ret['account']->getParams() );
+       }
+}
diff --git a/tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..8d0bf96
--- /dev/null
@@ -0,0 +1,749 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @group Database
+ * @covers MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider
+ */
+class TemporaryPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase {
+
+       private $manager = null;
+       private $config = null;
+       private $validity = null;
+
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       /**
+        * Get an instance of the provider
+        *
+        * $provider->checkPasswordValidity is mocked to return $this->validity,
+        * because we don't need to test that here.
+        *
+        * @param array $params
+        * @return TemporaryPasswordPrimaryAuthenticationProvider
+        */
+       protected function getProvider( $params = [] ) {
+               if ( !$this->config ) {
+                       $this->config = new \HashConfig( [
+                               'EmailEnabled' => true,
+                       ] );
+               }
+               $config = new \MultiConfig( [
+                       $this->config,
+                       \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+               ] );
+
+               if ( !$this->manager ) {
+                       $this->manager = new AuthManager( new \FauxRequest(), $config );
+               }
+               $this->validity = \Status::newGood();
+
+               $mockedMethods[] = 'checkPasswordValidity';
+               $provider = $this->getMock(
+                       TemporaryPasswordPrimaryAuthenticationProvider::class,
+                       $mockedMethods,
+                       [ $params ]
+               );
+               $provider->expects( $this->any() )->method( 'checkPasswordValidity' )
+                       ->will( $this->returnCallback( function () {
+                               return $this->validity;
+                       } ) );
+               $provider->setConfig( $config );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setManager( $this->manager );
+
+               return $provider;
+       }
+
+       protected function hookMailer( $func = null ) {
+               \Hooks::clear( 'AlternateUserMailer' );
+               if ( $func ) {
+                       \Hooks::register( 'AlternateUserMailer', $func );
+                       // Safety
+                       \Hooks::register( 'AlternateUserMailer', function () {
+                               return false;
+                       } );
+               } else {
+                       \Hooks::register( 'AlternateUserMailer', function () {
+                               $this->fail( 'AlternateUserMailer hook called unexpectedly' );
+                               return false;
+                       } );
+               }
+
+               return new \ScopedCallback( function () {
+                       \Hooks::clear( 'AlternateUserMailer' );
+                       \Hooks::register( 'AlternateUserMailer', function () {
+                               return false;
+                       } );
+               } );
+       }
+
+       public function testBasics() {
+               $provider = new TemporaryPasswordPrimaryAuthenticationProvider();
+
+               $this->assertSame(
+                       PrimaryAuthenticationProvider::TYPE_CREATE,
+                       $provider->accountCreationType()
+               );
+
+               $this->assertTrue( $provider->testUserExists( 'UTSysop' ) );
+               $this->assertTrue( $provider->testUserExists( 'uTSysop' ) );
+               $this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) );
+               $this->assertFalse( $provider->testUserExists( '<invalid>' ) );
+
+               $req = new PasswordAuthenticationRequest;
+               $req->action = AuthManager::ACTION_CHANGE;
+               $req->username = '<invalid>';
+               $provider->providerChangeAuthenticationData( $req );
+       }
+
+       public function testConfig() {
+               $config = new \HashConfig( [
+                       'EnableEmail' => false,
+                       'NewPasswordExpiry' => 100,
+                       'PasswordReminderResendTime' => 101,
+               ] );
+
+               $p = \TestingAccessWrapper::newFromObject( new TemporaryPasswordPrimaryAuthenticationProvider() );
+               $p->setConfig( $config );
+               $this->assertSame( false, $p->emailEnabled );
+               $this->assertSame( 100, $p->newPasswordExpiry );
+               $this->assertSame( 101, $p->passwordReminderResendTime );
+
+               $p = \TestingAccessWrapper::newFromObject( new TemporaryPasswordPrimaryAuthenticationProvider( [
+                       'emailEnabled' => true,
+                       'newPasswordExpiry' => 42,
+                       'passwordReminderResendTime' => 43,
+               ] ) );
+               $p->setConfig( $config );
+               $this->assertSame( true, $p->emailEnabled );
+               $this->assertSame( 42, $p->newPasswordExpiry );
+               $this->assertSame( 43, $p->passwordReminderResendTime );
+       }
+
+       public function testTestUserCanAuthenticate() {
+               $dbw = wfGetDB( DB_MASTER );
+
+               $passwordFactory = new \PasswordFactory();
+               $passwordFactory->init( \RequestContext::getMain()->getConfig() );
+               // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
+               $passwordFactory->setDefaultType( 'A' );
+               $pwhash = $passwordFactory->newFromPlaintext( 'password' )->toString();
+
+               $provider = $this->getProvider();
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $this->assertFalse( $provider->testUserCanAuthenticate( '<invalid>' ) );
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'DoesNotExist' ) );
+
+               $dbw->update(
+                       'user',
+                       [
+                               'user_newpassword' => \PasswordFactory::newInvalidPassword()->toString(),
+                               'user_newpass_time' => null,
+                       ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+
+               $dbw->update(
+                       'user',
+                       [
+                               'user_newpassword' => $pwhash,
+                               'user_newpass_time' => null,
+                       ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'uTSysop' ) );
+
+               $dbw->update(
+                       'user',
+                       [
+                               'user_newpassword' => $pwhash,
+                               'user_newpass_time' => $dbw->timestamp( time() - 10 ),
+                       ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+               $providerPriv->newPasswordExpiry = 100;
+               $this->assertTrue( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+               $providerPriv->newPasswordExpiry = 1;
+               $this->assertFalse( $provider->testUserCanAuthenticate( 'UTSysop' ) );
+
+               $dbw->update(
+                       'user',
+                       [
+                               'user_newpassword' => \PasswordFactory::newInvalidPassword()->toString(),
+                               'user_newpass_time' => null,
+                       ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+       }
+
+       /**
+        * @dataProvider provideGetAuthenticationRequests
+        * @param string $action
+        * @param array $options
+        * @param array $expected
+        */
+       public function testGetAuthenticationRequests( $action, $options, $expected ) {
+               $actual = $this->getProvider()->getAuthenticationRequests( $action, $options );
+               foreach ( $actual as $req ) {
+                       if ( $req instanceof TemporaryPasswordAuthenticationRequest && $req->password !== null ) {
+                               $req->password = 'random';
+                       }
+               }
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public static function provideGetAuthenticationRequests() {
+               $anon = [ 'username' => null ];
+               $loggedIn = [ 'username' => 'UTSysop' ];
+
+               return [
+                       [ AuthManager::ACTION_LOGIN, $anon, [
+                               new PasswordAuthenticationRequest
+                       ] ],
+                       [ AuthManager::ACTION_LOGIN, $loggedIn, [
+                               new PasswordAuthenticationRequest
+                       ] ],
+                       [ AuthManager::ACTION_CREATE, $anon, [] ],
+                       [ AuthManager::ACTION_CREATE, $loggedIn, [
+                               new TemporaryPasswordAuthenticationRequest( 'random' )
+                       ] ],
+                       [ AuthManager::ACTION_LINK, $anon, [] ],
+                       [ AuthManager::ACTION_LINK, $loggedIn, [] ],
+                       [ AuthManager::ACTION_CHANGE, $anon, [
+                               new TemporaryPasswordAuthenticationRequest( 'random' )
+                       ] ],
+                       [ AuthManager::ACTION_CHANGE, $loggedIn, [
+                               new TemporaryPasswordAuthenticationRequest( 'random' )
+                       ] ],
+                       [ AuthManager::ACTION_REMOVE, $anon, [
+                               new TemporaryPasswordAuthenticationRequest
+                       ] ],
+                       [ AuthManager::ACTION_REMOVE, $loggedIn, [
+                               new TemporaryPasswordAuthenticationRequest
+                       ] ],
+               ];
+       }
+
+       public function testAuthentication() {
+               $password = 'TemporaryPassword';
+               $hash = ':A:' . md5( $password );
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->update(
+                       'user',
+                       [ 'user_newpassword' => $hash, 'user_newpass_time' => $dbw->timestamp( time() - 10 ) ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+
+               $req = new PasswordAuthenticationRequest();
+               $req->action = AuthManager::ACTION_LOGIN;
+               $reqs = [ PasswordAuthenticationRequest::class => $req ];
+
+               $provider = $this->getProvider();
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+
+               $providerPriv->newPasswordExpiry = 100;
+
+               // General failures
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( [] )
+               );
+
+               $req->username = 'foo';
+               $req->password = null;
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = null;
+               $req->password = 'bar';
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = '<invalid>';
+               $req->password = 'WhoCares';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               $req->username = 'DoesNotExist';
+               $req->password = 'DoesNotExist';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+
+               // Validation failure
+               $req->username = 'UTSysop';
+               $req->password = $password;
+               $this->validity = \Status::newFatal( 'arbitrary-failure' );
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'arbitrary-failure',
+                       $ret->message->getKey()
+               );
+
+               // Successful auth
+               $this->manager->removeAuthenticationSessionData( null );
+               $this->validity = \Status::newGood();
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+               $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
+
+               $this->manager->removeAuthenticationSessionData( null );
+               $this->validity = \Status::newGood();
+               $req->username = 'uTSysop';
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( 'UTSysop' ),
+                       $provider->beginPrimaryAuthentication( $reqs )
+               );
+               $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
+               $req->username = 'UTSysop';
+
+               // Expired password
+               $providerPriv->newPasswordExpiry = 1;
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'wrongpassword',
+                       $ret->message->getKey()
+               );
+
+               // Bad password
+               $providerPriv->newPasswordExpiry = 100;
+               $this->validity = \Status::newGood();
+               $req->password = 'Wrong';
+               $ret = $provider->beginPrimaryAuthentication( $reqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status
+               );
+               $this->assertEquals(
+                       'wrongpassword',
+                       $ret->message->getKey()
+               );
+
+       }
+
+       /**
+        * @dataProvider provideProviderAllowsAuthenticationDataChange
+        * @param string $type
+        * @param string $user
+        * @param \Status $validity Result of the password validity check
+        * @param \StatusValue $expect1 Expected result with $checkData = false
+        * @param \StatusValue $expect2 Expected result with $checkData = true
+        */
+       public function testProviderAllowsAuthenticationDataChange( $type, $user, \Status $validity,
+               \StatusValue $expect1, \StatusValue $expect2
+       ) {
+               if ( $type === PasswordAuthenticationRequest::class ||
+                       $type === TemporaryPasswordAuthenticationRequest::class
+               ) {
+                       $req = new $type();
+               } else {
+                       $req = $this->getMock( $type );
+               }
+               $req->action = AuthManager::ACTION_CHANGE;
+               $req->username = $user;
+               $req->password = 'NewPassword';
+
+               $provider = $this->getProvider();
+               $this->validity = $validity;
+               $this->assertEquals( $expect1, $provider->providerAllowsAuthenticationDataChange( $req, false ) );
+               $this->assertEquals( $expect2, $provider->providerAllowsAuthenticationDataChange( $req, true ) );
+       }
+
+       public static function provideProviderAllowsAuthenticationDataChange() {
+               $err = \StatusValue::newGood();
+               $err->error( 'arbitrary-warning' );
+
+               return [
+                       [ AuthenticationRequest::class, 'UTSysop', \Status::newGood(),
+                               \StatusValue::newGood( 'ignored' ), \StatusValue::newGood( 'ignored' ) ],
+                       [ PasswordAuthenticationRequest::class, 'UTSysop', \Status::newGood(),
+                               \StatusValue::newGood( 'ignored' ), \StatusValue::newGood( 'ignored' ) ],
+                       [ TemporaryPasswordAuthenticationRequest::class, 'UTSysop', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood() ],
+                       [ TemporaryPasswordAuthenticationRequest::class, 'uTSysop', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood() ],
+                       [ TemporaryPasswordAuthenticationRequest::class, 'UTSysop', \Status::wrap( $err ),
+                               \StatusValue::newGood(), $err ],
+                       [ TemporaryPasswordAuthenticationRequest::class, 'UTSysop',
+                               \Status::newFatal( 'arbitrary-error' ), \StatusValue::newGood(),
+                               \StatusValue::newFatal( 'arbitrary-error' ) ],
+                       [ TemporaryPasswordAuthenticationRequest::class, 'DoesNotExist', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood( 'ignored' ) ],
+                       [ TemporaryPasswordAuthenticationRequest::class, '<invalid>', \Status::newGood(),
+                               \StatusValue::newGood(), \StatusValue::newGood( 'ignored' ) ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideProviderChangeAuthenticationData
+        * @param string $user
+        * @param string $type
+        * @param bool $changed
+        */
+       public function testProviderChangeAuthenticationData( $user, $type, $changed ) {
+               $cuser = ucfirst( $user );
+               $oldpass = 'OldTempPassword';
+               $newpass = 'NewTempPassword';
+
+               $hash = ':A:' . md5( $oldpass );
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->update(
+                       'user',
+                       [ 'user_newpassword' => $hash, 'user_newpass_time' => $dbw->timestamp( time() + 10 ) ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $oldHash = $dbw->selectField( 'user', 'user_newpassword', [ 'user_name' => $cuser ] );
+               $cb = new \ScopedCallback( function () use ( $dbw, $cuser, $oldHash ) {
+                       $dbw->update( 'user', [ 'user_newpassword' => $oldHash ], [ 'user_name' => $cuser ] );
+               } );
+
+               $provider = $this->getProvider();
+
+               // Sanity check
+               $loginReq = new PasswordAuthenticationRequest();
+               $loginReq->action = AuthManager::ACTION_CHANGE;
+               $loginReq->username = $user;
+               $loginReq->password = $oldpass;
+               $loginReqs = [ PasswordAuthenticationRequest::class => $loginReq ];
+               $this->assertEquals(
+                       AuthenticationResponse::newPass( $cuser ),
+                       $provider->beginPrimaryAuthentication( $loginReqs ),
+                       'Sanity check'
+               );
+
+               if ( $type === PasswordAuthenticationRequest::class ||
+                       $type === TemporaryPasswordAuthenticationRequest::class
+               ) {
+                       $changeReq = new $type();
+               } else {
+                       $changeReq = $this->getMock( $type );
+               }
+               $changeReq->action = AuthManager::ACTION_CHANGE;
+               $changeReq->username = $user;
+               $changeReq->password = $newpass;
+               $resetMailer = $this->hookMailer();
+               $provider->providerChangeAuthenticationData( $changeReq );
+               \ScopedCallback::consume( $resetMailer );
+
+               $loginReq->password = $oldpass;
+               $ret = $provider->beginPrimaryAuthentication( $loginReqs );
+               $this->assertEquals(
+                       AuthenticationResponse::FAIL,
+                       $ret->status,
+                       'old password should fail'
+               );
+               $this->assertEquals(
+                       'wrongpassword',
+                       $ret->message->getKey(),
+                       'old password should fail'
+               );
+
+               $loginReq->password = $newpass;
+               $ret = $provider->beginPrimaryAuthentication( $loginReqs );
+               if ( $changed ) {
+                       $this->assertEquals(
+                               AuthenticationResponse::newPass( $cuser ),
+                               $ret,
+                               'new password should pass'
+                       );
+                       $this->assertNotNull(
+                               $dbw->selectField( 'user', 'user_newpass_time', [ 'user_name' => $cuser ] )
+                       );
+               } else {
+                       $this->assertEquals(
+                               AuthenticationResponse::FAIL,
+                               $ret->status,
+                               'new password should fail'
+                       );
+                       $this->assertEquals(
+                               'wrongpassword',
+                               $ret->message->getKey(),
+                               'new password should fail'
+                       );
+                       $this->assertNull(
+                               $dbw->selectField( 'user', 'user_newpass_time', [ 'user_name' => $cuser ] )
+                       );
+               }
+       }
+
+       public static function provideProviderChangeAuthenticationData() {
+               return [
+                       [ 'UTSysop', AuthenticationRequest::class, false ],
+                       [ 'UTSysop', PasswordAuthenticationRequest::class, false ],
+                       [ 'UTSysop', TemporaryPasswordAuthenticationRequest::class, true ],
+               ];
+       }
+
+       public function testProviderChangeAuthenticationDataEmail() {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->update(
+                       'user',
+                       [ 'user_newpass_time' => $dbw->timestamp( time() - 5 * 3600 ) ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+
+               $user = \User::newFromName( 'UTSysop' );
+               $reset = new \ScopedCallback( function ( $email ) use ( $user ) {
+                       $user->setEmail( $email );
+                       $user->saveSettings();
+               }, [ $user->getEmail() ] );
+
+               $user->setEmail( 'test@localhost.localdomain' );
+               $user->saveSettings();
+
+               $req = TemporaryPasswordAuthenticationRequest::newRandom();
+               $req->username = $user->getName();
+               $req->mailpassword = true;
+
+               $provider = $this->getProvider( [ 'emailEnabled' => false ] );
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newFatal( 'passwordreset-emaildisabled' ), $status );
+               $req->hasBackchannel = true;
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertFalse( $status->hasMessage( 'passwordreset-emaildisabled' ) );
+               $req->hasBackchannel = false;
+
+               $provider = $this->getProvider( [ 'passwordReminderResendTime' => 10 ] );
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newFatal( 'throttled-mailpassword', 10 ), $status );
+
+               $provider = $this->getProvider( [ 'passwordReminderResendTime' => 3 ] );
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertFalse( $status->hasMessage( 'throttled-mailpassword' ) );
+
+               $dbw->update(
+                       'user',
+                       [ 'user_newpass_time' => $dbw->timestamp( time() + 5 * 3600 ) ],
+                       [ 'user_name' => 'UTSysop' ]
+               );
+               $provider = $this->getProvider( [ 'passwordReminderResendTime' => 0 ] );
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertFalse( $status->hasMessage( 'throttled-mailpassword' ) );
+
+               $req->caller = null;
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nocaller' ), $status );
+
+               $req->caller = '127.0.0.256';
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nosuchcaller', '127.0.0.256' ),
+                       $status );
+
+               $req->caller = '<Invalid>';
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nosuchcaller', '<Invalid>' ),
+                       $status );
+
+               $req->caller = '127.0.0.1';
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newGood(), $status );
+
+               $req->caller = 'UTSysop';
+               $status = $provider->providerAllowsAuthenticationDataChange( $req, true );
+               $this->assertEquals( \StatusValue::newGood(), $status );
+
+               $mailed = false;
+               $resetMailer = $this->hookMailer( function ( $headers, $to, $from, $subject, $body )
+                       use ( &$mailed, $req )
+               {
+                       $mailed = true;
+                       $this->assertSame( 'test@localhost.localdomain', $to[0]->address );
+                       $this->assertContains( $req->password, $body );
+                       return false;
+               } );
+               $provider->providerChangeAuthenticationData( $req );
+               \ScopedCallback::consume( $resetMailer );
+               $this->assertTrue( $mailed );
+
+               $priv = \TestingAccessWrapper::newFromObject( $provider );
+               $req->username = '<invalid>';
+               $status = $priv->sendPasswordResetEmail( $req );
+               $this->assertEquals( \Status::newFatal( 'noname' ), $status );
+       }
+
+       public function testTestForAccountCreation() {
+               $user = \User::newFromName( 'foo' );
+               $req = new TemporaryPasswordAuthenticationRequest();
+               $req->username = 'Foo';
+               $req->password = 'Bar';
+               $reqs = [ TemporaryPasswordAuthenticationRequest::class => $req ];
+
+               $provider = $this->getProvider();
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, [] ),
+                       'No password request'
+               );
+
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $user, $reqs ),
+                       'Password request, validated'
+               );
+
+               $this->validity->error( 'arbitrary warning' );
+               $expect = \StatusValue::newGood();
+               $expect->error( 'arbitrary warning' );
+               $this->assertEquals(
+                       $expect,
+                       $provider->testForAccountCreation( $user, $user, $reqs ),
+                       'Password request, not validated'
+               );
+       }
+
+       public function testAccountCreation() {
+               $resetMailer = $this->hookMailer();
+
+               $user = \User::newFromName( 'Foo' );
+
+               $req = new TemporaryPasswordAuthenticationRequest();
+               $reqs = [ TemporaryPasswordAuthenticationRequest::class => $req ];
+
+               $authreq = new PasswordAuthenticationRequest();
+               $authreq->action = AuthManager::ACTION_CREATE;
+               $authreqs = [ PasswordAuthenticationRequest::class => $authreq ];
+
+               $provider = $this->getProvider();
+
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, [] )
+               );
+
+               $req->username = 'foo';
+               $req->password = null;
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $req->username = null;
+               $req->password = 'bar';
+               $this->assertEquals(
+                       AuthenticationResponse::newAbstain(),
+                       $provider->beginPrimaryAccountCreation( $user, $user, $reqs )
+               );
+
+               $req->username = 'foo';
+               $req->password = 'bar';
+
+               $expect = AuthenticationResponse::newPass( 'Foo' );
+               $expect->createRequest = clone( $req );
+               $expect->createRequest->username = 'Foo';
+               $this->assertEquals( $expect, $provider->beginPrimaryAccountCreation( $user, $user, $reqs ) );
+               $this->assertNull( $this->manager->getAuthenticationSessionData( 'no-email' ) );
+
+               // We have to cheat a bit to avoid having to add a new user to
+               // the database to test the actual setting of the password works right
+               $user = \User::newFromName( 'UTSysop' );
+               $req->username = $authreq->username = $user->getName();
+               $req->password = $authreq->password = 'NewPassword';
+               $expect = AuthenticationResponse::newPass( 'UTSysop' );
+               $expect->createRequest = $req;
+
+               $res2 = $provider->beginPrimaryAccountCreation( $user, $user, $reqs );
+               $this->assertEquals( $expect, $res2, 'Sanity check' );
+
+               $ret = $provider->beginPrimaryAuthentication( $authreqs );
+               $this->assertEquals( AuthenticationResponse::FAIL, $ret->status, 'sanity check' );
+
+               $this->assertSame( null, $provider->finishAccountCreation( $user, $user, $res2 ) );
+
+               $ret = $provider->beginPrimaryAuthentication( $authreqs );
+               $this->assertEquals( AuthenticationResponse::PASS, $ret->status, 'new password is set' );
+       }
+
+       public function testAccountCreationEmail() {
+               $creator = \User::newFromName( 'Foo' );
+               $user = \User::newFromName( 'UTSysop' );
+               $reset = new \ScopedCallback( function ( $email ) use ( $user ) {
+                       $user->setEmail( $email );
+                       $user->saveSettings();
+               }, [ $user->getEmail() ] );
+
+               $user->setEmail( null );
+
+               $req = TemporaryPasswordAuthenticationRequest::newRandom();
+               $req->username = $user->getName();
+               $req->mailpassword = true;
+
+               $provider = $this->getProvider( [ 'emailEnabled' => false ] );
+               $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
+               $this->assertEquals( \StatusValue::newFatal( 'emaildisabled' ), $status );
+               $req->hasBackchannel = true;
+               $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
+               $this->assertFalse( $status->hasMessage( 'emaildisabled' ) );
+               $req->hasBackchannel = false;
+
+               $provider = $this->getProvider( [ 'emailEnabled' => true ] );
+               $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
+               $this->assertEquals( \StatusValue::newFatal( 'noemailcreate' ), $status );
+               $req->hasBackchannel = true;
+               $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
+               $this->assertFalse( $status->hasMessage( 'noemailcreate' ) );
+               $req->hasBackchannel = false;
+
+               $user->setEmail( 'test@localhost.localdomain' );
+               $status = $provider->testForAccountCreation( $user, $creator, [ $req ] );
+               $this->assertEquals( \StatusValue::newGood(), $status );
+
+               $mailed = false;
+               $resetMailer = $this->hookMailer( function ( $headers, $to, $from, $subject, $body )
+                       use ( &$mailed, $req )
+               {
+                       $mailed = true;
+                       $this->assertSame( 'test@localhost.localdomain', $to[0]->address );
+                       $this->assertContains( $req->password, $body );
+                       return false;
+               } );
+
+               $expect = AuthenticationResponse::newPass( 'UTSysop' );
+               $expect->createRequest = clone( $req );
+               $expect->createRequest->username = 'UTSysop';
+               $res = $provider->beginPrimaryAccountCreation( $user, $creator, [ $req ] );
+               $this->assertEquals( $expect, $res );
+               $this->assertTrue( $this->manager->getAuthenticationSessionData( 'no-email' ) );
+               $this->assertFalse( $mailed );
+
+               $this->assertSame( 'byemail', $provider->finishAccountCreation( $user, $creator, $res ) );
+               $this->assertTrue( $mailed );
+
+               \ScopedCallback::consume( $resetMailer );
+               $this->assertTrue( $mailed );
+       }
+
+}
diff --git a/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php b/tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php
new file mode 100644 (file)
index 0000000..8b273b5
--- /dev/null
@@ -0,0 +1,235 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @group Database
+ * @covers MediaWiki\Auth\ThrottlePreAuthenticationProvider
+ */
+class ThrottlePreAuthenticationProviderTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testConstructor() {
+               $provider = new ThrottlePreAuthenticationProvider();
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $config = new \HashConfig( [
+                       'AccountCreationThrottle' => 123,
+                       'PasswordAttemptThrottle' => [ [
+                               'count' => 5,
+                               'seconds' => 300,
+                       ] ],
+               ] );
+               $provider->setConfig( $config );
+               $this->assertSame( [
+                       'accountCreationThrottle' => [ [ 'count' => 123, 'seconds' => 86400 ] ],
+                       'passwordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300 ] ]
+               ], $providerPriv->throttleSettings );
+               $accountCreationThrottle = \TestingAccessWrapper::newFromObject(
+                       $providerPriv->accountCreationThrottle );
+               $this->assertSame( [ [ 'count' => 123, 'seconds' => 86400 ] ],
+                       $accountCreationThrottle->conditions );
+               $passwordAttemptThrottle = \TestingAccessWrapper::newFromObject(
+                       $providerPriv->passwordAttemptThrottle );
+               $this->assertSame( [ [ 'count' => 5, 'seconds' => 300 ] ],
+                       $passwordAttemptThrottle->conditions );
+
+               $provider = new ThrottlePreAuthenticationProvider( [
+                       'accountCreationThrottle' => [ [ 'count' => 43, 'seconds' => 10000 ] ],
+                       'passwordAttemptThrottle' => [ [ 'count' => 11, 'seconds' => 100 ] ],
+               ] );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $config = new \HashConfig( [
+                       'AccountCreationThrottle' => 123,
+                       'PasswordAttemptThrottle' => [ [
+                               'count' => 5,
+                               'seconds' => 300,
+                       ] ],
+               ] );
+               $provider->setConfig( $config );
+               $this->assertSame( [
+                       'accountCreationThrottle' => [ [ 'count' => 43, 'seconds' => 10000 ] ],
+                       'passwordAttemptThrottle' => [ [ 'count' => 11, 'seconds' => 100 ] ],
+               ], $providerPriv->throttleSettings );
+
+               $cache = new \HashBagOStuff();
+               $provider = new ThrottlePreAuthenticationProvider( [ 'cache' => $cache ] );
+               $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
+               $provider->setConfig( new \HashConfig( [
+                       'AccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 1 ] ],
+                       'PasswordAttemptThrottle' => [ [ 'count' => 1, 'seconds' => 1 ] ],
+               ] ) );
+               $accountCreationThrottle = \TestingAccessWrapper::newFromObject(
+                       $providerPriv->accountCreationThrottle );
+               $this->assertSame( $cache, $accountCreationThrottle->cache );
+               $passwordAttemptThrottle = \TestingAccessWrapper::newFromObject(
+                       $providerPriv->passwordAttemptThrottle );
+               $this->assertSame( $cache, $passwordAttemptThrottle->cache );
+       }
+
+       public function testDisabled() {
+               $provider = new ThrottlePreAuthenticationProvider( [
+                       'accountCreationThrottle' => [],
+                       'passwordAttemptThrottle' => [],
+                       'cache' => new \HashBagOStuff(),
+               ] );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( new \HashConfig( [
+                       'AccountCreationThrottle' => null,
+                       'PasswordAttemptThrottle' => null,
+               ] ) );
+               $provider->setManager( AuthManager::singleton() );
+
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation(
+                               \User::newFromName( 'Created' ),
+                               \User::newFromName( 'Creator' ),
+                               []
+                       )
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAuthentication( [] )
+               );
+       }
+
+       /**
+        * @dataProvider provideTestForAccountCreation
+        * @param string $creatorname
+        * @param bool $succeed
+        * @param bool $hook
+        */
+       public function testTestForAccountCreation( $creatorname, $succeed, $hook ) {
+               $provider = new ThrottlePreAuthenticationProvider( [
+                       'accountCreationThrottle' => [ [ 'count' => 2, 'seconds' => 86400 ] ],
+                       'cache' => new \HashBagOStuff(),
+               ] );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( new \HashConfig( [
+                       'AccountCreationThrottle' => null,
+                       'PasswordAttemptThrottle' => null,
+               ] ) );
+               $provider->setManager( AuthManager::singleton() );
+
+               $user = \User::newFromName( 'RandomUser' );
+               $creator = \User::newFromName( $creatorname );
+               if ( $hook ) {
+                       $mock = $this->getMock( 'stdClass', [ 'onExemptFromAccountCreationThrottle' ] );
+                       $mock->expects( $this->any() )->method( 'onExemptFromAccountCreationThrottle' )
+                               ->will( $this->returnValue( false ) );
+                       $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                               'ExemptFromAccountCreationThrottle' => [ $mock ],
+                       ] );
+               }
+
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $creator, [] ),
+                       'attempt #1'
+               );
+               $this->assertEquals(
+                       \StatusValue::newGood(),
+                       $provider->testForAccountCreation( $user, $creator, [] ),
+                       'attempt #2'
+               );
+               $this->assertEquals(
+                       $succeed ? \StatusValue::newGood() : \StatusValue::newFatal( 'acct_creation_throttle_hit', 2 ),
+                       $provider->testForAccountCreation( $user, $creator, [] ),
+                       'attempt #3'
+               );
+       }
+
+       public static function provideTestForAccountCreation() {
+               return [
+                       'Normal user' => [ 'NormalUser', false, false ],
+                       'Sysop' => [ 'UTSysop', true, false ],
+                       'Normal user with hook' => [ 'NormalUser', true, true ],
+               ];
+       }
+
+       public function testTestForAuthentication() {
+               $provider = new ThrottlePreAuthenticationProvider( [
+                       'passwordAttemptThrottle' => [ [ 'count' => 2, 'seconds' => 86400 ] ],
+                       'cache' => new \HashBagOStuff(),
+               ] );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( new \HashConfig( [
+                       'AccountCreationThrottle' => null,
+                       'PasswordAttemptThrottle' => null,
+               ] ) );
+               $provider->setManager( AuthManager::singleton() );
+
+               $req = new UsernameAuthenticationRequest;
+               $req->username = 'SomeUser';
+               for ( $i = 1; $i <= 3; $i++ ) {
+                       $status = $provider->testForAuthentication( [ $req ] );
+                       $this->assertEquals( $i < 3, $status->isGood(), "attempt #$i" );
+               }
+               $this->assertCount( 1, $status->getErrors() );
+               $msg = new \Message( $status->getErrors()[0]['message'], $status->getErrors()[0]['params'] );
+               $this->assertEquals( 'login-throttled', $msg->getKey() );
+
+               $provider->postAuthentication( \User::newFromName( 'SomeUser' ),
+                       AuthenticationResponse::newFail( wfMessage( 'foo' ) ) );
+               $this->assertFalse( $provider->testForAuthentication( [ $req ] )->isGood(), 'after FAIL' );
+
+               $provider->postAuthentication( \User::newFromName( 'SomeUser' ),
+                       AuthenticationResponse::newPass() );
+               $this->assertTrue( $provider->testForAuthentication( [ $req ] )->isGood(), 'after PASS' );
+
+               $req1 = new UsernameAuthenticationRequest;
+               $req1->username = 'foo';
+               $req2 = new UsernameAuthenticationRequest;
+               $req2->username = 'bar';
+               $this->assertTrue( $provider->testForAuthentication( [ $req1, $req2 ] )->isGood() );
+
+               $req = new UsernameAuthenticationRequest;
+               $req->username = 'Some user';
+               $provider->testForAuthentication( [ $req ] );
+               $req->username = 'Some_user';
+               $provider->testForAuthentication( [ $req ] );
+               $req->username = 'some user';
+               $status = $provider->testForAuthentication( [ $req ] );
+               $this->assertFalse( $status->isGood(), 'denormalized usernames are normalized' );
+       }
+
+       public function testPostAuthentication() {
+               $provider = new ThrottlePreAuthenticationProvider( [
+                       'passwordAttemptThrottle' => [],
+                       'cache' => new \HashBagOStuff(),
+               ] );
+               $provider->setLogger( new \TestLogger );
+               $provider->setConfig( new \HashConfig( [
+                       'AccountCreationThrottle' => null,
+                       'PasswordAttemptThrottle' => null,
+               ] ) );
+               $provider->setManager( AuthManager::singleton() );
+               $provider->postAuthentication( \User::newFromName( 'SomeUser' ),
+                       AuthenticationResponse::newPass() );
+
+               $provider = new ThrottlePreAuthenticationProvider( [
+                       'passwordAttemptThrottle' => [ [ 'count' => 2, 'seconds' => 86400 ] ],
+                       'cache' => new \HashBagOStuff(),
+               ] );
+               $logger = new \TestLogger( true );
+               $provider->setLogger( $logger );
+               $provider->setConfig( new \HashConfig( [
+                       'AccountCreationThrottle' => null,
+                       'PasswordAttemptThrottle' => null,
+               ] ) );
+               $provider->setManager( AuthManager::singleton() );
+               $provider->postAuthentication( \User::newFromName( 'SomeUser' ),
+                       AuthenticationResponse::newPass() );
+               $this->assertSame( [
+                       [ \Psr\Log\LogLevel::ERROR, 'throttler data not found for {user}' ],
+               ], $logger->getBuffer() );
+       }
+}
diff --git a/tests/phpunit/includes/auth/ThrottlerTest.php b/tests/phpunit/includes/auth/ThrottlerTest.php
new file mode 100644 (file)
index 0000000..dba748b
--- /dev/null
@@ -0,0 +1,246 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+use BagOStuff;
+use HashBagOStuff;
+use InvalidArgumentException;
+use Psr\Log\AbstractLogger;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\Throttler
+ */
+class ThrottlerTest extends \MediaWikiTestCase {
+       protected function setUp() {
+               global $wgDisableAuthManager;
+
+               parent::setUp();
+               if ( $wgDisableAuthManager ) {
+                       $this->markTestSkipped( '$wgDisableAuthManager is set' );
+               }
+       }
+
+       public function testConstructor() {
+               $cache = new \HashBagOStuff();
+               $logger = $this->getMockBuilder( AbstractLogger::class )
+                       ->setMethods( [ 'log' ] )
+                       ->getMockForAbstractClass();
+
+               $throttler = new Throttler(
+                       [ [ 'count' => 123, 'seconds' => 456 ] ],
+                       [ 'type' => 'foo', 'cache' => $cache ]
+               );
+               $throttler->setLogger( $logger );
+               $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
+               $this->assertSame( [ [ 'count' => 123, 'seconds' => 456 ] ], $throttlerPriv->conditions );
+               $this->assertSame( 'foo', $throttlerPriv->type );
+               $this->assertSame( $cache, $throttlerPriv->cache );
+               $this->assertSame( $logger, $throttlerPriv->logger );
+
+               $throttler = new Throttler( [ [ 'count' => 123, 'seconds' => 456 ] ] );
+               $throttler->setLogger( new NullLogger() );
+               $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
+               $this->assertSame( [ [ 'count' => 123, 'seconds' => 456 ] ], $throttlerPriv->conditions );
+               $this->assertSame( 'custom', $throttlerPriv->type );
+               $this->assertInstanceOf( BagOStuff::class, $throttlerPriv->cache );
+               $this->assertInstanceOf( LoggerInterface::class, $throttlerPriv->logger );
+
+               $this->setMwGlobals( [ 'wgPasswordAttemptThrottle' => [ [ 'count' => 321,
+                       'seconds' => 654 ] ] ] );
+               $throttler = new Throttler();
+               $throttler->setLogger( new NullLogger() );
+               $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
+               $this->assertSame( [ [ 'count' => 321, 'seconds' => 654 ] ], $throttlerPriv->conditions );
+               $this->assertSame( 'password', $throttlerPriv->type );
+               $this->assertInstanceOf( BagOStuff::class, $throttlerPriv->cache );
+               $this->assertInstanceOf( LoggerInterface::class, $throttlerPriv->logger );
+
+               try {
+                       new Throttler( [], [ 'foo' => 1, 'bar' => 2, 'baz' => 3 ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( \InvalidArgumentException $ex ) {
+                       $this->assertSame( 'unrecognized parameters: foo, bar, baz', $ex->getMessage() );
+               }
+       }
+
+       /**
+        * @dataProvider provideNormalizeThrottleConditions
+        */
+       public function testNormalizeThrottleConditions( $condition, $normalized ) {
+               $throttler = new Throttler( $condition );
+               $throttler->setLogger( new NullLogger() );
+               $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
+               $this->assertSame( $normalized, $throttlerPriv->conditions );
+       }
+
+       public function provideNormalizeThrottleConditions() {
+               return [
+                       [
+                               [],
+                               [],
+                       ],
+                       [
+                               [ 'count' => 1, 'seconds' => 2 ],
+                               [ [ 'count' => 1, 'seconds' => 2 ] ],
+                       ],
+                       [
+                               [ [ 'count' => 1, 'seconds' => 2 ], [ 'count' => 2, 'seconds' => 3 ] ],
+                               [ [ 'count' => 1, 'seconds' => 2 ], [ 'count' => 2, 'seconds' => 3 ] ],
+                       ],
+               ];
+       }
+
+       public function testNormalizeThrottleConditions2() {
+               $priv = \TestingAccessWrapper::newFromClass( Throttler::class );
+               $this->assertSame( [], $priv->normalizeThrottleConditions( null ) );
+               $this->assertSame( [], $priv->normalizeThrottleConditions( 'bad' ) );
+       }
+
+       public function testIncrease() {
+               $cache = new \HashBagOStuff();
+               $throttler = new Throttler( [
+                       [ 'count' => 2, 'seconds' => 10, ],
+                       [ 'count' => 4, 'seconds' => 15, 'allIPs' => true ],
+               ], [ 'cache' => $cache ] );
+               $throttler->setLogger( new NullLogger() );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertSame( [ 'throttleIndex' => 0, 'count' => 2, 'wait' => 10 ], $result );
+
+               $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'SomeUser', '2.3.4.5' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'SomeUser', '3.4.5.6' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'SomeUser', '3.4.5.6' );
+               $this->assertSame( [ 'throttleIndex' => 1, 'count' => 4, 'wait' => 15 ], $result );
+       }
+
+       public function testZeroCount() {
+               $cache = new \HashBagOStuff();
+               $throttler = new Throttler( [ [ 'count' => 0, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
+               $throttler->setLogger( new NullLogger() );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle, count=0 is ignored' );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle, count=0 is ignored' );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle, count=0 is ignored' );
+       }
+
+       public function testNamespacing() {
+               $cache = new \HashBagOStuff();
+               $throttler1 = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ],
+                       [ 'cache' => $cache, 'type' => 'foo' ] );
+               $throttler2 = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ],
+                       [ 'cache' => $cache, 'type' => 'foo' ] );
+               $throttler3 = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ],
+                       [ 'cache' => $cache, 'type' => 'bar' ] );
+               $throttler1->setLogger( new NullLogger() );
+               $throttler2->setLogger( new NullLogger() );
+               $throttler3->setLogger( new NullLogger() );
+
+               $throttled = [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ];
+
+               $result = $throttler1->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler1->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertEquals( $throttled, $result, 'should throttle' );
+
+               $result = $throttler2->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertEquals( $throttled, $result, 'should throttle, same namespace' );
+
+               $result = $throttler3->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle, different namespace' );
+       }
+
+       public function testExpiration() {
+               $cache = $this->getMock( HashBagOStuff::class, [ 'add' ] );
+               $throttler = new Throttler( [ [ 'count' => 3, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
+               $throttler->setLogger( new NullLogger() );
+
+               $cache->expects( $this->once() )->method( 'add' )->with( $this->anything(), 1, 10 );
+               $throttler->increase( 'SomeUser' );
+       }
+
+       /**
+        * @expectedException \InvalidArgumentException
+        */
+       public function testException() {
+               $throttler = new Throttler( [ [ 'count' => 3, 'seconds' => 10 ] ] );
+               $throttler->setLogger( new NullLogger() );
+               $throttler->increase();
+       }
+
+       public function testLog() {
+               $cache = new \HashBagOStuff();
+               $throttler = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
+
+               $logger = $this->getMockBuilder( AbstractLogger::class )
+                       ->setMethods( [ 'log' ] )
+                       ->getMockForAbstractClass();
+               $logger->expects( $this->never() )->method( 'log' );
+               $throttler->setLogger( $logger );
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $logger = $this->getMockBuilder( AbstractLogger::class )
+                       ->setMethods( [ 'log' ] )
+                       ->getMockForAbstractClass();
+               $logger->expects( $this->once() )->method( 'log' )->with( $this->anything(), $this->anything(), [
+                       'type' => 'custom',
+                       'index' => 0,
+                       'ip' => '1.2.3.4',
+                       'username' => 'SomeUser',
+                       'count' => 1,
+                       'expiry' => 10,
+                       'method' => 'foo',
+               ] );
+               $throttler->setLogger( $logger );
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4', 'foo' );
+               $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
+       }
+
+       public function testClear() {
+               $cache = new \HashBagOStuff();
+               $throttler = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
+               $throttler->setLogger( new NullLogger() );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
+
+               $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
+               $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
+
+               $throttler->clear( 'SomeUser', '1.2.3.4' );
+
+               $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
+               $this->assertFalse( $result, 'should not throttle' );
+
+               $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
+               $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
+       }
+}
diff --git a/tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php b/tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..7fe3351
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\UserDataAuthenticationRequest
+ */
+class UserDataAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               return new UserDataAuthenticationRequest;
+       }
+
+       protected function setUp() {
+               parent::setUp();
+               $this->setMwGlobals( 'wgHiddenPrefs', [] );
+       }
+
+       /**
+        * @dataProvider providePopulateUser
+        * @param string $email Email to set
+        * @param string $realname Realname to set
+        * @param StatusValue $expect Expected return
+        */
+       public function testPopulateUser( $email, $realname, $expect ) {
+               $user = new \User();
+               $user->setEmail( 'default@example.com' );
+               $user->setRealName( 'Fake Name' );
+
+               $req = new UserDataAuthenticationRequest;
+               $req->email = $email;
+               $req->realname = $realname;
+               $this->assertEquals( $expect, $req->populateUser( $user ) );
+               if ( $expect->isOk() ) {
+                       $this->assertSame( $email ?: 'default@example.com', $user->getEmail() );
+                       $this->assertSame( $realname ?: 'Fake Name', $user->getRealName() );
+               }
+
+       }
+
+       public static function providePopulateUser() {
+               $good = \StatusValue::newGood();
+               return [
+                       [ 'email@example.com', 'Real Name', $good ],
+                       [ 'email@example.com', '', $good ],
+                       [ '', 'Real Name', $good ],
+                       [ '', '', $good ],
+                       [ 'invalid-email', 'Real Name', \StatusValue::newFatal( 'invalidemailaddress' ) ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideLoadFromSubmission
+        */
+       public function testLoadFromSubmission(
+               array $args, array $data, $expectState /* $hiddenPref, $enableEmail */
+       ) {
+               list( $args, $data, $expectState, $hiddenPref, $enableEmail ) = func_get_args();
+               $this->setMwGlobals( 'wgHiddenPrefs', $hiddenPref );
+               $this->setMwGlobals( 'wgEnableEmail', $enableEmail );
+               parent::testLoadFromSubmission( $args, $data, $expectState );
+       }
+
+       public function provideLoadFromSubmission() {
+               $unhidden = [];
+               $hidden = [ 'realname' ];
+
+               return [
+                       'Empty request, unhidden, email enabled' => [
+                               [],
+                               [],
+                               false,
+                               $unhidden,
+                               true
+                       ],
+                       'email + realname, unhidden, email enabled' => [
+                               [],
+                               $data = [ 'email' => 'Email', 'realname' => 'Name' ],
+                               $data,
+                               $unhidden,
+                               true
+                       ],
+                       'email empty, unhidden, email enabled' => [
+                               [],
+                               $data = [ 'email' => '', 'realname' => 'Name' ],
+                               $data,
+                               $unhidden,
+                               true
+                       ],
+                       'email omitted, unhidden, email enabled' => [
+                               [],
+                               [ 'realname' => 'Name' ],
+                               false,
+                               $unhidden,
+                               true
+                       ],
+                       'realname empty, unhidden, email enabled' => [
+                               [],
+                               $data = [ 'email' => 'Email', 'realname' => '' ],
+                               $data,
+                               $unhidden,
+                               true
+                       ],
+                       'realname omitted, unhidden, email enabled' => [
+                               [],
+                               [ 'email' => 'Email' ],
+                               false,
+                               $unhidden,
+                               true
+                       ],
+                       'Empty request, hidden, email enabled' => [
+                               [],
+                               [],
+                               false,
+                               $hidden,
+                               true
+                       ],
+                       'email + realname, hidden, email enabled' => [
+                               [],
+                               [ 'email' => 'Email', 'realname' => 'Name' ],
+                               [ 'email' => 'Email' ],
+                               $hidden,
+                               true
+                       ],
+                       'email empty, hidden, email enabled' => [
+                               [],
+                               $data = [ 'email' => '', 'realname' => 'Name' ],
+                               [ 'email' => '' ],
+                               $hidden,
+                               true
+                       ],
+                       'email omitted, hidden, email enabled' => [
+                               [],
+                               [ 'realname' => 'Name' ],
+                               false,
+                               $hidden,
+                               true
+                       ],
+                       'realname empty, hidden, email enabled' => [
+                               [],
+                               $data = [ 'email' => 'Email', 'realname' => '' ],
+                               [ 'email' => 'Email' ],
+                               $hidden,
+                               true
+                       ],
+                       'realname omitted, hidden, email enabled' => [
+                               [],
+                               [ 'email' => 'Email' ],
+                               [ 'email' => 'Email' ],
+                               $hidden,
+                               true
+                       ],
+                       'email + realname, unhidden, email disabled' => [
+                               [],
+                               [ 'email' => 'Email', 'realname' => 'Name' ],
+                               [ 'realname' => 'Name' ],
+                               $unhidden,
+                               false
+                       ],
+                       'email omitted, unhidden, email disabled' => [
+                               [],
+                               [ 'realname' => 'Name' ],
+                               [ 'realname' => 'Name' ],
+                               $unhidden,
+                               false
+                       ],
+                       'email empty, unhidden, email disabled' => [
+                               [],
+                               [ 'email' => '', 'realname' => 'Name' ],
+                               [ 'realname' => 'Name' ],
+                               $unhidden,
+                               false
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/auth/UsernameAuthenticationRequestTest.php b/tests/phpunit/includes/auth/UsernameAuthenticationRequestTest.php
new file mode 100644 (file)
index 0000000..63628dd
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers MediaWiki\Auth\UsernameAuthenticationRequest
+ */
+class UsernameAuthenticationRequestTest extends AuthenticationRequestTestCase {
+
+       protected function getInstance( array $args = [] ) {
+               return new UsernameAuthenticationRequest();
+       }
+
+       public function provideLoadFromSubmission() {
+               return [
+                       'Empty request' => [
+                               [],
+                               [],
+                               false
+                       ],
+                       'Username' => [
+                               [],
+                               $data = [ 'username' => 'User' ],
+                               $data,
+                       ],
+                       'Username empty' => [
+                               [],
+                               [ 'username' => '' ],
+                               false
+                       ],
+               ];
+       }
+}
index 2efc802..45f1382 100644 (file)
@@ -161,7 +161,7 @@ class RecentChangeTest extends MediaWikiTestCase {
                $pageProps->expects( $this->once() )
                        ->method( 'getProperties' )
                        ->with( $categoryTitle, 'hiddencat' )
-                       ->will( $this->returnValue( $isHidden ? [ $categoryTitle->getArticleID() => '' ] : [ ] ) );
+                       ->will( $this->returnValue( $isHidden ? [ $categoryTitle->getArticleID() => '' ] : [] ) );
 
                $scopedOverride = PageProps::overrideInstance( $pageProps );
 
index 2288507..f42cb95 100644 (file)
@@ -8,17 +8,107 @@ class ConfigFactoryTest extends MediaWikiTestCase {
        public function testRegister() {
                $factory = new ConfigFactory();
                $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $this->assertTrue( true ); // No exception thrown
+               $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInvalid() {
+               $factory = new ConfigFactory();
                $this->setExpectedException( 'InvalidArgumentException' );
                $factory->register( 'invalid', 'Invalid callback' );
        }
 
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInstance() {
+               $config = GlobalVarConfig::newInstance();
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', $config );
+               $this->assertSame( $config, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterAgain() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $config1 = $factory->makeConfig( 'unittest' );
+
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $config2 = $factory->makeConfig( 'unittest' );
+
+               $this->assertNotSame( $config1, $config2 );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testSalvage() {
+               $oldFactory = new ConfigFactory();
+               $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' );
+
+               // instantiate two of the three defined configurations
+               $foo = $oldFactory->makeConfig( 'foo' );
+               $bar = $oldFactory->makeConfig( 'bar' );
+               $quux = $oldFactory->makeConfig( 'quux' );
+
+               // define new config instance
+               $newFactory = new ConfigFactory();
+               $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $newFactory->register( 'bar', function() {
+                       return new HashConfig();
+               } );
+
+               // "foo" and "quux" are defined in the old and the new factory.
+               // The old factory has instances for "foo" and "bar", but not "quux".
+               $newFactory->salvage( $oldFactory );
+
+               $newFoo = $newFactory->makeConfig( 'foo' );
+               $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' );
+
+               $newBar = $newFactory->makeConfig( 'bar' );
+               $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
+
+               // the new factory doesn't have quux defined, so the quux instance should not be salvaged
+               $this->setExpectedException( 'ConfigException' );
+               $newFactory->makeConfig( 'quux' );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testGetConfigNames() {
+               $factory = new ConfigFactory();
+               $factory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $factory->register( 'bar', new HashConfig() );
+
+               $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() );
+       }
+
        /**
         * @covers ConfigFactory::makeConfig
         */
        public function testMakeConfig() {
                $factory = new ConfigFactory();
                $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+
+               $conf = $factory->makeConfig( 'unittest' );
+               $this->assertInstanceOf( 'Config', $conf );
+               $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigFallback() {
+               $factory = new ConfigFactory();
+               $factory->register( '*', 'GlobalVarConfig::newInstance' );
                $conf = $factory->makeConfig( 'unittest' );
                $this->assertInstanceOf( 'Config', $conf );
        }
@@ -48,10 +138,10 @@ class ConfigFactoryTest extends MediaWikiTestCase {
         * @covers ConfigFactory::getDefaultInstance
         */
        public function testGetDefaultInstance() {
+               // NOTE: the global config factory returned here has been overwritten
+               // for operation in test mode. It may not reflect LocalSettings.
                $factory = ConfigFactory::getDefaultInstance();
                $this->assertInstanceOf( 'Config', $factory->makeConfig( 'main' ) );
-
-               $this->setExpectedException( 'ConfigException' );
-               $factory->makeConfig( 'xyzzy' );
        }
+
 }
index 91f27fb..545b964 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+use MediaWiki\MediaWikiServices;
 
 /**
  * @group ContentHandler
@@ -36,7 +37,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                MWNamespace::getCanonicalNamespaces( true );
                $wgContLang->resetNamespaces();
                // And LinkCache
-               LinkCache::destroySingleton();
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
        }
 
        protected function tearDown() {
@@ -46,7 +47,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                MWNamespace::getCanonicalNamespaces( true );
                $wgContLang->resetNamespaces();
                // And LinkCache
-               LinkCache::destroySingleton();
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
 
                parent::tearDown();
        }
index 8a48734..de8e371 100644 (file)
@@ -6,12 +6,6 @@
  */
 class JsonContentTest extends MediaWikiLangTestCase {
 
-       protected function setUp() {
-               parent::setUp();
-
-               $this->setMwGlobals( 'wgWellFormedXml', true );
-       }
-
        public static function provideValidConstruction() {
                return [
                        [ 'foo', false, null ],
index 9c2bc75..5c65483 100644 (file)
@@ -4,20 +4,20 @@ class MWDebugTest extends MediaWikiTestCase {
 
        protected function setUp() {
                parent::setUp();
-               // Make sure MWDebug class is enabled
-               static $MWDebugEnabled = false;
-               if ( !$MWDebugEnabled ) {
-                       MWDebug::init();
-                       $MWDebugEnabled = true;
-               }
                /** Clear log before each test */
                MWDebug::clearLog();
+       }
+
+       public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
+               MWDebug::init();
                MediaWiki\suppressWarnings();
        }
 
-       protected function tearDown() {
+       public static function tearDownAfterClass() {
+               parent::tearDownAfterClass();
+               MWDebug::deinit();
                MediaWiki\restoreWarnings();
-               parent::tearDown();
        }
 
        /**
diff --git a/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php b/tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php
new file mode 100644 (file)
index 0000000..db6d002
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+/**
+ * @covers MediaWiki\Interwiki\ClassicInterwikiLookup
+ *
+ * @group MediaWiki
+ * @group Database
+ */
+class ClassicInterwikiLookupTest extends MediaWikiTestCase {
+
+       private function populateDB( $iwrows ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete( 'interwiki', '*', __METHOD__ );
+               $dbw->insert( 'interwiki', array_values( $iwrows ), __METHOD__ );
+               $this->tablesUsed[] = 'interwiki';
+       }
+
+       public function testDatabaseStorage() {
+               // NOTE: database setup is expensive, so we only do
+               //  it once and run all the tests in one go.
+               $dewiki = [
+                       'iw_prefix' => 'de',
+                       'iw_url' => 'http://de.wikipedia.org/wiki/',
+                       'iw_api' => 'http://de.wikipedia.org/w/api.php',
+                       'iw_wikiid' => 'dewiki',
+                       'iw_local' => 1,
+                       'iw_trans' => 0
+               ];
+
+               $zzwiki = [
+                       'iw_prefix' => 'zz',
+                       'iw_url' => 'http://zzwiki.org/wiki/',
+                       'iw_api' => 'http://zzwiki.org/w/api.php',
+                       'iw_wikiid' => 'zzwiki',
+                       'iw_local' => 0,
+                       'iw_trans' => 0
+               ];
+
+               $this->populateDB( [ $dewiki, $zzwiki ] );
+               $lookup = new \MediaWiki\Interwiki\ClassicInterwikiLookup(
+                       Language::factory( 'en' ),
+                       WANObjectCache::newEmpty(),
+                       60*60,
+                       false,
+                       3,
+                       'en'
+               );
+
+               $this->assertEquals(
+                       [ $dewiki, $zzwiki ],
+                       $lookup->getAllPrefixes(),
+                       'getAllPrefixes()'
+               );
+               $this->assertEquals(
+                       [ $dewiki ],
+                       $lookup->getAllPrefixes( true ),
+                       'getAllPrefixes()'
+               );
+               $this->assertEquals(
+                       [ $zzwiki ],
+                       $lookup->getAllPrefixes( false ),
+                       'getAllPrefixes()'
+               );
+
+               $this->assertTrue( $lookup->isValidInterwiki( 'de' ), 'known prefix is valid' );
+               $this->assertFalse( $lookup->isValidInterwiki( 'xyz' ), 'unknown prefix is valid' );
+
+               $this->assertNull( $lookup->fetch( null ), 'no prefix' );
+               $this->assertFalse( $lookup->fetch( 'xyz' ), 'unknown prefix' );
+
+               $interwiki = $lookup->fetch( 'de' );
+               $this->assertInstanceOf( 'Interwiki', $interwiki );
+               $this->assertSame( $interwiki, $lookup->fetch( 'de' ), 'in-process caching' );
+
+               $this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
+               $this->assertSame( 'http://de.wikipedia.org/w/api.php', $interwiki->getAPI(), 'getAPI' );
+               $this->assertSame( 'dewiki', $interwiki->getWikiID(), 'getWikiID' );
+               $this->assertSame( true, $interwiki->isLocal(), 'isLocal' );
+               $this->assertSame( false, $interwiki->isTranscludable(), 'isTranscludable' );
+
+               $lookup->invalidateCache( 'de' );
+               $this->assertNotSame( $interwiki, $lookup->fetch( 'de' ), 'invalidate cache' );
+       }
+
+       /**
+        * @param string $thisSite
+        * @param string[] $local
+        * @param string[] $global
+        *
+        * @return string[]
+        */
+       private function populateHash( $thisSite, $local, $global ) {
+               $hash = [];
+               $hash[ '__sites:' . wfWikiID() ] = $thisSite;
+
+               $globals = [];
+               $locals = [];
+
+               foreach ( $local as $row ) {
+                       $prefix = $row['iw_prefix'];
+                       $data = $row['iw_local'] . ' ' . $row['iw_url'];
+                       $locals[] = $prefix;
+                       $hash[ "_{$thisSite}:{$prefix}" ] = $data;
+               }
+
+               foreach ( $global as $row ) {
+                       $prefix = $row['iw_prefix'];
+                       $data = $row['iw_local'] . ' ' . $row['iw_url'];
+                       $globals[] = $prefix;
+                       $hash[ "__global:{$prefix}" ] = $data;
+               }
+
+               $hash[ '__list:__global' ] = implode( ' ', $globals );
+               $hash[ '__list:_' . $thisSite ] = implode( ' ', $locals );
+
+               return $hash;
+       }
+
+       private function populateCDB( $thisSite, $local, $global ) {
+               $cdbFile = tempnam( wfTempDir(), 'MW-ClassicInterwikiLookupTest-' ) . '.cdb';
+               $cdb = \Cdb\Writer::open( $cdbFile );
+
+               $hash = $this->populateHash( $thisSite, $local, $global );
+
+               foreach ( $hash as $key => $value ) {
+                       $cdb->set( $key, $value );
+               }
+
+               $cdb->close();
+               return $cdbFile;
+       }
+
+       public function testCDBStorage() {
+               // NOTE: CDB setup is expensive, so we only do
+               //  it once and run all the tests in one go.
+
+               $dewiki = [
+                       'iw_prefix' => 'de',
+                       'iw_url' => 'http://de.wikipedia.org/wiki/',
+                       'iw_local' => 1
+               ];
+
+               $zzwiki = [
+                       'iw_prefix' => 'zz',
+                       'iw_url' => 'http://zzwiki.org/wiki/',
+                       'iw_local' => 0
+               ];
+
+               $cdbFile = $this->populateCDB(
+                       'en',
+                       [ $dewiki ],
+                       [ $zzwiki ]
+               );
+               $lookup = new \MediaWiki\Interwiki\ClassicInterwikiLookup(
+                       Language::factory( 'en' ),
+                       WANObjectCache::newEmpty(),
+                       60*60,
+                       $cdbFile,
+                       3,
+                       'en'
+               );
+
+               $this->assertEquals(
+                       [ $dewiki, $zzwiki ],
+                       $lookup->getAllPrefixes(),
+                       'getAllPrefixes()'
+               );
+
+               $this->assertTrue( $lookup->isValidInterwiki( 'de' ), 'known prefix is valid' );
+               $this->assertTrue( $lookup->isValidInterwiki( 'zz' ), 'known prefix is valid' );
+
+               $interwiki = $lookup->fetch( 'de' );
+               $this->assertInstanceOf( 'Interwiki', $interwiki );
+
+               $this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
+               $this->assertSame( true, $interwiki->isLocal(), 'isLocal' );
+
+               $interwiki = $lookup->fetch( 'zz' );
+               $this->assertInstanceOf( 'Interwiki', $interwiki );
+
+               $this->assertSame( 'http://zzwiki.org/wiki/', $interwiki->getURL(), 'getURL' );
+               $this->assertSame( false, $interwiki->isLocal(), 'isLocal' );
+
+               // cleanup temp file
+               unlink( $cdbFile );
+       }
+
+       public function testArrayStorage() {
+               $dewiki = [
+                       'iw_prefix' => 'de',
+                       'iw_url' => 'http://de.wikipedia.org/wiki/',
+                       'iw_local' => 1
+               ];
+
+               $zzwiki = [
+                       'iw_prefix' => 'zz',
+                       'iw_url' => 'http://zzwiki.org/wiki/',
+                       'iw_local' => 0
+               ];
+
+               $hash = $this->populateHash(
+                       'en',
+                       [ $dewiki ],
+                       [ $zzwiki ]
+               );
+               $lookup = new \MediaWiki\Interwiki\ClassicInterwikiLookup(
+                       Language::factory( 'en' ),
+                       WANObjectCache::newEmpty(),
+                       60*60,
+                       $hash,
+                       3,
+                       'en'
+               );
+
+               $this->assertEquals(
+                       [ $dewiki, $zzwiki ],
+                       $lookup->getAllPrefixes(),
+                       'getAllPrefixes()'
+               );
+
+               $this->assertTrue( $lookup->isValidInterwiki( 'de' ), 'known prefix is valid' );
+               $this->assertTrue( $lookup->isValidInterwiki( 'zz' ), 'known prefix is valid' );
+
+               $interwiki = $lookup->fetch( 'de' );
+               $this->assertInstanceOf( 'Interwiki', $interwiki );
+
+               $this->assertSame( 'http://de.wikipedia.org/wiki/', $interwiki->getURL(), 'getURL' );
+               $this->assertSame( true, $interwiki->isLocal(), 'isLocal' );
+
+               $interwiki = $lookup->fetch( 'zz' );
+               $this->assertInstanceOf( 'Interwiki', $interwiki );
+
+               $this->assertSame( 'http://zzwiki.org/wiki/', $interwiki->getURL(), 'getURL' );
+               $this->assertSame( false, $interwiki->isLocal(), 'isLocal' );
+       }
+
+}
index 411d6a3..137dfb7 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * @covers Interwiki
  *
@@ -47,7 +49,15 @@ class InterwikiTest extends MediaWikiTestCase {
                $this->tablesUsed[] = 'interwiki';
        }
 
+       private function setWgInterwikiCache( $interwikiCache ) {
+               $this->overrideMwServices();
+               MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' );
+               $this->setMwGlobals( 'wgInterwikiCache', $interwikiCache );
+       }
+
        public function testDatabaseStorage() {
+               $this->markTestSkipped( 'Needs I37b8e8018b3 <https://gerrit.wikimedia.org/r/#/c/270555/>' );
+
                // NOTE: database setup is expensive, so we only do
                //  it once and run all the tests in one go.
                $dewiki = [
@@ -70,8 +80,7 @@ class InterwikiTest extends MediaWikiTestCase {
 
                $this->populateDB( [ $dewiki, $zzwiki ] );
 
-               Interwiki::resetLocalCache();
-               $this->setMwGlobals( 'wgInterwikiCache', false );
+               $this->setWgInterwikiCache( false );
 
                $this->assertEquals(
                        [ $dewiki, $zzwiki ],
@@ -179,8 +188,7 @@ class InterwikiTest extends MediaWikiTestCase {
                        [ $zzwiki ]
                );
 
-               Interwiki::resetLocalCache();
-               $this->setMwGlobals( 'wgInterwikiCache', $cdbFile );
+               $this->setWgInterwikiCache( $cdbFile );
 
                $this->assertEquals(
                        [ $dewiki, $zzwiki ],
@@ -226,8 +234,7 @@ class InterwikiTest extends MediaWikiTestCase {
                        [ $zzwiki ]
                );
 
-               Interwiki::resetLocalCache();
-               $this->setMwGlobals( 'wgInterwikiCache', $cdbData );
+               $this->setWgInterwikiCache( $cdbData );
 
                $this->assertEquals(
                        [ $dewiki, $zzwiki ],
diff --git a/tests/phpunit/includes/libs/XhprofDataTest.php b/tests/phpunit/includes/libs/XhprofDataTest.php
new file mode 100644 (file)
index 0000000..a0fb563
--- /dev/null
@@ -0,0 +1,277 @@
+<?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
+ */
+
+/**
+ * @uses XhprofData
+ * @uses AutoLoader
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ * @since 1.25
+ */
+class XhprofDataTest extends PHPUnit_Framework_TestCase {
+
+       /**
+        * @covers XhprofData::splitKey
+        * @dataProvider provideSplitKey
+        */
+       public function testSplitKey( $key, $expect ) {
+               $this->assertSame( $expect, XhprofData::splitKey( $key ) );
+       }
+
+       public function provideSplitKey() {
+               return [
+                       [ 'main()', [ null, 'main()' ] ],
+                       [ 'foo==>bar', [ 'foo', 'bar' ] ],
+                       [ 'bar@1==>bar@2', [ 'bar@1', 'bar@2' ] ],
+                       [ 'foo==>bar==>baz', [ 'foo', 'bar==>baz' ] ],
+                       [ '==>bar', [ '', 'bar' ] ],
+                       [ '', [ null, '' ] ],
+               ];
+       }
+
+       /**
+        * @covers XhprofData::pruneData
+        */
+       public function testInclude() {
+               $xhprofData = $this->getXhprofDataFixture( [
+                       'include' => [ 'main()' ],
+               ] );
+               $raw = $xhprofData->getRawData();
+               $this->assertArrayHasKey( 'main()', $raw );
+               $this->assertArrayHasKey( 'main()==>foo', $raw );
+               $this->assertArrayHasKey( 'main()==>xhprof_disable', $raw );
+               $this->assertSame( 3, count( $raw ) );
+       }
+
+       /**
+        * Validate the structure of data returned by
+        * Xhprof::getInclusiveMetrics(). This acts as a guard against unexpected
+        * structural changes to the returned data in lieu of using a more heavy
+        * weight typed response object.
+        *
+        * @covers XhprofData::getInclusiveMetrics
+        */
+       public function testInclusiveMetricsStructure() {
+               $metricStruct = [
+                       'ct' => 'int',
+                       'wt' => 'array',
+                       'cpu' => 'array',
+                       'mu' => 'array',
+                       'pmu' => 'array',
+               ];
+               $statStruct = [
+                       'total' => 'numeric',
+                       'min' => 'numeric',
+                       'mean' => 'numeric',
+                       'max' => 'numeric',
+                       'variance' => 'numeric',
+                       'percent' => 'numeric',
+               ];
+
+               $xhprofData = $this->getXhprofDataFixture();
+               $metrics = $xhprofData->getInclusiveMetrics();
+
+               foreach ( $metrics as $name => $metric ) {
+                       $this->assertArrayStructure( $metricStruct, $metric );
+
+                       foreach ( $metricStruct as $key => $type ) {
+                               if ( $type === 'array' ) {
+                                       $this->assertArrayStructure( $statStruct, $metric[$key] );
+                                       if ( $name === 'main()' ) {
+                                               $this->assertEquals( 100, $metric[$key]['percent'] );
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Validate the structure of data returned by
+        * Xhprof::getCompleteMetrics(). This acts as a guard against unexpected
+        * structural changes to the returned data in lieu of using a more heavy
+        * weight typed response object.
+        *
+        * @covers XhprofData::getCompleteMetrics
+        */
+       public function testCompleteMetricsStructure() {
+               $metricStruct = [
+                       'ct' => 'int',
+                       'wt' => 'array',
+                       'cpu' => 'array',
+                       'mu' => 'array',
+                       'pmu' => 'array',
+                       'calls' => 'array',
+                       'subcalls' => 'array',
+               ];
+               $statsMetrics = [ 'wt', 'cpu', 'mu', 'pmu' ];
+               $statStruct = [
+                       'total' => 'numeric',
+                       'min' => 'numeric',
+                       'mean' => 'numeric',
+                       'max' => 'numeric',
+                       'variance' => 'numeric',
+                       'percent' => 'numeric',
+                       'exclusive' => 'numeric',
+               ];
+
+               $xhprofData = $this->getXhprofDataFixture();
+               $metrics = $xhprofData->getCompleteMetrics();
+
+               foreach ( $metrics as $name => $metric ) {
+                       $this->assertArrayStructure( $metricStruct, $metric, $name );
+
+                       foreach ( $metricStruct as $key => $type ) {
+                               if ( in_array( $key, $statsMetrics ) ) {
+                                       $this->assertArrayStructure(
+                                               $statStruct, $metric[$key], $key
+                                       );
+                                       $this->assertLessThanOrEqual(
+                                               $metric[$key]['total'], $metric[$key]['exclusive']
+                                       );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @covers XhprofData::getCallers
+        * @covers XhprofData::getCallees
+        * @uses XhprofData
+        */
+       public function testEdges() {
+               $xhprofData = $this->getXhprofDataFixture();
+               $this->assertSame( [], $xhprofData->getCallers( 'main()' ) );
+               $this->assertSame( [ 'foo', 'xhprof_disable' ],
+                       $xhprofData->getCallees( 'main()' )
+               );
+               $this->assertSame( [ 'main()' ],
+                       $xhprofData->getCallers( 'foo' )
+               );
+               $this->assertSame( [], $xhprofData->getCallees( 'strlen' ) );
+       }
+
+       /**
+        * @covers XhprofData::getCriticalPath
+        * @uses XhprofData
+        */
+       public function testCriticalPath() {
+               $xhprofData = $this->getXhprofDataFixture();
+               $path = $xhprofData->getCriticalPath();
+
+               $last = null;
+               foreach ( $path as $key => $value ) {
+                       list( $func, $call ) = XhprofData::splitKey( $key );
+                       $this->assertSame( $last, $func );
+                       $last = $call;
+               }
+               $this->assertSame( $last, 'bar@1' );
+       }
+
+       /**
+        * Get an Xhprof instance that has been primed with a set of known testing
+        * data. Tests for the Xhprof class should laregly be concerned with
+        * evaluating the manipulations of the data collected by xhprof rather
+        * than the data collection process itself.
+        *
+        * The returned Xhprof instance primed will be with a data set created by
+        * running this trivial program using the PECL xhprof implementation:
+        * @code
+        * function bar( $x ) {
+        *   if ( $x > 0 ) {
+        *     bar($x - 1);
+        *   }
+        * }
+        * function foo() {
+        *   for ( $idx = 0; $idx < 2; $idx++ ) {
+        *     bar( $idx );
+        *     $x = strlen( 'abc' );
+        *   }
+        * }
+        * xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY );
+        * foo();
+        * $x = xhprof_disable();
+        * var_export( $x );
+        * @endcode
+        *
+        * @return Xhprof
+        */
+       protected function getXhprofDataFixture( array $opts = [] ) {
+               return new XhprofData( [
+                       'foo==>bar' => [
+                               'ct' => 2,
+                               'wt' => 57,
+                               'cpu' => 92,
+                               'mu' => 1896,
+                               'pmu' => 0,
+                       ],
+                       'foo==>strlen' => [
+                               'ct' => 2,
+                               'wt' => 21,
+                               'cpu' => 141,
+                               'mu' => 752,
+                               'pmu' => 0,
+                       ],
+                       'bar==>bar@1' => [
+                               'ct' => 1,
+                               'wt' => 18,
+                               'cpu' => 19,
+                               'mu' => 752,
+                               'pmu' => 0,
+                       ],
+                       'main()==>foo' => [
+                               'ct' => 1,
+                               'wt' => 304,
+                               'cpu' => 307,
+                               'mu' => 4008,
+                               'pmu' => 0,
+                       ],
+                       'main()==>xhprof_disable' => [
+                               'ct' => 1,
+                               'wt' => 8,
+                               'cpu' => 10,
+                               'mu' => 768,
+                               'pmu' => 392,
+                       ],
+                       'main()' => [
+                               'ct' => 1,
+                               'wt' => 353,
+                               'cpu' => 351,
+                               'mu' => 6112,
+                               'pmu' => 1424,
+                       ],
+               ], $opts );
+       }
+
+       /**
+        * Assert that the given array has the described structure.
+        *
+        * @param array $struct Array of key => type mappings
+        * @param array $actual Array to check
+        * @param string $label
+        */
+       protected function assertArrayStructure( $struct, $actual, $label = null ) {
+               $this->assertInternalType( 'array', $actual, $label );
+               $this->assertCount( count( $struct ), $actual, $label );
+               foreach ( $struct as $key => $type ) {
+                       $this->assertArrayHasKey( $key, $actual );
+                       $this->assertInternalType( $type, $actual[$key] );
+               }
+       }
+}
index 22925bf..6748115 100644 (file)
  * @file
  */
 
-/**
- * @uses Xhprof
- * @uses AutoLoader
- * @author Bryan Davis <bd808@wikimedia.org>
- * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
- * @since 1.25
- */
 class XhprofTest extends PHPUnit_Framework_TestCase {
-
-       public function setUp() {
-               if ( !function_exists( 'xhprof_enable' ) ) {
-                       $this->markTestSkipped( 'No xhprof support detected.' );
-               }
-       }
-
-       /**
-        * @covers Xhprof::splitKey
-        * @dataProvider provideSplitKey
-        */
-       public function testSplitKey( $key, $expect ) {
-               $this->assertSame( $expect, Xhprof::splitKey( $key ) );
-       }
-
-       public function provideSplitKey() {
-               return [
-                       [ 'main()', [ null, 'main()' ] ],
-                       [ 'foo==>bar', [ 'foo', 'bar' ] ],
-                       [ 'bar@1==>bar@2', [ 'bar@1', 'bar@2' ] ],
-                       [ 'foo==>bar==>baz', [ 'foo', 'bar==>baz' ] ],
-                       [ '==>bar', [ '', 'bar' ] ],
-                       [ '', [ null, '' ] ],
-               ];
-       }
-
-       /**
-        * @covers Xhprof::__construct
-        * @covers Xhprof::stop
-        * @covers Xhprof::getRawData
-        * @dataProvider provideRawData
-        */
-       public function testRawData( $flags, $keys ) {
-               $xhprof = new Xhprof( [ 'flags' => $flags ] );
-               $raw = $xhprof->getRawData();
-               $this->assertArrayHasKey( 'main()', $raw );
-               foreach ( $keys as $key ) {
-                       $this->assertArrayHasKey( $key, $raw['main()'] );
-               }
-       }
-
-       public function provideRawData() {
-               $tests = [
-                       [ 0, [ 'ct', 'wt' ] ],
-               ];
-
-               if ( defined( 'XHPROF_FLAGS_CPU' ) && defined( 'XHPROF_FLAGS_CPU' ) ) {
-                       $tests[] = [ XHPROF_FLAGS_MEMORY, [
-                               'ct', 'wt', 'mu', 'pmu',
-                       ] ];
-                       $tests[] = [ XHPROF_FLAGS_CPU, [
-                               'ct', 'wt', 'cpu',
-                       ] ];
-                       $tests[] = [ XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU, [
-                                       'ct', 'wt', 'mu', 'pmu', 'cpu',
-                               ] ];
-               }
-
-               return $tests;
-       }
-
-       /**
-        * @covers Xhprof::pruneData
-        */
-       public function testInclude() {
-               $xhprof = $this->getXhprofFixture( [
-                       'include' => [ 'main()' ],
-               ] );
-               $raw = $xhprof->getRawData();
-               $this->assertArrayHasKey( 'main()', $raw );
-               $this->assertArrayHasKey( 'main()==>foo', $raw );
-               $this->assertArrayHasKey( 'main()==>xhprof_disable', $raw );
-               $this->assertSame( 3, count( $raw ) );
-       }
-
        /**
-        * Validate the structure of data returned by
-        * Xhprof::getInclusiveMetrics(). This acts as a guard against unexpected
-        * structural changes to the returned data in lieu of using a more heavy
-        * weight typed response object.
+        * Trying to enable Xhprof when it is already enabled causes an exception
+        * to be thrown.
         *
-        * @covers Xhprof::getInclusiveMetrics
-        */
-       public function testInclusiveMetricsStructure() {
-               $metricStruct = [
-                       'ct' => 'int',
-                       'wt' => 'array',
-                       'cpu' => 'array',
-                       'mu' => 'array',
-                       'pmu' => 'array',
-               ];
-               $statStruct = [
-                       'total' => 'numeric',
-                       'min' => 'numeric',
-                       'mean' => 'numeric',
-                       'max' => 'numeric',
-                       'variance' => 'numeric',
-                       'percent' => 'numeric',
-               ];
-
-               $xhprof = $this->getXhprofFixture();
-               $metrics = $xhprof->getInclusiveMetrics();
-
-               foreach ( $metrics as $name => $metric ) {
-                       $this->assertArrayStructure( $metricStruct, $metric );
-
-                       foreach ( $metricStruct as $key => $type ) {
-                               if ( $type === 'array' ) {
-                                       $this->assertArrayStructure( $statStruct, $metric[$key] );
-                                       if ( $name === 'main()' ) {
-                                               $this->assertEquals( 100, $metric[$key]['percent'] );
-                                       }
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Validate the structure of data returned by
-        * Xhprof::getCompleteMetrics(). This acts as a guard against unexpected
-        * structural changes to the returned data in lieu of using a more heavy
-        * weight typed response object.
-        *
-        * @covers Xhprof::getCompleteMetrics
-        */
-       public function testCompleteMetricsStructure() {
-               $metricStruct = [
-                       'ct' => 'int',
-                       'wt' => 'array',
-                       'cpu' => 'array',
-                       'mu' => 'array',
-                       'pmu' => 'array',
-                       'calls' => 'array',
-                       'subcalls' => 'array',
-               ];
-               $statsMetrics = [ 'wt', 'cpu', 'mu', 'pmu' ];
-               $statStruct = [
-                       'total' => 'numeric',
-                       'min' => 'numeric',
-                       'mean' => 'numeric',
-                       'max' => 'numeric',
-                       'variance' => 'numeric',
-                       'percent' => 'numeric',
-                       'exclusive' => 'numeric',
-               ];
-
-               $xhprof = $this->getXhprofFixture();
-               $metrics = $xhprof->getCompleteMetrics();
-
-               foreach ( $metrics as $name => $metric ) {
-                       $this->assertArrayStructure( $metricStruct, $metric, $name );
-
-                       foreach ( $metricStruct as $key => $type ) {
-                               if ( in_array( $key, $statsMetrics ) ) {
-                                       $this->assertArrayStructure(
-                                               $statStruct, $metric[$key], $key
-                                       );
-                                       $this->assertLessThanOrEqual(
-                                               $metric[$key]['total'], $metric[$key]['exclusive']
-                                       );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * @covers Xhprof::getCallers
-        * @covers Xhprof::getCallees
-        * @uses Xhprof
-        */
-       public function testEdges() {
-               $xhprof = $this->getXhprofFixture();
-               $this->assertSame( [], $xhprof->getCallers( 'main()' ) );
-               $this->assertSame( [ 'foo', 'xhprof_disable' ],
-                       $xhprof->getCallees( 'main()' )
-               );
-               $this->assertSame( [ 'main()' ],
-                       $xhprof->getCallers( 'foo' )
-               );
-               $this->assertSame( [], $xhprof->getCallees( 'strlen' ) );
-       }
-
-       /**
-        * @covers Xhprof::getCriticalPath
-        * @uses Xhprof
-        */
-       public function testCriticalPath() {
-               $xhprof = $this->getXhprofFixture();
-               $path = $xhprof->getCriticalPath();
-
-               $last = null;
-               foreach ( $path as $key => $value ) {
-                       list( $func, $call ) = Xhprof::splitKey( $key );
-                       $this->assertSame( $last, $func );
-                       $last = $call;
-               }
-               $this->assertSame( $last, 'bar@1' );
-       }
-
-       /**
-        * Get an Xhprof instance that has been primed with a set of known testing
-        * data. Tests for the Xhprof class should laregly be concerned with
-        * evaluating the manipulations of the data collected by xhprof rather
-        * than the data collection process itself.
-        *
-        * The returned Xhprof instance primed will be with a data set created by
-        * running this trivial program using the PECL xhprof implementation:
-        * @code
-        * function bar( $x ) {
-        *   if ( $x > 0 ) {
-        *     bar($x - 1);
-        *   }
-        * }
-        * function foo() {
-        *   for ( $idx = 0; $idx < 2; $idx++ ) {
-        *     bar( $idx );
-        *     $x = strlen( 'abc' );
-        *   }
-        * }
-        * xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY );
-        * foo();
-        * $x = xhprof_disable();
-        * var_export( $x );
-        * @endcode
-        *
-        * @return Xhprof
-        */
-       protected function getXhprofFixture( array $opts = [] ) {
-               $xhprof = new Xhprof( $opts );
-               $xhprof->loadRawData( [
-                       'foo==>bar' => [
-                               'ct' => 2,
-                               'wt' => 57,
-                               'cpu' => 92,
-                               'mu' => 1896,
-                               'pmu' => 0,
-                       ],
-                       'foo==>strlen' => [
-                               'ct' => 2,
-                               'wt' => 21,
-                               'cpu' => 141,
-                               'mu' => 752,
-                               'pmu' => 0,
-                       ],
-                       'bar==>bar@1' => [
-                               'ct' => 1,
-                               'wt' => 18,
-                               'cpu' => 19,
-                               'mu' => 752,
-                               'pmu' => 0,
-                       ],
-                       'main()==>foo' => [
-                               'ct' => 1,
-                               'wt' => 304,
-                               'cpu' => 307,
-                               'mu' => 4008,
-                               'pmu' => 0,
-                       ],
-                       'main()==>xhprof_disable' => [
-                               'ct' => 1,
-                               'wt' => 8,
-                               'cpu' => 10,
-                               'mu' => 768,
-                               'pmu' => 392,
-                       ],
-                       'main()' => [
-                               'ct' => 1,
-                               'wt' => 353,
-                               'cpu' => 351,
-                               'mu' => 6112,
-                               'pmu' => 1424,
-                       ],
-               ] );
-               return $xhprof;
-       }
-
-       /**
-        * Assert that the given array has the described structure.
-        *
-        * @param array $struct Array of key => type mappings
-        * @param array $actual Array to check
-        * @param string $label
-        */
-       protected function assertArrayStructure( $struct, $actual, $label = null ) {
-               $this->assertInternalType( 'array', $actual, $label );
-               $this->assertCount( count( $struct ), $actual, $label );
-               foreach ( $struct as $key => $type ) {
-                       $this->assertArrayHasKey( $key, $actual );
-                       $this->assertInternalType( $type, $actual[$key] );
-               }
+        * @expectedException        Exception
+        * @expectedExceptionMessage already enabled
+        * @covers Xhprof::enable
+        */
+       public function testEnable() {
+               $xhprof = new ReflectionClass( 'Xhprof' );
+               $enabled = $xhprof->getProperty( 'enabled' );
+               $enabled->setAccessible( true );
+               $enabled->setValue( true );
+               $xhprof->getMethod( 'enable' )->invoke( null );
        }
 }
index f24b68b..aaa3ac4 100644 (file)
@@ -150,8 +150,8 @@ class GIFHandlerTest extends MediaWikiMediaTestCase {
        }
 
        /**
-        * @param $filename string
-        * @param $expectedLength float
+        * @param string $filename
+        * @param float $expectedLength
         * @dataProvider provideGetLength
         */
        public function testGetLength( $filename, $expectedLength ) {
index 1eddaff..7a052f6 100644 (file)
@@ -53,9 +53,9 @@ class MediaHandlerTest extends MediaWikiTestCase {
         * out of parameters:
         * $width, $height, { $max => $expected, $max2 => $expected2, ... }
         *
-        * @param $width int
-        * @param $height int
-        * @param $tests array associative array of $max => $expected values
+        * @param int $width
+        * @param int $height
+        * @param array $tests associative array of $max => $expected values
         * @return array
         */
        private static function generateTestFitBoxWidthData( $width, $height, $tests ) {
index afc338e..32d54df 100644 (file)
@@ -139,8 +139,8 @@ class PNGHandlerTest extends MediaWikiMediaTestCase {
        }
 
        /**
-        * @param $filename string
-        * @param $expectedLength float
+        * @param string $filename
+        * @param float $expectedLength
         * @dataProvider provideGetLength
         */
        public function testGetLength( $filename, $expectedLength ) {
diff --git a/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php b/tests/phpunit/includes/objectcache/RedisBagOStuffTest.php
new file mode 100644 (file)
index 0000000..cf87a98
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * @group BagOStuff
+ */
+class RedisBagOStuffTest extends MediaWikiTestCase {
+       /** @var RedisBagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->cache = TestingAccessWrapper::newFromObject( new RedisBagOStuff( [ 'servers' => [] ] ) );
+       }
+
+       /**
+        * @covers RedisBagOStuff::unserialize
+        * @dataProvider unserializeProvider
+        */
+       public function testUnserialize( $expected, $input, $message ) {
+               $actual = $this->cache->unserialize( $input );
+               $this->assertSame( $expected, $actual, $message );
+       }
+
+       public function unserializeProvider() {
+               return [
+                       [
+                               -1,
+                               '-1',
+                               'String representation of \'-1\'',
+                       ],
+                       [
+                               0,
+                               '0',
+                               'String representation of \'0\'',
+                       ],
+                       [
+                               1,
+                               '1',
+                               'String representation of \'1\'',
+                       ],
+                       [
+                               -1.0,
+                               'd:-1;',
+                               'Serialized negative double',
+                       ],
+                       [
+                               'foo',
+                               's:3:"foo";',
+                               'Serialized string',
+                       ]
+               ];
+       }
+
+       /**
+        * @covers RedisBagOStuff::serialize
+        * @dataProvider serializeProvider
+        */
+       public function testSerialize( $expected, $input, $message ) {
+               $actual = $this->cache->serialize( $input );
+               $this->assertSame( $expected, $actual, $message );
+       }
+
+       public function serializeProvider() {
+               return [
+                       [
+                               -1,
+                               -1,
+                               '-1 as integer',
+                       ],
+                       [
+                               0,
+                               0,
+                               '0 as integer',
+                       ],
+                       [
+                               1,
+                               1,
+                               '1 as integer',
+                       ],
+                       [
+                               'd:-1;',
+                               -1.0,
+                               'Negative double',
+                       ],
+                       [
+                               's:3:"2.1";',
+                               '2.1',
+                               'Decimal string',
+                       ],
+                       [
+                               's:1:"1";',
+                               '1',
+                               'String representation of 1',
+                       ],
+                       [
+                               's:3:"foo";',
+                               'foo',
+                               'String',
+                       ],
+               ];
+       }
+}
index d6e1d9e..9f4e1fa 100644 (file)
@@ -22,7 +22,7 @@ class WikiCategoryPageTest extends MediaWikiLangTestCase {
                $pageProps->expects( $this->once() )
                        ->method( 'getProperties' )
                        ->with( $title, 'hiddencat' )
-                       ->will( $this->returnValue( [ ] ) );
+                       ->will( $this->returnValue( [] ) );
 
                $scopedOverride = PageProps::overrideInstance( $pageProps );
 
@@ -50,7 +50,7 @@ class WikiCategoryPageTest extends MediaWikiLangTestCase {
                $pageProps->expects( $this->once() )
                        ->method( 'getProperties' )
                        ->with( $categoryTitle, 'hiddencat' )
-                       ->will( $this->returnValue( $isHidden ? [ $categoryTitle->getArticleID() => '' ] : [ ] ) );
+                       ->will( $this->returnValue( $isHidden ? [ $categoryTitle->getArticleID() => '' ] : [] ) );
 
                $scopedOverride = PageProps::overrideInstance( $pageProps );
 
index 22bb237..c024555 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * Although marked as a stub, can work independently.
  *
@@ -101,7 +100,6 @@ class NewParserTest extends MediaWikiTestCase {
                $tmpGlobals['wgUseImageResize'] = true;
                $tmpGlobals['wgAllowExternalImages'] = true;
                $tmpGlobals['wgRawHtml'] = false;
-               $tmpGlobals['wgWellFormedXml'] = true;
                $tmpGlobals['wgExperimentalHtmlIds'] = false;
                $tmpGlobals['wgAdaptiveMessageCache'] = true;
                $tmpGlobals['wgUseDatabaseMessages'] = true;
@@ -151,7 +149,10 @@ class NewParserTest extends MediaWikiTestCase {
                $tmpGlobals['wgHooks'] = $tmpHooks;
                # add a namespace shadowing a interwiki link, to test
                # proper precedence when resolving links. (bug 51680)
-               $tmpGlobals['wgExtraNamespaces'] = [ 100 => 'MemoryAlpha' ];
+               $tmpGlobals['wgExtraNamespaces'] = [
+                       100 => 'MemoryAlpha',
+                       101 => 'MemoryAlpha_talk'
+               ];
 
                $tmpGlobals['wgLocalInterwikis'] = [ 'local', 'mi' ];
                # "extra language links"
@@ -179,6 +180,7 @@ class NewParserTest extends MediaWikiTestCase {
 
                MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
                $wgContLang->resetNamespaces(); # reset namespace cache
+               ParserTest::resetTitleServices();
        }
 
        protected function tearDown() {
@@ -433,7 +435,6 @@ class NewParserTest extends MediaWikiTestCase {
                        'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
                        'wgMaxTocLevel' => $maxtoclevel,
                        'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ),
-                       'wgWellFormedXml' => true,
                        'wgMathDirectory' => $uploadDir . '/math',
                        'wgDefaultLanguageVariant' => $variant,
                        'wgLinkHolderBatchSize' => $linkHolderBatchSize,
index 15b47b4..d57ad04 100644 (file)
@@ -56,17 +56,26 @@ class PoolCounterTest extends MediaWikiTestCase {
 
                $keysWithTwoSlots = $keysWithFiveSlots = [];
                foreach ( range( 1, 100 ) as $i ) {
-                       $keysWithTwoSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'key ' . $i, 2 );
-                       $keysWithFiveSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'key ' . $i, 5 );
+                       $keysWithTwoSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'key ' . $i, 2 );
+                       $keysWithFiveSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'key ' . $i, 5 );
                }
 
-               $this->assertArrayEquals( range( 0, 1 ), array_unique( $keysWithTwoSlots ) );
-               $this->assertArrayEquals( range( 0, 4 ), array_unique( $keysWithFiveSlots ) );
+               $twoSlotKeys = [];
+               for ( $i = 0; $i <= 1; $i++ ) {
+                       $twoSlotKeys[] = "test:$i";
+               }
+               $fiveSlotKeys = [];
+               for ( $i = 0; $i <= 4; $i++ ) {
+                       $fiveSlotKeys[] = "test:$i";
+               }
+
+               $this->assertArrayEquals( $twoSlotKeys, array_unique( $keysWithTwoSlots ) );
+               $this->assertArrayEquals( $fiveSlotKeys, array_unique( $keysWithFiveSlots ) );
 
                // make sure it is deterministic
                $this->assertEquals(
-                       $hashKeyIntoSlots->invoke( $poolCounter, 'asdfgh', 1000 ),
-                       $hashKeyIntoSlots->invoke( $poolCounter, 'asdfgh', 1000 )
+                       $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'asdfgh', 1000 ),
+                       $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'asdfgh', 1000 )
                );
        }
 }
index 27c0c60..0120d79 100644 (file)
@@ -50,7 +50,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
                                self::$default,
                                $merge,
                        ],
-                       // No current hooks, adding one for "FooBaz"
+                       // No current hooks, adding one for "FooBaz" in string format
                        [
                                [],
                                [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
@@ -62,6 +62,12 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
                                [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
                                [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
                        ],
+                       // No current hooks, adding one for "FooBaz" in verbose array format
+                       [
+                               [],
+                               [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
+                               [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
+                       ],
                        // Hook for "BarBaz", adding one for "FooBaz"
                        [
                                [ 'BarBaz' => [ 'BarBazCallback' ] ],
index 9ed5244..e0de588 100644 (file)
@@ -6,6 +6,7 @@ use MediaWiki\MediaWikiServices;
  * @group Database
  */
 class SearchEnginePrefixTest extends MediaWikiLangTestCase {
+       private $originalHandlers;
 
        /**
         * @var SearchEngine
@@ -47,9 +48,26 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
                }
 
                // Avoid special pages from extensions interferring with the tests
-               $this->setMwGlobals( 'wgSpecialPages', [] );
+               $this->setMwGlobals( [
+                       'wgSpecialPages' => [],
+                       'wgHooks' => [],
+               ] );
+
                $this->search = MediaWikiServices::getInstance()->newSearchEngine();
                $this->search->setNamespaces( [] );
+
+               $this->originalHandlers = TestingAccessWrapper::newFromClass( 'Hooks' )->handlers;
+               TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = [];
+
+               SpecialPageFactory::resetList();
+       }
+
+       public function tearDown() {
+               parent::tearDown();
+
+               TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = $this->originalHandlers;
+
+               SpecialPageFactory::resetList();
        }
 
        protected function searchProvision( array $results = null ) {
index edab0dc..d4b1587 100644 (file)
@@ -65,9 +65,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
        public function addDBDataOnce() {
                $passwordFactory = new \PasswordFactory();
                $passwordFactory->init( \RequestContext::getMain()->getConfig() );
-               // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordFactory->setDefaultType( 'A' );
-               $pwhash = $passwordFactory->newFromPlaintext( 'foobaz' );
+               $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
 
                $userId = \CentralIdLookup::factory( 'local' )->centralIdFromName( 'UTSysop' );
 
@@ -82,7 +80,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
                        [
                                'bp_user' => $userId,
                                'bp_app_id' => 'BotPasswordSessionProvider',
-                               'bp_password' => $pwhash->toString(),
+                               'bp_password' => $passwordHash->toString(),
                                'bp_token' => 'token!',
                                'bp_restrictions' => '{"IPAddresses":["127.0.0.0/8"]}',
                                'bp_grants' => '["test"]',
index 70e89d4..9600184 100644 (file)
@@ -14,7 +14,6 @@ use Psr\Log\LogLevel;
 class CookieSessionProviderTest extends MediaWikiTestCase {
 
        private function getConfig() {
-               global $wgCookieExpiration;
                return new \HashConfig( [
                        'CookiePrefix' => 'CookiePrefix',
                        'CookiePath' => 'CookiePath',
@@ -22,8 +21,9 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                        'CookieSecure' => true,
                        'CookieHttpOnly' => true,
                        'SessionName' => false,
+                       'CookieExpiration' => 100,
                        'ExtendedLoginCookies' => [ 'UserID', 'Token' ],
-                       'ExtendedLoginCookieExpiration' => $wgCookieExpiration * 2,
+                       'ExtendedLoginCookieExpiration' => 200,
                ] );
        }
 
@@ -377,8 +377,6 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
        }
 
        public function testPersistSession() {
-               $this->setMwGlobals( [ 'wgCookieExpiration' => 100 ] );
-
                $provider = new CookieSessionProvider( [
                        'priority' => 1,
                        'sessionName' => 'MySessionName',
@@ -461,7 +459,6 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
         */
        public function testCookieData( $secure, $remember ) {
                $this->setMwGlobals( [
-                       'wgCookieExpiration' => 100,
                        'wgSecureLogin' => false,
                ] );
 
@@ -783,4 +780,39 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertNull( $provider->getCookie( $request, 'Baz', 'x' ) );
        }
 
+       public function testGetRememberUserDuration() {
+               $config = $this->getConfig();
+               $provider = new CookieSessionProvider( [ 'priority' => 10 ] );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( $config );
+               $provider->setManager( SessionManager::singleton() );
+
+               $this->assertSame( 200, $provider->getRememberUserDuration() );
+
+               $config->set( 'ExtendedLoginCookieExpiration', null );
+
+               $this->assertSame( 100, $provider->getRememberUserDuration() );
+
+               $config->set( 'ExtendedLoginCookieExpiration', 0 );
+
+               $this->assertSame( null, $provider->getRememberUserDuration() );
+       }
+
+       public function testGetLoginCookieExpiration() {
+               $config = $this->getConfig();
+               $provider = \TestingAccessWrapper::newFromObject( new CookieSessionProvider( [
+                       'priority' => 10
+               ] ) );
+               $provider->setLogger( new \Psr\Log\NullLogger() );
+               $provider->setConfig( $config );
+               $provider->setManager( SessionManager::singleton() );
+
+               $this->assertSame( 200, $provider->getLoginCookieExpiration( 'Token' ) );
+               $this->assertSame( 100, $provider->getLoginCookieExpiration( 'User' ) );
+
+               $config->set( 'ExtendedLoginCookieExpiration', null );
+
+               $this->assertSame( 100, $provider->getLoginCookieExpiration( 'Token' ) );
+               $this->assertSame( 100, $provider->getLoginCookieExpiration( 'User' ) );
+       }
 }
index d705fc0..78edb76 100644 (file)
@@ -249,7 +249,7 @@ class ImmutableSessionProviderWithCookieTest extends MediaWikiTestCase {
                        }
                        $this->assertEquals( [
                                'value' => 'true',
-                               'expire' => $remember ? 100 : null,
+                               'expire' => null,
                                'path' => 'CookiePath',
                                'domain' => 'CookieDomain',
                                'secure' => false,
index ff22bfa..8f7b2a6 100644 (file)
@@ -103,6 +103,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $anonInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -118,6 +119,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -132,6 +134,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertTrue( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -150,6 +153,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $anonInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertTrue( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -165,6 +169,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertTrue( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -180,6 +185,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertTrue( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -231,6 +237,25 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertTrue( $info->isIdSafe() );
 
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [
+                       'id' => $id,
+                       'forceUse' => true,
+               ] );
+               $this->assertFalse( $info->forceUse(), 'no provider' );
+
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [
+                       'provider' => $provider,
+                       'forceUse' => true,
+               ] );
+               $this->assertFalse( $info->forceUse(), 'no id' );
+
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [
+                       'provider' => $provider,
+                       'id' => $id,
+                       'forceUse' => true,
+               ] );
+               $this->assertTrue( $info->forceUse(), 'correct use' );
+
                $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
                        'id' => $id,
                        'forceHTTPS' => 1,
@@ -242,6 +267,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                        'provider' => $provider,
                        'userInfo' => $userInfo,
                        'idIsSafe' => true,
+                       'forceUse' => true,
                        'persisted' => true,
                        'remembered' => true,
                        'forceHTTPS' => true,
@@ -255,6 +281,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( $provider, $info->getProvider() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertTrue( $info->forceUse() );
                $this->assertTrue( $info->wasPersisted() );
                $this->assertTrue( $info->wasRemembered() );
                $this->assertTrue( $info->forceHTTPS() );
@@ -265,6 +292,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                        'provider' => $provider2,
                        'userInfo' => $unverifiedUserInfo,
                        'idIsSafe' => false,
+                       'forceUse' => false,
                        'persisted' => false,
                        'remembered' => false,
                        'forceHTTPS' => false,
@@ -276,6 +304,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( $provider2, $info->getProvider() );
                $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
index d04d7ec..e725fee 100644 (file)
@@ -642,6 +642,35 @@ class SessionManagerTest extends MediaWikiTestCase {
                }
        }
 
+       public function testInvalidateSessionsForUser() {
+               $user = User::newFromName( 'UTSysop' );
+               $manager = $this->getManager();
+
+               $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+                       ->setMethods( [ 'invalidateSessionsForUser', '__toString' ] );
+
+               $provider1 = $providerBuilder->getMock();
+               $provider1->expects( $this->once() )->method( 'invalidateSessionsForUser' )
+                       ->with( $this->identicalTo( $user ) );
+               $provider1->expects( $this->any() )->method( '__toString' )
+                       ->will( $this->returnValue( 'MockProvider1' ) );
+
+               $provider2 = $providerBuilder->getMock();
+               $provider2->expects( $this->once() )->method( 'invalidateSessionsForUser' )
+                       ->with( $this->identicalTo( $user ) );
+               $provider2->expects( $this->any() )->method( '__toString' )
+                       ->will( $this->returnValue( 'MockProvider2' ) );
+
+               $this->config->set( 'SessionProviders', [
+                       $this->objectCacheDef( $provider1 ),
+                       $this->objectCacheDef( $provider2 ),
+               ] );
+
+               $oldToken = $user->getToken( true );
+               $manager->invalidateSessionsForUser( $user );
+               $this->assertNotEquals( $oldToken, $user->getToken() );
+       }
+
        public function testGetVaryHeaders() {
                $manager = $this->getManager();
 
@@ -839,7 +868,11 @@ class SessionManagerTest extends MediaWikiTestCase {
        }
 
        public function testAutoCreateUser() {
-               global $wgGroupPermissions;
+               global $wgGroupPermissions, $wgDisableAuthManager;
+
+               if ( !$wgDisableAuthManager ) {
+                       $this->markTestSkipped( 'AuthManager is not disabled' );
+               }
 
                \ObjectCache::$instances[__METHOD__] = new TestBagOStuff();
                $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
@@ -1756,5 +1789,21 @@ class SessionManagerTest extends MediaWikiTestCase {
                        [ LogLevel::WARNING, 'Session "{session}": Hook aborted' ],
                ], $logger->getBuffer() );
                $logger->clearBuffer();
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionCheckInfo' => [] ] );
+
+               // forceUse deletes bad backend data
+               $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
+                       'provider' => $provider,
+                       'id' => $id,
+                       'userInfo' => $userInfo,
+                       'forceUse' => true,
+               ] );
+               $this->assertTrue( $loadSessionInfoFromStore( $info ) );
+               $this->assertFalse( $this->store->getSession( $id ) );
+               $this->assertSame( [
+                       [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
        }
 }
index 18b1efd..4cbeeb9 100644 (file)
@@ -27,12 +27,16 @@ class SessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $manager, $priv->manager );
                $this->assertSame( $manager, $provider->getManager() );
 
+               $provider->invalidateSessionsForUser( new \User );
+
                $this->assertSame( [], $provider->getVaryHeaders() );
                $this->assertSame( [], $provider->getVaryCookies() );
                $this->assertSame( null, $provider->suggestLoginUsername( new \FauxRequest ) );
 
                $this->assertSame( get_class( $provider ), (string)$provider );
 
+               $this->assertNull( $provider->getRememberUserDuration() );
+
                $this->assertNull( $provider->whyNoSession() );
 
                $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
index e87f41d..3815416 100644 (file)
@@ -267,21 +267,9 @@ class SessionTest extends MediaWikiTestCase {
        }
 
        public function testTokens() {
-               $rc = new \ReflectionClass( Session::class );
-               if ( !method_exists( $rc, 'newInstanceWithoutConstructor' ) ) {
-                       $this->markTestSkipped(
-                               'ReflectionClass::newInstanceWithoutConstructor isn\'t available'
-                       );
-               }
-
-               // Instead of actually constructing the Session, we use reflection to
-               // bypass the constructor and plug a mock SessionBackend into the
-               // private fields to avoid having to actually create a SessionBackend.
-               $backend = new DummySessionBackend;
-               $session = $rc->newInstanceWithoutConstructor();
+               $session = TestUtils::getDummySession();
                $priv = \TestingAccessWrapper::newFromObject( $session );
-               $priv->backend = $backend;
-               $priv->index = 42;
+               $backend = $priv->backend;
 
                $token = \TestingAccessWrapper::newFromObject( $session->getToken() );
                $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
@@ -313,4 +301,72 @@ class SessionTest extends MediaWikiTestCase {
                $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data );
 
        }
+
+       /**
+        * @dataProvider provideSecretsRoundTripping
+        * @param mixed $data
+        */
+       public function testSecretsRoundTripping( $data ) {
+               $session = TestUtils::getDummySession();
+
+               // Simple round-trip
+               $session->setSecret( 'secret', $data );
+               $this->assertNotEquals( $data, $session->get( 'secret' ) );
+               $this->assertEquals( $data, $session->getSecret( 'secret', 'defaulted' ) );
+       }
+
+       public static function provideSecretsRoundTripping() {
+               return [
+                       [ 'Foobar' ],
+                       [ 42 ],
+                       [ [ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
+                       [ (object)[ 'foo', 'bar' => 'baz', 'subarray' => [ 1, 2, 3 ] ] ],
+                       [ true ],
+                       [ false ],
+                       [ null ],
+               ];
+       }
+
+       public function testSecrets() {
+               $logger = new \TestLogger;
+               $session = TestUtils::getDummySession( null, -1, $logger );
+
+               // Simple defaulting
+               $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
+
+               // Bad encrypted data
+               $session->set( 'test', 'foobar' );
+               $logger->setCollect( true );
+               $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
+               $logger->setCollect( false );
+               $this->assertSame( [
+                       [ LogLevel::WARNING, 'Invalid sealed-secret format' ]
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               // Tampered data
+               $session->setSecret( 'test', 'foobar' );
+               $encrypted = $session->get( 'test' );
+               $session->set( 'test', $encrypted . 'x' );
+               $logger->setCollect( true );
+               $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
+               $logger->setCollect( false );
+               $this->assertSame( [
+                       [ LogLevel::WARNING, 'Sealed secret has been tampered with, aborting.' ]
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
+
+               // Unserializable data
+               $iv = \MWCryptRand::generate( 16, true );
+               list( $encKey, $hmacKey ) = \TestingAccessWrapper::newFromObject( $session )->getSecretKeys();
+               $ciphertext = openssl_encrypt( 'foobar', 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv );
+               $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
+               $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
+               $encrypted = base64_encode( $hmac ) . '.' . $sealed;
+               $session->set( 'test', $encrypted );
+               \MediaWiki\suppressWarnings();
+               $this->assertEquals( 'defaulted', $session->getSecret( 'test', 'defaulted' ) );
+               \MediaWiki\restoreWarnings();
+       }
+
 }
index e321bdb..e55a3a4 100644 (file)
@@ -179,6 +179,39 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $actual );
        }
 
+       public static function provideGetPrefixedDBkey() {
+               return [
+                       [ NS_MAIN, 'Foo_Bar', '', '', 'en', 'Foo_Bar' ],
+                       [ NS_USER, 'Hansi_Maier', 'stuff_and_so_on', '', 'en', 'User:Hansi_Maier' ],
+
+                       // No capitalization or normalization is applied while formatting!
+                       [ NS_USER_TALK, 'hansi__maier', '', '', 'en', 'User_talk:hansi__maier' ],
+
+                       // getGenderCache() provides a mock that considers first
+                       // names ending in "a" to be female.
+                       [ NS_USER, 'Lisa_Müller', '', '', 'de', 'Benutzerin:Lisa_Müller' ],
+
+                       [ NS_MAIN, 'Remote_page', '', 'remotetestiw', 'en', 'remotetestiw:Remote_page' ],
+
+                       // non-existent namespace
+                       [ 10000000, 'Foobar', '', '', 'en', ':Foobar' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetPrefixedDBkey
+        */
+       public function testGetPrefixedDBkey( $namespace, $dbkey, $fragment,
+               $interwiki, $lang, $expected
+       ) {
+               $codec = $this->makeCodec( $lang );
+               $title = new TitleValue( $namespace, $dbkey, $fragment, $interwiki );
+
+               $actual = $codec->getPrefixedDBkey( $title );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
        public static function provideGetFullText() {
                return [
                        [ NS_MAIN, 'Foo_Bar', '', 'en', 'Foo Bar' ],
index 7922553..4dbda74 100644 (file)
@@ -42,6 +42,7 @@ class TitleValueTest extends MediaWikiTestCase {
                $title = new TitleValue( $ns, $text, $fragment, $interwiki );
 
                $this->assertEquals( $ns, $title->getNamespace() );
+               $this->assertTrue( $title->inNamespace( $ns ) );
                $this->assertEquals( $text, $title->getText() );
                $this->assertEquals( $fragment, $title->getFragment() );
                $this->assertEquals( $hasFragment, $title->hasFragment() );
index 27ce287..629c6e5 100644 (file)
@@ -49,9 +49,7 @@ class BotPasswordTest extends MediaWikiTestCase {
        public function addDBData() {
                $passwordFactory = new \PasswordFactory();
                $passwordFactory->init( \RequestContext::getMain()->getConfig() );
-               // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordFactory->setDefaultType( 'A' );
-               $pwhash = $passwordFactory->newFromPlaintext( 'foobaz' );
+               $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
 
                $dbw = wfGetDB( DB_MASTER );
                $dbw->delete(
@@ -65,7 +63,7 @@ class BotPasswordTest extends MediaWikiTestCase {
                                [
                                        'bp_user' => 42,
                                        'bp_app_id' => 'BotPassword',
-                                       'bp_password' => $pwhash->toString(),
+                                       'bp_password' => $passwordHash->toString(),
                                        'bp_token' => 'token!',
                                        'bp_restrictions' => '{"IPAddresses":["127.0.0.0/8"]}',
                                        'bp_grants' => '["test"]',
@@ -73,7 +71,7 @@ class BotPasswordTest extends MediaWikiTestCase {
                                [
                                        'bp_user' => 43,
                                        'bp_app_id' => 'BotPassword',
-                                       'bp_password' => $pwhash->toString(),
+                                       'bp_password' => $passwordHash->toString(),
                                        'bp_token' => 'token!',
                                        'bp_restrictions' => '{"IPAddresses":["127.0.0.0/8"]}',
                                        'bp_grants' => '["test"]',
@@ -311,8 +309,6 @@ class BotPasswordTest extends MediaWikiTestCase {
        public function testSave( $password ) {
                $passwordFactory = new \PasswordFactory();
                $passwordFactory->init( \RequestContext::getMain()->getConfig() );
-               // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordFactory->setDefaultType( 'A' );
 
                $bp = BotPassword::newUnsaved( [
                        'centralId' => 42,
@@ -325,9 +321,9 @@ class BotPasswordTest extends MediaWikiTestCase {
                        BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST ), 'sanity check'
                );
 
-               $pwhash = $password ? $passwordFactory->newFromPlaintext( $password ) : null;
-               $this->assertFalse( $bp->save( 'update', $pwhash ) );
-               $this->assertTrue( $bp->save( 'insert', $pwhash ) );
+               $passwordHash = $password ? $passwordFactory->newFromPlaintext( $password ) : null;
+               $this->assertFalse( $bp->save( 'update', $passwordHash ) );
+               $this->assertTrue( $bp->save( 'insert', $passwordHash ) );
                $bp2 = BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST );
                $this->assertInstanceOf( 'BotPassword', $bp2 );
                $this->assertEquals( $bp->getUserCentralId(), $bp2->getUserCentralId() );
@@ -356,9 +352,9 @@ class BotPasswordTest extends MediaWikiTestCase {
                        $this->assertTrue( $pw->equals( $password ) );
                }
 
-               $pwhash = $passwordFactory->newFromPlaintext( 'XXX' );
+               $passwordHash = $passwordFactory->newFromPlaintext( 'XXX' );
                $token = $bp->getToken();
-               $this->assertTrue( $bp->save( 'update', $pwhash ) );
+               $this->assertTrue( $bp->save( 'update', $passwordHash ) );
                $this->assertNotEquals( $token, $bp->getToken() );
                $pw = TestingAccessWrapper::newFromObject( $bp )->getPassword();
                $this->assertTrue( $pw->equals( 'XXX' ) );
diff --git a/tests/phpunit/includes/user/PasswordResetTest.php b/tests/phpunit/includes/user/PasswordResetTest.php
new file mode 100644 (file)
index 0000000..4db636b
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * @group Database
+ */
+class PasswordResetTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @dataProvider provideIsAllowed
+        */
+       public function testIsAllowed( $passwordResetRoutes, $enableEmail,
+               $allowsAuthenticationDataChange, $canEditPrivate, $canSeePassword,
+               $userIsBlocked, $isAllowed, $isAllowedToDisplayPassword
+       ) {
+               $config = new HashConfig( [
+                       'PasswordResetRoutes' => $passwordResetRoutes,
+                       'EnableEmail' => $enableEmail,
+               ] );
+
+               $authManager = $this->getMockBuilder( AuthManager::class )->disableOriginalConstructor()
+                       ->getMock();
+               $authManager->expects( $this->any() )->method( 'allowsAuthenticationDataChange' )
+                       ->willReturn( $allowsAuthenticationDataChange ? Status::newGood() : Status::newFatal( 'foo' ) );
+
+               $user = $this->getMock( User::class );
+               $user->expects( $this->any() )->method( 'getName' )->willReturn( 'Foo' );
+               $user->expects( $this->any() )->method( 'isBlocked' )->willReturn( $userIsBlocked );
+               $user->expects( $this->any() )->method( 'isAllowed' )
+                       ->will( $this->returnCallback( function ( $perm ) use ( $canEditPrivate, $canSeePassword ) {
+                               if ( $perm === 'editmyprivateinfo' ) {
+                                       return $canEditPrivate;
+                               } elseif ( $perm === 'passwordreset' ) {
+                                       return $canSeePassword;
+                               } else {
+                                       $this->fail( 'Unexpected permission check' );
+                               }
+                       } ) );
+
+               $passwordReset = new PasswordReset( $config, $authManager );
+
+               $this->assertSame( $isAllowed, $passwordReset->isAllowed( $user )->isGood() );
+               $this->assertSame( $isAllowedToDisplayPassword,
+                       $passwordReset->isAllowed( $user, true )->isGood() );
+       }
+
+       public function provideIsAllowed() {
+               return [
+                       [
+                               'passwordResetRoutes' => [],
+                               'enableEmail' => true,
+                               'allowsAuthenticationDataChange' => true,
+                               'canEditPrivate' => true,
+                               'canSeePassword' => true,
+                               'userIsBlocked' => false,
+                               'isAllowed' => false,
+                               'isAllowedToDisplayPassword' => false,
+                       ],
+                       [
+                               'passwordResetRoutes' => [ 'username' => true ],
+                               'enableEmail' => false,
+                               'allowsAuthenticationDataChange' => true,
+                               'canEditPrivate' => true,
+                               'canSeePassword' => true,
+                               'userIsBlocked' => false,
+                               'isAllowed' => false,
+                               'isAllowedToDisplayPassword' => false,
+                       ],
+                       [
+                               'passwordResetRoutes' => [ 'username' => true ],
+                               'enableEmail' => true,
+                               'allowsAuthenticationDataChange' => false,
+                               'canEditPrivate' => true,
+                               'canSeePassword' => true,
+                               'userIsBlocked' => false,
+                               'isAllowed' => false,
+                               'isAllowedToDisplayPassword' => false,
+                       ],
+                       [
+                               'passwordResetRoutes' => [ 'username' => true ],
+                               'enableEmail' => true,
+                               'allowsAuthenticationDataChange' => true,
+                               'canEditPrivate' => false,
+                               'canSeePassword' => true,
+                               'userIsBlocked' => false,
+                               'isAllowed' => false,
+                               'isAllowedToDisplayPassword' => false,
+                       ],
+                       [
+                               'passwordResetRoutes' => [ 'username' => true ],
+                               'enableEmail' => true,
+                               'allowsAuthenticationDataChange' => true,
+                               'canEditPrivate' => true,
+                               'canSeePassword' => true,
+                               'userIsBlocked' => true,
+                               'isAllowed' => false,
+                               'isAllowedToDisplayPassword' => false,
+                       ],
+                       [
+                               'passwordResetRoutes' => [ 'username' => true ],
+                               'enableEmail' => true,
+                               'allowsAuthenticationDataChange' => true,
+                               'canEditPrivate' => true,
+                               'canSeePassword' => false,
+                               'userIsBlocked' => false,
+                               'isAllowed' => true,
+                               'isAllowedToDisplayPassword' => false,
+                       ],
+                       [
+                               'passwordResetRoutes' => [ 'username' => true ],
+                               'enableEmail' => true,
+                               'allowsAuthenticationDataChange' => true,
+                               'canEditPrivate' => true,
+                               'canSeePassword' => true,
+                               'userIsBlocked' => false,
+                               'isAllowed' => true,
+                               'isAllowedToDisplayPassword' => true,
+                       ],
+               ];
+       }
+
+       public function testExecute_email() {
+               $config = new HashConfig( [
+                       'PasswordResetRoutes' => [ 'username' => true, 'email' => true ],
+                       'EnableEmail' => true,
+               ] );
+
+               $authManager = $this->getMockBuilder( AuthManager::class )->disableOriginalConstructor()
+                       ->getMock();
+               $authManager->expects( $this->any() )->method( 'allowsAuthenticationDataChange' )
+                       ->willReturn( Status::newGood() );
+               $authManager->expects( $this->exactly( 2 ) )->method( 'changeAuthenticationData' );
+
+               $request = new FauxRequest();
+               $request->setIP( '1.2.3.4' );
+               $performingUser = $this->getMock( User::class );
+               $performingUser->expects( $this->any() )->method( 'getRequest' )->willReturn( $request );
+               $performingUser->expects( $this->any() )->method( 'isAllowed' )->willReturn( true );
+
+               $targetUser1 = $this->getMock( User::class );
+               $targetUser2 = $this->getMock( User::class );
+               $targetUser1->expects( $this->any() )->method( 'getName' )->willReturn( 'User1' );
+               $targetUser2->expects( $this->any() )->method( 'getName' )->willReturn( 'User2' );
+               $targetUser1->expects( $this->any() )->method( 'getId' )->willReturn( 1 );
+               $targetUser2->expects( $this->any() )->method( 'getId' )->willReturn( 2 );
+               $targetUser1->expects( $this->any() )->method( 'getEmail' )->willReturn( 'foo@bar.baz' );
+               $targetUser2->expects( $this->any() )->method( 'getEmail' )->willReturn( 'foo@bar.baz' );
+
+               $passwordReset = $this->getMockBuilder( PasswordReset::class )
+                       ->setMethods( [ 'getUsersByEmail' ] )->setConstructorArgs( [ $config, $authManager ] )
+                       ->getMock();
+               $passwordReset->expects( $this->any() )->method( 'getUsersByEmail' )->with( 'foo@bar.baz' )
+                       ->willReturn( [ $targetUser1, $targetUser2 ] );
+
+               $status = $passwordReset->isAllowed( $performingUser );
+               $this->assertTrue( $status->isGood() );
+
+               $status = $passwordReset->execute( $performingUser, null, 'foo@bar.baz' );
+               $this->assertTrue( $status->isGood() );
+       }
+}
index 77690cd..d13da60 100755 (executable)
@@ -77,6 +77,7 @@ class PHPUnitMaintClass extends Maintenance {
                global $wgDevelopmentWarnings;
                global $wgSessionProviders;
                global $wgJobTypeConf;
+               global $wgAuthManagerConfig, $wgAuth, $wgDisableAuthManager;
 
                // Inject test autoloader
                require_once __DIR__ . '/../TestsAutoLoader.php';
@@ -124,6 +125,27 @@ class PHPUnitMaintClass extends Maintenance {
                        ],
                ];
 
+               // Generic AuthManager configuration for testing
+               $wgAuthManagerConfig = [
+                       'preauth' => [],
+                       'primaryauth' => [
+                               [
+                                       'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
+                                       'args' => [ [
+                                               'authoritative' => false,
+                                       ] ],
+                               ],
+                               [
+                                       'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
+                                       'args' => [ [
+                                               'authoritative' => true,
+                                       ] ],
+                               ],
+                       ],
+                       'secondaryauth' => [],
+               ];
+               $wgAuth = $wgDisableAuthManager ? new AuthPlugin : new MediaWiki\Auth\AuthManagerAuthPlugin();
+
                // Bug 44192 Do not attempt to send a real e-mail
                Hooks::clear( 'AlternateUserMailer' );
                Hooks::register(
@@ -139,6 +161,10 @@ class PHPUnitMaintClass extends Maintenance {
                // may break testing against floating point values
                // treated with PHP's serialize()
                ini_set( 'serialize_precision', 17 );
+
+               // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
+               // But PHPUnit may not be loaded yet, so we have to wait until just
+               // before PHPUnit_TextUI_Command::main() is executed at the end of this file.
        }
 
        public function execute() {
@@ -237,4 +263,9 @@ echo defined( 'HHVM_VERSION' ) ?
        'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
        'Using PHP ' . PHP_VERSION . "\n";
 
+// Prepare global services for unit tests.
+// FIXME: this should be done in the finalSetup() method,
+// but PHPUnit may not have been loaded at that point.
+MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
+
 $wgPhpUnitClass::main();
index 152c2eb..5d2f37e 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerInterface;
 
 /**
  * @covers MediaWikiTestCase
@@ -98,6 +100,36 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
                $this->stashMwGlobals( self::GLOBAL_KEY_NONEXISTING );
        }
 
+       public function testOverrideMwServices() {
+               $initialServices = MediaWikiServices::getInstance();
+
+               $this->overrideMwServices();
+               $this->assertNotSame( $initialServices, MediaWikiServices::getInstance() );
+
+               $this->tearDown();
+               $this->assertSame( $initialServices, MediaWikiServices::getInstance() );
+       }
+
+       public function testSetService() {
+               $initialServices = MediaWikiServices::getInstance();
+               $initialService = $initialServices->getDBLoadBalancer();
+               $mockService = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()->getMock();
+
+               $this->setService( 'DBLoadBalancer', $mockService );
+               $this->assertNotSame( $initialServices, MediaWikiServices::getInstance() );
+               $this->assertNotSame(
+                       $initialService,
+                       MediaWikiServices::getInstance()->getDBLoadBalancer()
+               );
+               $this->assertSame( $mockService, MediaWikiServices::getInstance()->getDBLoadBalancer() );
+
+               $this->tearDown();
+               $this->assertSame( $initialServices, MediaWikiServices::getInstance() );
+               $this->assertNotSame( $mockService, MediaWikiServices::getInstance()->getDBLoadBalancer() );
+               $this->assertSame( $initialService, MediaWikiServices::getInstance()->getDBLoadBalancer() );
+       }
+
        /**
         * @covers MediaWikiTestCase::setLogger
         * @covers MediaWikiTestCase::restoreLogger
@@ -105,7 +137,7 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
        public function testLoggersAreRestoredOnTearDown() {
                // replacing an existing logger
                $logger1 = LoggerFactory::getInstance( 'foo' );
-               $this->setLogger( 'foo', $this->getMock( '\Psr\Log\LoggerInterface' ) );
+               $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
                $logger2 = LoggerFactory::getInstance( 'foo' );
                $this->tearDown();
                $logger3 = LoggerFactory::getInstance( 'foo' );
@@ -114,7 +146,7 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
                $this->assertNotSame( $logger1, $logger2 );
 
                // replacing a non-existing logger
-               $this->setLogger( 'bar', $this->getMock( '\Psr\Log\LoggerInterface' ) );
+               $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
                $logger1 = LoggerFactory::getInstance( 'bar' );
                $this->tearDown();
                $logger2 = LoggerFactory::getInstance( 'bar' );
@@ -124,8 +156,8 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
 
                // replacing same logger twice
                $logger1 = LoggerFactory::getInstance( 'baz' );
-               $this->setLogger( 'baz', $this->getMock( '\Psr\Log\LoggerInterface' ) );
-               $this->setLogger( 'baz', $this->getMock( '\Psr\Log\LoggerInterface' ) );
+               $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
+               $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
                $this->tearDown();
                $logger2 = LoggerFactory::getInstance( 'baz' );
 
index ee948bb..aa68bb2 100644 (file)
        } );
 
        // HTML in wikitext
-       QUnit.test( 'HTML', 32, function ( assert ) {
+       QUnit.test( 'HTML', 33, function ( assert ) {
                mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' );
 
                assertBothModes( assert, [ 'jquerymsg-italics-msg' ], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' );
                        'Self-closing tags don\'t cause a parse error'
                );
 
+               mw.messages.set( 'jquerymsg-asciialphabetliteral-regression', '<b >>>="dir">asd</b>' );
+               assert.htmlEqual(
+                       formatParse( 'jquerymsg-asciialphabetliteral-regression' ),
+                       '<b>&gt;&gt;="dir"&gt;asd</b>',
+                       'Regression test for bad "asciiAlphabetLiteral" definition'
+               );
+
                mw.messages.set( 'jquerymsg-entities1', 'A&B' );
                mw.messages.set( 'jquerymsg-entities2', 'A&gt;B' );
                mw.messages.set( 'jquerymsg-entities3', 'A&rarr;B' );
                );
        } );
 
+       QUnit.test( 'Nowiki', 3, function ( assert ) {
+               mw.messages.set( 'jquerymsg-nowiki-link', 'Foo <nowiki>[[bar]]</nowiki> baz.' );
+               assert.equal(
+                       formatParse( 'jquerymsg-nowiki-link' ),
+                       'Foo [[bar]] baz.',
+                       'Link inside nowiki is not parsed'
+               );
+
+               mw.messages.set( 'jquerymsg-nowiki-htmltag', 'Foo <nowiki><b>bar</b></nowiki> baz.' );
+               assert.equal(
+                       formatParse( 'jquerymsg-nowiki-htmltag' ),
+                       'Foo &lt;b&gt;bar&lt;/b&gt; baz.',
+                       'HTML inside nowiki is not parsed and escaped'
+               );
+
+               mw.messages.set( 'jquerymsg-nowiki-template', 'Foo <nowiki>{{bar}}</nowiki> baz.' );
+               assert.equal(
+                       formatParse( 'jquerymsg-nowiki-template' ),
+                       'Foo {{bar}} baz.',
+                       'Template inside nowiki is not parsed and does not cause a parse error'
+               );
+       } );
+
        QUnit.test( 'Behavior in case of invalid wikitext', 3, function ( assert ) {
                mw.messages.set( 'invalid-wikitext', '<b>{{FAIL}}</b>' );