Merge "Remove support for StartProfiler.php"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 29 Aug 2018 01:16:33 +0000 (01:16 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 29 Aug 2018 01:16:33 +0000 (01:16 +0000)
86 files changed:
RELEASE-NOTES-1.32
autoload.php
docs/extension.schema.v1.json
docs/extension.schema.v2.json
includes/DefaultSettings.php
includes/Linker.php
includes/OutputPage.php
includes/Storage/DerivedPageDataUpdater.php
includes/Title.php
includes/api/ApiQueryDeletedRevisions.php
includes/api/ApiQueryRevisions.php
includes/api/i18n/pl.json
includes/api/i18n/zh-hant.json
includes/cache/localisation/LCStoreStaticArray.php
includes/content/ContentHandler.php
includes/diff/TextSlotDiffRenderer.php
includes/installer/i18n/pl.json
includes/libs/RiffExtractor.php
includes/libs/StaticArrayWriter.php
includes/libs/objectcache/WANObjectCache.php
includes/libs/rdbms/lbfactory/LBFactorySingle.php
includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/registration/ExtensionProcessor.php
includes/registration/VersionChecker.php
includes/resourceloader/ResourceLoaderSkinModule.php
includes/revisiondelete/RevDelArchiveItem.php
includes/revisiondelete/RevDelArchivedFileItem.php
includes/revisiondelete/RevDelArchivedRevisionItem.php
includes/revisiondelete/RevDelFileItem.php
includes/revisiondelete/RevDelRevisionItem.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specials/SpecialDeletedContributions.php
includes/specials/SpecialExport.php
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/ca.json
languages/i18n/ckb.json
languages/i18n/de.json
languages/i18n/es.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gcr.json
languages/i18n/he.json
languages/i18n/ia.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/my.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/zh-hant.json
maintenance/jsduck/categories.json
maintenance/resources/foreign-resources.yaml
maintenance/resources/manageForeignResources.php
maintenance/tidyUpBug37714.php [deleted file]
maintenance/tidyUpT39714.php [new file with mode: 0644]
profileinfo.php
resources/src/mediawiki.base/mediawiki.base.js
resources/src/mediawiki.notification/notification.js
resources/src/startup/mediawiki.js
resources/src/startup/startup.js
tests/phan/config.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/Storage/RevisionStoreDbTestBase.php
tests/phpunit/includes/libs/StaticArrayWriterTest.php

index 6f15d46..c765fc1 100644 (file)
@@ -40,6 +40,8 @@ production.
 * The $wgPasswordSenderName setting, ignored since 1.23 by MediaWiki and almost
   all extensions, is no longer set at all. Instead, you can modify the system
   message `emailsender`.
+* A new configuration setting, $wgRawHtmlMessages, is added, for listing
+  messages which are displayed as raw HTML.
 
 === New features in 1.32 ===
 * (T112474) Generalized the ResourceLoader mechanism for overriding modules
index e6ae2bf..10aab64 100644 (file)
@@ -1487,7 +1487,7 @@ $wgAutoloadLocalClasses = [
        'ThrottledError' => __DIR__ . '/includes/exception/ThrottledError.php',
        'ThumbnailImage' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
-       'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php',
+       'TidyUpT39714' => __DIR__ . '/maintenance/tidyUpT39714.php',
        'TiffHandler' => __DIR__ . '/includes/media/TiffHandler.php',
        'Timing' => __DIR__ . '/includes/libs/Timing.php',
        'Title' => __DIR__ . '/includes/Title.php',
index c9a887d..0ff169c 100644 (file)
                                "type": "string"
                        }
                },
+               "RawHtmlMessages": {
+                       "type": "array",
+                       "description": "Messages which are rendered as raw HTML",
+                       "items": {
+                               "type": "string"
+                       }
+               },
                "callback": {
                        "type": [
                                "array",
index 24212a9..7de5ed5 100644 (file)
                                "type": "string"
                        }
                },
+               "RawHtmlMessages": {
+                       "type": "array",
+                       "description": "Messages which are rendered as raw HTML",
+                       "items": {
+                               "type": "string"
+                       }
+               },
                "callback": {
                        "type": [
                                "array",
index 5672a27..4ddc23e 100644 (file)
@@ -4399,7 +4399,7 @@ $wgPreprocessorCacheThreshold = 1000;
 $wgEnableScaryTranscluding = false;
 
 /**
- * Expiry time for transcluded templates cached in transcache database table.
+ * Expiry time for transcluded templates cached in object cache.
  * Only used $wgEnableInterwikiTranscluding is set to true.
  */
 $wgTranscludeCacheExpiry = 3600;
@@ -8843,6 +8843,22 @@ $wgCSPHeader = false;
  */
 $wgCSPReportOnlyHeader = false;
 
+/**
+ * List of messages which might contain raw HTML.
+ * Extensions should add their messages here. The list is used for access control:
+ * changing messages listed here will require editsitecss and editsitejs rights.
+ *
+ * @since 1.32
+ * @var string[]
+ */
+$wgRawHtmlMessages = [
+       'copyright',
+       'history_copyright',
+       'googlesearch',
+       'feedback-terms',
+       'feedback-termsofuse',
+];
+
 /**
  * Mapping of event channels (or channel categories) to EventRelayer configuration.
  *
index 08a5724..7e56522 100644 (file)
@@ -431,7 +431,11 @@ class Linker {
                        $s = $thumb->toHtml( $params );
                }
                if ( $frameParams['align'] != '' ) {
-                       $s = "<div class=\"float{$frameParams['align']}\">{$s}</div>";
+                       $s = Html::rawElement(
+                               'div',
+                               [ 'class' => 'float' . $frameParams['align'] ],
+                               $s
+                       );
                }
                return str_replace( "\n", ' ', $prefix . $s . $postfix );
        }
index c8e18e0..3675e8a 100644 (file)
@@ -2364,10 +2364,6 @@ class OutputPage extends ContextSource {
 
                if ( !$this->mArticleBodyOnly ) {
                        $sk = $this->getSkin();
-
-                       if ( $sk->shouldPreloadLogo() ) {
-                               $this->addLogoPreloadLinkHeaders();
-                       }
                }
 
                $linkHeader = $this->getLinkHeader();
@@ -3915,80 +3911,6 @@ class OutputPage extends ContextSource {
                ] );
        }
 
-       /**
-        * Add Link headers for preloading the wiki's logo.
-        *
-        * @since 1.26
-        */
-       protected function addLogoPreloadLinkHeaders() {
-               $logo = ResourceLoaderSkinModule::getLogo( $this->getConfig() );
-
-               $tags = [];
-               $logosPerDppx = [];
-               $logos = [];
-
-               if ( !is_array( $logo ) ) {
-                       // No media queries required if we only have one variant
-                       $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' );
-                       return;
-               }
-
-               if ( isset( $logo['svg'] ) ) {
-                       // No media queries required if we only have a 1x and svg variant
-                       // because all preload-capable browsers support SVGs
-                       $this->addLinkHeader( '<' . $logo['svg'] . '>;rel=preload;as=image' );
-                       return;
-               }
-
-               foreach ( $logo as $dppx => $src ) {
-                       // Keys are in this format: "1.5x"
-                       $dppx = substr( $dppx, 0, -1 );
-                       $logosPerDppx[$dppx] = $src;
-               }
-
-               // Because PHP can't have floats as array keys
-               uksort( $logosPerDppx, function ( $a , $b ) {
-                       $a = floatval( $a );
-                       $b = floatval( $b );
-                       // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
-                       return $a <=> $b;
-               } );
-
-               foreach ( $logosPerDppx as $dppx => $src ) {
-                       $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
-               }
-
-               $logosCount = count( $logos );
-               // Logic must match ResourceLoaderSkinModule:
-               // - 1x applies to resolution < 1.5dppx
-               // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
-               // - 2x applies to resolution >= 2dppx
-               // Note that min-resolution and max-resolution are both inclusive.
-               for ( $i = 0; $i < $logosCount; $i++ ) {
-                       if ( $i === 0 ) {
-                               // Smallest dppx
-                               // min-resolution is ">=" (larger than or equal to)
-                               // "not min-resolution" is essentially "<"
-                               $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
-                       } elseif ( $i !== $logosCount - 1 ) {
-                               // In between
-                               // Media query expressions can only apply "not" to the entire expression
-                               // (e.g. can't express ">= 1.5 and not >= 2).
-                               // Workaround: Use <= 1.9999 in place of < 2.
-                               $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
-                               $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
-                                       'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
-                       } else {
-                               // Largest dppx
-                               $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
-                       }
-
-                       $this->addLinkHeader(
-                               '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query
-                       );
-               }
-       }
-
        /**
         * Get (and set if not yet set) the CSP nonce.
         *
index a00766f..dacec96 100644 (file)
@@ -1408,21 +1408,16 @@ class DerivedPageDataUpdater implements IDBAccessObject {
                $recursive = $this->options['changed']; // T52785
                $updates = $this->getSecondaryDataUpdates( $recursive );
 
+               $triggeringUser = $this->options['triggeringuser'] ?? $this->user;
+               if ( !$triggeringUser instanceof User ) {
+                       $triggeringUser = User::newFromIdentity( $triggeringUser );
+               }
                foreach ( $updates as $update ) {
                        // TODO: make an $option field for the cause
-                       $update->setCause( 'edit-page', $this->user->getName() );
+                       $update->setCause( 'edit-page', $triggeringUser->getName() );
                        if ( $update instanceof LinksUpdate ) {
                                $update->setRevision( $legacyRevision );
-
-                               if ( !empty( $this->options['triggeringuser'] ) ) {
-                                       /** @var UserIdentity|User $triggeringUser */
-                                       $triggeringUser = $this->options['triggeringuser'];
-                                       if ( !$triggeringUser instanceof User ) {
-                                               $triggeringUser = User::newFromIdentity( $triggeringUser );
-                                       }
-
-                                       $update->setTriggeringUser( $triggeringUser );
-                               }
+                               $update->setTriggeringUser( $triggeringUser );
                        }
                        DeferredUpdates::addUpdate( $update );
                }
index c919b18..895cc0e 100644 (file)
@@ -1480,6 +1480,22 @@ class Title implements LinkTarget {
                );
        }
 
+       /**
+        * Is this a message which can contain raw HTML?
+        *
+        * @return bool
+        * @since 1.32
+        */
+       public function isRawHtmlMessage() {
+               global $wgRawHtmlMessages;
+
+               if ( $this->inNamespace( NS_MEDIAWIKI ) ) {
+                       return false;
+               }
+               $message = lcfirst( $this->getRootText() );
+               return in_array( $message, $wgRawHtmlMessages, true );
+       }
+
        /**
         * Is this a talk page of some sort?
         *
@@ -2392,6 +2408,13 @@ class Title implements LinkTarget {
                                $error = [ 'sitejsonprotected', $action ];
                        } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
                                $error = [ 'sitejsprotected', $action ];
+                       } elseif ( $this->isRawHtmlMessage() ) {
+                               // Raw HTML can be used to deploy CSS or JS so require rights for both.
+                               if ( !$user->isAllowed( 'editsitejs' ) ) {
+                                       $error = [ 'sitejsprotected', $action ];
+                               } elseif ( !$user->isAllowed( 'editsitecss' ) ) {
+                                       $error = [ 'sitecssprotected', $action ];
+                               }
                        }
 
                        if ( $error ) {
@@ -2423,25 +2446,34 @@ class Title implements LinkTarget {
                # Protect css/json/js subpages of user pages
                # XXX: this might be better using restrictions
 
-               if ( $action != 'patrol' ) {
-                       if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
-                               if (
-                                       $this->isUserCssConfigPage()
-                                       && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
-                               ) {
-                                       $errors[] = [ 'mycustomcssprotected', $action ];
-                               } elseif (
-                                       $this->isUserJsonConfigPage()
-                                       && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
-                               ) {
-                                       $errors[] = [ 'mycustomjsonprotected', $action ];
-                               } elseif (
-                                       $this->isUserJsConfigPage()
-                                       && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
-                               ) {
-                                       $errors[] = [ 'mycustomjsprotected', $action ];
-                               }
-                       } else {
+               if ( $action === 'patrol' ) {
+                       return [];
+               }
+
+               if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
+                       // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
+                       if (
+                               $this->isUserCssConfigPage()
+                               && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
+                       ) {
+                               $errors[] = [ 'mycustomcssprotected', $action ];
+                       } elseif (
+                               $this->isUserJsonConfigPage()
+                               && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
+                       ) {
+                               $errors[] = [ 'mycustomjsonprotected', $action ];
+                       } elseif (
+                               $this->isUserJsConfigPage()
+                               && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
+                       ) {
+                               $errors[] = [ 'mycustomjsprotected', $action ];
+                       }
+               } else {
+                       // Users need editmyuser* to edit their own CSS/JSON/JS subpages, except for
+                       // deletion/suppression which cannot be used for attacks and we want to avoid the
+                       // situation where an unprivileged user can post abusive content on their subpages
+                       // and only very highly privileged users could remove it.
+                       if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
                                if (
                                        $this->isUserCssConfigPage()
                                        && !$user->isAllowed( 'editusercss' )
index c3af71b..48d6f30 100644 (file)
@@ -289,7 +289,7 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
        protected function getExamplesMessages() {
                return [
                        'action=query&prop=deletedrevisions&titles=Main%20Page|Talk:Main%20Page&' .
-                               'drvprop=user|comment|content'
+                               'drvslots=*&drvprop=user|comment|content'
                                => 'apihelp-query+deletedrevisions-example-titles',
                        'action=query&prop=deletedrevisions&revids=123456'
                                => 'apihelp-query+deletedrevisions-example-revids',
index 5e7b864..8c26024 100644 (file)
@@ -486,7 +486,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
        protected function getExamplesMessages() {
                return [
                        'action=query&prop=revisions&titles=API|Main%20Page&' .
-                               'rvprop=timestamp|user|comment|content'
+                               'rvslots=*&rvprop=timestamp|user|comment|content'
                                => 'apihelp-query+revisions-example-content',
                        'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
                                'rvprop=timestamp|user|comment'
index 1ded789..a0429ac 100644 (file)
@@ -53,7 +53,7 @@
        "apihelp-checktoken-example-simple": "Sprawdź poprawność tokenu <kbd>csrf</kbd>.",
        "apihelp-clearhasmsg-summary": "Czyści flagę <code>hasmsg</code> dla bieżącego użytkownika.",
        "apihelp-clearhasmsg-example-1": "Wyczyść flagę <code>hasmsg</code> dla bieżącego użytkownika.",
-       "apihelp-compare-summary": "Zauważ różnicę między dwoma stronami",
+       "apihelp-compare-summary": "Pokaż porównanie dwóch stron.",
        "apihelp-compare-param-fromtitle": "Pierwszy tytuł do porównania.",
        "apihelp-compare-param-fromid": "ID pierwszej strony do porównania.",
        "apihelp-compare-param-fromrev": "Pierwsza wersja do porównania.",
index 9f80a78..293fac3 100644 (file)
        "apihelp-help-example-query": "兩個查詢子模組的說明。",
        "apihelp-imagerotate-summary": "旋轉一張或多張圖片。",
        "apihelp-imagerotate-param-rotation": "順時針旋轉圖片的度數。",
+       "apihelp-imagerotate-param-tags": "在更新日誌裡套用到項目的標籤。",
+       "apihelp-imagerotate-example-simple": "<kbd>90</kbd> 度旋轉 <kbd>File:Example.png</kbd>。",
+       "apihelp-imagerotate-example-generator": "<kbd>180</kbd> 度旋轉所有在 <kbd>Category:Flip</kbd> 裡的圖片。",
        "apihelp-import-summary": "從其它 wiki 或 XML 檔案來匯入頁面。",
        "apihelp-import-param-summary": "匯入摘要。",
        "apihelp-import-param-xml": "上載的 XML 檔。",
        "apihelp-opensearch-param-suggest": "若<var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var>設定為false,則不做任何事。",
        "apihelp-opensearch-param-redirects": "如何處理重定向:\n;return:傳回重定向本身。\n;resolve:傳回目標頁面,傳回的結果數目可能少於$1limit。\n由於歷史原因,$1format=json的預設值為「return」,其他格式則為「resolve」。",
        "apihelp-opensearch-param-format": "輸出的格式。",
+       "apihelp-opensearch-example-te": "找出以 <kbd>Te</kbd> 為開頭的頁面。",
        "apihelp-options-summary": "更改目前使用者的偏好設定。",
        "apihelp-options-param-reset": "重設偏好設定為網站預設值。",
        "apihelp-options-example-reset": "重設所有偏好設定",
        "apihelp-query+alldeletedrevisions-param-user": "此列出由該使用者作出的修訂。",
        "apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
        "apihelp-query+alldeletedrevisions-param-namespace": "僅列出此命名空間的頁面。",
+       "apihelp-query+allfileusages-summary": "列出所有檔案用途,包含不存在的。",
+       "apihelp-query+allfileusages-param-from": "要起始列舉的檔案標題。",
+       "apihelp-query+allfileusages-param-to": "要終止列舉的檔案標題。",
        "apihelp-query+allfileusages-param-prefix": "搜尋以此值為開頭的所有檔案標題。",
        "apihelp-query+allfileusages-param-prop": "要包含到的資訊部份:",
        "apihelp-query+allfileusages-paramvalue-prop-title": "添加檔案標題。",
index 38700b8..3b6da73 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use Wikimedia\StaticArrayWriter;
+
 /**
  * @since 1.26
  */
@@ -84,9 +86,7 @@ class LCStoreStaticArray implements LCStore {
                }
                if ( is_array( $value ) ) {
                        // [A]rray
-                       return [ 'a', array_map( function ( $v ) {
-                               return LCStoreStaticArray::encode( $v );
-                       }, $value ) ];
+                       return [ 'a', array_map( 'LCStoreStaticArray::encode', $value ) ];
                }
 
                throw new RuntimeException( 'Cannot encode ' . var_export( $value, true ) );
@@ -109,9 +109,7 @@ class LCStoreStaticArray implements LCStore {
                        case 's':
                                return unserialize( $data );
                        case 'a':
-                               return array_map( function ( $v ) {
-                                       return LCStoreStaticArray::decode( $v );
-                               }, $data );
+                               return array_map( 'LCStoreStaticArray::decode', $data );
                        default:
                                throw new RuntimeException(
                                        'Unable to decode ' . var_export( $encoded, true ) );
@@ -119,13 +117,12 @@ class LCStoreStaticArray implements LCStore {
        }
 
        public function finishWrite() {
-               file_put_contents(
-                       $this->fname,
-                       "<?php\n" .
-                       "// Generated by LCStoreStaticArray.php -- do not edit!\n" .
-                       "return " .
-                       var_export( $this->data[$this->currentLang], true ) . ';'
+               $writer = new StaticArrayWriter();
+               $out = $writer->create(
+                       $this->data[$this->currentLang],
+                       'Generated by LCStoreStaticArray.php -- do not edit!'
                );
+               file_put_contents( $this->fname, $out );
                $this->currentLang = null;
                $this->fname = null;
        }
index 22fbe6f..344d040 100644 (file)
@@ -665,7 +665,7 @@ abstract class ContentHandler {
                        $differenceEngine = $this->createDifferenceEngine( $context );
                        if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
                                // TODO turn this into a deprecation warning in a later release
-                               LoggerFactory::getInstance( 'diff' )->notice(
+                               LoggerFactory::getInstance( 'diff' )->info(
                                        'Falling back to DifferenceEngineSlotDiffRenderer', [
                                                'modelID' => $this->getModelID(),
                                                'DifferenceEngine' => get_class( $differenceEngine ),
index baedcf0..9c60705 100644 (file)
@@ -209,7 +209,8 @@ class TextSlotDiffRenderer extends SlotDiffRenderer {
                        $wikidiff2Version = phpversion( 'wikidiff2' );
                        if (
                                $wikidiff2Version !== false &&
-                               version_compare( $wikidiff2Version, '1.5.0', '>=' )
+                               version_compare( $wikidiff2Version, '1.5.0', '>=' ) &&
+                               version_compare( $wikidiff2Version, '1.8.0', '<' )
                        ) {
                                $text = wikidiff2_do_diff(
                                        $oldText,
@@ -218,7 +219,7 @@ class TextSlotDiffRenderer extends SlotDiffRenderer {
                                        $this->wikiDiff2MovedParagraphDetectionCutoff
                                );
                        } else {
-                               // Don't pass the 4th parameter for compatibility with older versions of wikidiff2
+                               // Don't pass the 4th parameter introduced in version 1.5.0 and removed in version 1.8.0
                                $text = wikidiff2_do_diff(
                                        $oldText,
                                        $newText,
index 1d4d515..ae4ce21 100644 (file)
        "config-email-watchlist": "Włącz powiadomienie o zmianach stron obserwowanych",
        "config-email-watchlist-help": "Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronach obserwowanych, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.",
        "config-email-auth": "Włącz uwierzytelnianie e‐mailem",
-       "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest'''zalecane''' na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.",
+       "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest <strong>zalecane</strong> na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.",
        "config-email-sender": "Zwrotny adres e‐mail",
-       "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane szturchnięcia.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.",
+       "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane zwroty z serwerów pocztowych.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.",
        "config-upload-settings": "Przesyłanie obrazków i plików",
        "config-upload-enable": "Włącz przesyłanie plików na serwer",
        "config-upload-help": "Przesyłanie plików potencjalnie wystawia serwer na zagrożenia.\nWięcej informacji na ten temat można znaleźć w [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sekcji zabezpieczeń] podręcznika.\n\nAby włączyć przesyłanie plików, zmień właściwości podkatalogu <code>images</code> katalogu głównego MediaWiki tak, aby serwer sieci web mógł zapisywać do niego.\nNastępnie włącz tę opcję.",
index 304b99b..c060380 100644 (file)
@@ -96,4 +96,4 @@ class RiffExtractor {
        public static function extractUInt32( $string ) {
                return unpack( 'V', $string )[1];
        }
-};
+}
index cd1860f..1e0e1dc 100644 (file)
@@ -26,24 +26,47 @@ namespace Wikimedia;
 class StaticArrayWriter {
 
        /**
-        * @param string[] $data Array with string keys/values to export
+        * @param array $data Array with keys/values to export
         * @param string $header
         *
         * @return string PHP code
         */
        public function create( array $data, $header = 'Automatically generated' ) {
-               $format = "\t%s => %s,\n";
                $code = "<?php\n"
                        . "// " . implode( "\n// ", explode( "\n", $header ) ) . "\n"
                        . "return [\n";
                foreach ( $data as $key => $value ) {
-                       $code .= sprintf(
-                               $format,
-                               var_export( $key, true ),
-                               var_export( $value, true )
-                       );
+                       $code .= $this->encode( $key, $value, 1 );
                }
                $code .= "];\n";
                return $code;
        }
+
+       /**
+        * Recursively turn one k/v pair into properly-indented PHP
+        *
+        * @param string|int $key
+        * @param array|mixed $value
+        * @param int $indent Indentation level
+        *
+        * @return string
+        */
+       private function encode( $key, $value, $indent ) {
+               $tabs = str_repeat( "\t", $indent );
+               $line = $tabs .
+                       var_export( $key, true ) .
+                       ' => ';
+               if ( is_array( $value ) ) {
+                       $line .= "[\n";
+                       foreach ( $value as $key2 => $value2 ) {
+                               $line .= $this->encode( $key2, $value2, $indent + 1 );
+                       }
+                       $line .= "$tabs]";
+               } else {
+                       $line .= var_export( $value, true );
+               }
+
+               $line .= ",\n";
+               return $line;
+       }
 }
index 716641f..3af820b 100644 (file)
@@ -27,6 +27,8 @@ use Psr\Log\NullLogger;
 /**
  * Multi-datacenter aware caching interface
  *
+ * ### Using WANObjectCache
+ *
  * All operations go to the local datacenter cache, except for delete(),
  * touchCheckKey(), and resetCheckKey(), which broadcast to all datacenters.
  *
@@ -36,34 +38,63 @@ use Psr\Log\NullLogger;
  * The preferred way to do this logic is through getWithSetCallback().
  * When querying the store on cache miss, the closest DB replica
  * should be used. Try to avoid heavyweight DB master or quorum reads.
- * When the source data changes, a purge method should be called.
- * Since purges are expensive, they should be avoided. One can do so if:
- *   - a) The object cached is immutable; or
- *   - b) Validity is checked against the source after get(); or
- *   - c) Using a modest TTL is reasonably correct and performant
  *
+ * To ensure consumers of the cache see new values in a timely manner,
+ * you either need to follow either the validation strategy, or the
+ * purge strategy.
+ *
+ * The validation strategy refers to the natural avoidance of stale data
+ * by one of the following means:
+ *
+ *   - A) The cached value is immutable.
+ *        If the consumer has access to an identifier that uniquely describes a value,
+ *        cached value need not change. Instead, the key can change. This also allows
+ *        all servers to access their perceived current version. This is important
+ *        in context of multiple deployed versions of your application and/or cross-dc
+ *        database replication, to ensure deterministic values without oscillation.
+ *   - B) Validity is checked against the source after get().
+ *        This is the inverse of A. The unique identifier is embedded inside the value
+ *        and validated after on retreival. If outdated, the value is recomputed.
+ *   - C) The value is cached with a modest TTL (without validation).
+ *        If value recomputation is reasonably performant, and the value is allowed to
+ *        be stale, one should consider using TTL only – using the value's age as
+ *        method of validation.
+ *
+ * The purge strategy refers to the the approach whereby your application knows that
+ * source data has changed and can react by purging the relevant cache keys.
+ * As purges are expensive, this strategy should be avoided if possible.
  * The simplest purge method is delete().
  *
- * There are three supported ways to handle broadcasted operations:
- *   - a) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint
- *         that has subscribed listeners on the cache servers applying the cache updates.
- *   - b) Ommit the 'purge' EventRelayer parameter and set up mcrouter as the underlying cache
+ * No matter which strategy you choose, callers must not rely on updates or purges
+ * being immediately visible to other servers. It should be treated similarly as
+ * one would a database replica.
+ *
+ * The need for immediate updates should be avoided. If needed, solutions must be
+ * sought outside WANObjectCache.
+ *
+ * ### Deploying WANObjectCache
+ *
+ * There are three supported ways to set up broadcasted operations:
+ *
+ *   - A) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint
+ *        that has subscribed listeners on the cache servers applying the cache updates.
+ *   - B) Omit the 'purge' EventRelayer parameter and set up mcrouter as the underlying cache
  *        backend, using a memcached BagOStuff class for the 'cache' parameter. The 'region'
- *        and 'cluster' parameters must be provided and 'mcrouterAware' must be set to 'true'.
+ *        and 'cluster' parameters must be provided and 'mcrouterAware' must be set to `true`.
  *        Configure mcrouter as follows:
  *          - 1) Use Route Prefixing based on region (datacenter) and cache cluster.
- *                See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and
- *                https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
+ *               See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and
+ *               https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup.
  *          - 2) To increase the consistency of delete() and touchCheckKey() during cache
- *                server membership changes, you can use the OperationSelectorRoute to
- *                configure 'set' and 'delete' operations to go to all servers in the cache
- *                cluster, instead of just one server determined by hashing.
- *                See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles
- *   - c) Ommit the 'purge' EventRelayer parameter and set up dynomite as cache middleware
- *         between the web servers and either memcached or redis. This will also broadcast all
- *         key setting operations, not just purges, which can be useful for cache warming.
- *         Writes are eventually consistent via the Dynamo replication model.
- *         See https://github.com/Netflix/dynomite
+ *               server membership changes, you can use the OperationSelectorRoute to
+ *               configure 'set' and 'delete' operations to go to all servers in the cache
+ *               cluster, instead of just one server determined by hashing.
+ *               See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles.
+ *   - C) Omit the 'purge' EventRelayer parameter and set up dynomite as cache middleware
+ *        between the web servers and either memcached or redis. This will broadcast all
+ *        key setting operations, not just purges, which can be useful for cache warming.
+ *        Writes are eventually consistent via the Dynamo replication model.
+ *        See https://github.com/Netflix/dynomite.
  *
  * Broadcasted operations like delete() and touchCheckKey() are done asynchronously
  * in all datacenters this way, though the local one should likely be near immediate.
index 2c1a782..60044ba 100644 (file)
@@ -56,7 +56,11 @@ class LBFactorySingle extends LBFactory {
         * @since 1.28
         */
        public static function newFromConnection( IDatabase $db, array $params = [] ) {
-               return new static( [ 'connection' => $db ] + $params );
+               return new static( array_merge(
+                       [ 'localDomain' => $db->getDomainID() ],
+                       $params,
+                       [ 'connection' => $db ]
+               ) );
        }
 
        /**
index 1b72502..5c0af11 100644 (file)
@@ -54,7 +54,8 @@ class LoadBalancerSingle extends LoadBalancer {
                        ],
                        'trxProfiler' => $params['trxProfiler'] ?? null,
                        'srvCache' => $params['srvCache'] ?? null,
-                       'wanCache' => $params['wanCache'] ?? null
+                       'wanCache' => $params['wanCache'] ?? null,
+                       'localDomain' => $params['localDomain'] ?? $this->db->getDomainID()
                ] );
 
                if ( isset( $params['readOnlyReason'] ) ) {
@@ -69,7 +70,11 @@ class LoadBalancerSingle extends LoadBalancer {
         * @since 1.28
         */
        public static function newFromConnection( IDatabase $db, array $params = [] ) {
-               return new static( [ 'connection' => $db ] + $params );
+               return new static( array_merge(
+                       [ 'localDomain' => $db->getDomainID() ],
+                       $params,
+                       [ 'connection' => $db ]
+               ) );
        }
 
        protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
index 147c9f3..24cc8b5 100644 (file)
@@ -1961,7 +1961,7 @@ class WikiPage implements Page, IDBAccessObject {
         * Purges pages that include this page if the text was changed here.
         * Every 100th edit, prune the recent changes table.
         *
-        * @deprecated since 1.32, use PageUpdater::doEditUpdates instead.
+        * @deprecated since 1.32, use PageUpdater::doUpdates instead.
         *
         * @param Revision $revision
         * @param User $user User object that did the revision
@@ -3152,6 +3152,9 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Image redirects
                RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+
+               // Purge cross-wiki cache entities referencing this page
+               self::purgeInterwikiCheckKey( $title );
        }
 
        /**
@@ -3190,14 +3193,41 @@ class WikiPage implements Page, IDBAccessObject {
                // Clear file cache for this page only
                HTMLFileCache::clearFileCache( $title );
 
+               // Purge ?action=info cache
                $revid = $revision ? $revision->getId() : null;
                DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) {
                        InfoAction::invalidateCache( $title, $revid );
                } );
+
+               // Purge cross-wiki cache entities referencing this page
+               self::purgeInterwikiCheckKey( $title );
        }
 
        /**#@-*/
 
+       /**
+        * Purge the check key for cross-wiki cache entries referencing this page
+        *
+        * @param Title $title
+        */
+       private static function purgeInterwikiCheckKey( Title $title ) {
+               global $wgEnableScaryTranscluding;
+
+               if ( !$wgEnableScaryTranscluding ) {
+                       return; // @todo: perhaps this wiki is only used as a *source* for content?
+               }
+
+               DeferredUpdates::addCallableUpdate( function () use ( $title ) {
+                       $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+                       $cache->resetCheckKey(
+                               // Do not include the namespace since there can be multiple aliases to it
+                               // due to different namespace text definitions on different wikis. This only
+                               // means that some cache invalidations happen that are not strictly needed.
+                               $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
+                       );
+               } );
+       }
+
        /**
         * Returns a list of categories this page is a member of.
         * Results will include hidden categories
index 6bee169..78265e8 100644 (file)
@@ -3783,57 +3783,68 @@ class Parser {
         * Transclude an interwiki link.
         *
         * @param Title $title
-        * @param string $action
+        * @param string $action Usually one of (raw, render)
         *
         * @return string
         */
        public function interwikiTransclude( $title, $action ) {
-               global $wgEnableScaryTranscluding;
+               global $wgEnableScaryTranscluding, $wgTranscludeCacheExpiry;
 
                if ( !$wgEnableScaryTranscluding ) {
                        return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
                }
 
                $url = $title->getFullURL( [ 'action' => $action ] );
-
-               if ( strlen( $url ) > 255 ) {
+               if ( strlen( $url ) > 1024 ) {
                        return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
                }
-               return $this->fetchScaryTemplateMaybeFromCache( $url );
-       }
 
-       /**
-        * @param string $url
-        * @return mixed|string
-        */
-       public function fetchScaryTemplateMaybeFromCache( $url ) {
-               global $wgTranscludeCacheExpiry;
-               $dbr = wfGetDB( DB_REPLICA );
-               $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
-               $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
-                               [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
-               if ( $obj ) {
-                       return $obj->tc_contents;
-               }
-
-               $req = MWHttpRequest::factory( $url, [], __METHOD__ );
-               $status = $req->execute(); // Status object
-               if ( $status->isOK() ) {
-                       $text = $req->getContent();
-               } elseif ( $req->getStatus() != 200 ) {
+               $wikiId = $title->getTransWikiID(); // remote wiki ID or false
+
+               $fname = __METHOD__;
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+
+               $data = $cache->getWithSetCallback(
+                       $cache->makeGlobalKey(
+                               'interwiki-transclude',
+                               ( $wikiId !== false ) ? $wikiId : 'external',
+                               sha1( $url )
+                       ),
+                       $wgTranscludeCacheExpiry,
+                       function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
+                               $req = MWHttpRequest::factory( $url, [], $fname );
+
+                               $status = $req->execute(); // Status object
+                               if ( !$status->isOK() ) {
+                                       $ttl = $cache::TTL_UNCACHEABLE;
+                               } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
+                                       $ttl = min( $cache::TTL_LAGGED, $ttl );
+                               }
+
+                               return [
+                                       'text' => $status->isOK() ? $req->getContent() : null,
+                                       'code' => $req->getStatus()
+                               ];
+                       },
+                       [
+                               'checkKeys' => ( $wikiId !== false )
+                                       ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
+                                       : [],
+                               'pcGroup' => 'interwiki-transclude:5',
+                               'pcTTL' => $cache::TTL_PROC_LONG
+                       ]
+               );
+
+               if ( is_string( $data['text'] ) ) {
+                       $text = $data['text'];
+               } elseif ( $data['code'] != 200 ) {
                        // Though we failed to fetch the content, this status is useless.
-                       return wfMessage( 'scarytranscludefailed-httpstatus' )
-                               ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
+                       $text = wfMessage( 'scarytranscludefailed-httpstatus' )
+                               ->params( $url, $data['code'] )->inContentLanguage()->text();
                } else {
-                       return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
+                       $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
                }
 
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->replace( 'transcache', [ 'tc_url' ], [
-                       'tc_url' => $url,
-                       'tc_time' => $dbw->timestamp( time() ),
-                       'tc_contents' => $text
-               ] );
                return $text;
        }
 
index bf61779..eb56e13 100644 (file)
@@ -45,6 +45,7 @@ class ExtensionProcessor implements Processor {
                'MediaHandlers',
                'PasswordPolicy',
                'RateLimits',
+               'RawHtmlMessages',
                'RecentChangesFlags',
                'RemoveCredentialsBlacklist',
                'RemoveGroups',
index 59853b4..1569e08 100644 (file)
@@ -183,23 +183,19 @@ class VersionChecker {
                                'missing' => $dependencyName,
                        ];
                }
+               if ( $constraint === '*' ) {
+                       // short-circuit since any version is OK.
+                       return false;
+               }
                // Check if the dependency has specified a version
                if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
-                       // If we depend upon any version, and none is set, that's fine.
-                       if ( $constraint === '*' ) {
-                               wfDebug( "{$dependencyName} does not expose its version, but {$checkedExt}"
-                                       . " mentions it with constraint '*'. Assume it's ok so." );
-                               return false;
-                       } else {
-                               // Otherwise, mark it as incompatible.
-                               $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
-                                       . " requires: {$constraint}.";
-                               return [
-                                       'msg' => $msg,
-                                       'type' => "incompatible-$type",
-                                       'incompatible' => $checkedExt,
-                               ];
-                       }
+                       $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
+                               . " requires: {$constraint}.";
+                       return [
+                               'msg' => $msg,
+                               'type' => "incompatible-$type",
+                               'incompatible' => $checkedExt,
+                       ];
                } else {
                        // Try to get a constraint for the dependency version
                        try {
index de25d32..69313bf 100644 (file)
@@ -76,6 +76,89 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
                return $styles;
        }
 
+       /**
+        * @param ResourceLoaderContext $context
+        * @return array
+        */
+       public function getPreloadLinks( ResourceLoaderContext $context ) {
+               return $this->getLogoPreloadlinks();
+       }
+
+       /**
+        * Helper method for getPreloadLinks()
+        * @return array
+        */
+       private function getLogoPreloadlinks() {
+               $logo = $this->getLogoData( $this->getConfig() );
+
+               $tags = [];
+               $logosPerDppx = [];
+               $logos = [];
+
+               $preloadLinks = [];
+
+               if ( !is_array( $logo ) ) {
+                       // No media queries required if we only have one variant
+                       $preloadLinks[ $logo ] = [ 'as' => 'image' ];
+                       return $preloadLinks;
+               }
+
+               if ( isset( $logo['svg'] ) ) {
+                       // No media queries required if we only have a 1x and svg variant
+                       // because all preload-capable browsers support SVGs
+                       $preloadLinks [ $logo['svg'] ] = [ 'as' => 'image' ];
+                       return $preloadLinks;
+               }
+
+               foreach ( $logo as $dppx => $src ) {
+                       // Keys are in this format: "1.5x"
+                       $dppx = substr( $dppx, 0, -1 );
+                       $logosPerDppx[$dppx] = $src;
+               }
+
+               // Because PHP can't have floats as array keys
+               uksort( $logosPerDppx, function ( $a , $b ) {
+                       $a = floatval( $a );
+                       $b = floatval( $b );
+                       // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
+                       return $a <=> $b;
+               } );
+
+               foreach ( $logosPerDppx as $dppx => $src ) {
+                       $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
+               }
+
+               $logosCount = count( $logos );
+               // Logic must match ResourceLoaderSkinModule:
+               // - 1x applies to resolution < 1.5dppx
+               // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
+               // - 2x applies to resolution >= 2dppx
+               // Note that min-resolution and max-resolution are both inclusive.
+               for ( $i = 0; $i < $logosCount; $i++ ) {
+                       if ( $i === 0 ) {
+                               // Smallest dppx
+                               // min-resolution is ">=" (larger than or equal to)
+                               // "not min-resolution" is essentially "<"
+                               $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
+                       } elseif ( $i !== $logosCount - 1 ) {
+                               // In between
+                               // Media query expressions can only apply "not" to the entire expression
+                               // (e.g. can't express ">= 1.5 and not >= 2).
+                               // Workaround: Use <= 1.9999 in place of < 2.
+                               $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
+                               $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
+                                       'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
+                       } else {
+                               // Largest dppx
+                               $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
+                       }
+
+                       $preloadLinks[ $logos[$i]['src'] ] = [ 'as' => 'image', 'media' => $media_query ];
+               }
+
+               return $preloadLinks;
+       }
+
        /**
         * Ensure all media keys use array values.
         *
index 679acc6..11f2d13 100644 (file)
  * Item class for a archive table row
  */
 class RevDelArchiveItem extends RevDelRevisionItem {
-       public function __construct( $list, $row ) {
-               RevDelItem::__construct( $list, $row );
-               $this->revision = Revision::newFromArchiveRow( $row,
-                       [ 'page' => $this->list->title->getArticleID() ] );
+       protected static function initRevision( $list, $row ) {
+               return Revision::newFromArchiveRow( $row,
+                       [ 'page' => $list->title->getArticleID() ] );
        }
 
        public function getIdField() {
index d36fac9..00e40a0 100644 (file)
@@ -29,11 +29,14 @@ class RevDelArchivedFileItem extends RevDelFileItem {
        protected $lockFile;
 
        public function __construct( $list, $row ) {
-               RevDelItem::__construct( $list, $row );
-               $this->file = ArchivedFile::newFromRow( $row );
+               parent::__construct( $list, $row );
                $this->lockFile = RepoGroup::singleton()->getLocalRepo()->newFile( $row->fa_name );
        }
 
+       protected static function initFile( $list, $row ) {
+               return ArchivedFile::newFromRow( $row );
+       }
+
        public function getIdField() {
                return 'fa_id';
        }
index d839fcf..fd214e1 100644 (file)
  * used via RevDelRevisionList.
  */
 class RevDelArchivedRevisionItem extends RevDelArchiveItem {
-       public function __construct( $list, $row ) {
-               RevDelItem::__construct( $list, $row );
-
-               $this->revision = Revision::newFromArchiveRow( $row,
-                       [ 'page' => $this->list->title->getArticleID() ] );
-       }
-
        public function getIdField() {
                return 'ar_rev_id';
        }
index 0ca84d7..c7941b7 100644 (file)
@@ -30,7 +30,18 @@ class RevDelFileItem extends RevDelItem {
 
        public function __construct( $list, $row ) {
                parent::__construct( $list, $row );
-               $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+               $this->file = static::initFile( $list, $row );
+       }
+
+       /**
+        * Create file object from $row sourced from $list
+        *
+        * @param RevDelFileList $list
+        * @param mixed $row
+        * @return mixed
+        */
+       protected static function initFile( $list, $row ) {
+               return RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
        }
 
        public function getIdField() {
index 7b5d130..2cfa2ab 100644 (file)
@@ -28,7 +28,18 @@ class RevDelRevisionItem extends RevDelItem {
 
        public function __construct( $list, $row ) {
                parent::__construct( $list, $row );
-               $this->revision = new Revision( $row );
+               $this->revision = static::initRevision( $list, $row );
+       }
+
+       /**
+        * Create revision object from $row sourced from $list
+        *
+        * @param RevisionListBase $list
+        * @param mixed $row
+        * @return Revision
+        */
+       protected static function initRevision( $list, $row ) {
+               return new Revision( $row );
        }
 
        public function getIdField() {
index b05fb0b..2f5e0c8 100644 (file)
@@ -503,6 +503,10 @@ abstract class Skin extends ContextSource {
 
        /**
         * Whether the logo should be preloaded with an HTTP link header or not
+        *
+        * @deprecated since 1.32 Redundant. It now happens automatically based on whether
+        *  the skin loads a stylesheet based on ResourceLoaderSkinModule, which all
+        *  skins that use wgLogo in CSS do, and other's would not.
         * @since 1.29
         * @return bool
         */
index b44d409..564220c 100644 (file)
@@ -1055,13 +1055,13 @@ class SkinTemplate extends Skin {
                                        }
                                } else {
                                        // article doesn't exist or is deleted
-                                       if ( $user->isAllowed( 'deletedhistory' ) ) {
+                                       if ( $title->quickUserCan( 'deletedhistory', $user ) ) {
                                                $n = $title->isDeleted();
                                                if ( $n ) {
                                                        $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() );
                                                        // If the user can't undelete but can view deleted
                                                        // history show them a "View .. deleted" tab instead.
-                                                       $msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
+                                                       $msgKey = $title->quickUserCan( 'undelete', $user ) ? 'undelete' : 'viewdeleted';
                                                        $content_navigation['actions']['undelete'] = [
                                                                'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
                                                                'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
index 975d64e..3f87712 100644 (file)
@@ -61,7 +61,9 @@ class DeletedContributionsPage extends SpecialPage {
                $opts->validateIntBounds( 'limit', 0, $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
 
                if ( $par !== null ) {
-                       $opts->setValue( 'target', $par );
+                       // Beautify the username
+                       $par = User::getCanonicalName( $par, false );
+                       $opts->setValue( 'target', (string)$par );
                }
 
                $ns = $opts->getValue( 'namespace' );
index ac10c2f..be79cae 100644 (file)
@@ -437,7 +437,7 @@ class SpecialExport extends SpecialPage {
                $pages = [];
 
                foreach ( $res as $row ) {
-                       $pages[] = Title::makeName( $row->page_title, $row->page_namespace );
+                       $pages[] = Title::makeName( $row->page_namespace, $row->page_title );
                }
 
                return $pages;
@@ -462,7 +462,7 @@ class SpecialExport extends SpecialPage {
                $pages = [];
 
                foreach ( $res as $row ) {
-                       $pages[] = Title::makeName( $row->page_title, $row->page_namespace );
+                       $pages[] = Title::makeName( $row->page_namespace, $row->page_title );
                }
 
                return $pages;
index aaea4c7..b1977cf 100644 (file)
        "confirm-unwatch-top": "¿Desaniciar esta páxina de la to llista de vixilancia?",
        "confirm-rollback-button": "Aceutar",
        "confirm-rollback-top": "¿Revertir les ediciones a esta páxina?",
+       "confirm-mcrundo-title": "Desfacer un cambéu",
+       "mcrundofailed": "Falló desfacer",
+       "mcrundo-missingparam": "Faltan parámetros riquíos na solicitú.",
+       "mcrundo-changed": "La páxina cambió desque visti les diferencies. Revisa'l cambiu nuevu.",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← páxina anterior",
        "imgmultipagenext": "páxina siguiente →",
index d8757d3..98d0ee6 100644 (file)
        "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файлу «$1».",
        "backend-fail-notexists": "Файл $1 не існуе.",
        "backend-fail-hashes": "Немагчыма атрымаць хэшы файлаў для параўнаньня.",
-       "backend-fail-notsame": "Ð\9dеÑ\96дÑ\8dнÑ\82Ñ\8bÑ\84Ñ\96каванÑ\8b Ñ\84айл Ñ\83жо Ñ\96Ñ\81нÑ\83е $1.",
-       "backend-fail-invalidpath": "$1 не зьяўляецца слушным шляхам да сховішча.",
+       "backend-fail-notsame": "Ужо Ñ\96Ñ\81нÑ\83е Ð½ÐµÑ\96дÑ\8dнÑ\82Ñ\8bÑ\87нÑ\8b Ñ\84айл Â«$1».",
+       "backend-fail-invalidpath": "«$1» не зьяўляецца слушным шляхам да сховішча.",
        "backend-fail-delete": "Немагчыма выдаліць файл $1.",
        "backend-fail-describe": "Не атрымалася зьмяніць мэтазьвесткі для файла «$1».",
        "backend-fail-alreadyexists": "Файл $1 ужо існуе.",
        "confirm-unwatch-top": "Выдаліць гэтую старонку з Вашага сьпісу назіраньня?",
        "confirm-rollback-button": "Так",
        "confirm-rollback-top": "Адкаціць праўкі на гэтай старонцы?",
+       "confirm-mcrundo-title": "Адмяніць зьмену",
+       "mcrundofailed": "Адмена не атрымалася",
+       "mcrundo-missingparam": "Адсутнічаюць абавязковыя парамэтры для запыту.",
+       "mcrundo-changed": "Гэтая старонка была зьмененая з моманту, калі вы праглядалі зьмены. Калі ласка, праглядзіце новую зьмену.",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← папярэдняя старонка",
        "imgmultipagenext": "наступная старонка →",
index bd5ac50..dd4bd76 100644 (file)
@@ -34,7 +34,8 @@
                        "Andrus",
                        "Da voli",
                        "OlegCinema",
-                       "Gorgich"
+                       "Gorgich",
+                       "Vlad5250"
                ]
        },
        "tog-underline": "Падкрэсліваць спасылкі:",
        "right-bot": "Лічыцца аўтаматычным працэсам",
        "right-nominornewtalk": "Не паведамляць пра новыя паведамленні ў адказ на дробныя праўкі размоўных старонак",
        "right-apihighlimits": "Карыстацца вышэйшымі лімітамі ў API-зваротах",
-       "right-writeapi": "Карыстацца праграмным інтэрфейсам запісу (write API)",
+       "right-writeapi": "Карыстацца праграмным інтэрфейсам запісу (''write API'')",
        "right-delete": "Выдаляць старонкі",
        "right-bigdelete": "Выдаляць старонкі з вялікімі гісторыямі",
        "right-deletelogentry": "Выдаляць і аднаўляць асобныя запісы журналаў",
index 7d39af6..ee6699b 100644 (file)
        "ns-specialprotected": "Специалните страници не могат да бъдат редактирани.",
        "titleprotected": "Тази страница е била защитена срещу създаване от [[User:$1|$1]].\nПосочената причина е <em>$2</em>.",
        "filereadonlyerror": "Файлът „$1“ не може да бъде променен, тъй като файловото хранилище „$2“ е в режим само за четене.\n\nСистемният администратор, който го е заключил, е посочил следната причина: „$3“.",
+       "invalidtitle": "Невалидно заглавие",
        "invalidtitle-knownnamespace": "Невалидно заглавие с именно пространство „$2“ и текст „$3“",
        "invalidtitle-unknownnamespace": "Невалидно заглавие с неразпознато именно пространство номер $1 и текст „$2“",
        "exception-nologin": "Не сте влезли в системата",
        "rcfilters-group-results-by-page": "Групиране на резултатите по страница",
        "rcfilters-activefilters": "Активни филтри",
        "rcfilters-activefilters-hide": "Скриване",
+       "rcfilters-activefilters-show": "Показване",
        "rcfilters-advancedfilters": "Разширени филтри",
        "rcfilters-limit-title": "Резултати за показване",
        "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|промяна|промени}}, $2",
index f5c3f74..4ef0b8a 100644 (file)
@@ -60,7 +60,8 @@
                        "Abella",
                        "Pierpao",
                        "Amire80",
-                       "Leptictidium"
+                       "Leptictidium",
+                       "Townie"
                ]
        },
        "tog-underline": "Subratlla els enllaços:",
        "uploadstash-bad-path-invalid": "El camí no és vàlid.",
        "uploadstash-bad-path-unknown-type": "El tipus «$1» és desconegut.",
        "uploadstash-bad-path-unrecognized-thumb-name": "Nom de miniatura no reconegut.",
+       "uploadstash-file-not-found-no-thumb": "No s'ha pogut obtenir una miniatura.",
        "uploadstash-file-not-found-not-exists": "No es pot trobar el camí, o bé no és un fitxer pla.",
        "uploadstash-file-too-large": "No es pot servir un fitxer més gran de $1 bytes.",
+       "uploadstash-not-logged-in": "Cap usuari ha iniciat una sessió. Els fitxers han de pertànyer als usuaris.",
+       "uploadstash-wrong-owner": "Aquest fitxer ($1) no pertany a l'usuari actual.",
        "uploadstash-no-extension": "L’extensió és nul·la.",
        "uploadstash-zero-length": "El fitxer té mida zero.",
        "invalid-chunk-offset": "El desplaçament del fragment no és vàlid",
        "http-timed-out": "La petició HTTP ha expirat.",
        "http-curl-error": "Error en recuperar l'URL: $1",
        "http-bad-status": "Hi ha hagut un problema durant la petició HTTP: $1 $2",
+       "http-internal-error": "Error intern HTTP.",
        "upload-curl-error6": "No s'ha pogut accedir a l'URL",
        "upload-curl-error6-text": "No s'ha pogut accedir a l'URL que s'ha proporcionat. Torneu a comprovar que sigui correcte i que el lloc estigui funcionant.",
        "upload-curl-error28": "S'ha excedit el temps d'espera de la càrrega",
        "filehist-filesize": "Mida del fitxer",
        "filehist-comment": "Comentari",
        "imagelinks": "Ús del fitxer",
-       "linkstoimage": "{{PLURAL:$1|La pàgina següent enllaça|Les $1 pàgines següents enllacen}} a aquest fitxer:",
+       "linkstoimage": "{{PLURAL:$1|La pàgina següent utilitza|Les $1 pàgines següents utilitzen}} aquest fitxer:",
        "linkstoimage-more": "Hi ha més de $1 {{PLURAL:$1|pàgina que enllaça|pàgines que enllacen}} a aquest fitxer.\nLa següent llista només mostra {{PLURAL:$1|la primera d'aquestes pàgines|les primeres $1 d'aquestes pàgines}}.\nPodeu consultar la [[Special:WhatLinksHere/$2|llista completa]].",
        "nolinkstoimage": "No hi ha pàgines que enllacin a aquesta imatge.",
        "morelinkstoimage": "Visualitza [[Special:WhatLinksHere/$1|més enllaços]] que porten al fitxer.",
        "namespace_association": "Espai de noms associat",
        "tooltip-namespace_association": "Marqueu aquesta casella per incloure l'espai de noms de discussió o de no discussió associat a l'espai de noms seleccionat",
        "blanknamespace": "(Principal)",
-       "contributions": "Contribucions de {{GENDER:$1|lusuari|la usuària}}",
+       "contributions": "Contribucions de {{GENDER:$1|l'usuari|la usuària}}",
        "contributions-title": "Contribucions de l'usuari $1",
        "mycontris": "Contribucions",
        "anoncontribs": "Contribucions",
        "sp-contributions-newbies-title": "Contribucions dels comptes d'usuari més nous",
        "sp-contributions-blocklog": "Registre de blocatges",
        "sp-contributions-suppresslog": "contribucions suprimides de {{GENDER:$1|l'usuari|la usuària}}",
-       "sp-contributions-deleted": "Contribucions de {{GENDER:$1|lusuari|la usuària}} esborrades",
+       "sp-contributions-deleted": "Contribucions de {{GENDER:$1|l'usuari|la usuària}} esborrades",
        "sp-contributions-uploads": "càrregues",
        "sp-contributions-logs": "registres",
        "sp-contributions-talk": "discussió",
        "confirm-unwatch-top": "Voleu treure aquesta pàgina de la llista de seguiment?",
        "confirm-rollback-button": "D'acord",
        "confirm-rollback-top": "Voleu revertir les modificacions a la pàgina?",
+       "confirm-mcrundo-title": "Desfés un canvi",
        "colon-separator": ":&#32;",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← pàgina anterior",
index 8e2621e..901629c 100644 (file)
        "rcfilters-view-tags": "دەستکارییە تاگکراوەکان",
        "rcfilters-view-tags-help-icon-tooltip": "زیاتر بزانە لەسەر دەستکارییە تاگکراوەکان",
        "rcfilters-liveupdates-button": "نوێکردنەوەی زیندوو",
+       "rcfilters-watchlist-edit-watchlist-button": "دەستکاریکردنی پێڕستی پەڕە چاودێریکراوەکانت",
+       "rcfilters-watchlist-showupdated": "ئەو پەڕانەی دەستکاریکراون و لەکاتی دەستکاریکردنەکەوە سەردانت نەکردوونەتەوە بە <strong>تۆخ</strong> دەردەکەون، بە نیشانی پڕکراوەوە.",
        "rcnotefrom": "ژێرەوە {{PLURAL:$5|گۆڕانکارییەکەیە|گۆڕانکارییەکانە}} لە <strong>$3، $4</strong>ەوە (ھەتا <strong>$1</strong> نیشان دراوە).",
        "rclistfromreset": "گەڕاندنەوەی ھەڵبژاردەی بەروار",
        "rclistfrom": "گۆڕانکارییە نوێکان نیشان بدە بە دەستپێکردن لە $3 $2",
index 89eb5d7..96b50ec 100644 (file)
        "confirm-unwatch-top": "Diese Seite von der persönlichen Beobachtungsliste entfernen?",
        "confirm-rollback-button": "Okay",
        "confirm-rollback-top": "Bearbeitungen an dieser Seite zurücksetzen?",
+       "confirm-mcrundo-title": "Eine Änderung rückgängig machen",
+       "mcrundofailed": "Rückgängigmachung fehlgeschlagen",
+       "mcrundo-missingparam": "Erforderliche Parameter fehlen bei der Anfrage.",
+       "mcrundo-changed": "Die Seite wurde verändert, seit du dir den Versionsunterschied ansiehst. Bitte überprüfe die neue Änderung.",
        "ellipsis": "…",
        "percent": "$1&#160;%",
        "quotation-marks": "„$1“",
        "edit-error-long": "Fehler:\n\n$1",
        "revid": "Version $1",
        "pageid": "Seitenkennung $1",
-       "interfaceadmin-info": "$1\n\nBerechtigungen für das Bearbeiten von wikiweiten CSS/JS/JSON-Dateien wurden kürzlich von dem Recht <code>editinterface</code> getrennt. Falls du nicht verstehst, warum du diesen Fehler erhältst, siehe bitte [[mw:MediaWiki_1.32/interface-admin]].",
+       "interfaceadmin-info": "$1\n\nBerechtigungen für das Bearbeiten von wikiweiten CSS/JS/JSON-Dateien wurden kürzlich vom Recht <code>editinterface</code> getrennt. Falls du nicht verstehst, warum du diesen Fehler erhältst, siehe [[mw:MediaWiki_1.32/interface-admin]].",
        "rawhtml-notallowed": "&lt;html&gt;-Tags können nicht außerhalb von normalen Seiten verwendet werden.",
        "gotointerwiki": "{{SITENAME}} verlassen",
        "gotointerwiki-invalid": "Der angegebene Titel ist ungültig.",
index 2fd9783..72b7a15 100644 (file)
        "confirm-unwatch-top": "¿Quitar esta página de tu lista de seguimiento?",
        "confirm-rollback-button": "Aceptar",
        "confirm-rollback-top": "¿Revertir las ediciones a esta página?",
+       "confirm-mcrundo-title": "Deshacer un cambio",
+       "mcrundofailed": "Error al deshacer",
+       "mcrundo-missingparam": "Faltan parámetros requeridos en la solicitud.",
        "comma-separator": ",&#32;",
        "ellipsis": "…",
        "percent": "$1 %",
index ea902bb..609f692 100644 (file)
        "activeusers-noresult": "کاربری پیدا نشد.",
        "activeusers-submit": "نمایش کاربران فعال",
        "listgrouprights": "اختیارات گروه‌های کاربری",
-       "listgrouprights-summary": "فهرست زیر شامل گروه‌های کاربری تعریف شده در این ویکی و اختیارات داده شده به آن‌ها است.\nاطلاعات بیشتر در مورد هر یک از اختیارات را در [[{{MediaWiki:Listgrouprights-helppage}}]] بیابید.",
+       "listgrouprights-summary": "فهرست زیر شامل گروه‌های کاربری تعریف شده در این ویکی و اختیارات داده شده به آن‌ها است.\nاطلاعات بیشتر در مورد هر کدام از آنها را در [[{{MediaWiki:Listgrouprights-helppage}}|اختیارات گروه‌های کاربری]] بیابید.",
        "listgrouprights-key": "* <span class=\"listgrouprights-granted\">اختیارات داده‌شده</span>\n* <span class=\"listgrouprights-revoked\">اختیارات گرفته‌شده</span>",
        "listgrouprights-group": "گروه",
        "listgrouprights-rights": "دسترسی‌ها",
index c55acd3..8e6fcde 100644 (file)
        "filehist-filesize": "Tiedostokoko",
        "filehist-comment": "Kommentti",
        "imagelinks": "Tiedoston käyttö",
-       "linkstoimage": "{{PLURAL:$1|Seuraavalta sivulta|$1 sivulla}} on linkki tähän tiedostoon:",
-       "linkstoimage-more": "Enemmän kuin $1 {{PLURAL:$1|sivu|sivua}} linkittää tähän tiedostoon.\nSeuraava lista näyttää {{PLURAL:$1|ensimmäisen linkittävän sivun|$1 ensimmäistä linkittävää sivua}} tähän tiedostoon.\n[[Special:WhatLinksHere/$2|Koko lista]] on saatavilla.",
-       "nolinkstoimage": "Tähän tiedostoon ei ole linkkejä miltään sivulta.",
+       "linkstoimage": "{{PLURAL:$1|Seuraava sivu käyttää|Seuraavat $1 sivua käyttävät}} tätä tiedostoa:",
+       "linkstoimage-more": "Enemmän kuin $1 {{PLURAL:$1|sivu linkittää|sivua linkittävät}} tähän tiedostoon.\nSeuraava lista näyttää {{PLURAL:$1|ensimmäisen sivun, joka käyttää|$1 ensimmäistä sivua, jotka käyttävät}} vain tätä tiedostoa.\n[[Special:WhatLinksHere/$2|Koko lista]] on saatavilla.",
+       "nolinkstoimage": "Tätä tiedostoa ei käytetä millään sivulla.",
        "morelinkstoimage": "Näytä [[Special:WhatLinksHere/$1|lisää linkkejä]] tähän tiedostoon.",
        "linkstoimage-redirect": "$1 (tiedosto-ohjaus) $2",
        "duplicatesoffile": "{{PLURAL:$1|Seuraava tiedosto on tämän tiedoston kaksoiskappale|Seuraavat $1 tiedostoa ovat tämän tiedoston kaksoiskappaleita}} ([[Special:FileDuplicateSearch/$2|lisätietoja]]):",
        "uctop": "(uusin)",
        "month": "Alkaen kuukaudesta (ja aiemmin):",
        "year": "Vuosi",
-       "date": "Alkaen päivämäärästä (tai sitä aikaisemmasta):",
+       "date": "Alkaen päivämäärästä (ja sitä aiemmat):",
        "sp-contributions-newbies": "Näytä uusien tulokkaiden muutokset",
        "sp-contributions-newbies-sub": "Uusien käyttäjien muokkaukset",
        "sp-contributions-newbies-title": "Uusien käyttäjien muokkaukset",
        "confirm-unwatch-top": "Poistetaanko tämä sivu tarkkailulistaltasi?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Palauta tämän sivun muokkaukset?",
+       "confirm-mcrundo-title": "Kumoa muutos",
+       "mcrundofailed": "Kumoaminen epäonnistui",
+       "mcrundo-missingparam": "Tarvittavat parametrit puuttuvat pyynnöstä.",
+       "mcrundo-changed": "Sivu on muuttunut siitä lähtien, kun katsoit tätä muokkausta. Arvioi uusi muokkaus.",
        "percent": "$1&#160;%",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← edellinen sivu",
index 0e85f3c..7bc1cf3 100644 (file)
        "confirm-unwatch-top": "Supprimer cette page de votre liste de suivi ?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Révoquer les modifications de cette page ?",
+       "confirm-mcrundo-title": "Annuler une modification",
+       "mcrundofailed": "L’annulation a échoué",
+       "mcrundo-missingparam": "Paramètres obligatoires absents dans la requête.",
+       "mcrundo-changed": "La page a été modifiée depuis que vous avez affiché le diff. Veuillez revoir la nouvelle modification.",
        "semicolon-separator": "&nbsp;;&#32;",
        "colon-separator": "&nbsp;:&#32;",
        "percent": "$1&#160;%",
index 261420e..2889465 100644 (file)
        "userlogin-yourname-ph": "Antré zòt non di itilizatò",
        "createacct-another-username-ph": "Antré non-an di itilizatò",
        "yourpassword": "Mo di pas :",
-       "userlogin-yourpassword": "Mo di pas",
+       "userlogin-yourpassword": "Modipas",
        "userlogin-yourpassword-ph": "Antré zòt mo di pas",
        "createacct-yourpassword-ph": "Antré oun mo di pas",
        "yourpasswordagain": "Konfirmé mo di pas :",
-       "createacct-yourpasswordagain": "Konfirmé mo di pas",
+       "createacct-yourpasswordagain": "Konfirmen modipas-a",
        "createacct-yourpasswordagain-ph": "Antré òkò menm mo di pas",
        "userlogin-remembermypassword": "Gardé mo sésyon aktiv",
        "userlogin-signwithsecure": "Itilizé roun konnègsyon sékirizé",
index c379bd3..fc7756e 100644 (file)
        "confirm-unwatch-top": "להסיר את הדף הזה מרשימת המעקב שלך?",
        "confirm-rollback-button": "אישור",
        "confirm-rollback-top": "לשחזר את העריכות בדף זה?",
+       "confirm-mcrundo-title": "ביטול שינוי",
+       "mcrundofailed": "הביטול נכשל",
+       "mcrundo-missingparam": "חסרים פרמטרים נדרשים בבקשה.",
+       "mcrundo-changed": "הדף שונה מאז הצפייה האחרונה שלך בהבדלים בין הגרסאות. נא לבדוק את השינוי החדש.",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "→ לדף הקודם",
        "imgmultipagenext": "לדף הבא ←",
index 65fe679..bb6d864 100644 (file)
        "confirm-unwatch-top": "Remover iste pagina de tu observatorio?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Reverter le modificationes a iste pagina?",
+       "confirm-mcrundo-title": "Disfacer un modification",
+       "mcrundofailed": "Disfaction fallite",
+       "mcrundo-missingparam": "Manca parametros obligatori in le requesta.",
+       "mcrundo-changed": "Le pagina ha essite modificate post que tu examinava le differentias. Per favor revide le nove modification.",
        "quotation-marks": "“$1”",
        "imgmultipageprev": "← precedente pagina",
        "imgmultipagenext": "sequente pagina →",
index 7a3e68d..3c447f3 100644 (file)
        "confirm-unwatch-top": "Rimuovere questa pagina dalla tua lista degli osservati speciali?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Ripristinare le modifiche di questa pagina?",
+       "confirm-mcrundo-title": "Annulla una modifica",
+       "mcrundofailed": "Annullamento fallito",
+       "mcrundo-missingparam": "Parametri obbligatori mancanti nella richiesta.",
        "percent": "$1&#160;%",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← pagina precedente",
index 468bc69..4201ba8 100644 (file)
        "rcfilters-preference-help": "2017年のインターフェース更新およびそれ以降に追加された新しいツールを無効化します。",
        "rcfilters-watchlist-preference-label": "ウォッチリストの改善版を隠す",
        "rcfilters-filter-showlinkedfrom-label": "指定されたページのリンク先の変更を表示",
-       "rcfilters-filter-showlinkedfrom-option-label": "指定されたページから<strong>リンクされているページ</strong>",
+       "rcfilters-filter-showlinkedfrom-option-label": "指定されたページから<strong>リンクされているページ(リンク先)</strong>",
        "rcfilters-filter-showlinkedto-label": "指定されたページのリンク元の変更を表示",
-       "rcfilters-filter-showlinkedto-option-label": "指定されたページに<strong>リンクしているページ</strong>",
+       "rcfilters-filter-showlinkedto-option-label": "指定されたページに<strong>リンクしているページ(リンク元)</strong>",
        "rcfilters-target-page-placeholder": "ページ名(またはカテゴリ名)を入力",
        "rcnotefrom": "以下は<strong>$3 $4</strong>以降の{{PLURAL:$5|更新です}} (最大 <strong>$1</strong> 件)。",
        "rclistfromreset": "日時指定をリセット",
index 1b8d809..455a2fe 100644 (file)
        "cannotcreateaccount-text": "이 위키에서 직접 계정 만들기는 활성화되어 있지 않습니다.",
        "yourdomainname": "도메인 이름:",
        "password-change-forbidden": "이 위키에서 비밀번호를 바꿀 수 없습니다.",
-       "externaldberror": "인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
+       "externaldberror": "인증 데이터베이스에 오류가 있거나 외부 계정을 새로 고칠 권한이 없습니다.",
        "login": "로그인",
        "login-security": "사용자 정보 확인",
        "nav-login-createaccount": "로그인 / 계정 만들기",
        "powersearch-toggleall": "모두",
        "powersearch-togglenone": "모두 제외",
        "powersearch-remember": "향후 검색에 선택 기억하기",
-       "search-external": "바깥 검색",
+       "search-external": "외부 검색",
        "searchdisabled": "{{SITENAME}} 검색이 비활성화되어 있습니다.\n검색이 작동하지 않는 동안 Google을 통해 검색할 수 있습니다.\n검색 엔진의 내용은 최신이 아닐 수 있다는 점을 참고하세요.",
        "search-error": "검색하는 동안 오류가 발생했습니다: $1",
        "search-warning": "검색하는 동안 경고가 발생했습니다: $1",
        "img-auth-nofile": "\"$1\" 파일이 없습니다.",
        "img-auth-isdir": "\"$1\" 디렉터리에 접근을 시도했습니다.\n파일에만 접근할 수 있습니다.",
        "img-auth-streaming": "\"$1\" 파일을 전송하는 중입니다.",
-       "img-auth-public": "img_auth.php는 개인 위키 파일을 바깥 사이트로 전송하는 기능입니다.\n이 기능은 기본적으로 공개적인 위키에서 사용하도록 설계되어 있습니다.\n보안적인 문제로 기본적으로 img_auth.php 기능은 비활성화되어 있습니다.",
+       "img-auth-public": "img_auth.php의 기능은 개인 위키의 파일을 외부로 전송하는 기능입니다.\n이 위키는 공개된 위키로 구성되어 있습니다.\n최적의 보안을 위해 img_auth.php는 비활성화되어 있습니다.",
        "img-auth-noread": "\"$1\" 파일을 볼 권한이 없습니다.",
        "http-invalid-url": "잘못된 URL: $1",
        "http-invalid-scheme": "\"$1\"(으)로 시작하는 URL은 지원되지 않습니다.",
        "wantedpages-summary": "다른 문서들에 링크는 걸려 있지만 존재하지 않는 문서들 중, 넘겨주기 문서를 제외한 목록입니다. 존재하지 않는 문서로 넘겨주는 문서 목록을 보려면 [[{{#special:BrokenRedirects}}|끊긴 넘겨주기 목록]]을 참조하세요.",
        "wantedpages-badtitle": "문서 제목이 잘못되었습니다: $1",
        "wantedfiles": "필요한 파일 목록",
-       "wantedfiletext-cat": "다음 파일은 쓰이고는 있지만 없는 파일입니다. 바깥 저장소에 있는 파일은 실제로는 있지만 여기 올라 있을 수 있습니다. 그런 오류는 <del>삭제선</del>이 그어질 것입니다. 또한 없는 파일을 포함하고 있는 문서는 [[:$1]]에 올라 있습니다.",
+       "wantedfiletext-cat": "다음 파일은 쓰이고는 있지만 없는 파일입니다. 외부 저장소의 파일은 존재하더라도 여기에 나열될 수 있습니다. 이러한 오류는 <del>삭제선</del>이 그어질 것입니다. 또한 없는 파일을 포함하고 있는 문서는 [[:$1]]에 나열됩니다.",
        "wantedfiletext-cat-noforeign": "다음 파일은 쓰이고 있지만 존재하지 않습니다. 또한, 존재하지 않는 파일이 포함된 문서가 [[:$1]]에 나열되어 있습니다.",
-       "wantedfiletext-nocat": "다음 파일은 쓰이고 있지만 존재하지 않습니다. 바깥 저장소에 있는 파일은 실제로는 있지만 여기 올라 있을 수 있습니다. 그런 오류는 <del>삭제선</del>이 그어질 것입니다.",
+       "wantedfiletext-nocat": "다음 파일은 쓰이고는 있지만 없는 파일입니다. 외부 저장소의 파일은 존재하더라도 여기에 나열될 수 있습니다. 이러한 오류는 <del>삭제선</del>이 그어질 것입니다.",
        "wantedfiletext-nocat-noforeign": "다음 파일은 쓰이고 있지만 존재하지 않습니다.",
        "wantedtemplates": "필요한 틀 목록",
        "mostlinked": "가장 많이 연결된 문서 목록",
        "booksources-search-legend": "책 원본 검색",
        "booksources-isbn": "ISBN:",
        "booksources-search": "검색",
-       "booksources-text": "ì\95\84ë\9e\98ì\9d\98 ëª©ë¡\9dì\9d\80 ì\83\88 ì±\85ì\9d´ë\82\98 ì¤\91ê³  ì±\85ì\9d\84 í\8c\90매í\95\98ë\8a\94 ë°\94ê¹¥ ì\82¬ì\9d´í\8a¸ë¡\9c, ì\9b\90í\95\98ë\8a\94 ì±\85ì\9d\98 ì \95보를 ì\96»ì\9d\84 ì\88\98 ì\9e\88ì\8aµë\8b\88ë\8b¤.",
+       "booksources-text": "ì\95\84ë\9e\98ì\97\90 ì\83\88 ì±\85ì\9d´ë\82\98 ì¤\91ê³  ì±\85ì\9d\84 í\8c\90매í\95\98ë\8a\94 ë\8b¤ë¥¸ ì\82¬ì\9d´í\8a¸ì\9d\98 ë§\81í\81¬ ëª©ë¡\9dì\9d´ ì\9e\88ì\9c¼ë©°, ì\9b\90í\95\98ë\8a\94 ì±\85ì\9d\98 ì¶\94ê°\80 ì \95ë³´ë\8f\84 í\99\95ì\9d¸í\95  ì\88\98 ì\9e\88ì\8aµë\8b\88ë\8b¤:",
        "booksources-invalid-isbn": "입력한 ISBN이 올바르지 않은 것으로 보입니다. 원본과 대조해 문제가 있는지 확인해보세요.",
        "magiclink-tracking-rfc": "RFC 매직 링크를 사용하는 문서",
        "magiclink-tracking-rfc-desc": "이 문서는 RFC 매직 링크를 사용합니다. 이관 방법을 보려면 [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org]를 참조하십시오.",
        "ipaddressorusername": "IP 주소 또는 사용자 이름:",
        "ipbexpiry": "기한:",
        "ipbreason": "이유:",
-       "ipbreason-dropdown": "*일반적인 차단 이유\n** 거짓 정보를 넣음\n** 문서 내용을 지움\n** 바깥 사이트의 광고성 링크를 넣음\n** 문서에 장난성 내용을 넣음\n** 협박성 행동\n** 다중 계정 악용\n** 부적절한 사용자 이름",
+       "ipbreason-dropdown": "*일반적인 차단 이유\n** 거짓 정보를 넣음\n** 문서 내용을 지움\n** 외부 사이트의 광고성 링크를 넣음\n** 문서에 장난성 내용을 넣음\n** 협박성 행동\n** 다중 계정 악용\n** 부적절한 사용자 이름",
        "ipb-hardblock": "이 IP를 이용하는 로그인한 사용자가 편집하는 것을 막기",
        "ipbcreateaccount": "계정 만들기를 막기",
        "ipbemailban": "이메일을 보내지 못하도록 막기",
        "confirm-unwatch-top": "이 문서를 주시문서 목록에서 뺄까요?",
        "confirm-rollback-button": "확인",
        "confirm-rollback-top": "이 문서의 편집을 되돌리시겠습니까?",
+       "confirm-mcrundo-title": "변경사항 취소",
+       "mcrundofailed": "실행 취소를 실패했습니다",
+       "mcrundo-missingparam": "요청에 필요한 변수가 존재하지 않습니다.",
+       "mcrundo-changed": "차이를 본 이후로 문서가 변경되었습니다. 새로운 변경사항을 검토해 주십시오.",
        "quotation-marks": "“$1”",
        "imgmultipageprev": "← 이전 페이지",
        "imgmultipagenext": "다음 페이지 →",
        "specialpages-group-developer": "개발자 도구",
        "blankpage": "빈 문서",
        "intentionallyblankpage": "일부러 비워 둔 문서입니다.",
-       "external_image_whitelist": " #이 줄은 그대로 두십시오<pre>\n#정규 표현식(// 사이에 있는 부분)을 아래에 입력하세요.\n#이 목록은 바깥 그림의 URL과 대조할 것입니다.\n#이 목록과 일치하는 것은 그림으로 표시되지만, 그렇지 않은 경우 그림을 가리키는 링크만 보이게 될 것입니다.\n#\"#\" 문자에서 줄의 끝까지는 주석입니다\n#이 목록은 대소문자를 구별하지 않습니다\n\n#모든 정규 표현식은 이 줄 위에 넣어 주십시오. 그리고 이 줄은 그대로 두십시오.</pre>",
+       "external_image_whitelist": " #이 줄은 그대로 두십시오<pre>\n#정규 표현식(// 사이에 있는 부분)을 아래에 입력하세요.\n#이 목록은 외부 그림의 URL과 대조할 것입니다.\n#이 목록과 일치하는 것은 그림으로 표시되지만, 그렇지 않은 경우 그림을 가리키는 링크만 보이게 될 것입니다.\n#\"#\" 문자에서 줄의 끝까지는 주석입니다\n#이 목록은 대소문자를 구별하지 않습니다\n\n#모든 정규 표현식은 이 줄 위에 넣어 주십시오. 그리고 이 줄은 그대로 두십시오.</pre>",
        "tags": "올바른 편집 태그",
        "tag-filter": "[[Special:Tags|태그]] 필터:",
        "tag-filter-submit": "필터",
index a7450a5..2cb3e4c 100644 (file)
        "confirm-unwatch-top": "Dës Säit vun Ärer Iwwerwaachungslëscht erofhuelen?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Ännerunge vun dëser Säit zrécksetzen?",
+       "confirm-mcrundo-title": "Eng Ännerung réckgängeg maachen",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← Vireg Säit",
        "imgmultipagenext": "nächst Säit →",
index e3a3066..dcd8560 100644 (file)
@@ -27,7 +27,8 @@
                        "Zuiks",
                        "Martinsdzerve",
                        "Nixiéoffset",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Mailman"
                ]
        },
        "tog-underline": "Pasvītrot saites:",
        "title-invalid-characters": "Pieprasītais lapas nosaukums satur nederīgus simbolus: \"$1\".",
        "title-invalid-magic-tilde": "Pieprasītās lapas nosaukums satur nederīgu maģiskās tildes virkni (<nowiki>~~~</nowiki>).",
        "title-invalid-leading-colon": "Pieprasītās lapas nosaukums satur neatļautu kolu tā sākumā.",
-       "perfcached": "Šie dati ir no servera kešatmiņas un var būt novecojuši. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
+       "perfcached": "Šie dati ir no servera kešatmiņas un var būt novecojuši. Kešatmiņā ir {{PLURAL:$1|pieejami|pieejams|pieejami}} ne vairāk kā {{PLURAL:$1|$1 rezultāti|viens rezultāts|$1 rezultāti}}.",
        "perfcachedts": "Šie dati ir no servera kešatmiņas (''cache''), kas pēdējo reizi bija atjaunota $1. Kešatmiņā {{PLURAL:$4|pieejami|pieejams|pieejami}} ne vairāk kā {{PLURAL:$4|$4 rezultāti|viens rezultāts|$4 rezultāti}}.",
        "querypage-no-updates": "Šīs lapas atjaunošana pagaidām ir atslēgta. Te esošie dati tuvākajā laikā netiks atjaunoti.",
        "viewsource": "Aplūkot kodu",
        "cannotchangeemail": "Konta e-pasta adresi nevar nomainīt šajā wiki.",
        "emaildisabled": "Šī vietne nevar nosūtīt e-pastus.",
        "accountcreated": "Konts izveidots",
-       "accountcreatedtext": "Lietotāja konts priekš $1 tika izveidots.",
+       "accountcreatedtext": "Lietotāja konts priekš [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|diskusija]]) tika izveidots.",
        "createaccount-title": "Dalībnieka konta izveidošana {{grammar:lokatīvs|{{SITENAME}}}}",
        "login-throttled": "Jūs esat veicis pārāk daudz pieslēgšanās mēģinājumus.\nLūdzu, uzgaidiet $1 pirms mēģiniet vēlreiz.",
        "login-abort-generic": "Pieteikšanās neizdevās — darbība pārtraukta",
        "yourtext": "Tavs teksts",
        "storedversion": "Saglabātā versija",
        "editingold": "'''BRĪDINĀJUMS: Saglabājot šo lapu, tu izmainīsi šīs lapas novecojušu versiju, un ar to tiks dzēstas visas izmaiņas, kas izdarītas pēc šīs versijas.'''",
+       "unicode-support-fail": "Izskatās, ka tava pārlūkprogramma neatbalsta Unicode. Labojums netika saglabāts.",
        "yourdiff": "Atšķirības",
        "copyrightwarning": "Lūdzu, ņem vērā, ka viss ieguldījums, kas veikts {{grammar:lokatīvs|{{SITENAME}}}}, ir uzskatāms par publiskotu saskaņā ar $2 (vairāk info skatīt $1).\nJa nevēlies, lai Tevis rakstīto kāds labo un izplata tālāk, tad, lūdzu, nepievieno to šeit!<br />\n\nIzvēloties \"Saglabāt lapu\", Tu apliecini, ka šo rakstu esi rakstījis vai papildinājis pats vai izmantojis informāciju no darba, ko neaizsargā autortiesības, vai tamlīdzīga brīvi pieejama resursa.\n'''BEZ ATĻAUJAS NEPIEVIENO DARBU, KO AIZSARGĀ AUTORTIESĪBAS!'''",
        "copyrightwarning2": "Lūdz ņem vērā, ka visu ieguldījumu {{grammar:lokatīvs|{{SITENAME}}}} var rediģēt, mainīt vai izdzēst citi lietotāji. Ja negribi lai ar tavu rakstīto tā izrīkojas, nepievieno to šeit.\n\nTu apliecini, ka šo rakstu esi rakstījis vai papildinājis pats vai izmantojis informāciju no darba, ko neaizsargā autortiesības, vai tamlīdzīga brīvi pieejama resursa (sīkāk skatīt $1).\n\n'''BEZ ATĻAUJAS NEPIEVIENO DARBU, KO AIZSARGĀ AUTORTIESĪBAS!'''",
        "page_last": "pēdējā",
        "histlegend": "Atšķirību izvēle: atzīmē vajadzīgo versiju apaļās pogas un spied \"Salīdzināt izvēlētās versijas\".<br />\nApzīmējumi:\n\"ar pašreizējo\" = salīdzināt ar pašreizējo versiju,\n\"ar iepriekšējo\" = salīdzināt ar iepriekšējo versiju,\nm = maznozīmīgs labojums.",
        "history-fieldset-title": "Versiju meklēšana",
-       "history-show-deleted": "Tikai dzēstās",
+       "history-show-deleted": "Tikai dzēstie labojumi",
        "histfirst": "Senākās",
        "histlast": "Jaunākās",
        "historysize": "({{PLURAL:$1|$1 baiti|1 baits|$1 baiti}})",
        "group-autoconfirmed": "Automātiski apstiprinātie dalībnieki",
        "group-bot": "Boti",
        "group-sysop": "Administratori",
+       "group-interface-admin": "Interfeisa administrators",
        "group-bureaucrat": "Birokrāti",
        "group-suppress": "Cenzētāji",
        "group-all": "(visi)",
        "grouppage-autoconfirmed": "{{ns:project}}:Automātiski apstiprināti dalībnieki",
        "grouppage-bot": "{{ns:project}}:Boti",
        "grouppage-sysop": "{{ns:project}}:Administratori",
+       "grouppage-interface-admin": "{{ns:project}}:Interfeisa administratori",
        "grouppage-bureaucrat": "{{ns:project}}:Birokrāti",
        "grouppage-suppress": "{{ns:project}}:Cenzētāji",
        "right-read": "Lasīt lapas",
        "right-deletedtext": "Apskatīt izdzēsto tekstu un izmaiņas starp izdzēstām versijām",
        "right-browsearchive": "Meklēt izdzēstās lapas",
        "right-undelete": "Atjaunot lapu",
-       "right-suppressrevision": "Apskatīt un atjaunot versijas, kas paslēptas no adminiem",
+       "right-suppressrevision": "Apskatīt un atjaunot visas lapas versijas",
        "right-suppressionlog": "Skatīt personīgos reģistrus",
        "right-block": "Bloķēt citus dalībniekus (lapu izmainīšana)",
        "right-blockemail": "Bloķēt citus dalībniekus (iespēja sūtīt e-pastu)",
index 64fbb7c..584a169 100644 (file)
        "filehist-filesize": "Големина",
        "filehist-comment": "Коментар",
        "imagelinks": "Употреба на податотеката",
-       "linkstoimage": "Ð\94о Ð¾Ð²Ð°Ð° Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82ека {{PLURAL:$1|води Ñ\81леднава Ñ\81Ñ\82Ñ\80аниÑ\86а|водаÑ\82 следниве $1 страници}}:",
-       "linkstoimage-more": "Ð\9fовеÑ\9cе Ð¾Ð´ {{PLURAL:$1|една Ñ\81Ñ\82Ñ\80аниÑ\86а Ðµ Ð¿Ð¾Ð²Ñ\80зана|$1 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\81е Ð¿Ð¾Ð²Ñ\80зани}} Ñ\81о Ð¾Ð²Ð°Ð° Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82ека.\nСледниов Ñ\81пиÑ\81ок {{PLURAL:$1|Ñ\98а Ð¿Ñ\80икажÑ\83ва Ñ\81амо Ð¿Ñ\80ваÑ\82а Ð¿Ð¾Ð²Ñ\80зана Ñ\81Ñ\82Ñ\80аниÑ\86а|ги Ð¿Ñ\80икажÑ\83ва Ñ\81амо Ð¿Ñ\80виÑ\82е $1 Ð¿Ð¾Ð²Ñ\80зани Ñ\81Ñ\82Ñ\80аниÑ\86и}} Ð´Ð¾ Ð¾Ð²Ð°Ð° Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82ека.\nЦелоÑ\81ен Ñ\81пиÑ\81ок Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð´Ð¾Ð±Ð¸ете [[Special:WhatLinksHere/$2|тука]].",
+       "linkstoimage": "Ð\9fодаÑ\82оÑ\82екава Ñ\81е ÐºÐ¾Ñ\80иÑ\81Ñ\82и Ð²Ð¾ {{PLURAL:$1|Ñ\81леднава Ñ\81Ñ\82Ñ\80аниÑ\86а|следниве $1 страници}}:",
+       "linkstoimage-more": "Ð\9fодаÑ\82оÑ\82екава Ñ\81е ÐºÐ¾Ñ\80иÑ\81Ñ\82и Ð²Ð¾ Ð¿Ð¾Ð²ÐµÑ\9cе Ð¾Ð´ {{PLURAL:$1|една Ñ\81Ñ\82Ñ\80аниÑ\86а|$1 Ñ\81Ñ\82Ñ\80аниÑ\86и}}.\nСледниов Ñ\81пиÑ\81ок {{PLURAL:$1|Ñ\98а Ð¿Ñ\80икажÑ\83ва Ñ\81амо Ð¿Ñ\80ваÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а|ги Ð¿Ñ\80икажÑ\83ва Ñ\81амо Ð¿Ñ\80виÑ\82е $1 Ñ\81Ñ\82Ñ\80аниÑ\86и}} Ñ\88Ñ\82о Ñ\98а ÐºÐ¾Ñ\80иÑ\81Ñ\82аÑ\82 Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82екаÑ\82а.\nЦелоÑ\81ен Ñ\81пиÑ\81ок Ñ\9cе Ð½Ð°Ñ\98дете [[Special:WhatLinksHere/$2|тука]].",
        "nolinkstoimage": "Нема страници што ја користат оваа податотека.",
        "morelinkstoimage": "Погледајте ги [[Special:WhatLinksHere/$1|останатите врски]] кон оваа податотека.",
        "linkstoimage-redirect": "$1 (пренасочување) $2",
        "confirm-unwatch-top": "Да ја отстранам страницава од набљудуваните?",
        "confirm-rollback-button": "ОК",
        "confirm-rollback-top": "Да ги отповикам уредувањата на страницава?",
+       "confirm-mcrundo-title": "Откажи промена",
+       "mcrundofailed": "Откажувањето не успеа",
+       "mcrundo-missingparam": "Недостасуваат задолжителни параметри за барањето.",
+       "mcrundo-changed": "Страницата е изменета откако ги гледавте разликите. Прегледајте ја новата промена.",
        "percent": "$1&#160;%",
        "quotation-marks": "„$1“",
        "imgmultipageprev": "← претходна страница",
index 94be465..3cbb4a7 100644 (file)
        "about": "အကြောင်း",
        "article": "မာတိကာစာမျက်နှာ",
        "newwindow": "(ဝင်းဒိုးအသစ်တစ်ခုတွင် ဖွင့်ရန်)",
-       "cancel": "မ​လုပ်​တော့​",
+       "cancel": "မ​လုပ်​တော့​ပါ",
        "moredotdotdot": "နောက်ထပ်...",
        "morenotlisted": "ဤစာရင်းမှာ မပြည့်စုံနိုင်ပါ။",
        "mypage": "စာမျက်နှာ",
        "updatedmarker": "နောက်ဆုံးကြည့်ပြီးသည့်နောက်ပိုင်း တည်းဖြတ်ထားသည်။",
        "printableversion": "ပရင့်ထုတ်နိုင်သော ဗားရှင်း",
        "permalink": "ပုံ​သေ​လိပ်​စာ​",
-       "print": "ပရင့်",
+       "print": "ပရင့်ထုတ်",
        "view": "ကြည့်ရန်",
        "view-foreign": "$1 တွင် ကြည့်ရန်",
        "edit": "ပြင်ဆင်ရန်",
        "yourpasswordagain": "စကားဝှက် ပြန်​ရိုက်​ပါ -",
        "createacct-yourpasswordagain": "စကားဝှက်ကို အတည်ပြုပါ",
        "createacct-yourpasswordagain-ph": "စကားဝှက်ကို ထပ်မံ ရိုက်ထည့်ပါ",
-       "userlogin-remembermypassword": "Log in ဝင်ထားမည်",
+       "userlogin-remembermypassword": "အကောင့်ထဲ ဝင်ထားမည်",
        "userlogin-signwithsecure": "လုံခြုံသော ဆက်သွယ်မှုကို သုံးမည်",
        "cannotlogin-title": "လော့ဂ်အင် မဝင်ရောက်နိုင်ပါ",
        "cannotlogin-text": "အကောင့်ထဲ ဝင်ရောက်ခြင်းမှာ မဖြစ်နိုင်ပါ",
        "userlogin-resetpassword-link": "စကားဝှက် မေ့နေသလား။",
        "userlogin-helplink2": "log in အကူအညီ",
        "userlogin-loggedin": "သင်သည် {{GENDER:$1|$1}} အနေဖြင့် လော့အင်ဝင်ထားပြီး ဖြစ်သည်။ အခြားအသုံးပြုသူ အနေဖြင့် ဝင်ရောက်ရန် အောက်ပါပုံစံကို အသုံးပြုပါ။",
-       "userlogin-reauth": "သင်သည် {{GENDER:$1|}} ဖြစ်ကြောင်း အတည်ပြုရန်အတွက် အကောင့်ထဲ ထပ်မံဝင်ရောက်ရပါမည်။",
+       "userlogin-reauth": "သင် {{GENDER:$1|}}ဖြစ်ကြောင်း အတည်ပြုရန်အတွက် အကောင့်ထဲ ထပ်မံဝင်ရောက်ရပါမည်။",
        "userlogin-createanother": "အခြားအကောင့် ဖန်တီးရန်",
        "createacct-emailrequired": "အီးမေး လိပ်စာ",
        "createacct-emailoptional": "အီးမေး လိပ်စာ (ဖြည့်လိုက)",
        "sig_tip": "အချိန်ပါပြသော သင့်လက်မှတ်",
        "hr_tip": "မျဉ်းလဲ (စိစစ်သုံးရန်)",
        "summary": "အ​ကျဉ်း​ချုပ်​ -",
-       "subject": "အကြောင်းအရာ -",
+       "subject": "အကြောင်းအရာ:",
        "minoredit": "အရေးမကြီးသော ​ပြင်​ဆင်​မှု ​ဖြစ်​သည်​",
        "watchthis": "ဤစာမျက်နှာကို စောင့်ကြည့်ရန်",
        "savearticle": "ဤစာမျက်နှာကို သိမ်းရန်",
        "email-allow-new-users-label": "အသစ်စက်စက် အသုံးပြုသူများဆီမှ အီးမေးလ်လက်ခံရန် ခွင့်ပြုမည်",
        "email-blacklist-label": "ဤအသုံးပြုသူများ မိမိအား အီးမေးလ်ပို့ခြင်းကို ပိတ်ထားမည်",
        "prefs-searchoptions": "ရှာဖွေရန်",
-       "prefs-namespaces": "အမည်ညွှန်း",
+       "prefs-namespaces": "အမည်ညွှန်းများ",
        "default": "ပုံမှန်အားဖြင့်",
        "prefs-files": "ဖိုင်များ",
        "prefs-custom-css": "စိတ်ကြိုက် CSS",
        "sharedupload-desc-create": "ဤဖိုင်သည် $1 မှဖြစ်ပြီး အခြားပရောဂျက်များတွင်လည်း အသုံးပြုနိုင်သည်။ [$2 ဖိုင်ဖော်ပြချက် စာမျက်နှာ]ပေါ်ရှိ ဖော်ပြချက်ကို တည်းဖြတ်နိုင်သည်။",
        "filepage-nofile": "ဤအမည်ဖြင့် မည်သည့်ဖိုင်မှ မရှိပါ။",
        "filepage-nofile-link": "ဤအမည်ဖြင့် မည်သည့်ဖိုင်မှ မရှိပါ။ သိုရာတွင် ယင်းကို [$1 upload တင်]နိုင်သည်။",
-       "uploadnewversion-linktext": "ဤဖိုင်၏ နောက်ဆုံး version ကို upload တင်ရန်",
+       "uploadnewversion-linktext": "ဤဖိုင်၏ နောက်ဆုံးဗာရှင်းကို အပ်လုပ်တင်ရန်",
        "shared-repo-from": "$1 ထံမှ",
        "shared-repo-name-wikimediacommons": "ဝီကီမီဒီယာ ကွန်မွန်းစ်",
        "upload-disallowed-here": "သင်သည် ဤဖိုင်အား ထပ်၍ ရေးသားမရနိုင်ပါ။",
        "filerevert-submit": "ပြောင်းရန်",
        "filedelete": "$1 ကိုဖျက်ပါ",
        "filedelete-legend": "ဖိုင်ကိုဖျက်ပါ",
+       "filedelete-intro": "သင်သည် <strong>[[Media:$1|$1]]</strong> ဖိုင်အား ယင်း၏ ရာဇဝင်နှင့်တကွ ဖျက်ပစ်တော့မည် ဖြစ်သည်။",
        "filedelete-comment": "အ​ကြောင်း​ပြ​ချက်:",
        "filedelete-submit": "ဖျက်",
        "filedelete-success": "'''$1''' ကို ဖျက်ပစ်လိုက်ပြီ။",
        "checkbox-select": "ရွေးချယ်: $1",
        "checkbox-all": "အားလုံး",
        "checkbox-none": "ဘာမှမရှိ",
+       "checkbox-invert": "ပြောင်းပြန်",
        "allpages": "စာမျက်နှာအားလုံး",
        "nextpage": "နောက်ထပ်စာမျက်နှာ ($1)",
        "prevpage": "ယခင် စာမျက်နှာ ($1)",
        "allmessages": "စနစ်၏ သတင်းများ",
        "allmessagesname": "အမည်",
        "allmessagesdefault": "ပုံမှန် အသိပေးချက် စာသား",
+       "allmessagescurrent": "လက်ရှိ မက်ဆေ့စာသား",
        "allmessages-filter-legend": "စစ်ထုတ်ခြင်း",
        "allmessages-filter-unmodified": "မပြုပြင်ထားသော",
        "allmessages-filter-all": "အားလုံး",
        "exif-datetimedigitized": "ဒီဂျစ်တယ်ပြောင်းသည့် နေ့ရက်နှင့် အချိန်",
        "exif-exposuretime-format": "$1 စက္ကန့် ($2)",
        "exif-shutterspeedvalue": "APEX ရှပ်တာ အမြန်နှုန်း",
+       "exif-lightsource": "အလင်းရင်းမြစ်",
        "exif-flash": "ဖလက်ရှ်",
        "exif-filesource": "ဖိုင်ရင်းမြစ်",
        "exif-gpslatitude": "လတ္တီကျု",
        "exif-gpslongitude": "လောင်ဂျီကျု",
        "exif-gpsaltitude": "အမြင့်",
+       "exif-gpstimestamp": "ဂျီပီအက်စ်အချိန် (အက်တော့မစ် နာရီ)",
        "exif-gpsimgdirection": "ရုပ်ပုံ၏ လမ်းကြောင်း",
        "exif-gpsdatestamp": "ဂျီပီအက်စ်ရက်စွဲ",
        "exif-objectname": "ခေါင်းစဉ်တို",
        "exif-lightsource-9": "ကောင်းမွန်သော ရာသီဥတု",
        "exif-lightsource-10": "တိမ်ထူသော ရာသီဥတု",
        "exif-lightsource-11": "အရိပ်",
+       "exif-lightsource-255": "အခြား အလင်းရင်းမြစ်",
        "exif-focalplaneresolutionunit-2": "လက်မှတ်",
        "exif-sensingmethod-1": "မသတ်မှတ်ထားသော",
        "exif-scenecapturetype-3": "ညနေပုံ",
        "tag-mw-undo": "နောက်ပြန် ပြန်ပြင်ခြင်း",
        "tags-title": "အမည်တွဲများ",
        "tags-tag": "အမည်တွဲ အမည်",
+       "tags-description-header": "ဆိုလိုရင်းအဓိပ္ပာယ် အပြည့်အစုံ",
+       "tags-source-header": "ရင်းမြစ်",
        "tags-active-yes": "မှန်",
        "tags-active-no": "မလုပ်ပါ",
        "tags-source-extension": "ဆော့ဝဲလ်မှ သတ်မှတ်ထားသော",
        "htmlform-submit": "ထည့်သွင်းရန်",
        "htmlform-reset": "ပြောင်းလဲထားသည်များ မလုပ်တော့ရန်",
        "htmlform-selectorother-other": "အခြား",
+       "htmlform-no": "မလုပ်ပါ",
        "htmlform-chosen-placeholder": "လုပ်ဆောင်ချက်တစ်ခု ရွေးချယ်ရန်",
        "htmlform-cloner-create": "ပို၍ ထပ်ပေါင်းရန်",
        "htmlform-cloner-delete": "ဖယ်ရှားရန်",
index e8d6c7a..42bb96c 100644 (file)
        "protect-summary-cascade": "kaskade",
        "protect-expiring": "löp aof op $1 (UTC)",
        "protect-expiring-local": "vervölt op $1",
-       "protect-expiry-indefinite": "onbepark",
+       "protect-expiry-indefinite": "unbetöänd",
        "protect-cascade": "Kaskadebeveiliging (beveilig alle ziejen en mallen die in disse zied op-eneumen bin)",
        "protect-cantedit": "Je kunnen t beveiligingsnivo van disse zied niet wiezigen, umda'j gien rechten hebben um t te bewarken.",
        "protect-othertime": "Aandere tiedsduur:",
index 1e731ee..8716ae9 100644 (file)
@@ -89,7 +89,8 @@
                        "Ooswesthoesbes",
                        "MarcoSwart",
                        "Pahles",
-                       "Optilete"
+                       "Optilete",
+                       "Goefie"
                ]
        },
        "tog-underline": "Verwijzingen onderstrepen:",
        "group-autoconfirmed": "autobevestigde gebruikers",
        "group-bot": "bots",
        "group-sysop": "beheerders",
-       "group-interface-admin": "interfacemoderatoren",
+       "group-interface-admin": "interfacebeheerders",
        "group-bureaucrat": "bureaucraten",
        "group-suppress": "toezichthouders",
        "group-all": "(iedereen)",
        "filehist-comment": "Opmerking",
        "imagelinks": "Bestandsgebruik",
        "linkstoimage": "Dit bestand wordt op de volgende {{PLURAL:$1|pagina|$1 pagina's}} gebruikt:",
-       "linkstoimage-more": "Meer dan $1 {{PLURAL:$1|pagina gebruikt|pagina's gebruiken}} dit bestand.\nDe volgende lijst geeft alleen de eerste {{PLURAL:$1|pagina|$1 pagina's}} die dit bestand gebruiken weer.\nEr is ook een [[Special:WhatLinksHere/$2|volledige lijst]] beschikbaar.",
+       "linkstoimage-more": "Meer dan $1 {{PLURAL:$1|page uses|pagina's gebruiken}} dit bestand.\nDe volgende lijst geeft alleen de {{PLURAL:$1|first page|eerste $1 pagina's}} weer die dit bestand gebruiken.\nEr is ook een [[Special:WhatLinksHere/$2|volledige lijst]] beschikbaar.",
        "nolinkstoimage": "Geen enkele pagina gebruikt dit bestand.",
        "morelinkstoimage": "[[Special:WhatLinksHere/$1|Meer koppelingen]] naar dit bestand bekijken.",
        "linkstoimage-redirect": "$1 (bestandsdoorverwijzing) $2",
        "confirm-unwatch-top": "Deze pagina verwijderen uit uw volglijst?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Bewerkingen op deze pagina ongedaan maken?",
+       "confirm-mcrundo-title": "Een wijziging ongedaan maken",
+       "mcrundofailed": "Ongedaan maken mislukt",
+       "mcrundo-missingparam": "Er ontbreken nodige parameters in het verzoek.",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← vorige pagina",
        "imgmultipagenext": "volgende pagina →",
index 154264a..c0eb191 100644 (file)
        "rcfilters-watchlist-markseen-button": "Merk alle endringar som sette",
        "rcfilters-watchlist-edit-watchlist-button": "Endra lista over sider du overvaker",
        "rcfilters-watchlist-showupdated": "Sider du ikkje har vitja sidan dei vart endra er viste med <strong>feit</strong> skrift.",
+       "rcfilters-filter-showlinkedfrom-label": "Vis endringar på sider det vert lenkja til på sida",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Sider som det vert lenkja til på</strong> den valde sida",
+       "rcfilters-filter-showlinkedto-label": "Vis endringar på sider som lenkjar til sida",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Sider som lenkjar til</strong> den valde sida",
        "rcnotefrom": "Nedanfor er endringane gjorde sidan <strong>$2</strong> viste (opp til <strong>$1</strong> stykke)",
        "rclistfromreset": "Nullstill datoval",
        "rclistfrom": "Vis nye endringar sidan $3 $2",
index 476a8bc..d44616b 100644 (file)
        "confirm-unwatch-top": "Remover esta página das páginas vigiadas?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Reverter edições nesta página?",
+       "confirm-mcrundo-title": "Desfazer uma mudança",
+       "mcrundofailed": "A reversão falhou",
+       "mcrundo-missingparam": "Faltam parâmetros obrigatórios no pedido.",
+       "mcrundo-changed": "Esta página foi alterada desde que começou a ver as diferenças. Reveja a nova mudança, por favor.",
        "semicolon-separator": ";&#32;",
        "comma-separator": ",&#32;",
        "colon-separator": ":&#32;",
index c22e638..4296432 100644 (file)
        "confirm-unwatch-top": "Remover esta página da lista de páginas vigiadas?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Reverter as edições desta página?",
+       "confirm-mcrundo-title": "Desfazer uma mudança",
+       "mcrundofailed": "A reversão falhou",
+       "mcrundo-missingparam": "Faltam parâmetros obrigatórios no pedido.",
+       "mcrundo-changed": "Esta página foi alterada desde que começou a ver as diferenças. Reveja a nova mudança, por favor.",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← página anterior",
        "imgmultipagenext": "página seguinte →",
index 2a77994..8c324ed 100644 (file)
        "edit-error-long": "Error message. Parameters:\n* $1 - the error details\nSee also:\n* {{msg-mw|edit-error-short}}\n{{Identical|Error}}",
        "revid": "Used to format a revision ID number in text. Parameters:\n* $1 - Revision ID number.\n{{Identical|Revision}}",
        "pageid": "Used to format a page ID number in text. Parameters:\n* $1 - Page ID number.",
-       "interfaceadmin-info": "Used to wrap the normal permission error for actions which used to only require editinterface but now require more permissions. Parameters:\n* $1 - The normal permission error.",
+       "interfaceadmin-info": "Part of the error message shown when someone with the <code>editinterface</code> right but without the appropriate <code>editsite*</code> right tries to edit a sitewide CSS/JSON/JS page.",
        "rawhtml-notallowed": "Error message given when $wgRawHtml = true; is set and a user uses an &lt;html&gt; tag in a system message or somewhere other than a normal page.",
        "gotointerwiki": "{{doc-special|GoToInterwiki}}\n\nSpecial:GoToInterwiki is a warning page displayed before redirecting users to external interwiki links. Its triggered by people going to something like [[Special:Search/google:foo]].\n{{Identical|Leaving}}",
        "gotointerwiki-invalid": "Message shown on Special:GoToInterwiki if given an invalid title.",
index 9546e52..39bd60d 100644 (file)
                        "Vcohen",
                        "AttemptToCallNil",
                        "Stjn",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Marshmallych",
+                       "Atsirlin"
                ]
        },
        "tog-underline": "Подчёркивание ссылок:",
        "redirectedfrom": "(перенаправлено с «$1»)",
        "redirectpagesub": "Страница-перенаправление",
        "redirectto": "Перенаправление на:",
-       "lastmodifiedat": "ЭÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а Ð¿Ð¾Ñ\81ледний Ñ\80аз Ð±Ñ\8bла Ð¾Ñ\82Ñ\80едакÑ\82иÑ\80ована $1 в $2.",
+       "lastmodifiedat": "Ð\92 Ð¿Ð¾Ñ\81ледний Ñ\80аз Ñ\8dÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\80едакÑ\82иÑ\80овалаÑ\81Ñ\8c $1, в $2.",
        "viewcount": "К этой странице обращались $1 {{PLURAL:$1|раз|раза|раз}}.",
        "protectedpage": "Защищённая страница",
        "jumpto": "Перейти к:",
        "jumptonavigation": "навигация",
        "jumptosearch": "поиск",
-       "view-pool-error": "Ð\98звиниÑ\82е, Ð² Ð½Ð°Ñ\81Ñ\82оÑ\8fÑ\89ий Ð¼Ð¾Ð¼ÐµÐ½Ñ\82 Ñ\81еÑ\80веÑ\80Ñ\8b Ð¿ÐµÑ\80егÑ\80Ñ\83женÑ\8b.\nЭÑ\82Ñ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 Ð¿Ñ\8bÑ\82аÑ\8eÑ\82Ñ\81Ñ\8f Ð¾Ð´Ð½Ð¾Ð²Ñ\80еменно Ð¿Ñ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c Ñ\81лиÑ\88ком Ð¼Ð½Ð¾Ð³Ð¸Ðµ.\nПожалуйста, подождите немного перед повторной попыткой обращения к этой странице.\n\n$1",
-       "generic-pool-error": "Извините, в настоящий момент серверы перегружены.\nСлишком много пользователей пытаются просмотреть этот ресурс.\nПожалуйста, подождите и повторите попытку обращения к нему позже.",
+       "view-pool-error": "Ð\98звиниÑ\82е, Ð² Ð½Ð°Ñ\81Ñ\82оÑ\8fÑ\89ий Ð¼Ð¾Ð¼ÐµÐ½Ñ\82 Ñ\81еÑ\80веÑ\80Ñ\8b Ð¿ÐµÑ\80егÑ\80Ñ\83женÑ\8b.\nСлиÑ\88ком Ð¼Ð½Ð¾Ð³Ð¾ Ñ\83Ñ\87аÑ\81Ñ\82ников Ð¿Ñ\8bÑ\82аÑ\8eÑ\82Ñ\81Ñ\8f Ð¿Ñ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c ÐµÑ\91 Ð¾Ð´Ð½Ð¾Ð²Ñ\80еменно.\nПожалуйста, подождите немного перед повторной попыткой обращения к этой странице.\n\n$1",
+       "generic-pool-error": "Извините, в настоящий момент серверы перегружены.\nСлишком много участников пытаются просмотреть этот ресурс.\nПожалуйста, подождите и повторите попытку обращения к нему позже.",
        "pool-timeout": "Истекло время ожидания блокировки",
-       "pool-queuefull": "Ð\9dакопиÑ\82елÑ\8c запросов полон",
+       "pool-queuefull": "Ð\9fÑ\83л запросов полон",
        "pool-errorunknown": "Неизвестная ошибка",
        "pool-servererror": "Служба счётчика пула недоступна ($1).",
        "poolcounter-usage-error": "Ошибка использования: $1",
        "privacy": "Политика конфиденциальности",
        "privacypage": "Project:Политика конфиденциальности",
        "badaccess": "Ошибка доступа",
-       "badaccess-group0": "Вы не можете выполнять запрошенное действие.",
-       "badaccess-groups": "Ð\97апÑ\80оÑ\88енное Ð´ÐµÐ¹Ñ\81Ñ\82вие Ð¼Ð¾Ð³Ñ\83Ñ\82 Ð²Ñ\8bполнÑ\8fÑ\82Ñ\8c Ñ\82олÑ\8cко Ñ\83Ñ\87аÑ\81Ñ\82ники {{PLURAL:$2|1=из Ð³Ñ\80Ñ\83ппÑ\8b Â«$1»|одной Ð¸Ð· Ñ\81ледÑ\83Ñ\8eÑ\89иÑ\85 Ð³Ñ\80Ñ\83пп: $1}}",
+       "badaccess-group0": "Вы не можете выполнить запрошенное действие.",
+       "badaccess-groups": "Запрошенное действие могут выполнять участники {{PLURAL:$2|1=из группы «$1»|одной из следующих групп: $1}}",
        "versionrequired": "Требуется MediaWiki версии $1",
-       "versionrequiredtext": "Для работы с этой страницей требуется MediaWiki версии $1. См. [[Special:Version|информацию об программном обеспечении]].",
+       "versionrequiredtext": "Для работы с этой страницей требуется MediaWiki версии $1. См. [[Special:Version|информацию о программном обеспечении]].",
        "ok": "OK",
        "pagetitle": "$1 — {{SITENAME}}",
        "pagetitle-view-mainpage": "{{SITENAME}}",
        "nstab-help": "Справка",
        "nstab-category": "Категория",
        "mainpage-nstab": "Заглавная",
-       "nosuchaction": "Такого Ð´ÐµÐ¹Ñ\81Ñ\82виÑ\8f Ð½ÐµÑ\82",
+       "nosuchaction": "Ð\9dеÑ\82 Ñ\82акого Ð´ÐµÐ¹Ñ\81Ñ\82виÑ\8f",
        "nosuchactiontext": "Указанное в URL действие ошибочно.\nВозможно, вы допустили опечатку при наборе URL или перешли по ошибочной ссылке.\nЭто может также указывать на ошибку в проекте {{SITENAME}}.",
        "nosuchspecialpage": "Нет такой служебной страницы",
        "nospecialpagetext": "<strong>Запрошенной вами служебной страницы не существует.</strong>\n\nСписок существующих служебных страниц: [[Special:SpecialPages|{{int:specialpages}}]].",
        "databaseerror-query": "Запрос: $1",
        "databaseerror-function": "Функция: $1",
        "databaseerror-error": "Ошибка: $1",
-       "transaction-duration-limit-exceeded": "Ð\94лÑ\8f Ñ\82ого, Ñ\87Ñ\82обÑ\8b Ð¸Ð·Ð±ÐµÐ¶Ð°Ñ\82Ñ\8c Ð±Ð¾Ð»Ñ\8cÑ\88ого Ð»Ð°Ð³Ð° Ð¿Ñ\80и Ñ\80епликаÑ\86ии, Ñ\8dÑ\82а Ñ\82Ñ\80анзакÑ\86иÑ\8f Ð±Ñ\8bла Ð¿Ñ\80еÑ\80вана, Ð¿Ð¾Ñ\81колÑ\8cкÑ\83 Ð¿Ñ\80одолжиÑ\82елÑ\8cноÑ\81Ñ\82Ñ\8c Ð·Ð°Ð¿Ð¸Ñ\81и ($1) Ð¿Ñ\80евÑ\8bÑ\81ила Ð»Ð¸Ð¼Ð¸Ñ\82 Ð² $2 {{PLURAL:$2|Ñ\81екÑ\83ндÑ\83\81екÑ\83нд|Ñ\81екÑ\83ндÑ\8b}}.\nÐ\95Ñ\81ли Ð²Ñ\8b Ð¸Ð·Ð¼ÐµÐ½Ñ\8fеÑ\82е Ð½ÐµÑ\81колÑ\8cко Ñ\8dлеменÑ\82ов Ð·Ð° Ð¾Ð´Ð¸Ð½ раз, попробуйте вместо этого сделать несколько небольших операций.",
+       "transaction-duration-limit-exceeded": "Ð\94лÑ\8f Ñ\82ого, Ñ\87Ñ\82обÑ\8b Ð¸Ð·Ð±ÐµÐ¶Ð°Ñ\82Ñ\8c Ð±Ð¾Ð»Ñ\8cÑ\88ой Ð·Ð°Ð´ÐµÑ\80жки Ð¿Ñ\80и Ñ\80епликаÑ\86ии, Ñ\8dÑ\82а Ñ\82Ñ\80анзакÑ\86иÑ\8f Ð±Ñ\8bла Ð¿Ñ\80еÑ\80вана. Ð\9fÑ\80одолжиÑ\82елÑ\8cноÑ\81Ñ\82Ñ\8c Ð·Ð°Ð¿Ð¸Ñ\81и ($1) Ð¿Ñ\80евÑ\8bÑ\81ила Ð»Ð¸Ð¼Ð¸Ñ\82 Ð² $2 {{PLURAL:$2|Ñ\81екÑ\83ндÑ\83\81екÑ\83нд|Ñ\81екÑ\83ндÑ\8b}}.\nÐ\95Ñ\81ли Ð²Ñ\8b Ð¸Ð·Ð¼ÐµÐ½Ñ\8fеÑ\82е Ð½ÐµÑ\81колÑ\8cко Ñ\8dлеменÑ\82ов Ð·Ð° раз, попробуйте вместо этого сделать несколько небольших операций.",
        "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-diff": "(разность: $1, $2)",
        "perfcachedts": "Данные взяты из кэша; последний раз он обновлялся в $1. В кэше хранится не более {{PLURAL:$4|1=одной записи|$4 записи|$4 записей}}.",
        "querypage-no-updates": "Обновление этой страницы сейчас отключено.\nПредставленные здесь данные не будут обновляться.",
        "viewsource": "Просмотр кода",
-       "viewsource-title": "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80 Ð¸Ñ\81Ñ\85одного Ñ\82екÑ\81Ñ\82а страницы $1",
+       "viewsource-title": "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80 ÐºÐ¾Ð´а страницы $1",
        "actionthrottled": "Ограничение по скорости",
-       "actionthrottledtext": "Ð\94лÑ\8f Ð±Ð¾Ñ\80Ñ\8cбÑ\8b Ñ\81о Ñ\81памом Ð±Ñ\8bло Ñ\83Ñ\81Ñ\82ановлено Ð¾Ð³Ñ\80аниÑ\87ение Ð½Ð° Ð¼Ð°ÐºÑ\81ималÑ\8cное Ñ\87иÑ\81ло Ð¿Ð¾Ð¿Ñ\8bÑ\82ок Ð²Ñ\8bполнениÑ\8f Ñ\8dÑ\82ого Ð´ÐµÐ¹Ñ\81Ñ\82виÑ\8f Ð² ÐºÐ¾Ñ\80оÑ\82кий Ð¿Ñ\80омежÑ\83Ñ\82ок Ð²Ñ\80емени â\80\94 Ð¸ Ð²Ñ\8b Ð¸Ñ\81Ñ\87еÑ\80пали Ñ\8dÑ\82оÑ\82 Ð»Ð¸Ð¼Ð¸Ñ\82Пожалуйста, повторите попытку через несколько минут.",
+       "actionthrottledtext": "Ð\92Ñ\8b Ð¸Ñ\81Ñ\87еÑ\80пали Ñ\83Ñ\81Ñ\82ановленное Ð´Ð»Ñ\8f Ð±Ð¾Ñ\80Ñ\8cбÑ\8b Ñ\81о Ñ\81памом Ð¾Ð³Ñ\80аниÑ\87ение Ð½Ð° Ð¼Ð°ÐºÑ\81ималÑ\8cное ÐºÐ¾Ð»Ð¸Ñ\87еÑ\81Ñ\82во Ð¿Ð¾Ð¿Ñ\8bÑ\82ок Ð²Ñ\8bполнениÑ\8f Ð·Ð°Ð¿Ñ\80оÑ\88енного Ð´ÐµÐ¹Ñ\81Ñ\82виÑ\8f Ð² ÐºÐ¾Ñ\80оÑ\82кий Ð¿Ñ\80омежÑ\83Ñ\82ок Ð²Ñ\80емени.\nПожалуйста, повторите попытку через несколько минут.",
        "protectedpagetext": "Эта страница защищена для предотвращения её редактирования или совершений других действий.",
        "viewsourcetext": "Вы можете просмотреть и скопировать исходный код этой страницы.",
        "viewyourtext": "Вы можете просмотреть и скопировать исходный текст <strong>ваших правок</strong> на этой странице.",
        "editinginterface": "<strong>Внимание:</strong> Вы редактируете страницу, содержащую текст интерфейса программного обеспечения.\nЕё изменение повлияет на внешний вид интерфейса для других пользователей этой вики.",
        "translateinterface": "Чтобы добавить или изменить перевод этого сообщения, пожалуйста, используйте сайт локализации MediaWiki [https://translatewiki.net/ translatewiki.net].",
        "cascadeprotected": "Данная страница защищена от изменений, поскольку она включена в {{PLURAL:$1|1=следующую страницу, для которой|следующие страницы, для которых}} включена каскадная защита:\n$2",
-       "namespaceprotected": "У вас нет разрешения редактировать страницы в пространстве имён «$1».",
-       "customcssprotected": "У вас нет разрешения редактировать эту CSS-страницу, так как она содержит личные настройки другого участника.",
-       "customjsonprotected": "У вас нет разрешения редактировать эту JSON-страницу, так как она содержит личные настройки другого участника.",
-       "customjsprotected": "У вас нет разрешения редактировать эту JavaScript-страницу, так как она содержит личные настройки другого участника.",
-       "sitecssprotected": "У вас нет разрешения редактировать эту CSS-страницу, поскольку её изменение может повлиять на всех посетителей.",
-       "sitejsonprotected": "У вас нет разрешения для редактирования этой JSON-страницы, поскольку её изменение может повлиять на всех посетителей.",
-       "sitejsprotected": "У вас нет разрешения редактировать эту JavaScript страницу, поскольку её изменение может повлиять на всех посетителей.",
-       "mycustomcssprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð´Ð»Ñ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f Ñ\8dÑ\82ого CSS страницы.",
-       "mycustomjsonprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð´Ð»Ñ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f этой JSON-страницы.",
-       "mycustomjsprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð´Ð»Ñ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f JavaScript Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86е.",
-       "myprivateinfoprotected": "У вас нет разрешения на изменение вашей личной информации",
-       "mypreferencesprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð´Ð»Ñ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f настроек.",
-       "ns-specialprotected": "СÑ\82Ñ\80аниÑ\86Ñ\8b Ð¿Ñ\80оÑ\81Ñ\82Ñ\80анÑ\81Ñ\82ва Ð¸Ð¼Ñ\91н Â«{{ns:special}}» Ð½Ðµ Ð¼Ð¾Ð³Ñ\83Ñ\82 Ð¿Ñ\80авиÑ\82Ñ\8cÑ\81Ñ\8f.",
+       "namespaceprotected": "У вас нет прав на редактирование страниц в пространстве имён «<strong>$1</strong>».",
+       "customcssprotected": "У вас нет прав на редактирование этой CSS-страницы, поскольку она содержит личные настройки другого участника.",
+       "customjsonprotected": "У вас нет прав на редактирование этой JSON-страницы, поскольку она содержит личные настройки другого участника.",
+       "customjsprotected": "У вас нет прав на редактирование этой JavaScript-страницы, поскольку она содержит личные настройки другого участника.",
+       "sitecssprotected": "У вас нет прав на редактирование этой CSS-страницы, поскольку её изменение может повлиять на всех посетителей.",
+       "sitejsonprotected": "У вас нет прав на редактирование этой JSON-страницы, поскольку её изменение может повлиять на всех посетителей.",
+       "sitejsprotected": "У вас нет прав на редактирование этой JavaScript страницу, поскольку её изменение может повлиять на всех посетителей.",
+       "mycustomcssprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð½Ð° Ñ\80едакÑ\82иÑ\80ование Ñ\8dÑ\82ой CSS страницы.",
+       "mycustomjsonprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð½Ð° Ñ\80едакÑ\82иÑ\80ование этой JSON-страницы.",
+       "mycustomjsprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð½Ð° Ñ\80едакÑ\82иÑ\80ование Ñ\8dÑ\82ой JavaScript-Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b.",
+       "myprivateinfoprotected": "У вас нет прав на изменение вашей личной информации",
+       "mypreferencesprotected": "У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав Ð½Ð° Ñ\80едакÑ\82иÑ\80ование настроек.",
+       "ns-specialprotected": "СÑ\82Ñ\80аниÑ\86Ñ\8b Ð¿Ñ\80оÑ\81Ñ\82Ñ\80анÑ\81Ñ\82ва Ð¸Ð¼Ñ\91н Â«{{ns:special}}» Ð½Ðµ Ð¼Ð¾Ð³Ñ\83Ñ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ñ\8b.",
        "titleprotected": "Создание страницы с таким заголовком было запрещено участником [[User:$1|$1]].\nУказана следующая причина: <em>$2</em>.",
        "filereadonlyerror": "Не удаётся изменить файл «$1», так как хранилище «$2» находится в режиме «только для чтения».\n\nСистемный администратор, заблокировавший базу, оставил следующее объяснение: «$3».",
        "invalidtitle": "Недопустимое название",
        "virus-scanfailed": "ошибка сканирования (код $1)",
        "virus-unknownscanner": "неизвестный антивирус:",
        "logouttext": "<strong>Вы завершили сеанс работы.</strong>\n\nНекоторые страницы могут продолжить отображаться так, как будто вы все ещё не завершили сеанс, пока вы не обновите кэш браузера.",
-       "cannotlogoutnow-title": "Невозможно выйти прямо сейчас",
-       "cannotlogoutnow-text": "Нельзя выйти во время использования $1.",
+       "cannotlogoutnow-title": "Ð\9dевозможно Ð²Ñ\8bйÑ\82и Ð¸Ð· Ñ\81иÑ\81Ñ\82емÑ\8b Ð¿Ñ\80Ñ\8fмо Ñ\81ейÑ\87аÑ\81",
+       "cannotlogoutnow-text": "Ð\9dелÑ\8cзÑ\8f Ð²Ñ\8bйÑ\82и Ð¸Ð· Ñ\81иÑ\81Ñ\82емÑ\8b Ð²Ð¾ Ð²Ñ\80емÑ\8f Ð¸Ñ\81полÑ\8cзованиÑ\8f $1.",
        "welcomeuser": "Добро пожаловать, $1!",
-       "welcomecreation-msg": "Ваша учётная запись успешно создана.\nТеперь вы также можете провести  [[Special:Preferences|персональную настройку]] сайта {{SITENAME}}.",
+       "welcomecreation-msg": "Ваша учётная запись была создана.\nТеперь вы также можете изменить [[Special:Preferences|персональные настройки]] для сайта {{SITENAME}}, если вы желаете.",
        "yourname": "Имя учётной записи:",
        "userlogin-yourname": "Имя учётной записи",
        "userlogin-yourname-ph": "Введите имя вашей учётной записи",
        "userlogin-signwithsecure": "Защищённое соединение",
        "cannotlogin-title": "Невозможно войти",
        "cannotlogin-text": "Вход в систему невозможен.",
-       "cannotloginnow-title": "Невозможно войти прямо сейчас",
+       "cannotloginnow-title": "Ð\9dевозможно Ð²Ð¾Ð¹Ñ\82и Ð² Ñ\81иÑ\81Ñ\82емÑ\83 Ð¿Ñ\80Ñ\8fмо Ñ\81ейÑ\87аÑ\81",
        "cannotloginnow-text": "Нельзя войти во время использования $1.",
        "cannotcreateaccount-title": "Невозможно создать учётные записи",
        "cannotcreateaccount-text": "Прямое создание учетных записей не включено в этой вики.",
        "userlogout": "Завершение сеанса",
        "notloggedin": "Вы не представились системе",
        "userlogin-noaccount": "Нет учётной записи?",
-       "userlogin-joinproject": "Присоединиться к проекту",
+       "userlogin-joinproject": "Присоединиться к проекту {{SITENAME}}.",
        "createaccount": "Создать учётную запись",
        "userlogin-resetpassword-link": "Сбросить ваш пароль?",
        "userlogin-helplink2": "Помощь по входу",
        "createacct-error": "Ошибка создания учётной записи",
        "createaccounterror": "Невозможно создать учётную запись: $1",
        "nocookiesnew": "Участник зарегистрирован, но не представлен. {{SITENAME}} использует «cookies» для представления участников. У вас «cookies» запрещены. Пожалуйста, разрешите их, а затем представьтесь со своиим новым именем участника и паролем.",
-       "nocookieslogin": "{{SITENAME}} использует «cookies» для представления участников. Вы их отключили. Пожалуйста, включите их и попробуйте снова.",
+       "nocookieslogin": "{{SITENAME}} использует «cookies»-файлы для представления участников. Вы отключили использование «cookies»-файлов. Пожалуйста, включите использование «cookies»-файлов и попробуйте снова.",
        "nocookiesfornew": "Учётная запись участника не была создана из-за невозможности проверить её источник. \nУбедитесь, что включены «cookies», обновите страницу и попробуйте ещё раз.",
        "nocookiesforlogin": "{{int:nocookieslogin}}",
        "createacct-loginerror": "Учётная запись была успешно создана, но вы не смогли войти в систему автоматически. Пожалуйста, [[Special:UserLogin|авторизуйтесь вручную]].",
        "createacct-another-realname-tip": "Настоящее имя (необязательное поле).\nЕсли вы укажете его, то оно будет использовано для того, чтобы показать, кем была внесена правка страницы.",
        "pt-login": "Войти",
        "pt-login-button": "Войти",
-       "pt-login-continue-button": "Продолжить процедуру входа",
+       "pt-login-continue-button": "Продолжить процедуру входа в систему",
        "pt-createaccount": "Создать учётную запись",
        "pt-userlogout": "Выйти",
        "php-mail-error-unknown": "Неизвестная ошибка в PHP-функции mail()",
        "permissionserrorstext": "У вас нет прав на выполнение этой операции по {{PLURAL:$1|1=следующей причине|следующим причинам}}:",
        "permissionserrorstext-withaction": "У вас нет прав на выполнение действия «$2» по {{PLURAL:$1|1=следующей причине|следующим причинам}}:",
        "contentmodelediterror": "Вы не можете редактировать эту версию, поскольку модель её содержания — <code>$1</code>, отличающаяся от текущей модели содержания страницы — <code>$2</code>.",
-       "recreate-moveddeleted-warn": "'''Внимание. Вы пытаетесь воссоздать страницу, которая ранее удалялась.'''\n\nПроверьте, действительно ли вам нужно воссоздавать эту страницу.\nНиже приведены журналы удалений и переименований этой страницы.",
+       "recreate-moveddeleted-warn": "<strong>Внимание: Вы пытаетесь воссоздать страницу, которая ранее удалялась.</strong>\n\nПроверьте, действительно ли вам нужно воссоздавать эту страницу.\nНиже для справки приведены журналы удаления и переименований этой страницы.",
        "moveddeleted-notice": "Эта страница была удалена.\nНиже для справки приведены журналы удаления, защиты и перемещения для этой страницы.",
        "moveddeleted-notice-recent": "К сожалению, эта страница была недавно удалена (в течение последних 24 часов).\nНиже для справки приведены журналы удаления, защиты и перемещения для этой страницы.",
        "log-fulllog": "Просмотреть журнал целиком",
        "unstrip-size-warning": "Превышен лимит размера Unstrip ($1)",
        "unstrip-size-category": "Страницы с превышенным лимитом размера Unstrip",
        "converter-manual-rule-error": "Ошибка в ручном правиле преобразования языка",
-       "undo-success": "Ð\9fÑ\80авка Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð¾Ñ\82менена. Ð\9fожалÑ\83йÑ\81Ñ\82а, Ð¿Ñ\80оÑ\81моÑ\82Ñ\80иÑ\82е Ñ\81Ñ\80авнение Ð²ÐµÑ\80Ñ\81ий, Ñ\87Ñ\82обÑ\8b Ñ\83бедиÑ\82Ñ\8cÑ\81Ñ\8f, Ñ\87Ñ\82о Ñ\8dÑ\82о Ð¸Ð¼ÐµÐ½Ð½Ð¾ Ñ\82е Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f, ÐºÐ¾Ñ\82оÑ\80Ñ\8bе Ð²Ð°Ñ\81 Ð¸Ð½Ñ\82еÑ\80еÑ\81Ñ\83Ñ\8eÑ\82, Ð¸ Ð½Ð°Ð¶Ð¼Ð¸Ñ\82е Â«Ð\97апиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83», Ñ\87Ñ\82обÑ\8b Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð²Ñ\81Ñ\82Ñ\83пили Ð² Ñ\81илÑ\83.",
+       "undo-success": "Ð\9fÑ\80авка Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð¾Ñ\82менена. Ð\9fожалÑ\83йÑ\81Ñ\82а, Ð¿Ñ\80оÑ\81моÑ\82Ñ\80иÑ\82е Ñ\81Ñ\80авнение Ð²ÐµÑ\80Ñ\81ий, Ñ\87Ñ\82обÑ\8b Ñ\83бедиÑ\82Ñ\8cÑ\81Ñ\8f, Ñ\87Ñ\82о Ñ\8dÑ\82о Ð¸Ð¼ÐµÐ½Ð½Ð¾ Ñ\82е Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f, ÐºÐ¾Ñ\82оÑ\80Ñ\8bе Ð²Ð°Ñ\81 Ð¸Ð½Ñ\82еÑ\80еÑ\81Ñ\83Ñ\8eÑ\82, Ð¸ Ð½Ð°Ð¶Ð¼Ð¸Ñ\82е Â«Ð\97апиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83», Ñ\87Ñ\82обÑ\8b Ð²Ð°Ñ\88а Ð¾Ñ\82мена Ð¿Ñ\80авки Ð±Ñ\8bла Ñ\81оÑ\85Ñ\80анена.",
        "undo-failure": "Правка не может быть отменена из-за несовместимости промежуточных изменений.",
        "undo-main-slot-only": "Правка не может быть отменена, поскольку оно включает контент вне основного слота.",
        "undo-norev": "Правка не может быть отменена, так как её не существует или она была удалена.",
        "last": "пред.",
        "page_first": "первая",
        "page_last": "последняя",
-       "histlegend": "Выбор версий: отметьте версии страницы, которые вы хотите сравнить, и нажмите '''{{int:compare-submit}}'''.<br />\nПояснения: '''({{int:cur}})''' — отличия от текущей версии; '''({{int:last}})''' — отличия от предшествующей версии; '''{{int:minoreditletter}}''' — незначительные изменения.",
+       "histlegend": "Выбор версий: отметьте версии страницы, которые вы хотите сравнить, и нажмите <strong>{{int:compare-submit}}</strong>.<br />\nПояснения: <strong>({{int:cur}})</strong> — отличия от текущей версии; <strong>({{int:last}})</strong> — отличия от предшествующей версии; <strong>{{int:minoreditletter}}</strong> — незначительные изменения.",
        "history-fieldset-title": "Поиск правок",
        "history-show-deleted": "Только удалённые правки",
        "histfirst": "старейшие",
        "history-feed-item-nocomment": "$1 в $2",
        "history-feed-empty": "Запрашиваемой страницы не существует.\nОна могла быть удалена или переименована.\nПопробуйте [[Special:Search|найти в вики]] похожие страницы.",
        "history-edit-tags": "Изменить теги выбранных версий",
-       "rev-deleted-comment": "(опиÑ\81ание Ð¿Ñ\80авки Ñ\83далено)",
+       "rev-deleted-comment": "(опиÑ\81ание Ð¿Ñ\80авки Ñ\81Ñ\82Ñ\91Ñ\80Ñ\82о)",
        "rev-deleted-user": "(имя автора стёрто)",
-       "rev-deleted-event": "(деÑ\82али Ð¶Ñ\83Ñ\80нала Ñ\83далены)",
+       "rev-deleted-event": "(подÑ\80обноÑ\81Ñ\82и Ñ\81Ñ\82Ñ\91Ñ\80Ñ\82ы)",
        "rev-deleted-user-contribs": "[имя участника или IP-адрес удалены — правка скрыта со страницы вклада]",
        "rev-deleted-text-permission": "Эта версия страницы была '''удалена'''.\nПодробности приведены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале удалений].",
        "rev-suppressed-text-permission": "Эта версия страницы была <strong>скрыта</strong>.\nПодробности приведены в [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} журнале сокрытий].",
        "rev-suppressed-diff-view": "Одна из версий этого сравнения версий была <strong>скрыта</strong>.\nВы можете просмотреть это сравнение. Подробности приведены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале сокрытий].",
        "rev-delundel": "показать/скрыть",
        "rev-showdeleted": "показать",
-       "revisiondelete": "Удалить / восстановить версии страницы",
+       "revisiondelete": "Удалить/восстановить версии страницы",
        "revdelete-nooldid-title": "Не задана целевая версия",
-       "revdelete-nooldid-text": "Ð\92Ñ\8b Ð½Ðµ Ð·Ð°Ð´Ð°Ð»Ð¸ Ñ\86елевÑ\83Ñ\8e Ð²ÐµÑ\80Ñ\81иÑ\8e (веÑ\80Ñ\81ии) Ð´Ð»Ñ\8f Ð²Ñ\8bполнениÑ\8f Ñ\8dÑ\82ой Ñ\84Ñ\83нкÑ\86ии, Ñ\83казаннаÑ\8f Ð²ÐµÑ\80Ñ\81иÑ\8f Ð½Ðµ Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82, Ð¸Ð»Ð¸ вы пытаетесь скрыть текущую версию.",
+       "revdelete-nooldid-text": "ЦелеваÑ\8f Ð²ÐµÑ\80Ñ\81иÑ\8f Ð½Ðµ Ð·Ð°Ð´Ð°Ð½Ñ\8b, Ñ\83казаннаÑ\8f Ð²ÐµÑ\80Ñ\81иÑ\8f Ð½Ðµ Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 Ð¸Ð»Ð¸ Ð¶Ðµ вы пытаетесь скрыть текущую версию.",
        "revdelete-no-file": "Указанный файл не существует.",
        "revdelete-show-file-confirm": "Вы уверены, что вы хотите просмотреть удалённую версию файла «<nowiki>$1</nowiki>» от $2, $3?",
        "revdelete-show-file-submit": "Да",
        "revdelete-radio-same": "(не изменять)",
        "revdelete-radio-set": "Скрытая",
        "revdelete-radio-unset": "Видимая",
-       "revdelete-suppress": "Скрывать данные также и от администраторов",
-       "revdelete-unsuppress": "Снять ограничения с восстановленных версий",
+       "revdelete-suppress": "Скрыть данные также и от администраторов",
+       "revdelete-unsuppress": "Снять ограничения видимости с восстановленных версий",
        "revdelete-log": "Причина:",
        "revdelete-submit": "Применить к {{PLURAL:$1|1=выбранной версии|выбранным версиям}}",
        "revdelete-success": "Видимость версии обновлена.",
        "revdelete-concurrent-change": "Ошибка изменения записи от $2, $1: её статус был изменён кем-то другим, пока вы пытались изменить его.\nПожалуйста, проверьте журналы.",
        "revdelete-only-restricted": "Ошибка сокрытия записи от $2 $1: вы не можете скрыть запись от просмотра администраторами без выбора одной из других настроек сокрытия.",
        "revdelete-reason-dropdown": "* Стандартные причины удаления\n** Нарушение авторских прав\n** Неуместные личные сведения\n** Неуместное имя участника\n** Потенциально клеветнические сведения",
-       "revdelete-otherreason": "Другая/дополнительная причина:",
+       "revdelete-otherreason": "Другая причина/дополнение:",
        "revdelete-reasonotherlist": "Другая причина",
        "revdelete-edit-reasonlist": "Редактировать список причин",
        "revdelete-offender": "Автор версии страницы:",
        "default": "по умолчанию",
        "prefs-files": "Файлы",
        "prefs-custom-css": "Собственный CSS",
-       "prefs-custom-json": "Ð\9fолÑ\8cзоваÑ\82елÑ\8cÑ\81кий JSON",
+       "prefs-custom-json": "СобÑ\81Ñ\82веннÑ\8bй JSON",
        "prefs-custom-js": "Собственный JS",
        "prefs-common-config": "Общие CSS/JSON/JavaScript для всех тем оформления:",
        "prefs-reset-intro": "Эта страница может быть использована для сброса ваших настроек на стандартные.\nУчтите, что это действие невозможно отменить.",
        "userrights-groupsmember": "Состоит в группах:",
        "userrights-groupsmember-auto": "Неявно состоит в группах:",
        "userrights-groupsmember-type": "$1",
-       "userrights-groups-help": "Вы можете изменить группы, в которые входит {{GENDER:$1|этот участник|эта участница}}.\n* Если около названия группы стоит отметка — {{GENDER:$1|участник|участница}} входит в эту группу.\n* Если отметка не стоит — {{GENDER:$1|участник|участница}} не входит в эту группу.\n* Символ * указывает на то, что вы не сможете удалить {{GENDER:$1|участника|участницу}} из группы, если добавите {{GENDER:$1|его|её}} в неё (или наоборот).\n* Символ # указывает на то, что вы можете только отложить время истечения членства в этой группы, вы не можете перенести его на более ранний срок.",
+       "userrights-groups-help": "Вы можете изменить группы, в которые входит {{GENDER:$1|этот участник|эта участница}}.\n* Если около названия группы стоит отметка — {{GENDER:$1|участник|участница}} входит в эту группу.\n* Если отметка не стоит — {{GENDER:$1|участник|участница}} не входит в эту группу.\n* Символ * указывает на то, что вы не сможете удалить {{GENDER:$1|участника|участницу}} из группы, если добавите {{GENDER:$1|его|её}} в неё (или наоборот).\n* Символ # указывает на то, что вы можете только отложить, но не перенести время истечения членства в этой группе на более ранний срок.",
        "userrights-reason": "Причина:",
        "userrights-no-interwiki": "У вас нет разрешения изменять права участников в других вики.",
        "userrights-nodatabase": "База данных $1 не существует или расположена не локально.",
        "block": "Блокировка участника",
        "unblock": "Разблокировка участника",
        "blockip": "Заблокировать {{GENDER:$1|участника|участницу}}",
-       "blockiptext": "Используйте форму ниже, чтобы заблокировать возможность записи с определённого IP-адреса или имени участника.\nЭто может быть сделано только для предотвращения вандализма и только в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].\nНиже укажите конкретную причину (к примеру, процитируйте некоторые страницы с признаками вандализма).\nВы можете заблокировать диапазоны IP-адресов, используя [https://ru.wikipedia.org/wiki/Бесклассовая_адресация CIDR]-синтаксис. Максимально допустимый диапазон — /$1 для протокола IPv4 и /$2 для протокола IPv6.",
+       "blockiptext": "Используйте форму ниже, чтобы заблокировать возможность редактирования с определённого IP-адреса или имени участника.\nЭтот инструмент следует использовать для предотвращения вандализма и только в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].\nНиже укажите конкретную причину (к примеру, процитируйте некоторые страницы с признаками вандализма).\nВы можете заблокировать диапазоны IP-адресов, используя [https://ru.wikipedia.org/wiki/Бесклассовая_адресация CIDR]-синтаксис. Максимально допустимый диапазон — /$1 для протокола IPv4 и /$2 для протокола IPv6.",
        "ipaddressorusername": "IP-адрес или имя участника:",
        "ipbexpiry": "Закончится через:",
        "ipbreason": "Причина:",
        "ipboptions": "2 часа:2 hours,1 день:1 day,3 дня:3 days,1 неделя:1 week,2 недели:2 weeks,1 месяц:1 month,3 месяца:3 months,6 месяцев:6 months,1 год:1 year,бессрочно:infinite",
        "ipbhidename": "Скрыть имя участника из правок и списков",
        "ipbwatchuser": "Добавить в список наблюдения личную страницу участника и его страницу обсуждения",
-       "ipb-disableusertalk": "Запретить этому участнику редактировать свою страницу обсуждения во время блокировки",
+       "ipb-disableusertalk": "Запретить этому участнику редактировать свою страницу обсуждения",
        "ipb-change-block": "Переблокировать участника с этими настройками",
        "ipb-confirm": "Подтвердить блокировку",
        "badipaddress": "IP-адрес записан в неправильном формате, или участника с таким именем не существует.",
        "autoblocklist-submit": "Найти",
        "autoblocklist-legend": "Список автоблокировок",
        "autoblocklist-localblocks": "{{PLURAL:$1|Локальная автоблокировка|Локальные автоблокировки}}",
-       "autoblocklist-total-autoblocks": "Ð\92Ñ\81его Ð°Ð²Ñ\82облоков: $1",
+       "autoblocklist-total-autoblocks": "Ð\92Ñ\81его Ð°Ð²Ñ\82облокиÑ\80овок: $1",
        "autoblocklist-empty": "Список автоблокировок пуст.",
        "autoblocklist-otherblocks": "{{PLURAL:$1|Другая автоблокировка|Другие автоблокировки}}",
        "ipblocklist": "Заблокированные участники",
        "moveuserpage-warning": "<strong>Внимание:</strong> вы собираетесь переименовать страницу участника. Пожалуйста, обратите внимание, что переименована будет только страница, участник <strong>не</strong> будет переименован.",
        "movecategorypage-warning": "<strong>Предупреждение:</strong> Вы собираетесь переименовать страницу категории. Пожалуйста, обратите внимание, что будет переименована только эта страница, а все страницы старой категории <em>не</em> будут перекатегоризованы в новую.",
        "movenologintext": "Вы должны [[Special:UserLogin|представиться системе]],\nчтобы иметь возможность переименовать страницы.",
-       "movenotallowed": "У вас нет разрешения переименовывать страницы.",
-       "movenotallowedfile": "У вас нет разрешения переименовывать файлы.",
-       "cant-move-user-page": "У вас нет разрешения переименовывать основные страницы участников.",
+       "movenotallowed": "У вас нет прав на переименовывание страниц.",
+       "movenotallowedfile": "У вас нет прав на переименовывание файлов.",
+       "cant-move-user-page": "У вас нет прав на переименовывание основных страниц участников.",
        "cant-move-to-user-page": "У вас нет прав переименовывать страницу в страницу участника (можно переименовать в подстраницу).",
-       "cant-move-category-page": "У вас нет разрешения переименовывать страницы категорий.",
-       "cant-move-to-category-page": "У вас нет разрешения переименовывать страницы в страницу категории.",
-       "cant-move-subpages": "У вас нет разрешения переименовывать подстраницы.",
+       "cant-move-category-page": "У вас нет прав на переименовывание страниц категорий.",
+       "cant-move-to-category-page": "У вас нет прав на переименовывание страницы в страницу категории.",
+       "cant-move-subpages": "У вас нет прав на переименовывание подстраниц.",
        "namespace-nosubpages": "Пространство имён «$1» не разрешает создание страниц.",
        "newtitle": "Новое название:",
        "move-watch": "Добавить в список наблюдения исходную и целевую страницы",
        "movenosubpage": "У этой страницы нет подстраниц.",
        "movereason": "Причина:",
        "revertmove": "возврат",
-       "delete_and_move_text": "ЦелеваÑ\8f Ñ\81траница с именем «[[:$1]]» уже существует. \nХотите удалить её, чтобы сделать возможным переименование?",
+       "delete_and_move_text": "Страница с именем «[[:$1]]» уже существует. \nХотите удалить её, чтобы сделать возможным переименование?",
        "delete_and_move_confirm": "Да, удалить эту страницу",
        "delete_and_move_reason": "Удалено для возможности переименования «[[$1]]»",
        "selfmove": "Невозможно переименовать страницу: исходное и новое имя страницы совпадают.",
        "immobile-target-namespace-iw": "Ссылка интервики не может быть использована для переименования.",
        "immobile-source-page": "Эту страницу нельзя переименовать.",
        "immobile-target-page": "Нельзя присвоить странице это имя.",
-       "bad-target-model": "Невозможно преобразовать $1 в $2: несовместимые модели данных.",
+       "bad-target-model": "Невозможно преобразовать $1 в $2. У страниц несовместимые модели содержимого.",
        "imagenocrossnamespace": "Невозможно дать файлу имя из другого пространства имён",
-       "nonfile-cannot-move-to-file": "Невозможно переименовывать страницы в файлы",
+       "nonfile-cannot-move-to-file": "Невозможно переименовывать не-файловые страницы в файлы",
        "imagetypemismatch": "Новое расширение файла не соответствует его типу",
        "imageinvalidfilename": "Целевое имя файла ошибочно",
        "fix-double-redirects": "Исправить перенаправления, указывающие на прежнее название",
        "anonymous": "{{PLURAL:$1|1=Анонимный участник|Анонимные участники}} {{grammar:genitive|{{SITENAME}}}}",
        "siteuser": "{{GENDER:$2|участник|участница}} {{grammar:genitive|{{SITENAME}}}} $1",
        "anonuser": "анонимный участник {{grammar:genitive|{{SITENAME}}}} $1",
-       "lastmodifiedatby": "ЭÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а Ð¿Ð¾Ñ\81ледний Ñ\80аз Ð±Ñ\8bла Ð¾Ñ\82Ñ\80едакÑ\82иÑ\80ована $1 Ð² $2, Ð°Ð²Ñ\82оÑ\80 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f â\80\94 $3.",
+       "lastmodifiedatby": "Ð\92 Ð¿Ð¾Ñ\81ледний Ñ\80аз Ñ\8dÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\80едакÑ\82иÑ\80овалаÑ\81Ñ\8c $1, Ð² $2 Ð°Ð²Ñ\82оÑ\80ом $3.",
        "othercontribs": "В создании приняли участие: $1.",
        "others": "другие",
        "siteusers": "{{PLURAL:$2|1={{GENDER:$1|участник|участница}}|участники}} {{grammar:genitive|{{SITENAME}}}} $1",
        "filedelete-archive-read-only": "Архивная директория «$1» не доступна для записи веб-серверу.",
        "previousdiff": "← Предыдущая правка",
        "nextdiff": "Следующая правка →",
-       "mediawarning": "'''Внимание'''. Этот тип файла может содержать вредоносный программный код.\nПри его запуске ваша система может быть заражена.",
+       "mediawarning": "<strong>Внимание</strong>. Этот тип файла может содержать вредоносный программный код.\nПри его запуске ваша система может быть заражена.",
        "imagemaxsize": "Ограничение на размер изображения:<br />''(для страницы описания файла)''",
        "thumbsize": "Размер уменьшенной версии изображения:",
        "widthheight": "$1 × $2",
        "confirm-unwatch-top": "Удалить эту страницу из вашего списка наблюдения?",
        "confirm-rollback-button": "ОК",
        "confirm-rollback-top": "Откатить правки на этой странице?",
+       "confirm-mcrundo-title": "Отменить изменение",
+       "mcrundofailed": "Отменить не удалось",
+       "mcrundo-missingparam": "Отсутствуют обязательные параметры по запросу.",
+       "mcrundo-changed": "Эта страница была изменена с тех пор, как вы просмотрели различия. Пожалуйста, проверьте новое изменение.",
        "semicolon-separator": ";&#32;",
        "comma-separator": ",&#32;",
        "colon-separator": ":&#32;",
        "edit-error-long": "Ошибки:\n\n$1",
        "revid": "версия $1",
        "pageid": "ID страницы $1",
-       "interfaceadmin-info": "$1\n\nПрава на редактирование общесайтных CSS/JS/JSON были недавно вынесены из права <code>editinterface</code>. Если вы не понимаете, почему вы наткнулись на эту ошибку, см. [[mw:MediaWiki_1.32/interface-admin]].",
+       "interfaceadmin-info": "$1\n\nПрава на редактирование общесайтных CSS/JS/JSON-файлов были недавно вынесены из права <code>editinterface</code>. Если вы не понимаете, почему вы наткнулись на эту ошибку, см. [[mw:MediaWiki_1.32/interface-admin]].",
        "rawhtml-notallowed": "&lt;html&gt; теги могут быть использованы только в пределах обычных страниц.",
        "gotointerwiki": "Покидаем {{grammar:accusative|{{SITENAME}}}}...",
        "gotointerwiki-invalid": "Указан некорректный заголовок.",
index 4df3c9d..76735d5 100644 (file)
        "edit-hook-aborted": "Urejanje je bilo brez obrazložitve prekinjeno zaradi neznane napake.",
        "edit-gone-missing": "Strani ni mogoče posodobiti.\nIzgleda, da je bila izbrisana.",
        "edit-conflict": "Navzkrižje urejanj.",
-       "edit-no-change": "Vaše urejanje je bilo prezrto, saj ni vsebovalo sprememb.",
+       "edit-no-change": "Tvoje urejanje je bilo prezrto, saj ni vsebovalo sprememb.",
        "postedit-confirmation-created": "Stran je bila ustvarjena.",
        "postedit-confirmation-restored": "Stran je bila obnovljena.",
-       "postedit-confirmation-saved": "Vaše urejanje smo shranili.",
-       "postedit-confirmation-published": "Vaše urejanje smo objavili.",
+       "postedit-confirmation-saved": "Tvoje urejanje je bilo shranjeno.",
+       "postedit-confirmation-published": "Tvoje urejanje smo objavili.",
        "edit-already-exists": "Ni bilo mogoče ustvariti nove strani, ker že obstaja.",
        "defaultmessagetext": "Prednastavljeno besedilo",
        "content-failed-to-parse": "Nisem mogel razčleniti vsebine $2 za obliko $1: $3",
        "confirm-unwatch-top": "Odstranim stran z vašega spiska nadzorov?",
        "confirm-rollback-button": "V redu",
        "confirm-rollback-top": "Povrnemo urejanja te strani?",
+       "confirm-mcrundo-title": "Razveljavi spremembo",
+       "mcrundofailed": "Razveljavitev ni uspela",
+       "mcrundo-missingparam": "Pri zahtevi manjkajo zahtevani parametri.",
+       "mcrundo-changed": "Stran je bila spremenjena, odkar ste si ogledali primerjavo. Prosimo, preglejte nove spremembe.",
        "percent": "$1&#160;%",
        "quotation-marks": "»$1«",
        "imgmultipageprev": "← prejšnja stran",
index 657e2f5..9ad3fb2 100644 (file)
@@ -76,7 +76,7 @@
        "tog-watchlisthideminor": "Сакриј мање измене са списка надгледања",
        "tog-watchlisthideliu": "Сакриј измене пријављених корисника са списка надгледања",
        "tog-watchlistreloadautomatically": "Аутоматски освежи списак надгледања кад год се филтер промени (потребан JavaScript)",
-       "tog-watchlistunwatchlinks": "Ð\94одаÑ\98 Ð²ÐµÐ·Ðµ Ð·Ð° Ð´Ð¸Ñ\80екÑ\82но Ð´Ð¾Ð´Ð°Ð²Ð°Ñ\9aе/Ñ\83клаÑ\9aаÑ\9aе Ñ\81Ñ\82авки Ñ\81а Ñ\81пиÑ\81ка Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ñ\9aа (поÑ\82Ñ\80ебан JavaScript)",
+       "tog-watchlistunwatchlinks": "Ð\94одаÑ\98 Ð¾Ð·Ð½Ð°Ñ\87иваÑ\87е Ð·Ð° Ð¿Ñ\80екид Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ñ\9aа/нагледаÑ\9aе ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) Ð½Ð° Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ð½Ðµ Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81а Ð¿Ñ\80оменама (Ð\88аваÑ\81кÑ\80ипÑ\82 Ñ\98е Ð½ÐµÐ¾Ð¿Ñ\85одан Ð·Ð° Ñ\84Ñ\83нкÑ\86ионалноÑ\81Ñ\82 Ð¿Ñ\80ебаÑ\86иваÑ\9aа)",
        "tog-watchlisthideanons": "Сакриј измене анонимних корисника са списка надгледања",
        "tog-watchlisthidepatrolled": "Сакриј патролиране измене са списка надгледања",
        "tog-watchlisthidecategorization": "Сакриј категоризацију страница",
        "views": "Прегледи",
        "toolbox": "Алатке",
        "tool-link-userrights": "Промени {{GENDER:$1|корисничке}} групе",
-       "tool-link-userrights-readonly": "Ð\9fÑ\80еглед {{GENDER:$1|корисничких}} група",
+       "tool-link-userrights-readonly": "Ð\9fÑ\80иказ {{GENDER:$1|корисничких}} група",
        "tool-link-emailuser": "Слање имејла {{GENDER:$1|кориснику|корисници}}",
        "imagepage": "Погледај страницу датотеке",
        "mediawikipage": "Погледај страницу поруке",
        "missingarticle-diff": "(разлика: $1, $2)",
        "readonly_lag": "База података је аутоматски закључана да би се секундарни сервери базе података ускладили с главним.",
        "internalerror": "Унутрашња грешка",
-       "internalerror_info": "Ð\98нÑ\82еÑ\80на грешка: $1",
+       "internalerror_info": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа грешка: $1",
        "internalerror-fatal-exception": "Грешка необрађеног изузетка типа „$1“",
        "filecopyerror": "Не могу да копирам датотеку „$1“ у „$2“.",
        "filerenameerror": "Не могу да преименујем датотеку „$1“ у „$2“.",
        "cannotloginnow-title": "Пријава тренутно није могућа",
        "cannotloginnow-text": "Пријава није могућа када се користи $1.",
        "cannotcreateaccount-title": "Не могу да отворим налоге",
-       "cannotcreateaccount-text": "Ð\94иÑ\80екÑ\82но Ð¿Ñ\80авÑ\99ење налога није омогућено на овом викију.",
+       "cannotcreateaccount-text": "Ð\94иÑ\80екÑ\82но Ð¾Ñ\82ваÑ\80ање налога није омогућено на овом викију.",
        "yourdomainname": "Домен:",
        "password-change-forbidden": "Не можете да промените лозинку на овом викију.",
        "externaldberror": "Дошло је до грешке при потврди идентитета базе података или вам није дозвољено да ажурирате свој спољни налог.",
        "prefs-watchlist-edits-max": "Највећи број: 1000",
        "prefs-watchlist-token": "Токен списка надгледања:",
        "prefs-watchlist-managetokens": "Управљај жетонима",
-       "prefs-misc": "Ð\94Ñ\80Ñ\83га Ð¿Ð¾Ð´ÐµÑ\88аваÑ\9aа",
+       "prefs-misc": "Разно",
        "prefs-resetpass": "промени лозинку",
        "prefs-changeemail": "промени или уклони имејл-адресу",
        "prefs-setemail": "постави имејл-адресу",
        "prefs-files": "Датотеке",
        "prefs-custom-css": "прилагођени CSS",
        "prefs-custom-json": "Прилагођени JSON",
-       "prefs-custom-js": "прилагођени јаваскрипт",
+       "prefs-custom-js": "прилагођени JavaScript",
        "prefs-common-config": "Дељени CSS/JSON/јаваскрипт за све теме:",
        "prefs-reset-intro": "Можете користити ову страницу да поново поставите своја подешавања на подразумеване вредности сајта.\nОво се не може опозвати.",
        "prefs-emailconfirm-label": "Потврда имејла:",
        "editusergroup": "Учитај корисничке групе",
        "editinguser": "Мењате корисничка права {{GENDER:$1|корисника|кориснице}} <strong>[[User:$1|$1]]</strong> $2",
        "viewinguserrights": "Корисничка права {{GENDER:$1|корисника|кориснице}} <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Ð\9fÑ\80омена {{GENDER:$1|корисничких}} група",
-       "userrights-viewusergroup": "Ð\9fÑ\80еглед {{GENDER:$1|корисничких}} група",
+       "userrights-editusergroup": "УÑ\80еÑ\92иваÑ\9aе {{GENDER:$1|корисничких}} група",
+       "userrights-viewusergroup": "Ð\9fÑ\80иказ {{GENDER:$1|корисничких}} група",
        "saveusergroups": "Сачувај {{GENDER:$1|корисничке}} групе",
        "userrights-groupsmember": "Члан група:",
        "userrights-groupsmember-auto": "{{GENDER:$2|Имплицитан члан|Имплицитна чланица}} група:",
        "grouppage-sysop": "{{ns:project}}:Администратори",
        "grouppage-interface-admin": "{{ns:project}}:Администратори интерфејса",
        "grouppage-bureaucrat": "{{ns:project}}:Бирократе",
-       "grouppage-suppress": "{{ns:project}}:РевизоÑ\80",
+       "grouppage-suppress": "{{ns:project}}:Ð\91Ñ\80иÑ\81аÑ\87и Ð¸Ð·Ð¼ÐµÐ½Ð°",
        "right-read": "читање страница",
        "right-edit": "уређивање страница",
        "right-createpage": "прављење страница (изузев страница за разговор)",
        "grant-createaccount": "Отварање налога",
        "grant-createeditmovepage": "Прављење, уређивање и премештање страница",
        "grant-delete": "Брисање страница, ревизија и уноса у евиденцијама",
-       "grant-editinterface": "УÑ\80еÑ\92иваÑ\9aе Ð\9cедиÑ\98авики Ð¸Ð¼ÐµÐ½Ñ\81ког Ð¿Ñ\80оÑ\81Ñ\82оÑ\80а Ð¸ ÐºÐ¾Ñ\80иÑ\81ниÑ\87киÑ\85 CSS/JSON/Ð\88аваÑ\81кÑ\80ипÑ\82 Ñ\81Ñ\82Ñ\80аниÑ\86а",
+       "grant-editinterface": "УÑ\80еÑ\92иваÑ\9aе Ð¸Ð¼ÐµÐ½Ñ\81ког Ð¿Ñ\80оÑ\81Ñ\82оÑ\80а Ð\9cедиÑ\98авики Ð¸ JSON-а Ñ\81аÑ\98Ñ\82а/коÑ\80иÑ\81ника",
        "grant-editmycssjs": "Уређивање вашег CSS/JSON/Јаваскрипта",
        "grant-editmyoptions": "Уређивање ваших корисничких подешавања",
        "grant-editmywatchlist": "Уређивање вашег списка надгледања",
        "rcfilters-filter-editsbyself-label": "Ваше промене",
        "rcfilters-filter-editsbyself-description": "Ваши сопствени доприноси.",
        "rcfilters-filter-editsbyother-label": "Промене других",
-       "rcfilters-filter-editsbyother-description": "Све Ð¸Ð·мене осим ваших.",
+       "rcfilters-filter-editsbyother-description": "Све Ð¿Ñ\80омене осим ваших.",
        "rcfilters-filtergroup-userExpLevel": "Корисничка регистрација и искуство",
        "rcfilters-filter-user-experience-level-registered-label": "Регистровани",
        "rcfilters-filter-user-experience-level-registered-description": "Пријављени уредници.",
        "rcfilters-filtergroup-watchlistactivity": "Стање на списку надгледања",
        "rcfilters-filter-watchlistactivity-unseen-label": "Непогледане промене",
        "rcfilters-filter-watchlistactivity-unseen-description": "Промене на страницама које нисте посетили од када су промене направљене.",
-       "rcfilters-filter-watchlistactivity-seen-label": "Ð\9fогледане Ð¸Ð·мене",
+       "rcfilters-filter-watchlistactivity-seen-label": "Ð\9fогледане Ð¿Ñ\80омене",
        "rcfilters-filter-watchlistactivity-seen-description": "Промене на страницама које сте посетили од када су промене направљене.",
        "rcfilters-filtergroup-changetype": "Тип промене",
        "rcfilters-filter-pageedits-label": "Измене страница",
        "rcfilters-filter-categorization-label": "Промене категорија",
        "rcfilters-filter-categorization-description": "Записи о страницама додатим или уклоњеним из категорија.",
        "rcfilters-filter-logactions-label": "Евидентиране радње",
-       "rcfilters-filter-logactions-description": "Ð\90дминиÑ\81Ñ\82Ñ\80аÑ\82ивне Ñ\80адÑ\9aе, Ð¿Ñ\80авÑ\99ење налога, брисање страница, отпремања…",
+       "rcfilters-filter-logactions-description": "Ð\90дминиÑ\81Ñ\82Ñ\80аÑ\82ивне Ñ\80адÑ\9aе, Ð¾Ñ\82ваÑ\80ање налога, брисање страница, отпремања…",
        "rcfilters-hideminor-conflicts-typeofchange-global": "Филтер за „мање” измене је у сукобу са једним или више филтера типа промена, зато што одређени типови промена не могу да се означе као „мање”. Сукобљени филтери су означени у подручју Активни филтери, изнад.",
        "rcfilters-hideminor-conflicts-typeofchange": "Одређени типови промена не могу да се означе као „мање”, тако да је овај филтер у сукобу са следећим филтерима типа промена: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "Овај филтер типа измене је у сукобу са филтером за „мање” измене. Одређени типови измена не могу да се означе као „мање”.",
-       "rcfilters-filtergroup-lastRevision": "Ð\9fоÑ\81ледÑ\9aе ревизије",
-       "rcfilters-filter-lastrevision-label": "Ð\9fоÑ\81ледÑ\9aа Ð¸Ð·Ð¼ÐµÐ½а",
+       "rcfilters-filtergroup-lastRevision": "Ð\9dаÑ\98новиÑ\98е ревизије",
+       "rcfilters-filter-lastrevision-label": "Ð\9dаÑ\98новиÑ\98а Ñ\80евизиÑ\98а",
        "rcfilters-filter-lastrevision-description": "Само најновија промена на страници.",
-       "rcfilters-filter-previousrevision-label": "Ð\9dиÑ\98е Ð¿Ð¾Ñ\81ледÑ\9aа ревизија",
+       "rcfilters-filter-previousrevision-label": "Ð\9dиÑ\98е Ð½Ð°Ñ\98новиÑ\98а ревизија",
        "rcfilters-filter-previousrevision-description": "Све промене које нису „последње ревизије”.",
        "rcfilters-filter-excluded": "Изузето",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:није</strong> $1",
        "rcfilters-liveupdates-button": "Ажурирај уживо",
        "rcfilters-liveupdates-button-title-on": "Искључите ажурирања уживо",
        "rcfilters-liveupdates-button-title-off": "Прикажите нове промене уживо",
-       "rcfilters-watchlist-markseen-button": "Ð\9eзнаÑ\87и Ñ\81ве Ð¸Ð·мене као погледане",
+       "rcfilters-watchlist-markseen-button": "Ð\9eзнаÑ\87и Ñ\81ве Ð¿Ñ\80омене као погледане",
        "rcfilters-watchlist-edit-watchlist-button": "Промени списак надгледаних страница",
        "rcfilters-watchlist-showupdated": "Промене на страницама које нисте посетили од када је измена извршена су <strong>подебљане</strong>, с испуњеним ознакама.",
        "rcfilters-preference-label": "Сакриј побољшану верзију скорашњих измена",
        "php-uploaddisabledtext": "Отпремање датотека је онемогућено у PHP-у.\nПроверите подешавања file_uploads.",
        "uploadscripted": "Датотека садржи HTML или скриптни код који може бити погрешно протумачен од стране прегледача.",
        "upload-scripted-pi-callback": "Датотека која садржи инструкције за обраду XML стилског облика се не може отпремити.",
-       "upload-scripted-dtd": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð¾Ñ\82пÑ\80емим SVG Ð´Ð°Ñ\82оÑ\82еке које садрже нестандардну DTD декларацију.",
+       "upload-scripted-dtd": "Ð\9dиÑ\98е Ð¼Ð¾Ð³Ñ\83Ñ\9bе Ð¾Ñ\82пÑ\80емаÑ\9aе SVG Ð´Ð°Ñ\82оÑ\82ека које садрже нестандардну DTD декларацију.",
        "uploaded-script-svg": "Пронађен скриптни елеменат „$1“ у постављеној SVG датотеци.",
        "uploaded-hostile-svg": "Пронађен небезбедан CSS у стилском елементу постављене SVG датотеке.",
        "uploaded-event-handler-on-svg": "Није дозвољено постављање атрибута који контролишу догађаје <code>$1=\"$2\"</code> у SVG датотекама.",
        "common.js": "/* Јаваскрипт постављен овде ће се користити за све кориснике при отварању сваке странице. */",
        "group-autoconfirmed.js": "/* Јаваскрипт постављен овде ће се учитати за самопотврђене кориснике */",
        "group-bot.js": "/* Јаваскрипт постављен овде ће се учитати само за ботове */",
-       "group-sysop.js": "/* Јаваскрипт постављен овде ће се учитати само за системске операторе */",
+       "group-sysop.js": "/* JavaScript постављен овде ће се учитати само за системске операторе */",
        "group-bureaucrat.js": "/* Јаваскрипт постављен овде ће се учитати само за бирократе */",
        "anonymous": "Анонимни {{PLURAL:$1|корисник|корисници}} пројекта {{SITENAME}}",
        "siteuser": "{{SITENAME}} корисник $1",
        "confirm-unwatch-top": "Уклонити ову страницу са списка надгледања?",
        "confirm-rollback-button": "У реду",
        "confirm-rollback-top": "Врати измене на овој страници?",
+       "confirm-mcrundo-title": "Поништавање промене",
+       "mcrundofailed": "Поништавање није успело",
+       "mcrundo-missingparam": "Недостаје потребан параметар на захтеву.",
+       "mcrundo-changed": "Страница је промењена док сте гледали разлику. Прегледајте нову промену.",
        "semicolon-separator": ";&#32;",
        "comma-separator": ",&#32;",
        "colon-separator": ":&#32;",
        "version-libraries-license": "Лиценца",
        "version-libraries-description": "Опис",
        "version-libraries-authors": "Аутори",
-       "redirect": "Ð\9fÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе Ð½Ð° Ð´Ð°Ñ\82оÑ\82екÑ\83, ÐºÐ¾Ñ\80иÑ\81ника, Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\80евизиÑ\98Ñ\83 Ð¸Ð»Ð¸ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº (ID)",
+       "redirect": "Ð\9fÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе Ð½Ð° Ð´Ð°Ñ\82оÑ\82екÑ\83, ÐºÐ¾Ñ\80иÑ\81ника, Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\80евизиÑ\98Ñ\83 Ð¸Ð»Ð¸ ÐµÐ²Ð¸Ð´ÐµÐ½Ñ\86иÑ\98Ñ\83 (ID)",
        "redirect-summary": "Ова посебна страница преусмерава до датотеке (с датим именом датотеке), странице (с датим ID-ом ревизије или ID-ом странице), корисничке странице (с датим нумеричким корисничким ID-ом), или уноса у дневнику (с датим дневничким ID-ом). Употреба: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], or [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Иди",
        "redirect-lookup": "Тип вредности:",
        "log-description-tag": "Ова страница приказује када су корисници додали/уклонили [[Special:Tags|ознаке]] с појединачних ревизија или уноса у евиденцијама. Евиденција не приказује радње означавања када су се догодиле приликом уређивања, брисања или сличне радње.",
        "rightsnone": "(нема)",
        "rightslogentry-temporary-group": "$1 (привремено, до $2)",
-       "feedback-adding": "Додајем повратну информацију на страницу…",
+       "feedback-adding": "Додајем повратне информације на страницу…",
        "feedback-back": "Назад",
-       "feedback-bugcheck": "Одлично! Проверите да ли је грешка [$1 позната од пре].",
+       "feedback-bugcheck": "Одлично! Проверите да се не ради о некој [$1 познатој грешци].",
        "feedback-bugnew": "Проверено. Пријави нову грешку",
        "feedback-bugornote": "Ако сте спремни да детаљно опишете технички проблем, онда [$1 пријавите грешку].\nУ супротном, послужите се једноставним обрасцем испод. Ваш коментар ће стајати на страници „[$3 $2]“, заједно с корисничким именом и прегледачем који користите.",
        "feedback-cancel": "Откажи",
-       "feedback-close": "УÑ\80аÑ\92ено",
-       "feedback-external-bug-report-button": "Ð\9fÑ\80иÑ\98ави Ð³Ñ\80еÑ\88кÑ\83",
-       "feedback-dialog-title": "СлаÑ\9aе Ð¿Ð¾Ð²Ñ\80аÑ\82не Ð¸Ð½Ñ\84оÑ\80маÑ\86иÑ\98е",
-       "feedback-error1": "Грешка: непрепознат резултат од АПИ-ја",
+       "feedback-close": "Ð\93оÑ\82ово",
+       "feedback-external-bug-report-button": "Ð\90Ñ\80Ñ\85ивиÑ\80аÑ\98 Ñ\82еÑ\85ниÑ\87ки Ð·Ð°Ð´Ð°Ñ\82ак",
+       "feedback-dialog-title": "СлаÑ\9aе Ð¿Ð¾Ð²Ñ\80аÑ\82ниÑ\85 Ð¸Ð½Ñ\84оÑ\80маÑ\86иÑ\98а",
+       "feedback-error1": "Грешка: непрепознат резултат од API-ја",
        "feedback-error2": "Грешка: уређивање није успело",
-       "feedback-error3": "Грешка: нема одговора од АПИ-ја",
+       "feedback-error3": "Грешка: нема одговора од API-ја",
+       "feedback-error4": "Грешка: не могу да поставим повратне информације на дати наслов",
        "feedback-message": "Порука:",
-       "feedback-subject": "Ð\9dаÑ\81лов:",
+       "feedback-subject": "Тема:",
        "feedback-submit": "Пошаљи",
        "feedback-termsofuse": "Прихватам да пошаљем повратне информације у складу са условима коришћења.",
        "feedback-thanks": "Хвала! Ваша повратна информација је постављена на страницу „[$2 $1]“.",
        "searchsuggest-search": "Претрага",
        "searchsuggest-containing": "садржи…",
        "api-error-badtoken": "Унутрашња грешка: лош токен.",
-       "api-error-emptypage": "СÑ\82ваÑ\80ање нових празних страница није дозвољено.",
+       "api-error-emptypage": "Ð\9fÑ\80авÑ\99ење нових празних страница није дозвољено.",
        "api-error-publishfailed": "Унутрашња грешка: сервер није успео да објави привремену датотеку.",
-       "api-error-stashfailed": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа Ð³Ñ\80еÑ\88ка: Ñ\81еÑ\80веÑ\80 Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ñ\81аÑ\87Ñ\83ва привремену датотеку.",
+       "api-error-stashfailed": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа Ð³Ñ\80еÑ\88ка: Ñ\81еÑ\80веÑ\80 Ð½Ð¸Ñ\98е Ñ\83Ñ\81пео Ð´Ð° Ñ\81меÑ\81Ñ\82и привремену датотеку.",
        "api-error-unknown-warning": "Непознато упозорење: „$1”.",
-       "api-error-unknownerror": "Ð\9dепознаÑ\82а Ð³Ñ\80еÑ\88ка: â\80\9e$1â\80\9c.",
+       "api-error-unknownerror": "Ð\9dепознаÑ\82а Ð³Ñ\80еÑ\88ка: â\80\9e$1â\80\9d.",
        "duration-seconds": "$1 {{PLURAL:$1|секунд|секунда|секунди}}",
-       "duration-minutes": "$1 {{PLURAL:$1|минут|минута|минута}}",
+       "duration-minutes": "$1 {{PLURAL:$1|минут|минута}}",
        "duration-hours": "$1 {{PLURAL:$1|сат|сата|сати}}",
-       "duration-days": "$1 {{PLURAL:$1|дан|дана|дана}}",
+       "duration-days": "$1 {{PLURAL:$1|дан|дана}}",
        "duration-weeks": "$1 {{PLURAL:$1|недеља|недеље|недеља}}",
        "duration-years": "$1 {{PLURAL:$1|година|године|година}}",
        "duration-decades": "$1 {{PLURAL:$1|деценија|деценије|деценија}}",
        "duration-centuries": "$1 {{PLURAL:$1|век|века|векова}}",
-       "duration-millennia": "$1 {{PLURAL:$1|миленијум|миленијума|миленијума}}",
-       "rotate-comment": "Слика је ротирана за $1° у смеру казаљке на сату",
+       "duration-millennia": "$1 {{PLURAL:$1|миленијум|миленијума}}",
+       "rotate-comment": "Слика је ротирана за $1 {{PLURAL:$1|степен|степена|степени}} у смеру казаљке на сату",
        "limitreport-title": "Подаци профилисања анализатора:",
-       "limitreport-cputime": "Време коришћења CPU",
+       "limitreport-cputime": "Време коришћења CPU",
        "limitreport-cputime-value": "$1 {{PLURAL:$1|секунд|секунда|секунди}}",
        "limitreport-walltime": "Коришћење у реалном времену",
        "limitreport-walltime-value": "$1 {{PLURAL:$1|секунд|секунда|секунди}}",
        "limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|бајт|бајта|бајтова}}",
        "expandtemplates": "Проширавање шаблона",
        "expand_templates_intro": "Ова посебна страница узима викитекст и мења све шаблоне у њему рекурзивно.\nТакође мења функције парсера као што је <code><nowiki>{{</nowiki>#language:…}}</code> и променљиве као што је <code><nowiki>{{</nowiki>CURRENTDAY}}</code>. \nЗаправо практично све што се налази између витичастих заграда.",
-       "expand_templates_title": "Назив контекста; за {{СТРАНИЦА}} итд.:",
+       "expand_templates_title": "Наслов контекста, за {{FULLPAGENAME}} итд.:",
        "expand_templates_input": "Унос викитекста:",
        "expand_templates_output": "Резултат",
        "expand_templates_xml_output": "XML излаз",
        "expand_templates_ok": "У реду",
        "expand_templates_remove_comments": "Уклони коментаре",
        "expand_templates_remove_nowiki": "Поништава ефекат <nowiki> тагова у приказу чланака",
-       "expand_templates_generate_xml": "Прикажи XML стабло",
+       "expand_templates_generate_xml": "Прикажи XML стабло за рашчлањивање",
        "expand_templates_generate_rawhtml": "Прикажи сиров HTML",
        "expand_templates_preview": "Претпреглед",
        "pagelanguage": "Промена језика странице",
        "pagelang-select-lang": "Изабери језик",
        "pagelang-reason": "Разлог",
        "pagelang-submit": "Пошаљи",
-       "pagelang-nonexistent-page": "Страница $1 не постоји.",
-       "pagelang-unchanged-language": "Страница $1  је већ постављена на језик $2.",
-       "pagelang-db-failed": "Ð\91аза Ð¿Ð¾Ð´Ð°Ñ\82ака Ð½Ð¸Ñ\98е Ñ\83Ñ\81пела Ð¿Ñ\80омениÑ\82и језик странице.",
+       "pagelang-nonexistent-page": "Страница „$1” не постоји.",
+       "pagelang-unchanged-language": "Страница „$1” је већ постављена на језик $2.",
+       "pagelang-db-failed": "Ð\91аза Ð¿Ð¾Ð´Ð°Ñ\82ака Ð½Ð¸Ñ\98е Ñ\83Ñ\81пела Ð´Ð° Ð¿Ñ\80Ð¾Ð¼ÐµÐ½и језик странице.",
        "right-pagelang": "мењање језика странице",
        "action-pagelang": "промените језик странице",
        "log-name-pagelang": "Евиденција промене језика",
        "log-description-pagelang": "Ово је евиденција промена у језицима страница.",
-       "logentry-pagelang-pagelang": "$1 је {{GENDER:$2|променио|променила}} језик странице $3 из $4 у $5.",
+       "logentry-pagelang-pagelang": "$1 је {{GENDER:$2|променио|променила}} језик странице „$3” из $4 у $5.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (омогућена)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>онемогућена</strong>)",
        "mediastatistics": "Статистика медија",
        "mediastatistics-table-mimetype": "MIME тип",
        "mediastatistics-table-extensions": "Могући додаци",
        "mediastatistics-table-count": "Број датотека",
-       "mediastatistics-table-totalbytes": "УкÑ\83пна величина",
+       "mediastatistics-table-totalbytes": "Ð\9aомбинована величина",
        "mediastatistics-header-unknown": "Непознато",
        "mediastatistics-header-bitmap": "Битмап слике",
        "mediastatistics-header-drawing": "Цртежи (векторске слике)",
-       "mediastatistics-header-audio": "Ð\90Ñ\83дио",
-       "mediastatistics-header-video": "Ð\92идео",
+       "mediastatistics-header-audio": "Ð\97вÑ\83к",
+       "mediastatistics-header-video": "Ð\92идеи",
        "mediastatistics-header-multimedia": "Обогаћени медији",
        "mediastatistics-header-office": "Канцеларија",
        "mediastatistics-header-text": "Текстуалне",
        "mediastatistics-header-executable": "Извршне",
-       "mediastatistics-header-archive": "Ð\9aомпÑ\80еÑ\81оване",
+       "mediastatistics-header-archive": "Ð\9aомпÑ\80еÑ\81овани Ñ\84оÑ\80маÑ\82и",
        "mediastatistics-header-total": "Све датотеке",
        "json-warn-trailing-comma": "$1 {{PLURAL:$1|пратећа тачка је уклоњена|пратеће тачке су уклоњене|пратећих тачки је уклоњено}} из JSON-a",
        "json-error-unknown": "Догодио се проблем с JSON-ом. Грешка: $1",
        "special-characters-group-cyrillic": "Ћирилица",
        "special-characters-group-arabic": "Арапски",
        "special-characters-group-arabicextended": "Проширени арапски",
-       "special-characters-group-persian": "персијски",
+       "special-characters-group-persian": "Ð\9fерсијски",
        "special-characters-group-hebrew": "Хебрејски",
        "special-characters-group-bangla": "Бенгалски",
        "special-characters-group-tamil": "Тамилски",
        "special-characters-group-khmer": "Кмерски",
        "special-characters-group-canadianaboriginal": "Канадски абориџински",
        "special-characters-title-endash": "цртица",
-       "special-characters-title-emdash": "дÑ\83га Ñ\86Ñ\80Ñ\82иÑ\86а",
-       "special-characters-title-minus": "минус",
+       "special-characters-title-emdash": "дуга црта",
+       "special-characters-title-minus": "знак Ð·Ð° Ð¼Ð¸Ð½Ñ\83Ñ\81",
        "mw-widgets-dateinput-no-date": "Датум није изабран",
        "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
-       "mw-widgets-mediasearch-input-placeholder": "Ð\9fÑ\80еÑ\82Ñ\80ажиÑ\82е Ð´Ð°Ñ\82оÑ\82еке",
-       "mw-widgets-mediasearch-noresults": "Ð\9dема Ñ\80езÑ\83лÑ\82аÑ\82а.",
+       "mw-widgets-mediasearch-input-placeholder": "Ð\9fÑ\80еÑ\82Ñ\80ажиÑ\82е Ð¼ÐµÐ´Ð¸Ñ\98е",
+       "mw-widgets-mediasearch-noresults": "РезÑ\83лÑ\82аÑ\82и Ð½Ð¸Ñ\81Ñ\83 Ð¿Ñ\80онаÑ\92ени.",
        "mw-widgets-titleinput-description-new-page": "страница још увек не постоји",
        "mw-widgets-titleinput-description-redirect": "преусмерава на $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Додајте категорију…",
-       "mw-widgets-usersmultiselect-placeholder": "Додај још...",
+       "mw-widgets-usersmultiselect-placeholder": "Додајте још…",
        "date-range-from": "Од датума:",
        "date-range-to": "До датума:",
+       "sessionmanager-tie": "Не можете да комбинујете више типова потврде идентитета: $1.",
        "sessionprovider-generic": "$1 сесије",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "сесије са колачићима",
-       "sessionprovider-nocookies": "Колачићи су можда онемогућени. Уверите се да су колачићи омогућени и почните поново.",
+       "sessionprovider-nocookies": "Колачићи су можда онемогућени. Уверите се да имате колачиће омогућене и почните поново.",
        "randomrootpage": "Случајна коренска страница",
        "log-action-filter-block": "Тип блокирања:",
        "log-action-filter-contentmodel": "Тип промене модела садржаја:",
        "log-action-filter-import": "Тип увоза:",
        "log-action-filter-managetags": "Тип радње управљања ознакама:",
        "log-action-filter-move": "Тип премештања:",
-       "log-action-filter-newusers": "Тип Ð½Ð¾Ð²Ð¾Ð³ налога:",
+       "log-action-filter-newusers": "Тип Ð¾Ñ\82ваÑ\80аÑ\9aа налога:",
        "log-action-filter-patrol": "Тип патролирања:",
        "log-action-filter-protect": "Тип заштите:",
        "log-action-filter-rights": "Тип промене корисничких права:",
        "log-action-filter-block-reblock": "измена блокирања",
        "log-action-filter-block-unblock": "деблокирање",
        "log-action-filter-contentmodel-change": "Промена модела садржаја",
-       "log-action-filter-contentmodel-new": "Ð\9dова Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81 нестандардним моделом садржаја",
+       "log-action-filter-contentmodel-new": "Ð\9fÑ\80авÑ\99еÑ\9aе Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81а нестандардним моделом садржаја",
        "log-action-filter-delete-delete": "брисање странице",
        "log-action-filter-delete-delete_redir": "преснимавање преусмерења",
        "log-action-filter-delete-restore": "враћање странице",
        "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-create": "пÑ\80авÑ\99еÑ\9aе Ð¾Ð·Ð½Ð°ÐºÐµ",
        "log-action-filter-managetags-delete": "брисање ознаке",
        "log-action-filter-managetags-activate": "активирање ознаке",
        "log-action-filter-managetags-deactivate": "деактивирање ознаке",
        "authmanager-authn-no-local-user-link": "Пружени су важећи акредитиви, али нису повезани ни с једним корисником на овом викију. Пријавите се на неки други начин или направите нови кориснички налог, што ће вам дати могућност да повежете претходне акредитиве на нови налог.",
        "authmanager-authn-autocreate-failed": "Не могу да аутоматски направим локални налог: $1",
        "authmanager-change-not-supported": "Не могу да променим пружене акредитиве јер их ништа не би користило.",
-       "authmanager-create-disabled": "Онемогућено прављење налога.",
+       "authmanager-create-disabled": "Отварање налога је онемогућено.",
        "authmanager-create-from-login": "Попуните поља да бисте направили налог.",
-       "authmanager-create-not-in-progress": "Ð\9fÑ\80авÑ\99еÑ\9aе Ð½Ð°Ð»Ð¾Ð³Ð° Ð½Ð¸Ñ\98е Ñ\83 Ñ\82окÑ\83 Ð¸Ð»Ð¸ Ñ\81Ñ\83 Ð¿Ð¾Ð´Ð°Ñ\86и Ð¾ Ñ\81еÑ\81иÑ\98и Ð¸Ð·Ð³Ñ\83бÑ\99ени. Ð\9fоÑ\87ниÑ\82е испочетка.",
-       "authmanager-create-no-primary": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð¸Ñ\81коÑ\80иÑ\81Ñ\82им Ð¿Ñ\80Ñ\83жене Ð°ÐºÑ\80едиÑ\82иве Ð·Ð° Ð¿Ñ\80авÑ\99ење налога.",
+       "authmanager-create-not-in-progress": "Ð\9eÑ\82ваÑ\80аÑ\9aе Ð½Ð°Ð»Ð¾Ð³Ð° Ð½Ð¸Ñ\98е Ñ\83 Ñ\82окÑ\83 Ð¸Ð»Ð¸ Ñ\81Ñ\83 Ð¿Ð¾Ð´Ð°Ñ\86и Ð¾ Ñ\81еÑ\81иÑ\98и Ð¸Ð·Ð³Ñ\83бÑ\99ени. Ð\9fоÑ\87ниÑ\82е Ð¿Ð¾Ð½Ð¾Ð²Ð¾ испочетка.",
+       "authmanager-create-no-primary": "Ð\9dе Ð¼Ð¾Ð³Ñ\83 Ð´Ð° Ð¸Ñ\81коÑ\80иÑ\81Ñ\82им Ð¿Ñ\80Ñ\83жене Ð°ÐºÑ\80едиÑ\82иве Ð·Ð° Ð¾Ñ\82ваÑ\80ање налога.",
        "authmanager-link-no-primary": "Не могу да искористим пружене акредитиве за спајање налога.",
        "authmanager-link-not-in-progress": "Спајање налога није у току или је дошло до губитка података о сесији. Почните испочетка.",
        "authmanager-authplugin-setpass-failed-title": "Неуспешна промена лозинке",
        "authmanager-authplugin-setpass-failed-message": "Додатак за потврду идентитета је одбио промену лозинке.",
-       "authmanager-authplugin-create-fail": "Ð\94одаÑ\82ак Ð·Ð° Ð¿Ð¾Ñ\82вÑ\80дÑ\83 Ð¸Ð´ÐµÐ½Ñ\82иÑ\82еÑ\82а Ñ\98е Ð¾Ð´Ð±Ð¸Ð¾ Ð¿Ñ\80авÑ\99ење налога.",
+       "authmanager-authplugin-create-fail": "Ð\94одаÑ\82ак Ð·Ð° Ð¿Ð¾Ñ\82вÑ\80дÑ\83 Ð¸Ð´ÐµÐ½Ñ\82иÑ\82еÑ\82а Ñ\98е Ð¾Ð´Ð±Ð¸Ð¾ Ð¾Ñ\82ваÑ\80ање налога.",
        "authmanager-authplugin-setpass-denied": "Додатак за потврду идентитета не дозвољава мењање лозику.",
        "authmanager-authplugin-setpass-bad-domain": "Неважећи домен.",
-       "authmanager-autocreate-noperm": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ко Ð¿Ñ\80авÑ\99ење налога није дозвољено.",
+       "authmanager-autocreate-noperm": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ко Ð¾Ñ\82ваÑ\80ање налога није дозвољено.",
        "authmanager-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
        "authmanager-username-help": "Корисничко име за потврду идентитета.",
        "authmanager-password-help": "Лозинка за потврду идентитета.",
        "authmanager-domain-help": "Домен за спољашњу потврду идентитета.",
        "authmanager-retype-help": "Поновите лозинку да би сте потврдили.",
        "authmanager-email-label": "Имејл",
-       "authmanager-email-help": "Имејл адреса",
+       "authmanager-email-help": "Имејл-адреса",
        "authmanager-realname-label": "Право име",
        "authmanager-realname-help": "Право име корисника",
        "authmanager-provider-password": "Потврда идентитета лозинком",
index da95654..415a27a 100644 (file)
        "group-autoconfirmed": "Otomatik onaylanmış kullanıcılar",
        "group-bot": "Botlar",
        "group-sysop": "Hizmetliler",
-       "group-interface-admin": "Arayüz yöneticisi",
+       "group-interface-admin": "Arayüz yöneticileri",
        "group-bureaucrat": "Bürokratlar",
        "group-suppress": "Gözetmenler",
        "group-all": "(hepsi)",
        "right-reupload-shared": "Paylaşılan ortam deposundaki dosyaları yerel olarak geçersiz kıl",
        "right-upload_by_url": "Bir URL adresinden dosya yükle",
        "right-purge": "Doğrulama yapmadan bir sayfa için site belleğini temizle",
-       "right-autoconfirmed": "IP-tabanlı hız limitleri etkilenmeyecektir",
+       "right-autoconfirmed": "IP-tabanlı hız limitleri etkilenme",
        "right-bot": "Otomatik bir işlem gibi muamele gör",
        "right-nominornewtalk": "Kullanıcı tartışma sayfalarında yaptığı küçük değişiklikler kullanıcıya yeni mesaj bildirimiyle bildirilmez",
        "right-apihighlimits": "API sorgularında yüksek sınır kullan",
        "right-deletedtext": "Silinmiş metni ve silinmiş revizyonlar arasındaki değişiklikleri gör",
        "right-browsearchive": "Silinen sayfaları ara",
        "right-undelete": "Bir sayfanın silinmesini geri al",
-       "right-suppressrevision": "Sysoplardan gizlenmiş revizyonlarını gizle ve göster",
+       "right-suppressrevision": "Hizmetlilerden revizyon gizle ve geri getir",
        "right-viewsuppressed": "Herhangi bir kullanıcıdan saklanan sürümleri göster",
        "right-suppressionlog": "Özel günlükleri gör",
        "right-block": "Diğer kullanıcıların değişiklik yapmalarını engelle",
        "right-blockemail": "Bir kullanıcının e-posta göndermesini engelle",
-       "right-hideuser": "Bir kullanıcı adını engelle, genelden gizleyerek",
+       "right-hideuser": "Herkesden gizleyerek bir kullanıcı adını engelle",
        "right-ipblock-exempt": "IP engellemelerini atla, otomatik engelle ve aralık engellemeleri",
        "right-unblockself": "Kendi engellemesini kaldır",
        "right-protect": "Koruma düzeylerini değiştir ve kademeli korumalı sayfaları düzenle",
        "right-editprotected": "\"{{int:protect-level-sysop}}\" olarak korunan sayfalarda değişiklik yap",
        "right-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" olarak korunan sayfalarda değişiklik yap",
        "right-editcontentmodel": "Sayfanın içerik modelini düzenle",
-       "right-editinterface": "Kullanıcı arayüzünü değiştirmek",
-       "right-editusercss": "Diğer kullanıcıların CSS dosyalarında değişiklik yap",
-       "right-edituserjson": "Diğer kullanıcıların JSON dosyalarını düzenle",
-       "right-edituserjs": "Diğer kullanıcıların JS dosyalarında değişiklik yap",
+       "right-editinterface": "Kullanıcı arayüzünü değiştir",
+       "right-editusercss": "Diğer kullanıcıların CSS sayfalarında değişiklik yap",
+       "right-edituserjson": "Diğer kullanıcıların JSON sayfalarında değişiklik yap",
+       "right-edituserjs": "Diğer kullanıcıların JS sayfalarında değişiklik yap",
        "right-editsitecss": "Sitewide CSS düzenle",
        "right-editsitejson": "Sitewide JSON'u düzenle",
        "right-editsitejs": "Sitewide JavaScript'i düzenle",
        "right-editmywatchlist": "Kendi izleme listeni düzenle. Not, bazı eylemler bu yetki olmadan da sayfa ekleyebilir.",
        "right-viewmyprivateinfo": "Kendi özel bilgilerini görüntüle (e-posta adresi, gerçek isim vb.)",
        "right-editmyprivateinfo": "Kendi özel bilgilerini değiştir (e-posta adresi, gerçek isim vb.)",
-       "right-editmyoptions": "tercihlerini düzenle",
+       "right-editmyoptions": "Tercihlerini düzenle",
        "right-rollback": "Belirli bir sayfayı değiştiren son kullanıcının değişikliklerini hızlıca geri döndür",
        "right-markbotedits": "Geri döndürülen değişiklikleri, bot değişiklikleri olarak işaretle",
        "right-noratelimit": "Derecelendirme sınırlamalarından etkilenme",
        "log": "Günlükler",
        "logeventslist-submit": "Göster",
        "logeventslist-more-filters": "Daha fazla süzgeç:",
+       "logeventslist-patrol-log": "Devriye günlüğü",
+       "logeventslist-tag-log": "Etiket günlüğü",
        "all-logs-page": "Tüm genel günlükler",
        "alllogstext": "{{SITENAME}} için mevcut tüm günlüklerin birleşik gösterimi.\nGünlük tipini, kullanıcı adını (büyük-küçük harf duyarlı), ya da etkilenen sayfayı (yine büyük-küçük harf duyarlı) seçerek görünümü daraltabilirsiniz.",
        "logempty": "Kayıtlarda eşleşen bilgi yok.",
        "dellogpage": "Silme günlüğü",
        "dellogpagetext": "Aşağıda en son silme işlemlerinin bir listesi bulunmaktadır.",
        "deletionlog": "silme günlüğü",
+       "logentry-create-create": "$1, $3 adlı sayfayı {{GENDER:$2|oluşturdu}}",
        "reverted": "Önceki sürüm geri getirildi",
        "deletecomment": "Neden:",
        "deleteotherreason": "Diğer/ilave neden:",
index 2330ae4..789dc16 100644 (file)
        "compare-rev1": "Беренче юрама",
        "compare-rev2": "Икенче юрама",
        "compare-submit": "Чагыштыр",
+       "permanentlink": "Даими сылтама",
        "dberr-problems": "Гафу итегез! Сайтта техник кыенлыклар чыкты.",
        "dberr-again": "Сәхифәне берничә минуттан соң яңартып карагыз.",
        "dberr-info": "(Мәгълүматлар базасы серверы белән тоташырга мөмкин түгел: $1)",
index ca421c9..8ed22c4 100644 (file)
        "move-page-legend": "منتقلئ صفحہ",
        "movepagetext": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
        "movepagetext-noredirectfixer": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
-       "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا اگر اس عنوان کے تحت پہلے سے کوئی تبادلۂ خیال صفحہ موجود نہ ہو۔\n\nاس صورت میں آپ کو دستی طور پر اس صفحہ کو منتقل ضم کرنا ہوگا۔",
+       "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی (بشرطیکہ موجود ہو) نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا۔\n\nاگر آپ نے اس خانہ کو نشان زد نہیں کیا تو ملحقہ تبادلہ خیال صفحہ کو دستی طور پر منتقل کرکے ضم کرنا ہوگا۔",
        "moveuserpage-warning": "<strong>انتباہ:</strong> آپ صارف صفحہ کو منتقل کر رہے ہیں۔ واضح رہے کہ اس منتقلی کے بعد صارف کا محض صفحہ منتقل ہوگا، اس کا صارف نام تبدیل <em>نہیں</em> ہوگا۔",
        "movecategorypage-warning": "<strong>انتباہ:</strong> آپ زمرہ منتقل کر رہے ہیں۔ واضح رہے کہ منتقلی کے بعد اس زمرے میں موجود صفحات نئے زمرے میں منتقل <em>نہیں</em> ہونگے۔",
        "movenologintext": "صفحہ کو منتقل کرنے کے لیے آپ کو اپنے کھاتے میں [[Special:UserLogin|داخل ہونا]] ضروری ہے۔",
index 0473e43..85d56d9 100644 (file)
        "toc": "Mục lục",
        "showtoc": "hiện",
        "hidetoc": "ẩn",
-       "collapsible-collapse": "Thu gọn",
-       "collapsible-expand": "Mở rộng",
+       "collapsible-collapse": "n",
+       "collapsible-expand": "Hiện",
        "confirmable-confirm": "{{GENDER:$1}}Bạn chắc chứ?",
        "confirmable-yes": "Có",
        "confirmable-no": "Không",
index da4a1fa..d20f14a 100644 (file)
        "changed": "變更",
        "deletepage": "刪除頁面",
        "confirm": "確認",
-       "excontent": "內容為:\"$1\"",
+       "excontent": "內容為:「$1」",
        "excontentauthor": "內容為:\"$1\",且僅有一位貢獻者 \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|對話]])",
        "exbeforeblank": "被清空前的內容為:\"$1\"",
        "delete-confirm": "刪除 \"$1\"",
        "confirm-unwatch-top": "從您的監視清單中移除此頁面?",
        "confirm-rollback-button": "確定",
        "confirm-rollback-top": "還原編輯到此頁面?",
+       "confirm-mcrundo-title": "還原變更",
+       "mcrundofailed": "還原失敗",
+       "mcrundo-missingparam": "請求缺少必要參數。",
+       "mcrundo-changed": "自您檢視差異之後,頁面有被變更過。請檢閱新的變更。",
        "semicolon-separator": ";",
        "comma-separator": "、",
        "colon-separator": ":",
        "edit-error-long": "錯誤:\n\n$1",
        "revid": "修訂 $1",
        "pageid": "頁面 ID $1",
-       "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,剛剛已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
+       "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,近期已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
        "rawhtml-notallowed": "&lt;html&gt; 標籤無法在一般頁面之外使用。",
        "gotointerwiki": "離開 {{SITENAME}}",
        "gotointerwiki-invalid": "指定的標題無效。",
index 6f922a0..7ff972e 100644 (file)
@@ -8,7 +8,6 @@
                                        "mw",
                                        "mw.Message",
                                        "mw.loader",
-                                       "mw.loader.store",
                                        "mw.html",
                                        "mw.html.Cdata",
                                        "mw.html.Raw",
index b8d9848..6a02eea 100644 (file)
@@ -1,17 +1,39 @@
 ### Format of this file
 #
 # The top-level keys are module names (as registered in Resources.php).
-# The values of these keys are resource descriptors.
+# Each top-level key holds a resource descriptor that must have one of
+# the following `type` values:
 #
-# In each resource descriptor object, the `src` and `integrity` keys are required.
+# - `tar`: For tarball archive (may be gzip-compressed).
+# - `file: For a plain file.
+# - `multi-file`: For multiple plain files.
 #
-# * `src`: Full URL to a remote resource.
-# * `integrity`: Cryptographic hash used to verify the remote content.
-#    Uses the "integrity metadata" format defined at <https://www.w3.org/TR/SRI/>.
-# * `dest`: An object mapping paths from the remote resource to a destination in
-#    `/resources/lib/$module/`. The value may be omitted to indicate that
-#    paths should be extracted to the destination directory itself.
+### Type tar
+#
+# The `src` and `integrity` keys are quired.
+#
+# * `src`: Full URL to thes remote resource.
+# * `integrity`: Cryptographic hash (integrity metadata format per <https://www.w3.org/TR/SRI/>).
+# * `dest`: An object mapping paths to files or directory from the remote resource to a destination
+#    in the module directory. The value of key in dest may be omitted, which will extract the key
+#    directly to the module directory.
+#
+### Type file
+#
+# The `src` and `integrity` keys are quired.
+#
+# * `src`: Full URL to thes remote resource.
+# * `integrity`: Cryptographic hash (integrity metadata format per <https://www.w3.org/TR/SRI/>).
+# * `dest`: The name of the file in the module directory. Default: Basename of URL.
+#
+### Type mult-file
+#
+# The `files` key is required.
+#
+# * `files`: An object mapping destination paths to an object containing `src` and `integrity`
+#    keys.
 oojs:
+  type: tar
   src: https://registry.npmjs.org/oojs/-/oojs-2.2.2.tgz
   integrity: sha256-ebgQW2EGrSkBCnDJBGqDpsBDjA3PMN/M8U5DyLHt9mw=
   dest:
@@ -20,6 +42,7 @@ oojs:
     package/LICENSE-MIT:
     package/README.md:
 oojs-ui:
+  type: tar
   src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.28.0.tgz
   integrity: sha384-j8bzlCPrfS4sca+U9JO9tdcewDlLlDlOVOsLn+Vqlcg5GU59vLSd7TVm4FiuTowy
   dest:
@@ -48,3 +71,18 @@ oojs-ui:
     package/dist/History.md:
     package/dist/LICENSE-MIT:
     package/dist/README.md:
+jquery:
+  type: file
+  src: https://code.jquery.com/jquery-3.2.1.js
+  # From https://code.jquery.com/jquery/
+  integrity: sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=
+  dest: jquery.js
+qunitjs:
+  type: multi-file
+  files:
+    qunit.js:
+      src: https://code.jquery.com/qunit/qunit-2.6.0.js
+      integrity: sha384-5O3bKbJBbAbxsqV+w/I1fcXgWJgbqM+hmYAPOE9aELSYpcTEsv48X8H+Hnq66V/9
+    qunit.css:
+      src: https://code.jquery.com/qunit/qunit-2.6.0.css
+      integrity: sha384-8vDvsmsuiD7tCQyC+pW2LOwDDgsluGsIPeCqr3rHsDSF2k4WpmfvKKxcgSV5zPai
index 528d6e7..41e579b 100644 (file)
@@ -30,6 +30,8 @@ require_once __DIR__ . '/../Maintenance.php';
 class ManageForeignResources extends Maintenance {
        private $defaultAlgo = 'sha384';
        private $tmpParentDir;
+       private $action;
+       private $failAfterOutput = false;
 
        public function __construct() {
                global $IP;
@@ -40,17 +42,16 @@ Manage foreign resources registered with ResourceLoader.
 This helps developers to download, verify and update local copies of upstream
 libraries registered as ResourceLoader modules. See also foreign-resources.yaml.
 
-For sources that don't publish an integrity hash, leave the value empty at
-first, and run this script with --make-sri to compute the hashes.
+For sources that don't publish an integrity hash, omit "integrity" (or leave empty)
+and run the "make-sri" action to compute the missing hashes.
 
 This script runs in dry mode by default. Use --update to actually change, remove,
 or add files to /resources/lib/.
 TEXT
                );
+               $this->addArg( 'action', 'One of "update", "verify" or "make-sri"', true );
                $this->addArg( 'module', 'Name of a single module (Default: all)', false );
-               $this->addOption( 'update', ' resources/lib/ missing integrity metadata' );
-               $this->addOption( 'make-sri', 'Compute missing integrity metadata' );
-               $this->addOption( 'verbose', 'Be verbose' );
+               $this->addOption( 'verbose', 'Be verbose', false, false, 'v' );
 
                // Use a directory in $IP instead of wfTempDir() because
                // PHP's rename() does not work across file systems.
@@ -59,67 +60,127 @@ TEXT
 
        public function execute() {
                global $IP;
-               $module = $this->getArg();
-               $makeSRI = $this->hasOption( 'make-sri' );
+               $this->action = $this->getArg( 0 );
+               if ( !in_array( $this->action, [ 'update', 'verify', 'make-sri' ] ) ) {
+                       $this->fatalError( "Invalid action argument." );
+               }
 
                $registry = $this->parseBasicYaml(
                        file_get_contents( __DIR__ . '/foreign-resources.yaml' )
                );
+               $module = $this->getArg( 1, 'all' );
                foreach ( $registry as $moduleName => $info ) {
-                       if ( $module !== null && $moduleName !== $module ) {
+                       if ( $module !== 'all' && $moduleName !== $module ) {
                                continue;
                        }
                        $this->verbose( "\n### {$moduleName}\n\n" );
+                       $destDir = "{$IP}/resources/lib/$moduleName";
 
-                       // Validate required keys
-                       $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
-                       if ( $info['src'] === null ) {
-                               $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+                       if ( $this->action === 'update' ) {
+                               $this->output( "... updating '{$moduleName}'\n" );
+                               $this->verbose( "... emptying /resources/lib/$moduleName\n" );
+                               wfRecursiveRemoveDir( $destDir );
+                       } elseif ( $this->action === 'verify' ) {
+                               $this->output( "... verifying '{$moduleName}'\n" );
+                       } else {
+                               $this->output( "... checking '{$moduleName}'\n" );
                        }
-                       $integrity = is_string( $info['integrity'] ) ? $info['integrity'] : $makeSRI;
-                       if ( $integrity === false ) {
-                               $this->fatalError( "Module '$moduleName' must have an 'integrity' key." );
+
+                       $this->verbose( "... preparing {$this->tmpParentDir}\n" );
+                       wfRecursiveRemoveDir( $this->tmpParentDir );
+                       if ( !wfMkdirParents( $this->tmpParentDir ) ) {
+                               $this->fatalError( "Unable to create {$this->tmpParentDir}" );
                        }
 
-                       // Download the resource
-                       $data = Http::get( $info['src'], [ 'followRedirects' => false ] );
-                       if ( $data === false ) {
-                               $this->fatalError( "Failed to download resource for '$moduleName'." );
+                       if ( !isset( $info['type'] ) ) {
+                               $this->fatalError( "Module '$moduleName' must have a 'type' key." );
+                       }
+                       switch ( $info['type'] ) {
+                               case 'tar':
+                                       $this->handleTypeTar( $moduleName, $destDir, $info );
+                                       break;
+                               case 'file':
+                                       $this->handleTypeFile( $moduleName, $destDir, $info );
+                                       break;
+                               case 'multi-file':
+                                       $this->handleTypeMultiFile( $moduleName, $destDir, $info );
+                                       break;
+                               default:
+                                       $this->fatalError( "Unknown type '{$info['type']}' for '$moduleName'" );
                        }
+               }
 
-                       // Validate integrity metadata
-                       $this->output( "... checking integrity of '{$moduleName}'\n" );
-                       $algo = $integrity === true ? $this->defaultAlgo : explode( '-', $integrity )[0];
-                       $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
-                       if ( $integrity === true ) {
-                               $this->output( "Integrity for '{$moduleName}':\n\t${actualIntegrity}\n" );
-                               continue;
-                       } elseif ( $integrity !== $actualIntegrity ) {
-                               $this->fatalError( "Integrity check failed for '{$moduleName}:\n" .
-                                       "Expected: {$integrity}\n" .
-                                       "Actual: {$actualIntegrity}"
+               $this->cleanUp();
+               $this->output( "\nDone!\n" );
+               if ( $this->failAfterOutput ) {
+                       // The verify mode should check all modules/files and fail after, not during.
+                       return false;
+               }
+       }
+
+       private function fetch( $src, $integrity ) {
+               $data = Http::get( $src, [ 'followRedirects' => false ] );
+               if ( $data === false ) {
+                       $this->fatalError( "Failed to download resource at {$src}" );
+               }
+               $algo = $integrity === null ? $this->defaultAlgo : explode( '-', $integrity )[0];
+               $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
+               if ( $integrity === $actualIntegrity ) {
+                       $this->verbose( "... passed integrity check for {$src}\n" );
+               } else {
+                       if ( $this->action === 'make-sri' ) {
+                               $this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
+                       } else {
+                               $this->fatalError( "Integrity check failed for {$src}\n" .
+                                       "\tExpected: {$integrity}\n" .
+                                       "\tActual: {$actualIntegrity}"
                                );
                        }
-
-                       // Determine destination
-                       $destDir = "{$IP}/resources/lib/$moduleName";
-                       $this->output( "... extracting files for '{$moduleName}'\n" );
-                       $this->handleTypeTar( $moduleName, $data, $destDir, $info );
                }
+               return $data;
+       }
 
-               // Clean up
-               wfRecursiveRemoveDir( $this->tmpParentDir );
-               $this->output( "\nDone!\n" );
+       private function handleTypeFile( $moduleName, $destDir, array $info ) {
+               if ( !isset( $info['src'] ) ) {
+                       $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+               }
+               $data = $this->fetch( $info['src'], $info['integrity'] ?? null );
+               $dest = $info['dest'] ?? basename( $info['src'] );
+               $path = "$destDir/$dest";
+               if ( $this->action === 'verify' && sha1_file( $path ) !== sha1( $data ) ) {
+                       $this->fatalError( "File for '$moduleName' is different." );
+               } elseif ( $this->action === 'update' ) {
+                       wfMkdirParents( $destDir );
+                       file_put_contents( "$destDir/$dest", $data );
+               }
        }
 
-       private function handleTypeTar( $moduleName, $data, $destDir, array $info ) {
-               global $IP;
-               wfRecursiveRemoveDir( $this->tmpParentDir );
-               if ( !wfMkdirParents( $this->tmpParentDir ) ) {
-                       $this->fatalError( "Unable to create {$this->tmpParentDir}" );
+       private function handleTypeMultiFile( $moduleName, $destDir, array $info ) {
+               if ( !isset( $info['files'] ) ) {
+                       $this->fatalError( "Module '$moduleName' must have a 'files' key." );
                }
+               foreach ( $info['files'] as $dest => $file ) {
+                       if ( !isset( $file['src'] ) ) {
+                               $this->fatalError( "Module '$moduleName' file '$dest' must have a 'src' key." );
+                       }
+                       $data = $this->fetch( $file['src'], $file['integrity'] ?? null );
+                       $path = "$destDir/$dest";
+                       if ( $this->action === 'verify' && sha1_file( $path ) !== sha1( $data ) ) {
+                               $this->fatalError( "File '$dest' for '$moduleName' is different." );
+                       } elseif ( $this->action === 'update' ) {
+                               wfMkdirParents( $destDir );
+                               file_put_contents( "$destDir/$dest", $data );
+                       }
+               }
+       }
 
-               // Write resource to temporary file and open it
+       private function handleTypeTar( $moduleName, $destDir, array $info ) {
+               $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
+               if ( $info['src'] === null ) {
+                       $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+               }
+               // Download the resource to a temporary file and open it
+               $data = $this->fetch( $info['src'], $info['integrity' ] );
                $tmpFile = "{$this->tmpParentDir}/$moduleName.tar";
                $this->verbose( "... writing '$moduleName' src to $tmpFile\n" );
                file_put_contents( $tmpFile, $data );
@@ -129,46 +190,45 @@ TEXT
                unset( $data, $p );
 
                if ( $info['dest'] === null ) {
-                       // Replace the entire directory as-is
-                       if ( !$this->hasOption( 'update' ) ) {
-                               $this->output( "[dry run] Would replace /resources/lib/$moduleName\n" );
-                       } else {
-                               wfRecursiveRemoveDir( $destDir );
-                               if ( !rename( $tmpDir, $destDir ) ) {
-                                       $this->fatalError( "Could not move $destDir to $tmpDir." );
-                               }
-                       }
-                       return;
-               }
-
-               // Create and/or empty the destination
-               if ( !$this->hasOption( 'update' ) ) {
-                       $this->output( "... [dry run] would empty /resources/lib/$moduleName\n" );
+                       // Default: Replace the entire directory
+                       $toCopy = [ $tmpDir => $destDir ];
                } else {
-                       wfRecursiveRemoveDir( $destDir );
-                       wfMkdirParents( $destDir );
-               }
-
-               // Expand and normalise the 'dest' entries
-               $toCopy = [];
-               foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
-                       // Use glob() to expand wildcards and check existence
-                       $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
-                       if ( !$fromPaths ) {
-                               $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
-                       }
-                       foreach ( $fromPaths as $fromPath ) {
-                               $toCopy[$fromPath] = $toSubPath === null
-                                       ? "$destDir/" . basename( $fromPath )
-                                       : "$destDir/$toSubPath/" . basename( $fromPath );
+                       // Expand and normalise the 'dest' entries
+                       $toCopy = [];
+                       foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
+                               // Use glob() to expand wildcards and check existence
+                               $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
+                               if ( !$fromPaths ) {
+                                       $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
+                               }
+                               foreach ( $fromPaths as $fromPath ) {
+                                       $toCopy[$fromPath] = $toSubPath === null
+                                               ? "$destDir/" . basename( $fromPath )
+                                               : "$destDir/$toSubPath/" . basename( $fromPath );
+                               }
                        }
                }
                foreach ( $toCopy as $from => $to ) {
-                       if ( !$this->hasOption( 'update' ) ) {
-                               $shortFrom = strtr( $from, [ "$tmpDir/" => '' ] );
-                               $shortTo = strtr( $to, [ "$IP/" => '' ] );
-                               $this->output( "... [dry run] would move $shortFrom to $shortTo\n" );
-                       } else {
+                       if ( $this->action === 'verify' ) {
+                               $this->verbose( "... verifying $to\n" );
+                               if ( is_dir( $from ) ) {
+                                       $rii = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(
+                                               $from,
+                                               RecursiveDirectoryIterator::SKIP_DOTS
+                                       ) );
+                                       foreach ( $rii as $file ) {
+                                               $remote = $file->getPathname();
+                                               $local = strtr( $remote, [ $from => $to ] );
+                                               if ( sha1_file( $remote ) !== sha1_file( $local ) ) {
+                                                       $this->error( "File '$local' is different." );
+                                                       $this->failAfterOutput = true;
+                                               }
+                                       }
+                               } elseif ( sha1_file( $from ) !== sha1_file( $to ) ) {
+                                       $this->error( "File '$to' is different." );
+                                       $this->failAfterOutput = true;
+                               }
+                       } elseif ( $this->action === 'update' ) {
                                $this->verbose( "... moving $from to $to\n" );
                                wfMkdirParents( dirname( $to ) );
                                if ( !rename( $from, $to ) ) {
@@ -184,6 +244,15 @@ TEXT
                }
        }
 
+       private function cleanUp() {
+               wfRecursiveRemoveDir( $this->tmpParentDir );
+       }
+
+       protected function fatalError( $msg, $exitCode = 1 ) {
+               $this->cleanUp();
+               parent::fatalError( $msg, $exitCode );
+       }
+
        /**
         * Basic YAML parser.
         *
diff --git a/maintenance/tidyUpBug37714.php b/maintenance/tidyUpBug37714.php
deleted file mode 100644 (file)
index 0dd0341..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-require_once __DIR__ . '/Maintenance.php';
-
-/**
- * Fixes all rows affected by https://bugzilla.wikimedia.org/show_bug.cgi?id=37714
- */
-class TidyUpBug37714 extends Maintenance {
-       public function execute() {
-               // Search for all log entries which are about changing the visability of other log entries.
-               $result = $this->getDB( DB_REPLICA )->select(
-                       'logging',
-                       [ 'log_id', 'log_params' ],
-                       [
-                               'log_type' => [ 'suppress', 'delete' ],
-                               'log_action' => 'event',
-                               'log_namespace' => NS_SPECIAL,
-                               'log_title' => SpecialPage::getTitleFor( 'Log' )->getText()
-                       ],
-                       __METHOD__
-               );
-
-               foreach ( $result as $row ) {
-                       $ids = explode( ',', explode( "\n", $row->log_params )[0] );
-                       $result = $this->getDB( DB_REPLICA )->select( // Work out what log entries were changed here.
-                               'logging',
-                               'log_type',
-                               [ 'log_id' => $ids ],
-                               __METHOD__,
-                               'DISTINCT'
-                       );
-                       if ( $result->numRows() === 1 ) {
-                               // If there's only one type, the target title can be set to include it.
-                               $logTitle = SpecialPage::getTitleFor( 'Log', $result->current()->log_type )->getText();
-                               $this->output( 'Set log_title to "' . $logTitle . '" for log entry ' . $row->log_id . ".\n" );
-                               $this->getDB( DB_MASTER )->update(
-                                       'logging',
-                                       [ 'log_title' => $logTitle ],
-                                       [ 'log_id' => $row->log_id ],
-                                       __METHOD__
-                               );
-                               wfWaitForSlaves();
-                       }
-               }
-       }
-}
-
-$maintClass = TidyUpBug37714::class;
-require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/maintenance/tidyUpT39714.php b/maintenance/tidyUpT39714.php
new file mode 100644 (file)
index 0000000..9dacdaa
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Fixes all rows affected by T39714
+ */
+class TidyUpT39714 extends Maintenance {
+       public function execute() {
+               // Search for all log entries which are about changing the visability of other log entries.
+               $result = $this->getDB( DB_REPLICA )->select(
+                       'logging',
+                       [ 'log_id', 'log_params' ],
+                       [
+                               'log_type' => [ 'suppress', 'delete' ],
+                               'log_action' => 'event',
+                               'log_namespace' => NS_SPECIAL,
+                               'log_title' => SpecialPage::getTitleFor( 'Log' )->getText()
+                       ],
+                       __METHOD__
+               );
+
+               foreach ( $result as $row ) {
+                       $ids = explode( ',', explode( "\n", $row->log_params )[0] );
+                       $result = $this->getDB( DB_REPLICA )->select( // Work out what log entries were changed here.
+                               'logging',
+                               'log_type',
+                               [ 'log_id' => $ids ],
+                               __METHOD__,
+                               'DISTINCT'
+                       );
+                       if ( $result->numRows() === 1 ) {
+                               // If there's only one type, the target title can be set to include it.
+                               $logTitle = SpecialPage::getTitleFor( 'Log', $result->current()->log_type )->getText();
+                               $this->output( 'Set log_title to "' . $logTitle . '" for log entry ' . $row->log_id . ".\n" );
+                               $this->getDB( DB_MASTER )->update(
+                                       'logging',
+                                       [ 'log_title' => $logTitle ],
+                                       [ 'log_id' => $row->log_id ],
+                                       __METHOD__
+                               );
+                               wfWaitForSlaves();
+                       }
+               }
+       }
+}
+
+$maintClass = TidyUpT39714::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
index 264b5eb..0a60b08 100644 (file)
@@ -294,7 +294,7 @@ class profile_point {
        public function fmttime() {
                return sprintf( '%5.02f', $this->time );
        }
-};
+}
 
 function compare_point( profile_point $a, profile_point $b ) {
        // phpcs:ignore MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
index 9536b67..284b21a 100644 (file)
@@ -22,7 +22,6 @@
                mwLoaderTrack = mw.track,
                trackCallbacks = $.Callbacks( 'memory' ),
                trackHandlers = [],
-               hasOwn = Object.prototype.hasOwnProperty,
                queue;
 
        /**
         * @class mw.hook
         */
        mw.hook = ( function () {
-               var lists = {};
+               var lists = Object.create( null );
 
                /**
                 * Create an instance of mw.hook.
                 * @return {mw.hook}
                 */
                return function ( name ) {
-                       var list = hasOwn.call( lists, name ) ?
-                               lists[ name ] :
-                               lists[ name ] = $.Callbacks( 'memory' );
+                       var list = lists[ name ] || ( lists[ name ] = $.Callbacks( 'memory' ) );
 
                        return {
                                /**
index aa86a4b..a163a3d 100644 (file)
@@ -2,7 +2,7 @@
        'use strict';
 
        var notification,
-               // The #mw-notification-area div that all notifications are contained inside.
+               // The .mw-notification-area div that all notifications are contained inside.
                $area,
                // Number of open notification boxes at any time
                openNotificationCount = 0,
 
                // Write to the DOM:
                // Prepend the notification area to the content area and save its object.
+               // The ID attribute here is deprecated.
                $area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
                        // Pause auto-hide timers when the mouse is in the notification area.
                        .on( {
index 06eb80e..3fb7ffc 100644 (file)
                         * @class
                         */
                        function StringSet() {
-                               this.set = {};
+                               this.set = Object.create( null );
                        }
                        StringSet.prototype.add = function ( value ) {
                                this.set[ value ] = true;
                        };
                        StringSet.prototype.has = function ( value ) {
-                               return hasOwn.call( this.set, value );
+                               return value in this.set;
                        };
                        return StringSet;
                }() );
         *  copied in one direction only. Changes to globals do not reflect in the map.
         */
        function Map( global ) {
-               this.values = {};
+               this.values = Object.create( null );
                if ( global === true ) {
                        // Override #set to also set the global variable
                        this.set = function ( selection, value ) {
                                results = {};
                                for ( i = 0; i < selection.length; i++ ) {
                                        if ( typeof selection[ i ] === 'string' ) {
-                                               results[ selection[ i ] ] = hasOwn.call( this.values, selection[ i ] ) ?
+                                               results[ selection[ i ] ] = selection[ i ] in this.values ?
                                                        this.values[ selection[ i ] ] :
                                                        fallback;
                                        }
                        }
 
                        if ( typeof selection === 'string' ) {
-                               return hasOwn.call( this.values, selection ) ?
+                               return selection in this.values ?
                                        this.values[ selection ] :
                                        fallback;
                        }
                        var i;
                        if ( Array.isArray( selection ) ) {
                                for ( i = 0; i < selection.length; i++ ) {
-                                       if ( typeof selection[ i ] !== 'string' || !hasOwn.call( this.values, selection[ i ] ) ) {
+                                       if ( typeof selection[ i ] !== 'string' || !( selection[ i ] in this.values ) ) {
                                                return false;
                                        }
                                }
                                return true;
                        }
-                       return typeof selection === 'string' && hasOwn.call( this.values, selection );
+                       return typeof selection === 'string' && selection in this.values;
                }
        };
 
                                 */
                                marker = document.querySelector( 'meta[name="ResourceLoaderDynamicStyles"]' ),
 
-                               // For addEmbeddedCSS()
-                               cssBuffer = '',
-                               cssBufferTimer = null,
-                               cssCallbacks = [],
+                               // For #addEmbeddedCSS()
+                               nextCssBuffer,
                                rAF = window.requestAnimationFrame || setTimeout;
 
                        /**
                                return el;
                        }
 
+                       /**
+                        * @private
+                        * @param {Object} cssBuffer
+                        */
+                       function flushCssBuffer( cssBuffer ) {
+                               var i;
+                               // Mark this object as inactive now so that further calls to addEmbeddedCSS() from
+                               // the callbacks go to a new buffer instead of this one (T105973)
+                               cssBuffer.active = false;
+                               newStyleTag( cssBuffer.cssText, marker );
+                               for ( i = 0; i < cssBuffer.callbacks.length; i++ ) {
+                                       cssBuffer.callbacks[ i ]();
+                               }
+                       }
+
                        /**
                         * Add a bit of CSS text to the current browser page.
                         *
-                        * The CSS will be appended to an existing ResourceLoader-created `<style>` tag
-                        * or create a new one based on whether the given `cssText` is safe for extension.
+                        * The creation and insertion of the `<style>` element is debounced for two reasons:
+                        *
+                        * - Performing the insertion before the next paint round via requestAnimationFrame
+                        *   avoids forced or wasted style recomputations, which are expensive in browsers.
+                        * - Reduce how often new stylesheets are inserted by letting additional calls to this
+                        *   function accumulate into a buffer for at least one JavaScript tick. Modules are
+                        *   received from the server in batches, which means there is likely going to be many
+                        *   calls to this function in a row within the same tick / the same call stack.
+                        *   See also T47810.
                         *
                         * @private
-                        * @param {string} [cssText=cssBuffer] If called without cssText,
-                        *  the internal buffer will be inserted instead.
-                        * @param {Function} [callback]
+                        * @param {string} cssText CSS text to be added in a `<style>` tag.
+                        * @param {Function} callback Called after the insertion has occurred
                         */
                        function addEmbeddedCSS( cssText, callback ) {
-                               function fireCallbacks() {
-                                       var i,
-                                               oldCallbacks = cssCallbacks;
-                                       // Reset cssCallbacks variable so it's not polluted by any calls to
-                                       // addEmbeddedCSS() from one of the callbacks (T105973)
-                                       cssCallbacks = [];
-                                       for ( i = 0; i < oldCallbacks.length; i++ ) {
-                                               oldCallbacks[ i ]();
-                                       }
-                               }
-
-                               if ( callback ) {
-                                       cssCallbacks.push( callback );
+                               // Create a buffer if:
+                               // - We don't have one yet.
+                               // - The previous one is closed.
+                               // - The next CSS chunk syntactically needs to be at the start of a stylesheet (T37562).
+                               if ( !nextCssBuffer || nextCssBuffer.active === false || cssText.slice( 0, '@import'.length ) === '@import' ) {
+                                       nextCssBuffer = {
+                                               cssText: '',
+                                               callbacks: [],
+                                               active: null
+                                       };
                                }
 
-                               // Yield once before creating the <style> tag. This lets multiple stylesheets
-                               // accumulate into one buffer, allowing us to reduce how often new stylesheets
-                               // are inserted in the browser. Appending a stylesheet and waiting for the
-                               // browser to repaint is fairly expensive. (T47810)
-                               if ( cssText ) {
-                                       // Don't extend the buffer if the item needs its own stylesheet.
-                                       // Keywords like `@import` are only valid at the start of a stylesheet (T37562).
-                                       if ( !cssBuffer || cssText.slice( 0, '@import'.length ) !== '@import' ) {
-                                               // Linebreak for somewhat distinguishable sections
-                                               cssBuffer += '\n' + cssText;
-                                               if ( !cssBufferTimer ) {
-                                                       cssBufferTimer = rAF( function () {
-                                                               // Wrap in anonymous function that takes no arguments
-                                                               // Support: Firefox < 13
-                                                               // Firefox 12 has non-standard behaviour of passing a number
-                                                               // as first argument to a setTimeout callback.
-                                                               // http://benalman.com/news/2009/07/the-mysterious-firefox-settime/
-                                                               addEmbeddedCSS();
-                                                       } );
-                                               }
-                                               return;
-                                       }
+                               // Linebreak for somewhat distinguishable sections
+                               nextCssBuffer.cssText += '\n' + cssText;
+                               nextCssBuffer.callbacks.push( callback );
 
-                               // This is a scheduled flush for the buffer
-                               } else {
-                                       cssBufferTimer = null;
-                                       cssText = cssBuffer;
-                                       cssBuffer = '';
+                               if ( nextCssBuffer.active === null ) {
+                                       nextCssBuffer.active = true;
+                                       // The flushCssBuffer callback has its parameter bound by reference, which means
+                                       // 1) We can still extend the buffer from our object reference after this point.
+                                       // 2) We can safely re-assign the variable (not the object) to start a new buffer.
+                                       rAF( flushCssBuffer.bind( null, nextCssBuffer ) );
                                }
-
-                               newStyleTag( cssText, marker );
-
-                               fireCallbacks();
                        }
 
                        /**
                         * See also #work().
                         *
                         * @private
-                        * @param {string|string[]} dependencies Module name or array of string module names
+                        * @param {string[]} dependencies Array of module names in the registry
                         * @param {Function} [ready] Callback to execute when all dependencies are ready
                         * @param {Function} [error] Callback to execute when any dependency fails
                         */
                        function enqueue( dependencies, ready, error ) {
-                               // Allow calling by single module name
-                               if ( typeof dependencies === 'string' ) {
-                                       dependencies = [ dependencies ];
-                               }
-
                                if ( allReady( dependencies ) ) {
                                        // Run ready immediately
                                        if ( ready !== undefined ) {
                                                ready();
                                        }
-
                                        return;
                                }
 
                                                        dependencies
                                                );
                                        }
-
                                        return;
                                }
 
                                        jobs.push( {
                                                // Narrow down the list to modules that are worth waiting for
                                                dependencies: dependencies.filter( function ( module ) {
-                                                       var state = mw.loader.getState( module );
+                                                       var state = registry[ module ].state;
                                                        return state === 'registered' || state === 'loaded' || state === 'loading' || state === 'executing';
                                                } ),
                                                ready: ready,
                                }
 
                                dependencies.forEach( function ( module ) {
-                                       var state = mw.loader.getState( module );
                                        // Only queue modules that are still in the initial 'registered' state
                                        // (not ones already loading, ready or error).
-                                       if ( state === 'registered' && queue.indexOf( module ) === -1 ) {
+                                       if ( registry[ module ].state === 'registered' && queue.indexOf( module ) === -1 ) {
                                                // Private modules must be embedded in the page. Don't bother queuing
                                                // these as the server will deny them anyway (T101806).
                                                if ( registry[ module ].group === 'private' ) {
                         * @param {string} module Module name to execute
                         */
                        function execute( module ) {
-                               var key, value, media, i, urls, cssHandle, checkCssHandles, runScript,
-                                       cssHandlesRegistered = false;
+                               var key, value, media, i, urls, cssHandle, siteDeps, siteDepErr, runScript,
+                                       cssPending = 0;
 
                                if ( !hasOwn.call( registry, module ) ) {
                                        throw new Error( 'Module has not been registered yet: ' + module );
                                        mw.templates.set( module, registry[ module ].templates );
                                }
 
-                               // Make sure we don't run the scripts until all stylesheet insertions have completed.
-                               ( function () {
-                                       var pending = 0;
-                                       checkCssHandles = function () {
-                                               var ex, dependencies;
-                                               // cssHandlesRegistered ensures we don't take off too soon, e.g. when
-                                               // one of the cssHandles is fired while we're still creating more handles.
-                                               if ( cssHandlesRegistered && pending === 0 && runScript ) {
-                                                       if ( module === 'user' ) {
-                                                               // Implicit dependency on the site module. Not real dependency because
-                                                               // it should run after 'site' regardless of whether it succeeds or fails.
-                                                               // Note: This is a simplified version of mw.loader.using(), inlined here
-                                                               // as using() depends on jQuery (T192623).
-                                                               try {
-                                                                       dependencies = resolve( [ 'site' ] );
-                                                               } catch ( e ) {
-                                                                       ex = e;
-                                                                       runScript();
-                                                               }
-                                                               if ( ex === undefined ) {
-                                                                       enqueue( dependencies, runScript, runScript );
-                                                               }
-                                                       } else {
-                                                               runScript();
-                                                       }
-                                                       runScript = undefined; // Revoke
+                               // Adding of stylesheets is asynchronous via addEmbeddedCSS().
+                               // The below function uses a counting semaphore to make sure we don't call
+                               // runScript() until after this module's stylesheets have been inserted
+                               // into the DOM.
+                               cssHandle = function () {
+                                       // Increase semaphore, when creating a callback for addEmbeddedCSS.
+                                       cssPending++;
+                                       return function () {
+                                               var runScriptCopy;
+                                               // Decrease semaphore, when said callback is invoked.
+                                               cssPending--;
+                                               if ( cssPending === 0 ) {
+                                                       // Paranoia:
+                                                       // This callback is exposed to addEmbeddedCSS, which is outside the execute()
+                                                       // function and is not concerned with state-machine integrity. In turn,
+                                                       // addEmbeddedCSS() actually exposes stuff further into the browser (rAF).
+                                                       // If increment and decrement callbacks happen in the wrong order, or start
+                                                       // again afterwards, then this branch could be reached multiple times.
+                                                       // To protect the integrity of the state-machine, prevent that from happening
+                                                       // by making runScript() cannot be called more than once.  We store a private
+                                                       // reference when we first reach this branch, then deference the original, and
+                                                       // call our reference to it.
+                                                       runScriptCopy = runScript;
+                                                       runScript = undefined;
+                                                       runScriptCopy();
                                                }
                                        };
-                                       cssHandle = function () {
-                                               var check = checkCssHandles;
-                                               pending++;
-                                               return function () {
-                                                       if ( check ) {
-                                                               pending--;
-                                                               check();
-                                                               check = undefined; // Revoke
-                                                       }
-                                               };
-                                       };
-                               }() );
+                               };
 
                                // Process styles (see also mw.loader.implement)
                                // * back-compat: { <media>: css }
                                        }
                                }
 
-                               // End profiling of execute()-self before we call checkCssHandles(),
-                               // which (sometimes asynchronously) calls runScript(), which we want
-                               // to measure separately without overlap.
+                               // End profiling of execute()-self before we call runScript(),
+                               // which we want to measure separately without overlap.
                                $CODE.profileExecuteEnd();
 
-                               // Kick off.
-                               cssHandlesRegistered = true;
-                               checkCssHandles();
+                               if ( module === 'user' ) {
+                                       // Implicit dependency on the site module. Not a real dependency because it should
+                                       // run after 'site' regardless of whether it succeeds or fails.
+                                       // Note: This is a simplified version of mw.loader.using(), inlined here because
+                                       // mw.loader.using() is part of mediawiki.base (depends on jQuery; T192623).
+                                       try {
+                                               siteDeps = resolve( [ 'site' ] );
+                                       } catch ( e ) {
+                                               siteDepErr = e;
+                                               runScript();
+                                       }
+                                       if ( siteDepErr === undefined ) {
+                                               enqueue( siteDeps, runScript, runScript );
+                                       }
+                               } else if ( cssPending === 0 ) {
+                                       // Regular module without styles
+                                       runScript();
+                               }
+                               // else: runScript will get called via cssHandle()
                        }
 
                        function sortQuery( o ) {
                                 *  a list of arguments compatible with this method
                                 * @param {string|number} version Module version hash (falls backs to empty string)
                                 *  Can also be a number (timestamp) for compatibility with MediaWiki 1.25 and earlier.
-                                * @param {string|Array} dependencies One string or array of strings of module
-                                *  names on which this module depends.
+                                * @param {string[]} [dependencies] Array of module names on which this module depends.
                                 * @param {string} [group=null] Group which the module is in
                                 * @param {string} [source='local'] Name of the source
                                 * @param {string} [skip=null] Script body of the skip function
                                 */
                                register: function ( module, version, dependencies, group, source, skip ) {
-                                       var i, deps;
+                                       var i;
                                        // Allow multiple registration
                                        if ( typeof module === 'object' ) {
                                                resolveIndexedDependencies( module );
                                        if ( hasOwn.call( registry, module ) ) {
                                                throw new Error( 'module already registered: ' + module );
                                        }
-                                       if ( typeof dependencies === 'string' ) {
-                                               // A single module name
-                                               deps = [ dependencies ];
-                                       } else if ( typeof dependencies === 'object' ) {
-                                               // Array of module names
-                                               deps = dependencies;
-                                       }
                                        // List the module as registered
                                        registry[ module ] = {
                                                // Exposed to execute() for mw.loader.implement() closures.
                                                        exports: {}
                                                },
                                                version: String( version || '' ),
-                                               dependencies: deps || [],
+                                               dependencies: dependencies || [],
                                                group: typeof group === 'string' ? group : null,
                                                source: typeof source === 'string' ? source : 'local',
                                                state: 'registered',
                                 * modules and cache each of them separately, using each module's versioning scheme
                                 * to determine when the cache should be invalidated.
                                 *
+                                * @private
                                 * @singleton
                                 * @class mw.loader.store
                                 */
                                         * @return {string} String of concatenated vary conditions.
                                         */
                                        getVary: function () {
-                                               return [
-                                                       mw.config.get( 'skin' ),
-                                                       mw.config.get( 'wgResourceLoaderStorageVersion' ),
-                                                       mw.config.get( 'wgUserLanguage' )
-                                               ].join( ':' );
+                                               return mw.config.get( 'skin' ) + ':' +
+                                                       mw.config.get( 'wgResourceLoaderStorageVersion' ) + ':' +
+                                                       mw.config.get( 'wgUserLanguage' );
                                        },
 
                                        /**
                                                }
 
                                                try {
+                                                       // This a string we stored, or `null` if the key does not (yet) exist.
                                                        raw = localStorage.getItem( mw.loader.store.getStoreKey() );
                                                        // If we get here, localStorage is available; mark enabled
                                                        mw.loader.store.enabled = true;
+                                                       // If null, JSON.parse() will cast to string and re-parse, still null.
                                                        data = JSON.parse( raw );
                                                        if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
                                                                mw.loader.store.items = data.items;
                                                        } );
                                                }
 
+                                               // If we get here, one of four things happened:
+                                               //
+                                               // 1. localStorage did not contain our store key.
+                                               //    This means `raw` is `null`, and we're on a fresh page view (cold cache).
+                                               //    The store was enabled, and `items` starts fresh.
+                                               //
+                                               // 2. localStorage contained parseable data under our store key,
+                                               //    but it's not applicable to our current context (see getVary).
+                                               //    The store was enabled, and `items` starts fresh.
+                                               //
+                                               // 3. JSON.parse threw (localStorage contained corrupt data).
+                                               //    This means `raw` contains a string.
+                                               //    The store was enabled, and `items` starts fresh.
+                                               //
+                                               // 4. localStorage threw (disabled or otherwise unavailable).
+                                               //    This means `raw` was never assigned.
+                                               //    We will disable the store below.
                                                if ( raw === undefined ) {
                                                        // localStorage failed; disable store
                                                        mw.loader.store.enabled = false;
-                                               } else {
-                                                       mw.loader.store.update();
                                                }
                                        },
 
                                         *
                                         * @param {string} module Module name
                                         * @param {Object} descriptor The module's descriptor as set in the registry
-                                        * @return {boolean} Module was set
                                         */
                                        set: function ( module, descriptor ) {
                                                var args, key, src;
 
                                                if ( !mw.loader.store.enabled ) {
-                                                       return false;
+                                                       return;
                                                }
 
                                                key = getModuleKey( module );
                                                                descriptor.templates ].indexOf( undefined ) !== -1
                                                ) {
                                                        // Decline to store
-                                                       return false;
+                                                       return;
                                                }
 
                                                try {
                                                                JSON.stringify( descriptor.messages ),
                                                                JSON.stringify( descriptor.templates )
                                                        ];
-                                                       // Attempted workaround for a possible Opera bug (bug T59567).
-                                                       // This regex should never match under sane conditions.
-                                                       if ( /^\s*\(/.test( args[ 1 ] ) ) {
-                                                               args[ 1 ] = 'function' + args[ 1 ];
-                                                               mw.trackError( 'resourceloader.assert', { source: 'bug-T59567' } );
-                                                       }
                                                } catch ( e ) {
                                                        mw.trackError( 'resourceloader.exception', {
                                                                exception: e,
                                                                source: 'store-localstorage-json'
                                                        } );
-                                                       return false;
+                                                       return;
                                                }
 
                                                src = 'mw.loader.implement(' + args.join( ',' ) + ');';
                                                if ( src.length > mw.loader.store.MODULE_SIZE_MAX ) {
-                                                       return false;
+                                                       return;
                                                }
                                                mw.loader.store.items[ key ] = src;
                                                mw.loader.store.update();
-                                               return true;
                                        },
 
                                        /**
                                         * Iterate through the module store, removing any item that does not correspond
                                         * (in name and version) to an item in the module registry.
-                                        *
-                                        * @return {boolean} Store was pruned
                                         */
                                        prune: function () {
                                                var key, module;
 
-                                               if ( !mw.loader.store.enabled ) {
-                                                       return false;
-                                               }
-
                                                for ( key in mw.loader.store.items ) {
                                                        module = key.slice( 0, key.indexOf( '@' ) );
                                                        if ( getModuleKey( module ) !== key ) {
                                                                delete mw.loader.store.items[ key ];
                                                        }
                                                }
-                                               return true;
                                        },
 
                                        /**
                                                mw.loader.store.items = {};
                                                try {
                                                        localStorage.removeItem( mw.loader.store.getStoreKey() );
-                                               } catch ( ignored ) {}
+                                               } catch ( e ) {}
                                        },
 
                                        /**
                                         * Sync in-memory store back to localStorage.
                                         *
-                                        * This function debounces updates. When called with a flush already pending,
-                                        * the call is coalesced into the pending update. The call to
-                                        * localStorage.setItem will be naturally deferred until the page is quiescent.
+                                        * This function debounces updates. When called with a flush already pending, the
+                                        * scheduled flush is postponed. The call to localStorage.setItem will be keep
+                                        * being deferred until the page is quiescent for 2 seconds.
                                         *
                                         * Because localStorage is shared by all pages from the same origin, if multiple
                                         * pages are loaded with different module sets, the possibility exists that
-                                        * modules saved by one page will be clobbered by another. But the impact would
-                                        * be minor and the problem would be corrected by subsequent page views.
+                                        * modules saved by one page will be clobbered by another. The only impact of this
+                                        * is minor (merely a less efficient cache use) and the problem would be corrected
+                                        * by subsequent page views.
                                         *
                                         * @method
                                         */
                                        update: ( function () {
-                                               var hasPendingWrite = false;
+                                               var timer, hasPendingWrites = false;
 
                                                function flushWrites() {
                                                        var data, key;
-                                                       if ( !hasPendingWrite || !mw.loader.store.enabled ) {
+                                                       if ( !mw.loader.store.enabled ) {
                                                                return;
                                                        }
 
                                                                } );
                                                        }
 
-                                                       hasPendingWrite = false;
+                                                       hasPendingWrites = false;
                                                }
 
-                                               return function () {
-                                                       if ( !hasPendingWrite ) {
-                                                               hasPendingWrite = true;
+                                               function request() {
+                                                       // If another mw.loader.store.set()/update() call happens in the narrow
+                                                       // time window between requestIdleCallback() and flushWrites firing, ignore it.
+                                                       // It'll be saved by the already-scheduled flush.
+                                                       if ( !hasPendingWrites ) {
+                                                               hasPendingWrites = true;
                                                                mw.requestIdleCallback( flushWrites );
                                                        }
+                                               }
+
+                                               return function () {
+                                                       // Cancel the previous timer (if it hasn't fired yet)
+                                                       clearTimeout( timer );
+                                                       timer = setTimeout( request, 2000 );
                                                };
                                        }() )
                                }
index 7e7fd6a..ee72166 100644 (file)
@@ -145,5 +145,5 @@ window.isCompatible = function ( str ) {
        // This embeds mediawiki.js, which defines 'mw' and 'mw.loader'.
        $CODE.defineLoader();
 
-       mw.requestIdleCallback( startUp );
+       startUp();
 }() );
index f76b1e3..585ebb9 100644 (file)
@@ -346,8 +346,6 @@ return [
                "PhanUndeclaredMethod",
                // approximate error count: 1224
                "PhanUndeclaredProperty",
-               // approximate error count: 3
-               "PhanUndeclaredStaticMethod",
                // approximate error count: 58
                "PhanUndeclaredVariableDim",
        ],
index 34f93ad..5cc45f5 100644 (file)
@@ -110,6 +110,11 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        private $loggers = [];
 
+       /**
+        * @var LoggerInterface
+        */
+       private $testLogger;
+
        /**
         * Table name prefixes. Oracle likes it shorter.
         */
@@ -132,6 +137,11 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
 
                $this->backupGlobals = false;
                $this->backupStaticAttributes = false;
+               $this->testLogger = self::getTestLogger();
+       }
+
+       private static function getTestLogger() {
+               return LoggerFactory::getInstance( 'tests-phpunit' );
        }
 
        public function __destruct() {
@@ -447,6 +457,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
 
                if ( !self::$dbSetup || $this->needsDB() ) {
                        // set up a DB connection for this test to use
+                       $this->testLogger->info( "Setting up DB for " . $this->toString() );
 
                        self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
                        self::$reuseDB = $this->getCliArg( 'reuse-db' );
@@ -473,9 +484,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        $needsResetDB = true;
                }
 
+               $this->testLogger->info( "Starting test " . $this->toString() );
                parent::run( $result );
+               $this->testLogger->info( "Finished test " . $this->toString() );
 
                if ( $needsResetDB ) {
+                       $this->testLogger->info( "Resetting DB" );
                        $this->resetDB( $this->db, $this->tablesUsed );
                }
        }
@@ -566,7 +580,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                        }
                        // Check for unsafe queries
                        if ( $this->db->getType() === 'mysql' ) {
-                               $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'" );
+                               $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'", __METHOD__ );
                        }
                }
 
@@ -618,7 +632,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                $this->db->rollback( __METHOD__, 'flush' );
                        }
                        if ( $this->db->getType() === 'mysql' ) {
-                               $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ) );
+                               $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ),
+                                       __METHOD__ );
                        }
                }
 
@@ -1205,6 +1220,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         * @since 1.32
         */
        protected function addCoreDBData() {
+               $this->testLogger->info( __METHOD__ );
                if ( $this->db->getType() == 'oracle' ) {
                        # Insert 0 user to prevent FK violations
                        # Anonymous user
@@ -1449,7 +1465,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                // Assuming this isn't needed for External Store database, and not sure if the procedure
                // would be available there.
                if ( $db->getType() == 'oracle' ) {
-                       $db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+                       $db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
                }
 
                Hooks::run( 'UnitTestsAfterDatabaseSetup', [ $db, $prefix ] );
index b0cefc7..1f3ee27 100644 (file)
@@ -2039,26 +2039,26 @@ class OutputPageTest extends MediaWikiTestCase {
 
        /**
         * @dataProvider providePreloadLinkHeaders
-        * @covers OutputPage::addLogoPreloadLinkHeaders
+        * @covers ResourceLoaderSkinModule::getPreloadLinks
+        * @covers ResourceLoaderSkinModule::getLogoPreloadlinks
         * @covers ResourceLoaderSkinModule::getLogo
         */
-       public function testPreloadLinkHeaders( $config, $result, $baseDir = null ) {
-               if ( $baseDir ) {
-                       $this->setMwGlobals( 'IP', $baseDir );
-               }
-               $out = TestingAccessWrapper::newFromObject( $this->newInstance( $config ) );
-               $out->addLogoPreloadLinkHeaders();
+       public function testPreloadLinkHeaders( $config, $result ) {
+               $this->setMwGlobals( $config );
+               $ctx = $this->getMockBuilder( ResourceLoaderContext::class )
+                       ->disableOriginalConstructor()->getMock();
+               $module = new ResourceLoaderSkinModule();
 
-               $this->assertEquals( $result, $out->getLinkHeader() );
+               $this->assertEquals( [ $result ], $module->getHeaders( $ctx ) );
        }
 
        public function providePreloadLinkHeaders() {
                return [
                        [
                                [
-                                       'ResourceBasePath' => '/w',
-                                       'Logo' => '/img/default.png',
-                                       'LogoHD' => [
+                                       'wgResourceBasePath' => '/w',
+                                       'wgLogo' => '/img/default.png',
+                                       'wgLogoHD' => [
                                                '1.5x' => '/img/one-point-five.png',
                                                '2x' => '/img/two-x.png',
                                        ],
@@ -2071,17 +2071,17 @@ class OutputPageTest extends MediaWikiTestCase {
                        ],
                        [
                                [
-                                       'ResourceBasePath' => '/w',
-                                       'Logo' => '/img/default.png',
-                                       'LogoHD' => false,
+                                       'wgResourceBasePath' => '/w',
+                                       'wgLogo' => '/img/default.png',
+                                       'wgLogoHD' => false,
                                ],
                                'Link: </img/default.png>;rel=preload;as=image'
                        ],
                        [
                                [
-                                       'ResourceBasePath' => '/w',
-                                       'Logo' => '/img/default.png',
-                                       'LogoHD' => [
+                                       'wgResourceBasePath' => '/w',
+                                       'wgLogo' => '/img/default.png',
+                                       'wgLogoHD' => [
                                                '2x' => '/img/two-x.png',
                                        ],
                                ],
@@ -2091,9 +2091,9 @@ class OutputPageTest extends MediaWikiTestCase {
                        ],
                        [
                                [
-                                       'ResourceBasePath' => '/w',
-                                       'Logo' => '/img/default.png',
-                                       'LogoHD' => [
+                                       'wgResourceBasePath' => '/w',
+                                       'wgLogo' => '/img/default.png',
+                                       'wgLogoHD' => [
                                                'svg' => '/img/vector.svg',
                                        ],
                                ],
@@ -2102,13 +2102,13 @@ class OutputPageTest extends MediaWikiTestCase {
                        ],
                        [
                                [
-                                       'ResourceBasePath' => '/w',
-                                       'Logo' => '/w/test.jpg',
-                                       'LogoHD' => false,
-                                       'UploadPath' => '/w/images',
+                                       'wgResourceBasePath' => '/w',
+                                       'wgLogo' => '/w/test.jpg',
+                                       'wgLogoHD' => false,
+                                       'wgUploadPath' => '/w/images',
+                                       'IP' => dirname( __DIR__ ) . '/data/media',
                                ],
                                'Link: </w/test.jpg?edcf2>;rel=preload;as=image',
-                               'baseDir' => dirname( __DIR__ ) . '/data/media',
                        ],
                ];
        }
index 8137b27..6679754 100644 (file)
@@ -77,15 +77,12 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
 
                $this->tablesUsed += $this->getMcrTablesToReset();
 
-               $this->setMwGlobals(
-                       'wgMultiContentRevisionSchemaMigrationStage',
-                       $this->getMcrMigrationStage()
-               );
-
-               $this->setMwGlobals(
-                       'wgContentHandlerUseDB',
-                       $this->getContentHandlerUseDB()
-               );
+               $this->setMwGlobals( [
+                       'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
+                       'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
+                       'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
+                       'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+               ] );
 
                $this->overrideMwServices();
        }
index 276fee3..4bd845d 100644 (file)
@@ -29,6 +29,8 @@ class StaticArrayWriterTest extends PHPUnit\Framework\TestCase {
                        'foo' => 'bar',
                        'baz' => 'rawr',
                        "they're" => '"quoted properly"',
+                       'nested' => [ 'elements', 'work' ],
+                       'and' => [ 'these' => 'do too' ],
                ];
                $writer = new StaticArrayWriter();
                $actual = $writer->create( $data, "Header\nWith\nNewlines" );
@@ -41,6 +43,13 @@ return [
        'foo' => 'bar',
        'baz' => 'rawr',
        'they\'re' => '"quoted properly"',
+       'nested' => [
+               0 => 'elements',
+               1 => 'work',
+       ],
+       'and' => [
+               'these' => 'do too',
+       ],
 ];
 
 PHP;