Merge "Make interim WAN cache key deactivation logic broader"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 2 Feb 2018 04:04:13 +0000 (04:04 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 2 Feb 2018 04:04:13 +0000 (04:04 +0000)
56 files changed:
RELEASE-NOTES-1.31
composer.json
docs/extension.schema.v2.json
includes/Message.php
includes/api/ApiParse.php
includes/api/i18n/hu.json
includes/api/i18n/ja.json
includes/cache/MessageCache.php
includes/deferred/MWCallableUpdate.php
includes/diff/DifferenceEngine.php
includes/filerepo/FileRepo.php
includes/htmlform/HTMLFormElement.php
includes/htmlform/HTMLFormField.php
includes/http/MWHttpRequest.php
includes/installer/Installer.php
includes/installer/i18n/uk.json
includes/libs/objectcache/WANObjectCache.php
includes/libs/rdbms/TransactionProfiler.php
includes/parser/ParserOptions.php
includes/parser/ParserOutput.php
languages/i18n/be-tarask.json
languages/i18n/bs.json
languages/i18n/hu.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/lij.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/ru.json
languages/i18n/sah.json
languages/i18n/sr-ec.json
languages/i18n/sty.json [new file with mode: 0644]
languages/i18n/tr.json
maintenance/benchmarks/Benchmarker.php
maintenance/resources/update-oojs-ui.sh
resources/src/mediawiki.language/specialcharacters.json
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.special/mediawiki.special.userlogin.login.css
resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js
resources/src/mediawiki.widgets.datetime/mediawiki.widgets.datetime.definitions.less
resources/src/mediawiki/htmlform/autoinfuse.js
resources/src/mediawiki/htmlform/htmlform.Element.js
resources/src/oojs-ui-local.css
resources/src/oojs-ui-local.js
tests/integration/includes/http/MWHttpRequestTestCase.php
tests/parser/ParserTestRunner.php
tests/phpunit/includes/ExtraParserTest.php
tests/phpunit/includes/deferred/MWCallableUpdateTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/libs/rdbms/TransactionProfilerTest.php
tests/phpunit/includes/parser/ParserOptionsTest.php
tests/phpunit/includes/parser/ParserOutputTest.php
tests/phpunit/includes/parser/TagHooksTest.php
tests/qunit/data/testrunner.js
thumb.php

index f4e81b9..ad24852 100644 (file)
@@ -50,7 +50,7 @@ production.
 ==== Upgraded external libraries ====
 * Updated jquery.chosen from v0.9.14 to v1.8.2.
 * Updated composer/spdx-licenses from 1.1.4 to
-  1.2.0 (development dependency).
+  1.3.0 (development dependency).
 * Updated nikic/php-parser from 2.1.0 to 3.1.3
   (development dependency).
 * Updated wikimedia/ip-set from 1.1.0 to 1.2.0.
@@ -194,6 +194,8 @@ changes to languages because of Phabricator reports.
   Setting template variables by reference allowed violating the principle of data being
   immutable once added to the skin template. In practice, this method was not being
   used for that. Rather, setRef() existed as memory optimisation for PHP 4.
+* Passing false to ParserOptions::setWrapOutputClass() is deprecated. Use the
+  'unwrap' transform to ParserOutput::getText() instead.
 
 == Compatibility ==
 MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
index 4596c4c..1730942 100644 (file)
@@ -49,7 +49,7 @@
                "zordius/lightncandy": "0.23"
        },
        "require-dev": {
-               "composer/spdx-licenses": "1.2.0",
+               "composer/spdx-licenses": "1.3.0",
                "hamcrest/hamcrest-php": "^2.0",
                "jakub-onderka/php-parallel-lint": "0.9.2",
                "jetbrains/phpstorm-stubs": "dev-master#1b9906084d6635456fcf3f3a01f0d7d5b99a578a",
index 51f9417..e13129b 100644 (file)
                },
                "SkinOOUIThemes": {
                        "type": "object",
-                       "description": "Map of skin names to OOjs UI themes to use. Same format as ResourceLoaderOOUIModule::$builtinSkinThemeMap."
+                       "description": "Map of skin names to OOUI themes to use. Same format as ResourceLoaderOOUIModule::$builtinSkinThemeMap."
                },
                "PasswordPolicy": {
                        "type": "object",
index e55eaaf..fac9a59 100644 (file)
@@ -1245,7 +1245,14 @@ class Message implements MessageSpecifier, Serializable {
                );
 
                return $out instanceof ParserOutput
-                       ? $out->getText( [ 'enableSectionEditLinks' => false ] )
+                       ? $out->getText( [
+                               'enableSectionEditLinks' => false,
+                               // Wrapping messages in an extra <div> is probably not expected. If
+                               // they're outside the content area they probably shouldn't be
+                               // targeted by CSS that's targeting the parser output, and if
+                               // they're inside they already are from the outer div.
+                               'unwrap' => true,
+                       ] )
                        : $out;
        }
 
index cf1fd1e..2839ab9 100644 (file)
@@ -344,6 +344,7 @@ class ApiParse extends ApiBase {
                        $result_array['text'] = $p_result->getText( [
                                'allowTOC' => !$params['disabletoc'],
                                'enableSectionEditLinks' => !$params['disableeditsection'],
+                               'unwrap' => $params['wrapoutputclass'] === '',
                        ] );
                        $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
                }
@@ -538,9 +539,9 @@ class ApiParse extends ApiBase {
                if ( $params['disabletidy'] ) {
                        $popts->setTidy( false );
                }
-               $popts->setWrapOutputClass(
-                       $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
-               );
+               if ( $params['wrapoutputclass'] !== '' ) {
+                       $popts->setWrapOutputClass( $params['wrapoutputclass'] );
+               }
 
                $reset = null;
                $suppressCache = false;
index 02915a7..7c03097 100644 (file)
@@ -10,7 +10,7 @@
                        "Dj"
                ]
        },
-       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentáció]]\n* [[mw:Special:MyLanguage/API:FAQ|GYIK]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Levelezőlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-bejelentések]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Hibabejelentések és kérések]\n</div>\n<strong>Státusz:</strong> Minden ezen a lapon látható funkciónak működnie kell, de az API jelenleg is aktív fejlesztés alatt áll, és bármikor változhat. Iratkozz fel a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce levelezőlistára] a frissítések követéséhez.\n\n<strong>Hibás kérések:</strong> Ha az API hibás kérést kap, egy HTTP-fejlécet küld vissza „MediaWiki-API-Error” kulccsal, és a fejléc értéke és a visszaküldött hibakód ugyanarra az értékre lesz állítva. További információért lásd: [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Hibák és figyelmeztetések]].\n\n<strong>Tesztelés:</strong> Az API-kérések könnyebb teszteléséhez használható az [[Special:ApiSandbox|API-homokozó]].",
+       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentáció]]\n* [[mw:Special:MyLanguage/API:FAQ|GYIK]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Levelezőlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-bejelentések]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Hibabejelentések és kérések]\n</div>\n<strong>Státusz:</strong> Minden ezen a lapon látható funkciónak működnie kell, de az API jelenleg is aktív fejlesztés alatt áll, és bármikor változhat. Iratkozz fel a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce levelezőlistára] a frissítések követéséhez.\n\n<strong>Hibás kérések:</strong> Ha az API hibás kérést kap, egy HTTP-fejlécet küld vissza „MediaWiki-API-Error” kulccsal, és a fejléc értéke és a visszaküldött hibakód ugyanarra az értékre lesz állítva. További információért lásd: [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Hibák és figyelmeztetések]].\n\n<p class=\"mw-apisandbox-link\"><strong>Tesztelés:</strong> Az API-kérések könnyebb teszteléséhez használható az [[Special:ApiSandbox|API-homokozó]].</p>",
        "apihelp-main-param-action": "Milyen műveletet hajtson végre.",
        "apihelp-main-param-format": "A kimenet formátuma.",
        "apihelp-main-param-smaxage": "Az <code>s-maxage</code> gyorsítótár-vezérlő HTTP-fejléc beállítása ennyi másodpercre. A hibák soha nincsenek gyorsítótárazva.",
index 094c406..f51b03f 100644 (file)
@@ -15,7 +15,7 @@
                        "Omotecho"
                ]
        },
-       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|説明文書]]\n* [[mw:Special:MyLanguage/API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api メーリングリスト]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグの報告とリクエスト]\n</div>\n<strong>状態:</strong> このページに表示されている機能は全て動作するはずですが、この API は未だ活発に開発されており、変更される可能性があります。アップデートの通知を受け取るには、[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce メーリングリスト]に参加してください。\n\n<strong>誤ったリクエスト:</strong> 誤ったリクエストが API に送られた場合、\"MediaWiki-API-Error\" HTTP ヘッダーが送信され、そのヘッダーの値と送り返されるエラーコードは同じ値にセットされます。より詳しい情報は [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]] を参照してください。\n\n<strong>テスト:</strong> API のリクエストのテストは、[[Special:ApiSandbox]]で簡単に行えます。",
+       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|説明文書]]\n* [[mw:Special:MyLanguage/API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api メーリングリスト]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグの報告とリクエスト]\n</div>\n<strong>状態:</strong> このページに表示されている機能は全て動作するはずですが、この API は未だ活発に開発されており、変更される可能性があります。アップデートの通知を受け取るには、[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce メーリングリスト]に参加してください。\n\n<strong>誤ったリクエスト:</strong> 誤ったリクエストが API に送られた場合、\"MediaWiki-API-Error\" HTTP ヘッダーが送信され、そのヘッダーの値と送り返されるエラーコードは同じ値にセットされます。より詳しい情報は [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]] を参照してください。\n\n<p class=\"mw-apisandbox-link\"><strong>テスト:</strong> API のリクエストのテストは、[[Special:ApiSandbox]]で簡単に行えます。</p>",
        "apihelp-main-param-action": "実行する操作です。",
        "apihelp-main-param-format": "出力する形式です。",
        "apihelp-main-param-smaxage": "<code>s-maxage</code> HTTP キャッシュ コントロール ヘッダー に、この秒数を設定します。エラーがキャッシュされることはありません。",
index c9615b1..63c03af 100644 (file)
@@ -193,7 +193,6 @@ class MessageCache {
                                $po = ParserOptions::newFromAnon();
                                $po->setEditSection( false );
                                $po->setAllowUnsafeRawHtml( false );
-                               $po->setWrapOutputClass( false );
                                return $po;
                        }
 
@@ -203,11 +202,6 @@ class MessageCache {
                        // from malicious sources. As a precaution, disable
                        // the <html> parser tag when parsing messages.
                        $this->mParserOptions->setAllowUnsafeRawHtml( false );
-                       // Wrapping messages in an extra <div> is probably not expected. If
-                       // they're outside the content area they probably shouldn't be
-                       // targeted by CSS that's targeting the parser output, and if
-                       // they're inside they already are from the outer div.
-                       $this->mParserOptions->setWrapOutputClass( false );
                }
 
                return $this->mParserOptions;
index 5b822af..9803b7a 100644 (file)
@@ -14,14 +14,18 @@ class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
        /**
         * @param callable $callback
         * @param string $fname Calling method
-        * @param IDatabase|null $dbw Abort if this DB is rolled back [optional] (since 1.28)
+        * @param IDatabase|IDatabase[]|null $dbws Abort if any of the specified DB handles have
+        *   a currently pending transaction which later gets rolled back [optional] (since 1.28)
         */
-       public function __construct( callable $callback, $fname = 'unknown', IDatabase $dbw = null ) {
+       public function __construct( callable $callback, $fname = 'unknown', $dbws = [] ) {
                $this->callback = $callback;
                $this->fname = $fname;
 
-               if ( $dbw && $dbw->trxLevel() ) {
-                       $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
+               $dbws = is_array( $dbws ) ? $dbws : [ $dbws ];
+               foreach ( $dbws as $dbw ) {
+                       if ( $dbw && $dbw->trxLevel() ) {
+                               $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
+                       }
                }
        }
 
index 7e05be6..e76bffc 100644 (file)
@@ -650,6 +650,12 @@ class DifferenceEngine extends ContextSource {
                }
        }
 
+       /**
+        * @param WikiPage $page
+        * @param Revision $rev
+        *
+        * @return ParserOutput|bool False if the revision was not found
+        */
        protected function getParserOutput( WikiPage $page, Revision $rev ) {
                $parserOptions = $page->makeParserOptions( $this->getContext() );
 
index b4df68a..6b32953 100644 (file)
@@ -132,6 +132,13 @@ class FileRepo {
        /** @var array callable|bool Override these in the base class */
        protected $oldFileFactoryKey = false;
 
+       /** @var string URL of where to proxy thumb.php requests to.
+        *    Example: http://127.0.0.1:8888/wiki/dev/thumb/
+        */
+       protected $thumbProxyUrl;
+       /** @var string Secret key to pass as an X-Swift-Secret header to the proxied thumb service */
+       protected $thumbProxySecret;
+
        /**
         * @param array|null $info
         * @throws MWException
@@ -159,7 +166,7 @@ class FileRepo {
                $optionalSettings = [
                        'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
                        'thumbScriptUrl', 'pathDisclosureProtection', 'descriptionCacheExpiry',
-                       'scriptExtension', 'favicon'
+                       'scriptExtension', 'favicon', 'thumbProxyUrl', 'thumbProxySecret'
                ];
                foreach ( $optionalSettings as $var ) {
                        if ( isset( $info[$var] ) ) {
@@ -611,6 +618,24 @@ class FileRepo {
                return $this->thumbScriptUrl;
        }
 
+       /**
+        * Get the URL thumb.php requests are being proxied to
+        *
+        * @return string
+        */
+       public function getThumbProxyUrl() {
+               return $this->thumbProxyUrl;
+       }
+
+       /**
+        * Get the secret key for the proxied thumb service
+        *
+        * @return string
+        */
+       public function getThumbProxySecret() {
+               return $this->thumbProxySecret;
+       }
+
        /**
         * Returns true if the repository can transform files via a 404 handler
         *
index 66d6143..2830b9c 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * Allows custom data specific to HTMLFormField to be set for OOjs UI forms. A matching JS widget
+ * Allows custom data specific to HTMLFormField to be set for OOUI forms. A matching JS widget
  * (defined in htmlform.Element.js) picks up the extra config when constructed using OO.ui.infuse().
  *
  * Currently only supports passing 'hide-if' data.
@@ -21,7 +21,7 @@ trait HTMLFormElement {
                        $this->addClasses( [ 'mw-htmlform-hide-if' ] );
                }
                if ( $this->modules ) {
-                       // JS code must be able to read this before infusing (before OOjs UI is even loaded),
+                       // JS code must be able to read this before infusing (before OOUI is even loaded),
                        // so we put this in a separate attribute (not with the rest of the config).
                        // And it's not needed anymore after infusing, so we don't put it in JS config at all.
                        $this->setAttributes( [ 'data-mw-modules' => implode( ',', $this->modules ) ] );
index 9c301e6..aab8811 100644 (file)
@@ -673,7 +673,7 @@ abstract class HTMLFormField {
        }
 
        /**
-        * Whether the field should be automatically infused. Note that all OOjs UI HTMLForm fields are
+        * Whether the field should be automatically infused. Note that all OOUI HTMLForm fields are
         * infusable (you can call OO.ui.infuse() on them), but not all are infused by default, since
         * there is no benefit in doing it e.g. for buttons and it's a small performance hit on page load.
         *
@@ -686,7 +686,7 @@ abstract class HTMLFormField {
 
        /**
         * Get the list of extra ResourceLoader modules which must be loaded client-side before it's
-        * possible to infuse this field's OOjs UI widget.
+        * possible to infuse this field's OOUI widget.
         *
         * @return string[]
         */
index fff72ec..ac16032 100644 (file)
@@ -181,7 +181,7 @@ abstract class MWHttpRequest implements LoggerAwareInterface {
         * @return MWHttpRequest
         * @see MWHttpRequest::__construct
         */
-       public static function factory( $url, $options = null, $caller = __METHOD__ ) {
+       public static function factory( $url, array $options = [], $caller = __METHOD__ ) {
                return \MediaWiki\MediaWikiServices::getInstance()
                        ->getHttpRequestFactory()
                        ->create( $url, $options, $caller );
index 5e018e0..e42146d 100644 (file)
@@ -447,7 +447,6 @@ abstract class Installer {
                $this->parserTitle = Title::newFromText( 'Installer' );
                $this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :(
                $this->parserOptions->setEditSection( false );
-               $this->parserOptions->setWrapOutputClass( false );
                // Don't try to access DB before user language is initialised
                $this->setParserLanguage( Language::factory( 'en' ) );
        }
@@ -689,6 +688,7 @@ abstract class Installer {
                        $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
                        $html = $out->getText( [
                                'enableSectionEditLinks' => false,
+                               'unwrap' => true,
                        ] );
                } catch ( MediaWiki\Services\ServiceDisabledException $e ) {
                        $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
index 54339c3..a5b630a 100644 (file)
@@ -12,7 +12,8 @@
                        "아라",
                        "Amire80",
                        "Piramidion",
-                       "Macofe"
+                       "Macofe",
+                       "Movses"
                ]
        },
        "config-desc": "Інсталятор MediaWiki",
        "config-install-mainpage-failed": "Не вдається вставити головну сторінку: $1",
        "config-install-done": "<strong>Вітаємо!</strong>\nВи успішно встановили MediaWiki.\n\nІнсталятор згенерував файл <code>LocalSettings.php</code>, який містить усі Ваші налаштування.\n\nВам необхідно завантажити його і помістити у кореневу папку Вашої вікі (туди ж, де index.php). Завантаження мало початись автоматично.\n\nЯкщо завантаження не почалось або Ви його скасували, можете заново його почати, натиснувши на посилання внизу:\n\n$3\n\n<strong>Примітка</strong>: Якщо Ви не зробите цього зараз, цей файл не буде доступним пізніше, коли Ви вийдете з встановлення, не скачавши його.\n\nПісля виконання дій, описаних вище, Ви зможете <strong>[$2 увійти у свою вікі]</strong>.",
        "config-install-done-path": "<strong>Вітаємо!</strong>\nВи встановили Медіавікі.\n\nІнсталятор створив файл <code>LocalSettings.php</code>.\nУ ньому містяться всі Ваші налаштування.\n\nВам потрібно завантажити його й помістити в <code>$4</code>. Завантаження повинно було автоматично розпочатись.\n\nЯкщо завантаження не було запропоновано, або Ви його скасували, Ви можете перезапустити завантаження натиснувши на посилання нижче:\n\n$3\n\n<strong>Зверніть увагу:</strong> Якщо Ви не зробите це зараз, цей згенерований файл налаштувань не буде доступним для Вас пізніше якщо Ви вийдете зі встановлення не завантаживши його.\n\nКоли це було зроблено Ви можете <strong>[$2 зайти до своєї вікі]</strong>.",
+       "config-install-success": "Mediawiki успішно встановлено. Зараз ви можете перейти до <$1$2>, щоб переглянути свою вікі. Якщо у вас є питання, ознайомтеся з нашим FAQ: <https://www.mediawiki.org/wiki/Manual:FAQ> або використовуйте один з форумів підтримки, які вказано на цій сторінці.",
        "config-download-localsettings": "Завантажити <code>LocalSettings.php</code>",
        "config-help": "допомога",
        "config-help-tooltip": "натисніть, щоб розгорнути",
index 08424c9..d939819 100644 (file)
@@ -47,17 +47,23 @@ use Psr\Log\NullLogger;
  * 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) Ignore the 'purge' EventRelayer configuration (default is NullEventRelayer)
- *         and set up mcrouter as the underlying cache backend, using one of the memcached
- *         BagOStuff classes as 'cache'. Use OperationSelectorRoute in the mcrouter settings
- *         to configure 'set' and 'delete' operations to go to all DCs via AllAsyncRoute and
- *         configure other operations to go to the local DC via PoolRoute (for reference,
- *         see https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles).
- *   - c) Ignore the 'purge' EventRelayer configuration (default is NullEventRelayer)
- *         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).
+ *   - b) Ommit 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'.
+ *        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
+ *          - 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
  *
  * Broadcasted operations like delete() and touchCheckKey() are done asynchronously
  * in all datacenters this way, though the local one should likely be near immediate.
@@ -87,6 +93,12 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        protected $purgeChannel;
        /** @var EventRelayer Bus that handles purge broadcasts */
        protected $purgeRelayer;
+       /** @bar bool Whether to use mcrouter key prefixing for routing */
+       protected $mcrouterAware;
+       /** @var string Physical region for mcrouter use */
+       protected $region;
+       /** @var string Cache cluster name for mcrouter use */
+       protected $cluster;
        /** @var LoggerInterface */
        protected $logger;
        /** @var StatsdDataFactoryInterface */
@@ -200,6 +212,16 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *       callback supplied by the getWithSetCallback() caller. The result will be saved
         *       as normal. The handler is expected to call the WAN cache callback at an opportune
         *       time (e.g. HTTP post-send), though generally within a few 100ms. [optional]
+        *   - region: the current physical region. This is required when using mcrouter as the
+        *       backing store proxy. [optional]
+        *   - cluster: name of the cache cluster used by this WAN cache. The name must be the
+        *       same in all datacenters; the ("region","cluster") tuple is what distinguishes
+        *       the counterpart cache clusters among all the datacenter. The contents of
+        *       https://github.com/facebook/mcrouter/wiki/Config-Files give background on this.
+        *       This is required when using mcrouter as the backing store proxy. [optional]
+        *   - mcrouterAware: set as true if mcrouter is the backing store proxy and mcrouter
+        *       is configured to interpret /<region>/<cluster>/ key prefixes as routes. This
+        *       requires that "region" and "cluster" are both set above. [optional]
         */
        public function __construct( array $params ) {
                $this->cache = $params['cache'];
@@ -209,6 +231,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $this->purgeRelayer = isset( $params['relayers']['purge'] )
                        ? $params['relayers']['purge']
                        : new EventRelayerNull( [] );
+               $this->region = isset( $params['region'] ) ? $params['region'] : 'main';
+               $this->cluster = isset( $params['cluster'] ) ? $params['cluster'] : 'wan-main';
+               $this->mcrouterAware = !empty( $params['mcrouterAware'] );
+
                $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
                $this->stats = isset( $params['stats'] ) ? $params['stats'] : new NullStatsdDataFactory();
                $this->asyncHandler = isset( $params['asyncHandler'] ) ? $params['asyncHandler'] : null;
@@ -1779,9 +1805,18 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @return bool Success
         */
        protected function relayPurge( $key, $ttl, $holdoff ) {
-               if ( $this->purgeRelayer instanceof EventRelayerNull ) {
+               if ( $this->mcrouterAware ) {
+                       // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
+                       // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
+                       $ok = $this->cache->set(
+                               "/*/{$this->cluster}/{$key}",
+                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
+                               $ttl
+                       );
+               } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
                        // This handles the mcrouter and the single-DC case
-                       $ok = $this->cache->set( $key,
+                       $ok = $this->cache->set(
+                               $key,
                                $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
                                $ttl
                        );
@@ -1810,8 +1845,12 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @return bool Success
         */
        protected function relayDelete( $key ) {
-               if ( $this->purgeRelayer instanceof EventRelayerNull ) {
-                       // This handles the mcrouter and the single-DC case
+               if ( $this->mcrouterAware ) {
+                       // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
+                       // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
+                       $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
+               } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
+                       // Some other proxy handles broadcasting or there is only one datacenter
                        $ok = $this->cache->delete( $key );
                } else {
                        $event = $this->cache->modifySimpleRelayEvent( [
index a828cd3..c353a22 100644 (file)
@@ -177,7 +177,7 @@ class TransactionProfiler implements LoggerAwareInterface {
        public function transactionWritingIn( $server, $db, $id ) {
                $name = "{$server} ({$db}) (TRX#$id)";
                if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
-                       $this->logger->info( "Nested transaction for '$name' - out of sync." );
+                       $this->logger->warning( "Nested transaction for '$name' - out of sync." );
                }
                $this->dbTrxHoldingLocks[$name] = [
                        'start' => microtime( true ),
@@ -206,7 +206,7 @@ class TransactionProfiler implements LoggerAwareInterface {
                $elapsed = ( $eTime - $sTime );
 
                if ( $isWrite && $n > $this->expect['maxAffected'] ) {
-                       $this->logger->info(
+                       $this->logger->warning(
                                "Query affected $n row(s):\n" . $query . "\n" .
                                ( new RuntimeException() )->getTraceAsString() );
                }
@@ -271,7 +271,7 @@ class TransactionProfiler implements LoggerAwareInterface {
        public function transactionWritingOut( $server, $db, $id, $writeTime = 0.0, $affected = 0 ) {
                $name = "{$server} ({$db}) (TRX#$id)";
                if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
-                       $this->logger->info( "Detected no transaction for '$name' - out of sync." );
+                       $this->logger->warning( "Detected no transaction for '$name' - out of sync." );
                        return;
                }
 
@@ -317,7 +317,7 @@ class TransactionProfiler implements LoggerAwareInterface {
                                list( $query, $sTime, $end ) = $info;
                                $trace .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
                        }
-                       $this->logger->info( "Sub-optimal transaction on DB(s) [{dbs}]: \n{trace}", [
+                       $this->logger->warning( "Sub-optimal transaction on DB(s) [{dbs}]: \n{trace}", [
                                'dbs' => implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) ),
                                'trace' => $trace
                        ] );
@@ -336,7 +336,7 @@ class TransactionProfiler implements LoggerAwareInterface {
                        return;
                }
 
-               $this->logger->info(
+               $this->logger->warning(
                        "Expectation ({measure} <= {max}) by {by} not met (actual: {actual}):\n{query}\n" .
                        ( new RuntimeException() )->getTraceAsString(),
                        [
index 2f284af..1405c45 100644 (file)
@@ -781,6 +781,7 @@ class ParserOptions {
         * CSS class to use to wrap output from Parser::parse()
         * @since 1.30
         * @param string|bool $className Set false to disable wrapping.
+        *   Passing false is deprecated since MediaWiki 1.31
         * @return string|bool Current value
         */
        public function setWrapOutputClass( $className ) {
index 153a770..e2efaff 100644 (file)
  */
 class ParserOutput extends CacheTime {
        /**
-        * Feature flag to indicate to extensions that MediaWiki core supports and
+        * Feature flags to indicate to extensions that MediaWiki core supports and
         * uses getText() stateless transforms.
         */
        const SUPPORTS_STATELESS_TRANSFORMS = 1;
+       const SUPPORTS_UNWRAP_TRANSFORM = 1;
 
        /**
         * @var string $mText The output text
@@ -266,29 +267,47 @@ class ParserOutput extends CacheTime {
         *     to generate one and `__NOTOC__` wasn't used. Default is true,
         *     but might be statefully overridden.
         *  - enableSectionEditLinks: (bool) Include section edit links, assuming
-        *    section edit link tokens are present in the HTML. Default is true,
+        *     section edit link tokens are present in the HTML. Default is true,
         *     but might be statefully overridden.
+        *  - unwrap: (bool) Remove a wrapping mw-parser-output div. Default is false.
         * @return string HTML
         */
        public function getText( $options = [] ) {
-               // @todo Warn if !array_key_exists( 'allowTOC', $options ) && empty( $this->mTOCEnabled )
+               if ( !array_key_exists( 'allowTOC', $options ) && empty( $this->mTOCEnabled ) ) {
+                       wfDeprecated( 'ParserOutput stateful allowTOC', '1.31' );
+               }
 
-               // @todo Warn if !array_key_exists( 'enableSectionEditLinks', $options )
-               //     && !$this->mEditSectionTokens
                //  Note that while $this->mEditSectionTokens formerly defaulted to false,
                //  ParserOptions->getEditSection() defaults to true and Parser copies
                //  that to us so true makes more sense as the stateless default.
+               if ( !array_key_exists( 'enableSectionEditLinks', $options ) && !$this->mEditSectionTokens ) {
+                       wfDeprecated( 'ParserOutput stateful enableSectionEditLinks', '1.31' );
+               }
 
                $options += [
                        // empty() here because old cached versions might lack the field somehow.
                        // In that situation, the historical behavior (possibly buggy) is to remove the TOC.
                        'allowTOC' => !empty( $this->mTOCEnabled ),
                        'enableSectionEditLinks' => $this->mEditSectionTokens,
+                       'unwrap' => false,
                ];
                $text = $this->mText;
 
                Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
 
+               if ( $options['unwrap'] !== false ) {
+                       $start = Html::openElement( 'div', [
+                               'class' => 'mw-parser-output'
+                       ] );
+                       $startLen = strlen( $start );
+                       $end = Html::closeElement( 'div' );
+                       $endLen = strlen( $end );
+
+                       if ( substr( $text, 0, $startLen ) === $start && substr( $text, -$endLen ) === $end ) {
+                               $text = substr( $text, $startLen, -$endLen );
+                       }
+               }
+
                if ( $options['enableSectionEditLinks'] ) {
                        $text = preg_replace_callback(
                                self::EDITSECTION_REGEX,
index fbc1dff..6f0bd53 100644 (file)
        "right-autocreateaccount": "Аўтаматычны ўваход з вонкавага рахунку ўдзельніка",
        "right-minoredit": "Пазначэньне рэдагаваньняў як дробных",
        "right-move": "Перанос старонак",
-       "right-move-subpages": "перанос старонак разам зь іх падстаронкамі",
+       "right-move-subpages": "Ð\9fеранос старонак разам зь іх падстаронкамі",
        "right-move-rootuserpages": "перанос карэнных старонак удзельнікаў",
        "right-move-categorypages": "перанос старонак катэгорыяў",
        "right-movefile": "перайменаваньне файлаў",
index 1780f90..2715118 100644 (file)
        "category-empty": "<em>Ova kategorija trenutno ne sadrži članke ni medije.</em>",
        "hidden-categories": "{{PLURAL:$1|Sakrivena kategorija|Sakrivene kategorije}}",
        "hidden-category-category": "Skrivene kategorije",
-       "category-subcat-count": "{{PLURAL:$2|1=Ova kategorija samo ima sljedeću potkategoriju.|Ova kategorija ima {{PLURAL:$1|sljedeću potkategoriju|sljedeće $1 potkategorije|sljedećih $1 potkategorija}}, od $2 ukupno.}}",
+       "category-subcat-count": "{{PLURAL:$2|1=Ova kategorija sadrži samo sljedeću potkategoriju.|{{PLURAL:$1|Prikazana je $1 potkategorija|Prikazane su $1 potkategorije|Prikazano je $1 potkategorija}}, od ukupno $2.}}",
        "category-subcat-count-limited": "Ova kategorija sadrži {{PLURAL:$1|sljedeću $1 potkategoriju|sljedeće $1 potkategorije|sljedećih $1 potkategorija}}.",
-       "category-article-count": "{{PLURAL:$2|1=Ova kategorija sadrži samo sljedeću stranicu.|{{PLURAL:$1|Sljedeća stranica je|Sljedeće $1 stranice su|Sljedećih $1 stranica je}} u ovoj kategoriji, od ukupno $2.}}",
+       "category-article-count": "{{PLURAL:$2|1=Ova kategorija sadrži samo sljedeću stranicu.|{{PLURAL:$1|Prikazana je $1 stranica|Prikazane su $1 stranice|Prikazano je $1 stranica}}, od ukupno $2.}}",
        "category-article-count-limited": "{{PLURAL:$1|Slijedeća $1 stranica je|Slijedeće $1 stranice su|Slijedećih $1 stranica je}} u ovoj kategoriji.",
        "category-file-count": "{{PLURAL:$2|Ova kategorija ima slijedeću $1 datoteku.|{{PLURAL:$1|Prikazana je $1 datoteka|Prikazane su $1 datoteke|Prikazano je $1 datoteka}} u ovoj kategoriji, od ukupno $2.}}",
        "category-file-count-limited": "{{PLURAL:$1|Slijedeća $1 datoteka je|Slijedeće $1 datoteke su|Slijedećih $1 datoteka je}} u ovoj kategoriji.",
index 58d3d17..082760f 100644 (file)
        "postedit-confirmation-created": "Az oldal létrehozva.",
        "postedit-confirmation-restored": "Az oldal helyre lett állítva.",
        "postedit-confirmation-saved": "A szerkesztésedet elmentettük.",
+       "postedit-confirmation-published": "A szerkesztésed közzé lett téve.",
        "edit-already-exists": "Az új lap nem készíthető el.\nMár létezik.",
        "defaultmessagetext": "Alapértelmezett szöveg",
        "content-failed-to-parse": "Hiba történt a $2 tartalom $1 modellre történő konvertálása során: $3",
        "lockmanager-fail-closelock": "Nem sikerült a „$1” zárolási fájljának bezárása.",
        "lockmanager-fail-deletelock": "Nem sikerült a(z) „$1” zárolási fájljának törlése.",
        "lockmanager-fail-acquirelock": "Nem sikerült zárolást igényelni a „$1” fájlhoz.",
-       "lockmanager-fail-openlock": "Nem sikerült a „$1” zárolási fájljának megnyitása.",
+       "lockmanager-fail-openlock": "Nem sikerült a „$1” zárolási fájljának megnyitása. Győződj meg róla, hogy a feltöltési könyvtárad jól van konfigurálva és a webszerverednek van engedélye írni a könyvtárat. Lásd még további információért: https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory",
        "lockmanager-fail-releaselock": "Nem sikerült a(z) „$1” fájl zárolásának feloldása.",
        "lockmanager-fail-db-bucket": "Nem sikerült kapcsolatot létesíteni elég adatbázis zároláshoz a $1 vödörben.",
        "lockmanager-fail-db-release": "Nem lehet a $1 adatbázis zárolását feloldani.",
index 340ab40..8aeac3c 100644 (file)
        "timezoneregion-indian": "Indlandshaf",
        "timezoneregion-pacific": "Kyrrahaf",
        "allowemail": "Leyfa öðrum notendum að senda mér tölvupóst",
+       "email-allow-new-users-label": "Leyfa tölvupóst frá nýskráðum notendum",
        "prefs-searchoptions": "Leit",
        "prefs-namespaces": "Nafnrými",
        "default": "sjálfgefið",
        "lockmanager-fail-closelock": "Gat ekki lokað lásaskrá vegna „$1“.",
        "lockmanager-fail-deletelock": "Gat ekki eytt lásaskrá vegna „$1“.",
        "lockmanager-fail-acquirelock": "Gat ekki nálgast lás vegna „$1“.",
-       "lockmanager-fail-openlock": "Gat ekki opnað lásaskrá vegna „$1“.",
+       "lockmanager-fail-openlock": "Gat ekki opnað lásaskrá vegna „$1“. Gakkt úr skugga um að innsendingamappan þín sé rétt uppsett, og að vefþjónninn þinn hafi heimildir til að skrifa í þá möppu. Skoðaðu https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory til að sjá nánari upplýsingar.",
        "lockmanager-fail-releaselock": "Gat ekki opnað lás vegna „$1“.",
        "lockmanager-fail-db-bucket": "Náði ekki sambandi við nógu marga lása í fötunni $1.",
        "lockmanager-fail-db-release": "Gat ekki opnað lása á gagnagrunninum $1.",
        "uploadstash-bad-path": "Slóðin er ekki til.",
        "uploadstash-bad-path-invalid": "Slóðin er ógild.",
        "uploadstash-bad-path-unknown-type": "Óþekkt gerð \"$1\".",
+       "uploadstash-bad-path-unrecognized-thumb-name": "Óþekkt heiti á smámynd.",
+       "uploadstash-file-not-found-no-thumb": "Gat ekki náð í smámynd.",
        "invalid-chunk-offset": "Ógild raðbreyting bunka",
        "img-auth-accessdenied": "Aðgangur óheimill",
        "img-auth-nopathinfo": "PATH_INFO vantar.\nBiðlarinn þínn er ekki stilltur til að gefa upp þessar upplýsingar.\nÞær mega vera CGI-byggðar og mega ekki styðja img_auth.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
        "doubleredirects": "Tvöfaldar tilvísanir",
        "doubleredirectstext": "Þessi síða er listi yfir skrár sem eru tilvísanir á aðrar tilvísanir.\nHver lína inniheldur tengla á fyrstu og aðra tilvísun auk þeirrar síðu sem seinni tilvísunin beinist að, sem er oftast sú síða sem allar tilvísanirnar eiga að benda á.\n<del>Yfirstrikaðar</del> færslur hafa verið leiðréttar.",
        "double-redirect-fixed-move": "[[$1]] hefur verið færð.\nHún var uppfærð sjálfkrafa og tilvísar núna á [[$2]].",
-       "double-redirect-fixed-maintenance": "Laga sjálfvirkt tvöfalda tilvísun frá [[$1]] til [[$2]] í viðhalds aðgerð.",
+       "double-redirect-fixed-maintenance": "Laga sjálfvirkt tvöfalda endurbeiningu frá [[$1]] til [[$2]] í viðhaldsaðgerð",
        "double-redirect-fixer": "Laga tilvísun",
        "brokenredirects": "Rofnar endurbeiningar",
        "brokenredirectstext": "Eftirfarandi tilvísanir vísa á síður sem ekki eru til:",
        "newimages-hidepatrolled": "Fela yfirfarnar innsendingar",
        "newimages-mediatype": "Skrátegund:",
        "noimages": "Ekkert að sjá.",
+       "gallery-slideshow-toggle": "Víxla smámyndum af/á",
        "ilsubmit": "Leita",
        "bydate": "eftir dagsetningu",
        "sp-newimages-showfrom": "Leita af nýjum skráum frá $2, $1",
index 26da774..88a605b 100644 (file)
        "tog-enotifwatchlistpages": "Inviami una email quando viene modificata una pagina o un file presente tra gli osservati speciali",
        "tog-enotifusertalkpages": "Inviami una email quando viene modificata la mia pagina di discussione",
        "tog-enotifminoredits": "Inviami una email anche per le modifiche minori di pagine e file",
-       "tog-enotifrevealaddr": "Mostra il mio indirizzo nelle e-mail di notifica",
+       "tog-enotifrevealaddr": "Mostra il mio indirizzo nelle email di notifica",
        "tog-shownumberswatching": "Mostra il numero di utenti che hanno la pagina in osservazione",
        "tog-oldsig": "La tua firma attuale:",
        "tog-fancysig": "Gestisci la firma come wikitesto (senza collegamento automatico)",
        "mailmypassword": "Reimposta password",
        "passwordremindertitle": "Servizio Password Reminder di {{SITENAME}}",
        "passwordremindertext": "Qualcuno (probabilmente tu, con indirizzo IP $1) ha richiesto l'invio di una nuova password di accesso a {{SITENAME}} ($4).\nUna password temporanea per l'utente \"$2\" è stata impostata a \"$3\".\nÈ opportuno eseguire un accesso quanto prima e cambiare la password immediatamente. La password temporanea scadrà dopo {{PLURAL:$5|un giorno|$5 giorni}}.\n\nSe non sei stato tu a fare la richiesta, oppure hai ritrovato la password e non desideri più cambiarla, puoi ignorare questo messaggio e continuare a usare la vecchia password.",
-       "noemail": "Nessun indirizzo e-mail registrato per l'utente \"$1\".",
-       "noemailcreate": "È necessario fornire un indirizzo e-mail valido",
+       "noemail": "Nessun indirizzo email registrato per l'utente \"$1\".",
+       "noemailcreate": "È necessario fornire un indirizzo email valido",
        "passwordsent": "Una nuova password è stata inviata all'indirizzo e-mail registrato per l'utente \"$1\".\nPer favore, effettua un accesso non appena la ricevi.",
        "blocked-mailpassword": "Il tuo indirizzo IP è bloccato alla modifica. Per prevenire abusi, non è consentito usare la funzione di recupero password da questo indirizzo IP.",
        "eauthentsent": "Un messaggio email di conferma è stato spedito all'indirizzo indicato.\nPer abilitare l'invio di messaggi email per questo utente è necessario seguire le istruzioni che vi sono indicate, in modo da confermare che si è i legittimi proprietari dell'indirizzo.",
        "acct_creation_throttle_hit": "{{PLURAL:$1|1 registrazione è già stata effettuata|$1 registrazioni sono già state effettuate}} da qualcuno con il tuo stesso indirizzo IP negli ultimi $2, che è il massimo consentito in questo periodo di tempo.\nPerciò, gli utenti che usano questo indirizzo IP non possono più registrarsi per il momento.",
        "emailauthenticated": "L'indirizzo email è stato confermato il $2 alle $3.",
        "emailnotauthenticated": "L'indirizzo di posta elettronica non è stato ancora confermato.\nNon verranno inviati messaggi email per le funzioni elencate di seguito.",
-       "noemailprefs": "Indicare un indirizzo e-mail per attivare queste funzioni.",
+       "noemailprefs": "Indicare un indirizzo email per attivare queste funzioni.",
        "emailconfirmlink": "Conferma il tuo indirizzo email",
        "invalidemailaddress": "L'indirizzo e-mail indicato ha un formato non valido. Inserire un indirizzo valido o svuotare la casella.",
        "cannotchangeemail": "Gli indirizzi e-mail non possono essere modificati in questo wiki.",
        "pt-createaccount": "registrati",
        "pt-userlogout": "esci",
        "php-mail-error-unknown": "Errore sconosciuto nella funzione PHP mail()",
-       "user-mail-no-addy": "Hai cercato di inviare una e-mail senza un indirizzo.",
+       "user-mail-no-addy": "Hai cercato di inviare una email senza un indirizzo.",
        "user-mail-no-body": "Tentato di inviare una e-mail con un testo vuoto o estremamente breve.",
        "changepassword": "Cambia password",
        "resetpass_announce": "Per completare l'accesso, è necessario impostare una nuova password.",
        "changeemail-password": "La password su {{SITENAME}}:",
        "changeemail-submit": "Modifica email",
        "changeemail-throttled": "Sono stati effettuati troppi tentativi di accesso.\nAttendi $1 e riprova in seguito.",
-       "changeemail-nochange": "Per favore inserisci un nuovo indirizzo e-mail.",
+       "changeemail-nochange": "Per favore inserisci un nuovo indirizzo email.",
        "resettokens": "Reimposta token",
        "resettokens-text": "Qui puoi reimpostare le chiavi che permettono l'accesso a determinati dati privati associati alla tua utenza.\n\nDovresti farlo se le hai accidentalmente condivise con qualcuno o se la tua utenza è stato compromessa.",
        "resettokens-no-tokens": "Non ci sono token da reimpostare.",
        "defemailsubject": "Messaggio da {{SITENAME}} dall'utente \"$1\"",
        "usermaildisabled": "e-mail utente disabilitata",
        "usermaildisabledtext": "Non è possibile inviare e-mail ad altri utenti su questo wiki",
-       "noemailtitle": "Nessun indirizzo e-mail",
-       "noemailtext": "Questo utente non ha indicato un indirizzo e-mail valido.",
+       "noemailtitle": "Nessun indirizzo email",
+       "noemailtext": "Questo utente non ha indicato un indirizzo email valido.",
        "nowikiemailtext": "Questo utente ha scelto di non ricevere messaggi di posta elettronica dagli altri utenti.",
        "emailnotarget": "Nome utente del destinatario inesistente o non valido.",
        "emailtarget": "Inserisci il nome utente del destinatario",
        "confirmemail_oncreate": "Un codice di conferma è stato spedito all'indirizzo\ndi posta elettronica indicato. Il codice non è necessario per accedere al sito,\nma è necessario fornirlo per poter abilitare tutte le funzioni del sito che fanno\nuso della posta elettronica.",
        "confirmemail_sendfailed": "{{SITENAME}} non può inviare il messaggio e-mail di conferma. Verificare che il proprio indirizzo e-mail non contenga caratteri non validi.\n\nMessaggio di errore del mailer: $1",
        "confirmemail_invalid": "Codice di conferma non valido. Il codice potrebbe essere scaduto.",
-       "confirmemail_needlogin": "È necessario $1 per confermare il proprio indirizzo e-mail.",
+       "confirmemail_needlogin": "È necessario $1 per confermare il proprio indirizzo email.",
        "confirmemail_success": "L'indirizzo e-mail è confermato. Ora è possibile [[Special:UserLogin|eseguire l'accesso]] e fare pieno uso del sito.",
        "confirmemail_loggedin": "L'indirizzo email è stato confermato.",
        "confirmemail_subject": "{{SITENAME}}: richiesta di conferma dell'indirizzo",
        "confirmemail_body": "Qualcuno, probabilmente tu stesso dall'indirizzo IP $1, ha registrato l'account \"$2\" su {{SITENAME}} indicando questo indirizzo e-mail.\n\nPer confermare che l'account ti appartiene veramente e attivare le funzioni relative all'invio di e-mail su {{SITENAME}}, apri il collegamento seguente con il tuo browser:\n\n$3\n\nSe *non* hai registrato tu l'account, segui questo collegamento per annullare la conferma dell'indirizzo e-mail:\n\n$5\n\nQuesto codice di conferma scadrà automaticamente alle $4.",
        "confirmemail_body_changed": "Qualcuno, probabilmente tu stesso dall'indirizzo IP $1,\nha modificato l'indirizzo e-mail dell'account \"$2\" su {{SITENAME}} indicando questo indirizzo e-mail.\n\nPer confermare che l'account ti appartiene veramente e riattivare le funzioni relative all'invio\ndi e-mail su {{SITENAME}}, apri il collegamento seguente con il tuo browser:\n\n$3\n\nSe l'account *non* ti appartiene, segui questo collegamento\nper annullare la conferma dell'indirizzo e-mail:\n\n$5\n\nQuesto codice di conferma scadrà automaticamente alle $4.",
        "confirmemail_body_set": "Qualcuno, probabilmente tu stesso dall'indirizzo IP $1,\nha impostato l'indirizzo email dell'account \"$2\" su {{SITENAME}} indicando questo indirizzo email.\n\nPer confermare che l'account ti appartiene veramente e attivare le funzioni relative all'invio\ndi email su {{SITENAME}}, apri il collegamento seguente con il tuo browser:\n\n$3\n\nSe l'account *non* ti appartiene, segui questo collegamento\nper annullare la conferma dell'indirizzo email:\n\n$5\n\nQuesto codice di conferma scadrà automaticamente alle $4.",
-       "confirmemail_invalidated": "Richiesta di conferma indirizzo e-mail annullata",
+       "confirmemail_invalidated": "Richiesta di conferma indirizzo email annullata",
        "invalidateemail": "Annulla richiesta di conferma e-mail",
        "notificationemail_subject_changed": "L'indirizzo di posta elettronica registrato su {{SITENAME}} è stato modificato",
        "notificationemail_subject_removed": "L'indirizzo di posta elettronica registrato su {{SITENAME}} è stato rimosso",
index 367115c..6be5b5d 100644 (file)
        "uploaded-script-svg": "アップロードされたSVGファイルにスクリプト可能な要素「$1」が見つかりました。",
        "uploaded-hostile-svg": "アップロードされたSVGファイルのスタイル要素に安全ではないCSSが見つかりました。",
        "uploaded-event-handler-on-svg": "イベントハンドラをセットする属性 <code>$1=\"$2\"</code> は、SVGファイルを許可されていません。",
-       "uploaded-href-attribute-svg": "SVG ファイルの href 属性が http:// または https:// のターゲットのみにリンクする <code>&lt;$1 $2=\"$3\"&gt;</code> が見つかりました。",
+       "uploaded-href-attribute-svg": "<a> 要素では、データ (埋め込みファイル)、http://、https://、フラグメント (#、ID 属性など) のターゲットにのみリンクできます。<image> などの他の要素では、データとフラグメントのみが使用できます。SVG をエクスポートする際は画像を埋め込むようにしてください。アップロードされたファイルには <code>&lt;$1 $2=\"$3\"&gt;</code> が含まれています。",
        "uploaded-href-unsafe-target-svg": "アップロードされた SVG ファイルの、安全ではないデータ URI にターゲット <code>&lt;$1 $2=\"$3\"&gt;</code> の href が見つかりました。",
        "uploaded-animate-svg": "アップロードされたSVGファイルに、「from」属性 <code>&lt;$1 $2=\"$3\"&gt;</code> を使用した、href を変更させる可能性がある「animate」タグが見つかりました。",
        "uploaded-setting-event-handler-svg": "アップロードされたSVGファイルに、ブロックされているイベントハンドラ属性が設定された <code>&lt;$1 $2=\"$3\"&gt;</code> が見つかりました。",
        "lockmanager-fail-closelock": "「$1」用のロックファイルを閉じることができませんでした。",
        "lockmanager-fail-deletelock": "「$1」用のロックファイルを削除できませんでした。",
        "lockmanager-fail-acquirelock": "「$1」用のロックを取得できませんでした。",
-       "lockmanager-fail-openlock": "「$1」用のロックファイルを開くことができませんでした。",
+       "lockmanager-fail-openlock": "「$1」用のロックファイルを開くことができませんでした。アップロードディレクトリが正しく設定されており、ウェブサーバーによる書き込みの権限が許可されていることを確認してください。詳しくは https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory をご覧ください。",
        "lockmanager-fail-releaselock": "「$1」用のロックを解放できませんでした。",
        "lockmanager-fail-db-bucket": "バケツ $1 で十分な数のロックデータベースに接触できませんでした。",
        "lockmanager-fail-db-release": "データベース $1 上のロックを解放できませんでした。",
        "uploadstash-refresh": "ファイルの一覧を更新",
        "uploadstash-thumbnail": "サムネイルを表示",
        "uploadstash-exception": "スタッシュにアップロードできませんでした ($1): \"$2\"",
+       "uploadstash-bad-path": "パスが存在しません。",
+       "uploadstash-bad-path-invalid": "パスが無効です。",
+       "uploadstash-bad-path-unknown-type": "「$1」は不明なタイプです。",
+       "uploadstash-bad-path-unrecognized-thumb-name": "サムネイル名が認識できません。",
+       "uploadstash-bad-path-no-handler": "ファイル $2 の MIME $1 に対するハンドラが見つかりません。",
+       "uploadstash-bad-path-bad-format": "キー「$1」は適切な形式ではありません。",
        "uploadstash-zero-length": "ファイルのサイズがゼロです。",
        "invalid-chunk-offset": "無効なチャンクオフセット",
        "img-auth-accessdenied": "アクセスが拒否されました",
        "import-mapping-namespace": "名前空間へインポート:",
        "import-mapping-subpage": "次のページの下位ページとしてインポート:",
        "import-upload-filename": "ファイル名:",
+       "import-upload-username-prefix": "インターウィキ接頭辞:",
        "import-comment": "コメント:",
        "importtext": "元のウィキで[[Special:Export|書き出し機能]]を使用してファイルに書き出してください。\nそれをコンピューターに保存した後、こちらへアップロードしてください。",
        "importstart": "ページを取り込み中...",
        "pageinfo-category-subcats": "下位カテゴリ数",
        "pageinfo-category-files": "ファイル数",
        "pageinfo-user-id": "利用者 ID",
+       "pageinfo-file-hash": "ハッシュ値",
        "markaspatrolleddiff": "巡回済みにする",
        "markaspatrolledtext": "このページを巡回済みにする",
        "markaspatrolledtext-file": "このファイルの版を巡回済みにする",
        "limitreport-expensivefunctioncount": "高負荷パーサー関数の数",
        "limitreport-expensivefunctioncount-value": "$1/$2",
        "expandtemplates": "テンプレートを展開",
-       "expand_templates_intro": "この特別ページは、入力したテキストに含まれるすべてのテンプレートを再帰的に展開します。\n<code><nowiki>{{</nowiki>#language:…}}</code> のようなパーサー関数や、\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code> のような変数も展開します。\nつまり、二重中括弧で囲まれたものほぼすべてを展開します。",
+       "expand_templates_intro": "ã\81\93ã\81®ç\89¹å\88¥ã\83\9aã\83¼ã\82¸ã\81¯ã\80\81å\85¥å\8a\9bã\81\97ã\81\9fã\82¦ã\82£ã\82­ã\83\86ã\82­ã\82¹ã\83\88ã\81«å\90«ã\81¾ã\82\8cã\82\8bã\81\99ã\81¹ã\81¦ã\81®ã\83\86ã\83³ã\83\97ã\83¬ã\83¼ã\83\88ã\82\92å\86\8d帰ç\9a\84ã\81«å±\95é\96\8bã\81\97ã\81¾ã\81\99ã\80\82\n<code><nowiki>{{</nowiki>#language:â\80¦}}</code> ã\81®ã\82\88ã\81\86ã\81ªã\83\91ã\83¼ã\82µã\83¼é\96¢æ\95°ã\82\84ã\80\81\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code> ã\81®ã\82\88ã\81\86ã\81ªå¤\89æ\95°ã\82\82å±\95é\96\8bã\81\97ã\81¾ã\81\99ã\80\82\nã\81¤ã\81¾ã\82\8aã\80\81äº\8cé\87\8d中æ\8b¬å¼§ã\81§å\9b²ã\81¾ã\82\8cã\81\9fã\82\82ã\81®ã\81»ã\81¼ã\81\99ã\81¹ã\81¦ã\82\92å±\95é\96\8bã\81\97ã\81¾ã\81\99ã\80\82",
        "expand_templates_title": "{{FULLPAGENAME}} などで使用するページ名:",
-       "expand_templates_input": "展開するテキスト:",
+       "expand_templates_input": "å±\95é\96\8bã\81\99ã\82\8bã\82¦ã\82£ã\82­ã\83\86ã\82­ã\82¹ã\83\88:",
        "expand_templates_output": "展開結果",
        "expand_templates_xml_output": "XML 出力",
        "expand_templates_html_output": "出力される HTML ソース",
        "expand_templates_preview": "プレビュー",
        "expand_templates_preview_fail_html": "<em>{{SITENAME}} ではHTMLソースが有効になっており、セッションデータの損失が生じているので、JavaScript の攻撃に対する予防措置としてプレビューは表示されません。</em>\n\n<strong>これが合法的なプレビューの試みである場合には、もう一度試してください。</strong>\nそれでも動作しない場合は、[[Special:UserLogout|ログアウト]]してからログインし直し、現在使用しているブラウザでこのサイトからのクッキーが許可されていることを確認してください。",
        "expand_templates_preview_fail_html_anon": "<em>{{SITENAME}} ではHTMLソースが有効になっており、ログインしていないため、JavaScript の攻撃に対する予防措置としてプレビューは表示されません。</em>\n\n<strong>これが合法的なプレビューの試みである場合には、[[Special:UserLogin|ログイン]]してもう一度試してください。</strong>",
-       "expand_templates_input_missing": "文章を入力してください。",
+       "expand_templates_input_missing": "ウィキテキストを入力してください。",
        "pagelanguage": "ページ言語の変更",
        "pagelang-name": "ページ",
        "pagelang-language": "言語",
        "restrictionsfield-label": "許可する IP の範囲:",
        "restrictionsfield-help": "一行につき、単一の IP アドレス、もしくは CIDR による範囲。全帯域からの接続を許可する場合: <pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "エラー: $1",
-       "edit-error-long": "エラー\n\n\n\n$1",
+       "edit-error-long": "エラー:\n\n\n\n$1",
        "revid": "版 $1",
        "pageid": "ページID $1",
        "rawhtml-notallowed": "&lt;html&gt;タグは通常ページ以外では使用できません。",
index 87fd05c..171b989 100644 (file)
        "mediawikipage": "Vizualizza o messaggio",
        "templatepage": "Vizualizza o modello",
        "viewhelppage": "Móstra a pagina d'agiutto",
-       "categorypage": "Veddi a pagina da categorîa",
+       "categorypage": "Amia a pagina da categoria",
        "viewtalkpage": "Amîa a pagina de discusción",
        "otherlanguages": "In âtre léngoe",
        "redirectedfrom": "(Rendirissou da $1)",
        "perfcached": "I dæti chì apreuvo son estræti da 'na coppia ''cache'' do database, e porrieivan no ese agiornæ. Un mascimo de {{PLURAL:$1|un risultou o l'è disponibbile|$1 risultæ son disponibbili}} into cache.",
        "perfcachedts": "I dæti chì apreuvo son estræti da una coppia ''cache'' do database, o quæ urtimo agiornamento o remonta a-o $1. Un mascimo de {{PLURAL:$4|un risultou o l'è disponibbile|$4 risultæ son disponibili}} into cache.",
        "querypage-no-updates": "I agiornamenti da pagina son temporaniamente sospeixi. I dæti in lé contegnui no saian aggiornæ.",
-       "viewsource": "Veddi a fonte",
+       "viewsource": "Vixualizza wikitesto",
        "viewsource-title": "Visualizza sorgente de $1",
        "actionthrottled": "Assion ritardâ",
        "actionthrottledtext": "Comme mesua de segueça contra o spam, l'esecuçion de çerte açioin a l'è limitâ a un nummero mascimo de votte inte 'n determinòu periodo de tempo, limmite che ti t'hæ superòu. Se prega de riprovâ tra quarche menuto.",
        "publishpage": "Pubbrica a paggina",
        "publishchanges": "Pubbrica e modiffiche",
        "preview": "Anteprìmma",
-       "showpreview": "Veddi l'anteprimma",
-       "showdiff": "Veddi i cangiamenti",
+       "showpreview": "Amia l'anteprimma",
+       "showdiff": "Mostra modiffiche",
        "blankarticle": "<strong>Atençión:</strong> a pàgina che ti çerchi a l'é vêua.\nClicando tórna in sce \"$1\", a pàgina a saiâ creâ sénsa contegnûi.",
        "anoneditwarning": "<strong>Attension:</strong> No t'ê introu. Se ti fæ di cangi o teu adresso IP o saiâ vixibile pubbricamente. Se <strong>[$1 ti intri]</strong> ò <strong>[$2 ti crei un'utensa]</strong>, e teu modifiche saian attribuie a-o teu nomme utente, insemme a di atri benefiççi.",
        "anonpreviewwarning": "No t'hæ fæto l'accesso. Se ti sarvi inta stoia da paggina ghe saiâ solo o to adresso IP",
        "undo-summary-username-hidden": "Anullou a modiffica $1 de un utente ascoso",
        "cantcreateaccount-text": "A registrassion da questo addresso IP (<b>$1</b>) a l'è stæta bloccâ da [[User:$3|$3]].\n\nA raxon dæta a l'è ''$2''",
        "cantcreateaccount-range-text": "A registraçion da di addressi IP inte l'intervallo <strong>$1</strong>, ch'o  l'includde o teu (<strong>$4</strong>), a l'è stæta bloccâ da [[User:$3|$3]].\n\nA raxon dæta da $3 a l'è <em>$2</em>",
-       "viewpagelogs": "Veddi i log relativi a 'sta paggina.",
+       "viewpagelogs": "Amia i log relativi a 'sta paggina.",
        "nohistory": "A stoia de verscioin de sta paggina a no gh'è.",
        "currentrev": "Verscion attuâle",
        "currentrev-asof": "Ùrtima revixón do $1",
        "recentchanges-label-unpatrolled": "Sto cangiaménto o no l'é stæto ancón verificòu",
        "recentchanges-label-plusminus": "Variassion da paggina in nummero de byte",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (veddi e [[Special:NewPages|neuve paggine]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (amia e [[Special:NewPages|noeuve paggine]])",
        "recentchanges-submit": "Fanni vedde",
        "rcfilters-activefilters": "Filtri attivi",
        "rcfilters-advancedfilters": "Filtri avançæ",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]] rimossa da-a categoria, [[Special:WhatLinksHere/$1|questa pagina a l'è inclusa a l'interno di atre pagine]]",
        "autochange-username": "Modiffica aotomattica MediaWiki",
        "upload": "Carrega 'n file",
-       "uploadbtn": "Carrega 'n file",
+       "uploadbtn": "Carreghilo",
        "reuploaddesc": "Torna a-o moddulo pe-o caregamento.",
        "upload-tryagain": "Invia a descrission do file modificou",
        "uploadnologin": "No t'ê introu",
        "upload-curl-error6-text": "Imposcibile razonze a URL specificâ. Verifica che a URL a sæ scrita correttamente e che o scito in question o sæ attivo.",
        "upload-curl-error28": "Tempo descheito pe l'upload",
        "upload-curl-error28-text": "O scito remoto o l'ha impiegou troppo tempo a risponde. Verifica che o sito o sæ attivo, attendi quarche menuto e proeuva torna, se mai inte 'n momento con meno traffego.",
-       "license": "Licénsa:",
-       "license-header": "Licénsa",
+       "license": "Liçençia:",
+       "license-header": "Liçençia",
        "nolicense": "Nisciûnn-a liçensa indicâa",
        "licenses-edit": "Modiffica opçioin de liçença",
        "license-nopreview": "(Anteprimma non disponibbile)",
        "modifiedarticleprotection-comment": "{{GENDER:$2|Cangiou o livello de proteçion}} pe \"[[$1]]\"",
        "unprotectedarticle-comment": "{{GENDER:$2|Rimosso a proteçion}} da \"[[$1]]\"",
        "protect-title": "Cangio do livello de proteçion pe \"$1\"",
-       "protect-title-notallowed": "Veddi o livello de proteçion de \" $1 \"",
+       "protect-title-notallowed": "Amia o livello de proteçion de \" $1 \"",
        "prot_1movedto2": "[[$1]] mesciòu a [[$2]]",
        "protect-badnamespace-title": "Namespace non protezibbile",
        "protect-badnamespace-text": "E pagine de questo namespace no poeuan ese protezue.",
        "restriction-level-autoconfirmed": "semi-protezua",
        "restriction-level-all": "Tutti i livelli",
        "undelete": "Amîa e paggine scassæ",
-       "undeletepage": "Veddi e recuppera e pagine scançellæ",
+       "undeletepage": "Amia e recuppera e paggine scassæ",
        "undeletepagetitle": "'''Quanto segue o l'è composto da de revixoin scassæ de [[:$1|$1]]'''.",
-       "viewdeletedpage": "Veddi e paggine scassæ",
+       "viewdeletedpage": "Amia e paggine scassæ",
        "undeletepagetext": "{{PLURAL:$1|A seguente pagina a l'è stæta scassâ, ma a l'è ancon in archivio e pertanto a poeu ese recuperâ|E seguente pagine son stæte scassæ, ma son ancon in archivio e pertanto poeuan ese recuperæ}}. L'archivio o poeu ese vuou periodicamente.",
        "undelete-fieldset-title": "Ripristina revixoin",
        "undeleteextrahelp": "Pe recuperâ l'intrega cronologia da pagina, lascia tutte e caselle deseleçionæ e fanni clic insce '''''{{int:undeletebtn}}'''''.\nPe effettuâ un ripristino selettivo, seleçion-a e caselle corrispondente a-e verscioin da ripristinâ e fanni clic insce '''''{{int:undeletebtn}}'''''.",
        "tooltip-t-permalink": "Colegaménto fisso a sta revixión da pàgina",
        "tooltip-ca-nstab-main": "Véddi a vôxe",
        "tooltip-ca-nstab-user": "Amîa a paggina utente",
-       "tooltip-ca-nstab-media": "Veddi a paggina do file murtimediâ",
+       "tooltip-ca-nstab-media": "Amia a paggina do file murtimediâ",
        "tooltip-ca-nstab-special": "Sta chi l'è 'na pàgina speciâle e a no peu êse cangiâ",
        "tooltip-ca-nstab-project": "Véddi a pàgina de servìçio",
-       "tooltip-ca-nstab-image": "Veddi a paggina do file",
-       "tooltip-ca-nstab-mediawiki": "Veddi o messaggio de scistema",
-       "tooltip-ca-nstab-template": "Veddi o template",
-       "tooltip-ca-nstab-help": "Veddi a paggina d'agiûtto",
+       "tooltip-ca-nstab-image": "Amia a paggina do file",
+       "tooltip-ca-nstab-mediawiki": "Amia o messaggio de scistema",
+       "tooltip-ca-nstab-template": "Amia o template",
+       "tooltip-ca-nstab-help": "Amia a paggina d'agiutto",
        "tooltip-ca-nstab-category": "Véddi a pàgina da categorîa",
        "tooltip-minoredit": "Marchilo comme cangiaménto minô",
        "tooltip-save": "Sarva i cangiaménti",
        "watchlistedit-clear-removed": "L'è stæto eliminou {{PLURAL:$1|una paggina|$1 paggine}}:",
        "watchlistedit-too-many": "Gh'è troppe paggine da visualizzâ chì.",
        "watchlisttools-clear": "Svoeua a lista sott'öservaçion",
-       "watchlisttools-view": "Veddi e modiffiche pertinenti",
-       "watchlisttools-edit": "Veddi e modiffica a lista",
+       "watchlisttools-view": "amia e modiffiche pertinenti",
+       "watchlisttools-edit": "Amia e modiffica a lista",
        "watchlisttools-raw": "Modiffica a lista in formato testo",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|discuscioin]])",
        "timezone-local": "Locale",
index a3f3ce6..3820aa8 100644 (file)
        "postedit-confirmation-created": "A página foi criada.",
        "postedit-confirmation-restored": "Esta página foi restaurada.",
        "postedit-confirmation-saved": "Sua edição foi salva",
+       "postedit-confirmation-published": "A sua edição foi publicada.",
        "edit-already-exists": "Não foi possível criar uma nova página.\nEla já existia.",
        "defaultmessagetext": "Texto da mensagem padrão",
        "content-failed-to-parse": "Falha ao analisar o conteúdo $2 para o modelo $1: $3",
index 88ac09e..15d950f 100644 (file)
        "lockmanager-fail-closelock": "Não foi possível encerrar a referência de bloqueio para \"$1\".",
        "lockmanager-fail-deletelock": "Não foi possível eliminar a referência de bloqueio para \"$1\".",
        "lockmanager-fail-acquirelock": "Não foi possível adquirir bloqueio para \"$1\".",
-       "lockmanager-fail-openlock": "Não foi possível abrir o ficheiro de bloqueio de \"$1\".",
+       "lockmanager-fail-openlock": "Não foi possível abrir o ficheiro de bloqueio de \"$1\". Verifique que o seu diretório de carregamento está devidamente configurado e que o seu servidor de Internet tem permissão para escrever nesse diretório. Para mais informações, consulte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory.",
        "lockmanager-fail-releaselock": "Não foi possível libertar o bloqueio de \"$1\".",
        "lockmanager-fail-db-bucket": "Não foi possível contactar bases de dados de bloqueio suficientes no \"bucket\" $1.",
        "lockmanager-fail-db-release": "Não foi possível libertar bloqueios na base de dados $1.",
index 4bff6f5..d7c8990 100644 (file)
        "postedit-confirmation-created": "Страница создана.",
        "postedit-confirmation-restored": "Страница была восстановлена.",
        "postedit-confirmation-saved": "Ваша правка сохранена.",
+       "postedit-confirmation-published": "Ваша правка была опубликована.",
        "edit-already-exists": "Невозможно создать новую страницу.\nОна уже существует.",
        "defaultmessagetext": "Текст по умолчанию",
        "content-failed-to-parse": "Содержимое $2 не соответствует типу $1: $3.",
        "lockmanager-fail-closelock": "Не удалось закрыть файл блокировки для  «$1».",
        "lockmanager-fail-deletelock": "Не удалось удалить файл блокировки для «$1».",
        "lockmanager-fail-acquirelock": "Не удалось добиться блокировки «$1».",
-       "lockmanager-fail-openlock": "Не удалось открыть файл блокировки для «$1».",
+       "lockmanager-fail-openlock": "Не удалось открыть файл блокировки для «$1». Убедитесь, что ваш каталог загрузки настроен правильно, а ваш веб-сервер имеет разрешение на запись в этот каталог. Дополнительную информацию см. на https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory.",
        "lockmanager-fail-releaselock": "Не удалось разблокировать «$1».",
        "lockmanager-fail-db-bucket": "Не удалось связаться с достаточным количеством баз блокировок в сегменте $1.",
        "lockmanager-fail-db-release": "Не удалось снять блокировку базы данных  $1 .",
index 676b3e3..55494e2 100644 (file)
        "postedit-confirmation-created": "Сирэй оҥоһулунна.",
        "postedit-confirmation-restored": "Сирэй сөргүтүлүннэ.",
        "postedit-confirmation-saved": "Көннөрүүҥ бигэргэннэ.",
+       "postedit-confirmation-published": "Уларытыыҥ олоххо киирдэ.",
        "edit-already-exists": "Саҥа сирэйи оҥорор табыллыбат.\nМаннык сирэй баар эбит.",
        "defaultmessagetext": "Туспа этиллибэтэҕинэ суруллар тиэкис",
        "content-failed-to-parse": "$2 иһинээҕитэ $1 көрүҥэр сөп түбэспэт: $3.",
index 6e57b62..f1e57fa 100644 (file)
        "compare-title-not-exists": "Наведени наслов не постоји.",
        "compare-revision-not-exists": "Наведена измена не постоји.",
        "diff-form": "Разлике",
+       "diff-form-submit": "Прикажи разлике",
        "permanentlink": "Стална веза",
        "dberr-problems": "Дошло је до техничких проблема.",
        "dberr-again": "Сачекајте неколико минута и поново учитајте страницу.",
        "mw-widgets-mediasearch-noresults": "Нема резултата.",
        "mw-widgets-titleinput-description-new-page": "страница још увек не постоји",
        "mw-widgets-titleinput-description-redirect": "преусмерава на $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Додај категорију...",
        "mw-widgets-usersmultiselect-placeholder": "Додај још...",
        "date-range-from": "Од датума:",
        "date-range-to": "До датума:",
        "log-action-filter-rights-autopromote": "аутоматски",
        "log-action-filter-upload-upload": "Ново отпремање",
        "log-action-filter-upload-overwrite": "промена постојећег",
+       "authmanager-authplugin-setpass-failed-title": "Неуспешна промена лозинке",
        "authmanager-email-label": "Имејл",
        "authmanager-email-help": "Имејл адреса",
+       "authmanager-realname-label": "Право име",
+       "authmanager-realname-help": "Право име корисника",
        "authprovider-resetpass-skip-label": "Прескочи",
+       "cannotauth-not-allowed-title": "Приступ је одбијен",
        "changecredentials": "Промјена акредитива",
        "changecredentials-submit": "Промени",
        "removecredentials": "Уклањање акредитива",
        "credentialsform-account": "Назив налога:",
        "userjsispublic": "Напомена: JavaScript подстранице не би требале садржавати поверљиве информације будући да су видљиве другим корисницима.",
        "usercssispublic": "Напомена: CSS подстранице не би требале садржавати поверљиве информације будући да су видљиве другим корисницима.",
+       "edit-error-short": "Грешка: $1",
+       "edit-error-long": "Грешке:\n\n$1",
+       "revid": "измена $1",
        "rawhtml-notallowed": "&lt;html&gt; тагови не могу да се користе ван нормалних страница.",
        "gotointerwiki": "Напуштам пројекат {{SITENAME}}",
        "gotointerwiki-invalid": "Одабрани наслов је неисправан.",
diff --git a/languages/i18n/sty.json b/languages/i18n/sty.json
new file mode 100644 (file)
index 0000000..9b40b5c
--- /dev/null
@@ -0,0 +1,630 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Khanmarat",
+                       "Sorbat",
+                       "Stephanecbisson",
+                       "Рустам Нурыев"
+               ]
+       },
+       "sunday": "Йәкшәмпе",
+       "monday": "Түшәмпе",
+       "tuesday": "Сишәмпе",
+       "wednesday": "Царшампы",
+       "thursday": "Пәйшәмпе",
+       "friday": "Йома",
+       "saturday": "Шәмпе",
+       "sun": "Йәк",
+       "mon": "Түш",
+       "tue": "Сиш",
+       "wed": "Цар",
+       "thu": "Пәй",
+       "fri": "Йом",
+       "sat": "Шәм",
+       "january": "январь",
+       "february": "февраль",
+       "march": "март",
+       "april": "апрель",
+       "may_long": "май",
+       "june": "июнь",
+       "july": "июль",
+       "august": "август",
+       "september": "сентябрь",
+       "october": "октябрь",
+       "november": "ноябрь",
+       "december": "декабрь",
+       "january-gen": "январь",
+       "february-gen": "февраль",
+       "march-gen": "март",
+       "april-gen": "апрель",
+       "may-gen": "май",
+       "june-gen": "июнь",
+       "july-gen": "июль",
+       "august-gen": "август",
+       "september-gen": "сентябрь",
+       "october-gen": "октябрь",
+       "november-gen": "ноябрь",
+       "december-gen": "декабрь",
+       "jan": "янв",
+       "feb": "фев",
+       "mar": "мар",
+       "apr": "апр",
+       "may": "май",
+       "jun": "июн",
+       "jul": "июл",
+       "aug": "авг",
+       "sep": "сен",
+       "oct": "окт",
+       "nov": "ноя",
+       "dec": "дек",
+       "pagecategories": "{{PLURAL:$1|1=Категория|Категориялар}}",
+       "category_header": "«$1» категориятағы питләр",
+       "subcategories": "Эцке категориялар",
+       "category-media-header": "«$1» категориясынтағы файллар",
+       "category-empty": "Пы категория ҡәсерге уаҡытта пуш.",
+       "hidden-categories": "{{PLURAL:$1|Йәшерен категория|Йәшерен категориялар}}",
+       "category-subcat-count": "{{PLURAL:$2|1=Пы төргөнтә түмәнтәге астөргөн кенә пар. |Пы төргөнтә пулған $2-тән {{PLURAL:$1|астөргөн|астөргөннәр}} $1 ҡына күрсәтеләте.}}",
+       "category-article-count": "{{PLURAL:$2|1=Пы төргөнтә пер генә пит пар. Пы категориятан пулған {{PLURAL:$2|пулған|пулғаннар}}}} $2 -тән {{PLURAL:$1|$1 пит күрсәтелгән|$1 питләр күрсәтелгәннәр}}.",
+       "category-file-count": "{{PLURAL:$2|Пы категорията пер генә файл пар.|Категориятағы $2 файлныңҡы {{PLURAL:$1|$1 файлы күргәселгән}}.}}",
+       "listingcontinuesabbrev": "ҡушлау",
+       "noindex-category": "Индексланмайтығын питләр",
+       "broken-file-category": "Файлға эшләмәйтеген ссылкалары пулған питләр",
+       "about": "Сүрәтләмә",
+       "newwindow": "(йаңа тәрәсәтә)",
+       "cancel": "Пулмайын иткәле",
+       "mytalk": "Уйлашыу",
+       "navigation": "Навигация",
+       "and": "&#32;әм",
+       "namespaces": "Исемнәрнең киңнеге",
+       "variants": "Вариантлар",
+       "navigation-heading": "Навигация",
+       "returnto": "$1 питкә ҡайтыу.",
+       "tagline": "{{SITENAME}} проекттан",
+       "help": "Пелешмә",
+       "search": "Эстәү",
+       "searchbutton": "Тапҡалы",
+       "searcharticle": "‎Күцкәле",
+       "history": "Тариҡ",
+       "history_short": "Тариҡ",
+       "printableversion": "Пастырыр өцөн версия",
+       "permalink": "Келәң ссылка",
+       "view": "Ҡарау",
+       "view-foreign": "$1 сайтта ҡарағалы",
+       "edit": "Төсәткәле",
+       "create": "Пултырғалы",
+       "create-local": "Локаль сүрәтләмәне өстәгәле",
+       "delete": "Йуҡ иткәле",
+       "newpage": "Йаңа пит",
+       "talkpagelinktext": "уйлашыу",
+       "personaltools": "Персональ ҡораллар",
+       "talk": "Уйлап ҡарау",
+       "views": "Ҡараулар",
+       "toolbox": "‎Ҡораллар",
+       "otherlanguages": "Пашҡа телләртә",
+       "redirectedfrom": "($1 питтән йебәрелгән)",
+       "redirectpagesub": "Йусыҡлау пит",
+       "redirectto": "Йусыҡлағалы",
+       "lastmodifiedat": "Пы пит $1гә, $2 пуйта суңҡа төсәйтелгән.",
+       "jumpto": "Анта күцкәле:",
+       "jumptonavigation": "навигация",
+       "jumptosearch": "эстәү",
+       "aboutsite": "‎{{SITENAME}} турлы",
+       "aboutpage": "Project:Сүрәтләмә",
+       "copyright": "Эцтәлеге $1 лицензия пелән кенә ацыҡ (пашҡа исем күргәселмәсә).",
+       "copyrightpage": "{{ns:project}}:Авторның ҡаҡы",
+       "currentevents": "Ҡәсерге пулған ҡәлләр",
+       "currentevents-url": "Project:Ҡәсерге пулған ҡәлләр",
+       "disclaimers": "Пелеклелектән паш тартыу",
+       "disclaimerpage": "Project:Пелеклелектән паш тартыу",
+       "edithelp": "Төсәтеүгә йәртәм",
+       "mainpage": "Паш пит",
+       "mainpage-description": "Паш пит",
+       "portal": "Перләшмә",
+       "portal-url": "Project:Порталның перләшмәсе",
+       "privacy": "Серне саҡлау сәйәсәт",
+       "privacypage": "Project:Серне саҡлайтығын сәйәсәт",
+       "retrievedfrom": "Аҡма — «$1»",
+       "youhavenewmessages": "{{PLURAL:$3|Сестә}} $1 ($2) пар.",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|Сескә}} {{PLURAL:$3|$3 ҡатнашыуцытан}} $1 килте ($2).",
+       "newmessageslinkplural": "{{PLURAL:$1|1=йаңа ҡәбәр|999=йаңа ҡәбәрләр}}",
+       "newmessagesdifflinkplural": "{{PLURAL:$1|1=суңҡы пашҡартыу|999=суңҡы пашҡартыулар}}",
+       "editsection": "төсәткәле",
+       "editold": "төсәткәле",
+       "viewsourceold": "пашлапҡы кодны ҡарағалы",
+       "editlink": "төсәткәле",
+       "viewsourcelink": "паш кодны ҡарағалы",
+       "editsectionhint": "$1 ‎пүлекне төсәткәле",
+       "toc": "Эцтәлек",
+       "site-atom-feed": "$1 — Atom тасма",
+       "page-atom-feed": "«$1» — Atom-тасма",
+       "red-link-title": "$1 (пы пит йуҡ)",
+       "nstab-main": "Мәҡәлә",
+       "nstab-user": "Ҡатнашыуцы",
+       "nstab-special": "Ҡесмәт пит",
+       "nstab-project": "Проетның пите",
+       "nstab-image": "Файл",
+       "nstab-mediawiki": "Пелтереү",
+       "nstab-template": "Ҡалып",
+       "nstab-category": "Категория",
+       "mainpage-nstab": "Паш пит",
+       "nosuchspecialpage": "Мынтайын ҡесмәт пит йуҡ",
+       "nospecialpagetext": "<strong>Сес сураған ҡесмәт пит йуҡ.</strong>\n* Ҡесмәт питләр күцермәлекне ҡараң: [[Special:SpecialPages|{{int:specialpages}}]].",
+       "badtitle": "Йарамаған исем",
+       "badtitletext": "Питнең суралған исеме төрөс түгел, пуш йә интервикиныңҡы исеме төрөс күрсәтелмәгән. Пәйәкпәр, исемтә йарамаған символлар пар ты.",
+       "viewsource": "Вики-текстны ҡарау",
+       "viewsource-title": "$1 питнең паш текстын ҡарау",
+       "viewsourcetext": "Сес пы питнеңке паш текстын ҡарап күцерә аласыс.",
+       "userlogin-yourname": "Ҡулланыуцыныңҡы исеме",
+       "userlogin-yourname-ph": "Исәп йасмағысныңҡы исемен кергесең",
+       "userlogin-yourpassword": "Пароль",
+       "userlogin-yourpassword-ph": "Үсегеснең парольны йасың",
+       "createacct-yourpassword-ph": "Парольны йасың",
+       "createacct-yourpasswordagain": "Парольны төрөс тигәле",
+       "createacct-yourpasswordagain-ph": "Парольны тағын та йасың",
+       "userlogin-remembermypassword": "Системата ҡалғалы",
+       "login": "Кергәле",
+       "userlogin-noaccount": "Исәп йасмағыс йуҡ ма?",
+       "userlogin-joinproject": "{{SITENAME}} ‎проектҡа ҡушылғалы",
+       "createaccount": "Йаңа ҡатнашыуцыны теркәү",
+       "userlogin-resetpassword-link": "Паролеғысны оноттоғос ма?",
+       "userlogin-helplink2": "Керер өцөн йәртәм",
+       "createacct-emailoptional": "Электрон почтағысның адресы (тейешлецә түгел)",
+       "createacct-email-ph": "Электрон почтағысның адресын йасың",
+       "createacct-submit": "Исәп йасманы пултырғалы",
+       "createacct-benefit-heading": "{{SITENAME}} — сеснең шигелле кешеләрнең эше.",
+       "createacct-benefit-body1": "{{PLURAL:$1|төсәтеү}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|мәҡәлә|мәҡәләнең}}",
+       "createacct-benefit-body3": "Суңҡы уаҡыттағы {{PLURAL:$1|ҡатнашыуцы}}",
+       "loginlanguagelabel": "Тел: $1",
+       "pt-login": "Кергәле",
+       "pt-login-button": "Кергәле",
+       "pt-createaccount": "Йаңа аккаунт пултырғалы",
+       "pt-userlogout": "Цыҡҡалы",
+       "passwordreset": "Парольны пөтөрөү",
+       "bold_sample": "Ҡалын пелән йасыу",
+       "bold_tip": "Ҡалын пелән йасыу",
+       "italic_sample": "Курсив пелән йасыу",
+       "italic_tip": "Курсив пелән йасыу",
+       "link_sample": "Ссылканыңҡы төп исеме",
+       "link_tip": "Эцке ссылка",
+       "extlink_sample": "http://www.example.com ссылканың төп исеме",
+       "extlink_tip": "Тышҡы ссылка (http:// префиксны онотмаң)",
+       "headline_sample": "Төп исем",
+       "headline_tip": "2-нце ҡат төп исем",
+       "nowiki_sample": "Мынта форматлау кәрәкмәйтеген текстны өстәң",
+       "nowiki_tip": "Вики-форматлауға ҡолаҡ салмау",
+       "image_tip": "Кергеселгән файл",
+       "media_tip": "Файлға ссылка",
+       "sig_tip": "Ҡул ҡуйыуығыс пелән уаҡыт",
+       "hr_tip": "Горизонталь цыйыҡ (кел ҡулланмаң)",
+       "summary": "Пашҡартыуларны аңнатыу:",
+       "minoredit": "Кецкенә пашҡартыу",
+       "watchthis": "Пы питне көсәткәле",
+       "savearticle": "Питне йасып ҡуйғалы",
+       "preview": "Алттан ҡарау",
+       "showpreview": "Алттан ҡарап цығыу",
+       "showdiff": "Кергеселгән пашҡартыулар",
+       "anoneditwarning": "<strong>Саҡлыҡ!</strong> Сес сайтта теркәлмәтегес. Әгәр тә сес төрлө төсәтеүләр ҡылсағыс, сеснеңке IP-адресығыс пашҡаларға күренеп торор. Әгәр тә Сес <strong>[$1 керсәгез]</strong> йә <strong>[$2 ҡулланыуцы йасманы пултырсағыс]</strong>, сеснең төсәтеүләр ҡулланыуцы йасмағысҡа пәйле пулыр, шалай уҡ пашҡа өстөннөкләр тыуыр.",
+       "blockedtext": "<strong>Сеснең исәп йасмағыс йә IP адресығыс тыйылған.</strong>\n* Тыйған администратор: $1.\nКүргәселгән сәбәп: <em>$2.</em>\n\n* Тыйыу пашланҡан уаҡыт: $8\n* Тыйыуның пөтөү уаҡыты: $6\n* Тыйыуның кәрәге: $7\n\nСес $1 йә пүтән [[{{MediaWiki:Grouppage-sysop}}|администраторға]] тыйыу турлы сурауларығысны йебәрә аласыз.\nОнотмаң: әгәр сес  үсегеснеңке ҡоролошоғоста электрон почта адресығысны пирмәгән пулсағыс ([[Special:Preferences|йә аны төрөс күргәсмәгән пулсағыс]]), администраторға ҡат йебәрә алмайсыс. Шалай уҡ тыйыу уаҡытны сес ҡат йебәрә алмаған пуласыс.\nСеснең IP адрес — $3, тыйыу идентификатор — #$5.\nҠатларта пы ҡәбәр-пелемнәрне күргәскәле онотмаң.",
+       "loginreqlink": "кергәле",
+       "newarticletext": "Сес ссылка пелән әле йасылмаған питкә күцтегес. Йаңа пит ҡылыр өцөн астытағы тәрәсәгә текст йасың (тулыраҡ өцөн [$1 пелешмәлек питне] ҡараң). Әгәр мынта йалғыш кереп киткән пулсағыс, браузерығысныңҡы  \"сыртҡа\" кнопкаға пасың.",
+       "anontalkpagetext": "----\n<em>Пы уйлап ҡарау пит исәп йасыуны ҡылтырмаған йә аны ҡулланмайтығын аноним ҡатнашыуцыныңҡы пите.</em> Ҡулланыуцыны таныу өцөн аныңҡы IP-адресы ҡулланылаты. Әгәр сес аноним ҡулланыуцы пулсағыс, сескә йебәрелмәгән ҡатлар алтым тисәгес (пер IP-адрес кән ҡулланыуцыларта пулғалы мөмкин), пашҡа мынтайын аңнашылмауцылыҡлар килеп цыҡмасын өцөн, системаға керең йә теркәлең.",
+       "noarticletext": "Ҡәсерге уаҡытта пы питтә текст йуҡ. Сес [[Special:Search/{{PAGENAME}}|пы исем кергән пашҡа мәҡәләләрне]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} туры килгән йасмаларны] таба йә '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} шантайын уҡ исемле йаңа питне ҡыла]'''</span> аласыс.",
+       "noarticletext-nopermission": "Ҡәсерге уаҡытта пы питтә текст йуҡ. Сес пашҡа питләртә [[Special:Search/{{PAGENAME}}|пы исемне]] йә <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} журналтағы йасмаларны] эстәй аласыс. Сескә пит йасағалы рөҡсәт пирелмәгән.</span>",
+       "userpage-userdoesnotexist-view": "«$1» исемле исәп йасма йуҡ.",
+       "clearyourcache": "<strong>Искәрмә:</strong> Питне саҡлағаннан суң пашҡартыуларығыс күренер өцөн, браузерығысныңҡы кэшын тасартҡалы кәрәк пулыр.\n* <strong>Firefox / Safari:</strong> <em>Shift</em> төймәгә пасып, йебәрмәйен, <em>Йаңартҡалы</em> төймәгә пасың, йә <em>Ctrl-F5</em> йә <em>Ctrl-R</em> (Mac-та <em>⌘-R</em>) төймәләргә пасың\n* <strong>Google Chrome:</strong> <em>Ctrl-Shift-R </em>төймәгә пасың (Mac-та <em>⌘-Shift-R</em>)\n* <strong>Internet Explorer:</strong> <em>Ctrl </em>төймәгә пасып, йебәрмәйен, <em>Йаңартҡалы</em>төймәгә пасың, йә <em>Ctrl-F5 </em>төймәгә пасың\n* <strong>Opera:</strong> <em>Ҡораллар → Ҡоролошлар</em> (Mac-та <em>Opera → Ҡоролошлар</em>) пүлеккә күцең, суңынан <em>Ҡауапсыслыҡ → Кереүләр тариҡны тасартыу → Рәсемнәр кэшлау</em> менюта кэш тасартыуны сайлаң",
+       "previewnote": "<strong>Онотмаң, пы алттан ҡарап цығыу ғына.</strong>\nПашҡартыуларығыс әле саҡланмаған!",
+       "continue-editing": "Төсәтеүне ҡушлағалы",
+       "editing": "«$1» битне төсәтеү",
+       "creating": "«$1» питне пултырыу",
+       "editingsection": "«$1» питтә төсәтеү",
+       "templatesused": "Пы питтә ҡулланылған {{PLURAL:$1|1=ҡалып|ҡалыплар}}:",
+       "templatesusedpreview": "Алттан ҡарау режимта ҡулланылған {{PLURAL:$1|1=ҡалып|ҡалыплар}}:",
+       "template-protected": "(саҡланҡан)",
+       "template-semiprotected": "(өлөшө пелән саҡланҡан)",
+       "hiddencategories": "Пы пит $1 {{PLURAL:$1|йәшерен төргөнкә|$1 йәшерен төргөннәргә}} керәте:",
+       "permissionserrors": "Кереүнең ҡаҡ ҡатасы",
+       "permissionserrorstext-withaction": "$2 әмәлен ҡыла алмайсыс. {{PLURAL:$1|1=сәбәбе пуйынца|сәбәпләре пуйынца}}:",
+       "recreate-moveddeleted-warn": "Саҡлыҡ: Сес элек йуҡ ителгән питне йаңатан ҡылмаҡцы пуласыс.\n\n* Сескә пы питне йаңатан ҡылыу кәрәклекне йаңатан уйлап ҡараң.\nТүмәнтә питнең йуҡ итеү пелән исемен пашҡартыу журнал пар.",
+       "moveddeleted-notice": "Пы пит уйылған. Пелешмә өцөн астыта пөтөрөү пелән исем алмаштырыу журналларның йасылғаннары күргәселгән.",
+       "content-model-wikitext": "вики-текст",
+       "undo-failure": "Аралыҡ пашҡартыулар туры килмәгәнкә, төсәтеүне кире алып пулмайты.",
+       "viewpagelogs": "Пы питнең журналын ҡарағалы",
+       "currentrev-asof": "$1, ҡәсерге версия",
+       "revisionasof": "$1 версия",
+       "revision-info": "$1 аңышы; {{GENDER:$6|$2}}$7",
+       "previousrevision": "← Алттағы",
+       "nextrevision": "Мынан суңҡысы →",
+       "currentrevisionlink": "Ҡәсерге версия",
+       "cur": "ҡәсерге",
+       "last": "алттағы",
+       "histlegend": "Аңышларны сайлау: сес туры килтергәле теләгән питләрнең аңышларын сайлап алың та  {{int:compare-submit}}.<br /> -ға пасың. Аңнатмалар: ({{int:cur}}) — ҡәсерге аңыштан айырмалар; ({{int:last}}) — алтта пулған аңыштан айырмалар; {{int:minoreditletter}} — сур пулмаған айырмалар.",
+       "history-fieldset-title": "Төсәтеүләрне эстәү",
+       "histfirst": "иң элекеләре",
+       "histlast": "иң суңҡылары",
+       "history-feed-title": "Пашҡартыулар тариғы",
+       "history-feed-description": "Пы питнең викитағы пашҡартыулар тариғы",
+       "history-feed-item-nocomment": "$2-та $1",
+       "rev-delundel": "күргәскәле/йәшергәле",
+       "mergelog": "Перләштереүләр журнал",
+       "history-title": "$1 питенең пашҡартыу тариғы",
+       "difference-title": "$1 — версиялар арасынтағы айырмалар",
+       "lineno": "$1 йул:‎",
+       "compareselectedversions": "Сайланҡан аңышларны туры килтергәле",
+       "editundo": "пулмайын иткәле",
+       "diff-empty": "(айырмалар йуҡ)",
+       "diff-multi-sameuser": "(пы ҡулланыуцының {{PLURAL:$1|аралыҡ версия $1|аралыҡ версиялары $1}} күргәселмәгән)",
+       "diff-multi-otherusers": "({{PLURAL:$2|ҡатнашыуцының|$2 ҡатнашыуцыларның}} {{PLURAL:$1|ара аңышы|$1 арадаш аңышлары}} күргәселмәгән)",
+       "searchresults": "Эстәүнең йомҡаҡлары",
+       "searchresults-title": "«$1»ны эстәгәле",
+       "prevn": "алттағы {{PLURAL:$1|$1}}",
+       "nextn": "киләсе {{PLURAL:$1|$1}}",
+       "prevn-title": "Алттағы $1 {{PLURAL:$1|1=йасма|йасмалар}}",
+       "nextn-title": "{{PLURAL:$1|Киләсе $1 йасма}}",
+       "shown-title": "$1 пер питтә {{PLURAL:$1|йасыу|йасыулар}} күргәскәле",
+       "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) ҡарап паҡҡалы",
+       "searchmenu-exists": "Пы викита [[:$1]] тигән пит пар",
+       "searchmenu-new": "<strong>«[[:$1]]» Вики-проектта пит йасағалы!</strong> {{PLURAL:$2|0=|Шалай уҡ, эстәгән арағыстағы табылған питне караң.|Шалай уҡ, эстәгәнегестә табылған питләрне караң.}}",
+       "searchprofile-articles": "Төп питләр",
+       "searchprofile-images": "Мультимедиа",
+       "searchprofile-everything": "Пөтөн йертә",
+       "searchprofile-advanced": "Киңәйтелгән",
+       "searchprofile-articles-tooltip": "$1 тә эстәү",
+       "searchprofile-images-tooltip": "Файлларны эстәү",
+       "searchprofile-everything-tooltip": "Пөтөн питләртә (уйлашыу питләртә тә) эстәү",
+       "searchprofile-advanced-tooltip": "Пирелгән исемнәр киңнектә эстәгәгәле",
+       "search-result-size": "$1 ({{PLURAL:$2|1=$2 сүс|$2 сүс}})",
+       "search-result-category-size": "$1 {{PLURAL:$1|аса}} ($2 {{PLURAL:$2|астөргөн}}, $3 {{PLURAL:$3|файл}})",
+       "search-redirect": "($1 питтән йусыҡлау)",
+       "search-section": "($1 пүлек)",
+       "search-file-match": "(файлның эцтәлеге пелән туры киләте)",
+       "search-suggest": "Пәйәкпәр, ошоно эстәйсес пулыр: $1",
+       "searchall": "пөтөннәйе",
+       "search-showingresults": "{{PLURAL:$4|<strong>$3</strong> нәтижәтән <strong>$1</strong>| <strong>$3</strong> нәтижәләртән <strong>$1 — $2</strong>}}",
+       "search-nonefound": "Сурауға туры килгән йауаплар табылматы.",
+       "mypreferences": "Ҡороулар",
+       "group-bot": "Ботлар",
+       "group-sysop": "Администраторлар",
+       "grouppage-bot": "{{ns:project}}:Ботлар",
+       "grouppage-sysop": "{{ns:project}}:Администраторлар",
+       "right-writeapi": "Йасыр өцөн API-ны ҡулланыу",
+       "newuserlogpage": "Йаңа ҡулланыуцыларны теркәү журналы",
+       "rightslog": "Ҡатнашыуцының ҡаҡлары журналы",
+       "action-edit": "пы питне төсәткәле",
+       "action-createaccount": "Пы исәп йасманы ҡылыу",
+       "enhancedrc-history": "тариҡ",
+       "recentchanges": "Йаңа төсәтеүләр",
+       "recentchanges-legend": "Суңҡы төсәтеүлернең ҡоролошо",
+       "recentchanges-summary": "Төрлө питләртә эшләнкән суңҡы пашҡартыуларның күцермәлеге",
+       "recentchanges-noresult": "Күрсәтелгән уаҡытта тейешле шартларға туры килгән пашҡартыулар йуҡ.",
+       "recentchanges-feed-description": "Викита суңҡы пашҡартыуларны көсәтеү.",
+       "recentchanges-label-newpage": "Пы төсәтеү йаңа пит ҡылтырты",
+       "recentchanges-label-minor": "Пы сур пулмаған төсәтеү",
+       "recentchanges-label-bot": "Пы төсәтеүне бот ҡылған",
+       "recentchanges-label-unpatrolled": "Пы төсәтеүне әле иц кем тикшермәте",
+       "recentchanges-label-plusminus": "Питнең сурлығы ошо ҡәтәр байтҡа алмашынты",
+       "recentchanges-legend-heading": "<strong>Аңнатма:</strong>",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (шалай уҡ [[Special:NewPages|йаңа питләр күцермәлеген]] ҡараң)",
+       "rcfilters-filterlist-title": "Фильтрлар",
+       "rcfilters-filterlist-noresults": "Иц фильтр табылматы",
+       "rcnotefrom": "Астта <strong>$3, $4</strong> -ға туры килгән {{PLURAL:$5|пашҡартыулар күргәселгән}} (<strong>$1</strong> артыҡ түгел).",
+       "rclistfrom": "$3 $2 алып йаңа пашҡартыуларны күргәскәле",
+       "rcshowhideminor": "кецкенә төсәтеүләр $1",
+       "rcshowhideminor-show": "Күргәскәле",
+       "rcshowhideminor-hide": "Йәшергәле",
+       "rcshowhidebots": "ботларны $1",
+       "rcshowhidebots-show": "Күргәскәле",
+       "rcshowhidebots-hide": "Йәшергәле",
+       "rcshowhideliu": "$1 танылған ҡулланыуцылар",
+       "rcshowhideliu-show": "Күргәскәле",
+       "rcshowhideliu-hide": "Йәшергәле",
+       "rcshowhideanons": "танылмаған ҡулланыуцылар $1",
+       "rcshowhideanons-show": "Күргәскәле",
+       "rcshowhideanons-hide": "Йәшергәле",
+       "rcshowhidepatr": "$1 ҡараған төсәтеүләр",
+       "rcshowhidemine": "үс төсәтеүләр $1",
+       "rcshowhidemine-show": "Күргәскәле",
+       "rcshowhidemine-hide": "Йәшергәле",
+       "rclinks": "Суңҡы $2 көн арата йасалған $1 пашҡартыуларны күргәскәле",
+       "diff": "Төрлө",
+       "hist": "тариҡ",
+       "hide": "Йәшергәле",
+       "show": "Күргәскәле",
+       "minoreditletter": "м",
+       "newpageletter": "Й",
+       "boteditletter": "б",
+       "rc-change-size-new": "Төсәткәннән суңҡы сурлыҡ: $1 {{PLURAL:$1|1=байт|байт}}",
+       "rc-old-title": "\"$1\" итеп пашта ҡылынҡан",
+       "recentchangeslinked": "Пәйләнкән төсәтеүләр",
+       "recentchangeslinked-feed": "Пәйле пулған пашҡартыулар",
+       "recentchangeslinked-toolbox": "Пәйле төсәтеүләр",
+       "recentchangeslinked-title": "\"$1\" өцөн пәйләнкән төсәтеүләр",
+       "recentchangeslinked-summary": "Пы, күрсәтелгән пит йебәрәтеген (йә күрсәтелгән төргөнкә керәтегән), питләртә суңҡы пашҡартыуларның күцермәлеге. [[Special:Watchlist|көсәтеү күцермәлегегескә]] керәтеген питләр '''ҡалын''' итеп күрсәтелгән.",
+       "recentchangeslinked-page": "Питнең төп исеме:",
+       "recentchangeslinked-to": "Кирецә, пы питкә пәйләнкән питләртәге пашҡартыуларны күргәскәле",
+       "upload": "Файлны төйәгәлә",
+       "uploadlogpage": "Төйәүләр журнал",
+       "filedesc": "Ҡысҡа аңнатыу",
+       "license": "Лицензиясы:",
+       "license-header": "Лицензирование",
+       "imgfile": "файл",
+       "listfiles": "Файлларның күцермәлеге",
+       "file-anchor-link": "Файл",
+       "filehist": "Файлның тарығы",
+       "filehist-help": "Үткән уаҡытта файлныңҡы нинтәйен пулғанын күргегес килсә, көнө/уаҡытына пасың.",
+       "filehist-revert": "ҡайтарғалы",
+       "filehist-current": "ҡәсерге",
+       "filehist-datetime": "Көнө/уаҡыты",
+       "filehist-thumb": "Уаҡ рәсем",
+       "filehist-thumbtext": "$1 версиятан пулған сүрәт",
+       "filehist-nothumb": "Миниатюрасы йуҡ",
+       "filehist-user": "Ҡатнашыуцы",
+       "filehist-dimensions": "Сурлығы",
+       "filehist-comment": "Искәрмә",
+       "imagelinks": "Файлны ҡулланыу",
+       "linkstoimage": "Пы файлға {{PLURAL:$1|1=пит|$1 пит}} йебәрәте:",
+       "linkstoimage-more": "Пы файлға $1-на пашҡа та  {{PLURAL:$1|пит}} ссылка ҡылатылар.\nТүмәнтәге күцермәлектә пы файлға $1 {{PLURAL:$1|ссылка}} ғына күргәселгән.\nШалай уҡ тулы күцермәлекне ҡарап пулаты.",
+       "nolinkstoimage": "Пы файлға йебәргән питләр йуҡ.",
+       "linkstoimage-redirect": "$1 (файл йусыҡлау) $2",
+       "sharedupload-desc-here": "Пы файл $1-нан, ул пашҡа проектларта ҡулланыла алаты. Файл турлы [$2 сүрәтләү пите] тулыраҡ пелешмә түмәнтәрәк пирелгән.",
+       "filepage-nofile": "Мынтайын исемле файл йуҡ.",
+       "upload-disallowed-here": "Сес пы файлны йаңатан йастыра алмайсыс.",
+       "randompage": "Уйланмаған мәҡәлә",
+       "statistics": "Статистика",
+       "double-redirect-fixer": "Йаңа йусыҡлауның төсәтеүцесе",
+       "nbytes": "$1 {{PLURAL:$1|байт}}",
+       "nmembers": "$1 {{PLURAL:$1|объект}}",
+       "prefixindex": "Питләрнең исемнәренең пашы пуйынца күстәргец",
+       "listusers": "Ҡатнашыуцыларның күцермәлеге",
+       "newpages": "Йаңа питләр",
+       "move": "Йаңа исем ататҡалы",
+       "pager-newer-n": "{{PLURAL:$1|$1 йаңараҡ}}",
+       "pager-older-n": "$1 {{PLURAL:$1|искерәк}}",
+       "booksources": "Китапларның аҡмалары",
+       "booksources-search-legend": "Китап турлы информация эстәү",
+       "booksources-search": "Эстәгәле",
+       "specialloguserlabel": "Ҡылыуцы:",
+       "speciallogtitlelabel": "Морат (исеме йә {{ns:user}}:ҡулланыуцының исеме):",
+       "log": "Журналлар",
+       "all-logs-page": "Пөтөн ацыҡ журналлар",
+       "alllogstext": "{{SITENAME}} сайтыныңҡы журналларының тула күцермәлеге.\nСес йомҡаҡларны журнал төрөнә, ҡатаншыуцының исеменә ҡарап (сур йә кецкенә ҡәрептән йасылыуына ҡарап ) йә күргәселгән питнең исеменә ҡарап (шулай уҡ сур йә кецкенә ҡәрептән йасылыуына ҡарап) сайлап ала аласыс.",
+       "logempty": "Журналта йарығытайын асмалар йуҡ.",
+       "allpages": "Пөтөн питләр",
+       "allarticles": "Пөтөн питләр",
+       "allpagessubmit": "Ҡылғалы",
+       "allpages-hide-redirects": "Йусыҡлауларны йәшергәле",
+       "categories": "Төргөннәр",
+       "listgrouprights-members": "(ҡатнашыуцыларның күцермәлеге)",
+       "emailuser": "Ҡатнашыуцыға ҡат",
+       "usermessage-editor": "Системалыҡ тапшырыу",
+       "watchlist": "Көсәтеү күцермәлек",
+       "mywatchlist": "Көсәтеү күцермәлек",
+       "watchlistfor2": "$1 $2-ға тип",
+       "watch": "Көсәткәле",
+       "unwatch": "Көсәтмәгәле",
+       "watchlist-details": "Көсәтеү күцермәлегегестә, уйлап ҡарау питләрне исәпләмәйен, {{PLURAL:$1|$1 пит}} пар.",
+       "wlheader-showupdated": "Сеснең суңҡы пашҡартыулартан суң пашҡарған питләр <strong>ҡалын</strong> шрифт пелән күргәселгән.",
+       "wlnote": "Түмәнтә $3 $4 уаҡытта суңҡы {{PLURAL:$2|1=сәғәт|$2 сәғәт}} эцтә эшләнкән {{PLURAL:$1|1=пашҡартыу|$1пашҡартыулар}} күргәселгән.",
+       "wlshowlast": "Суңҡы $1 сәғәт $2 көн эцентәгесен күргәскәле",
+       "watchlist-options": "Көсәтеү күцермәлекнең ҡоролошлары",
+       "enotif_reset": "Пөтөн питләрне ҡаралған тип пилгеләгәле",
+       "dellogpage": "Уйыуларның журналы",
+       "rollbacklink": "кире ҡайтарғалы",
+       "rollbacklinkcount": "$1 {{PLURAL:$1|1=төсәтеүне|төсәтеүне}} кире алғалы",
+       "protectlogpage": "Саҡлау журнал",
+       "protectedarticle": "[[$1]] питен йаҡлаған",
+       "modifiedarticleprotection": "[[$1]]-ныңҡы питенең йаҡлау ҡаты пашҡартылты",
+       "protect-default": "Пөтөн ҡулланыуцыларға ацыҡ",
+       "restriction-edit": "Төсәткәле",
+       "restriction-move": "Йаңа исем ҡушыу",
+       "namespace": "Исемнәрнең киңнеге:",
+       "invert": "Сайланҡанны әйләнтергәле",
+       "tooltip-invert": "Сайланҡан исемнәр арасынта (күрсәтелгән пулса, исемнәрнең пәйле киңнегентә тә) питләртәге пашҡартыуларны йәшерер өцөн пы пилгене ҡороң",
+       "namespace_association": "Пәйле киңнек",
+       "tooltip-namespace_association": "Сайланҡан исемнәр киңнек пелән пәйле пулған уйлап ҡаралған исемнәрнең киңнеген кергесер өцөн пы пилгене ҡороң",
+       "blanknamespace": "(Төп)",
+       "contributions": "{{GENDER:$1|ҡатнашыуцының}} ҡылған эше",
+       "contributions-title": "$1 исемле ҡатнашыуцының ҡылған эше",
+       "mycontris": "Кергескән эшләр",
+       "anoncontribs": "Кергескән эшләр",
+       "contribsub2": "{{GENDER:$3|$1}}-ның ҡылған эше ($2)",
+       "nocontribs": "Күргәселгән шартларға туры килгән пашҡартыулар табылматы.",
+       "uctop": "(ҡәсерге)",
+       "month": "Айтан пашлап (анан алттараҡ та):",
+       "year": "Йылтан пашлап (анан алттараҡ та):",
+       "sp-contributions-newbies": "Йаңа исәп йасмалартан ҡылынҡан эшне генә күргәскәле",
+       "sp-contributions-blocklog": "тыйыулар",
+       "sp-contributions-uploads": "төйәүләр",
+       "sp-contributions-logs": "журналлар",
+       "sp-contributions-talk": "уйлап ҡарау",
+       "sp-contributions-search": "Ҡылынҡан эшне эстәү",
+       "sp-contributions-username": "Ҡатнашыуцының IP-адресы йә исеме:",
+       "sp-contributions-toponly": "Иң суңҡы аңышлар пулған төсәтеүләрне генә күргәскәле",
+       "sp-contributions-newonly": "Йаңа пит ацатығын төсәтеүләрне генә күргәскәле",
+       "sp-contributions-submit": "Тапҡалы",
+       "whatlinkshere": "Мынта ссылкалар",
+       "whatlinkshere-title": "«$1» питкә йебәргән питләр",
+       "whatlinkshere-page": "Пит:",
+       "linkshere": "''[[:$1]]''' питкә киләсе питләр тайанатылар:",
+       "nolinkshere": "[[:$1]] питкә пер питтән тә ссылка йуҡ.",
+       "isredirect": "йусыҡлау пит",
+       "istemplate": "ҡушылыу",
+       "isimage": "файллы ссылка",
+       "whatlinkshere-prev": "{{PLURAL:$1|1=алттағы}} $1",
+       "whatlinkshere-next": "{{PLURAL:$1|1=киләсе}} $1",
+       "whatlinkshere-links": "← ссылкалар",
+       "whatlinkshere-hideredirs": "$1 йусыҡлаулар",
+       "whatlinkshere-hidetrans": "Кергесеүләр $1",
+       "whatlinkshere-hidelinks": "$1 ссылкалар",
+       "whatlinkshere-hideimages": "$1 файлның ссылкалары",
+       "whatlinkshere-filters": "Фильтрлар",
+       "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",
+       "infiniteblock": "пилгеле пер уаҡытсыс",
+       "blocklink": "тыйғалы",
+       "contribslink": "өлөш",
+       "blocklogpage": "Тыйыулар журнал",
+       "blocklogentry": "[[$1]] $2 $3-ҡа пикләте",
+       "reblock-logentry": "[[$1]] ҡатнашыуцының тыйыуын пашҡартҡан, пөтөү уаҡыты — $2 $3",
+       "block-log-flags-nocreate": "исәп йасманың теркелеүе тыйылған",
+       "proxyblocker": "Проксины пикләү",
+       "movelogpage": "Исем алмаштырыуның журналы",
+       "export": "Питләрне цығарыу",
+       "thumbnail-more": "Сурайтҡалы",
+       "importlogpage": "Кергесеү журнал",
+       "tooltip-pt-userpage": "{{GENDER:|Сеснең}} ҡатнашыуцының пите",
+       "tooltip-pt-mytalk": "{{GENDER:|Сеснең}} уйлашыу питегес",
+       "tooltip-pt-preferences": "{{GENDER:|Сеснең}} ҡоролошларығыс",
+       "tooltip-pt-watchlist": "Сес көсәтеп парған питләрнең күцермәлеге",
+       "tooltip-pt-mycontris": "{{GENDER:|Сеснең}} төсәтеүләрнең күцермәлеге",
+       "tooltip-pt-login": "Мынта регистрация үткәле пулаты, әммә ул тейешлецә түгел",
+       "tooltip-pt-logout": "Цыҡҡалы",
+       "tooltip-pt-createaccount": "Тейешлецә пулмаса та, аккаунт ҡылып системаға кергәле киңәш итәбес",
+       "tooltip-ca-talk": "Төп пит турлы уйлап ҡарашыу",
+       "tooltip-ca-edit": "Пы питне төсәткәле",
+       "tooltip-ca-addsection": "Йаңа пүлек ҡылғалы",
+       "tooltip-ca-viewsource": "Пы пит пашҡартыулартан йаҡланҡан, әммә сес аның төп текстын ҡарай аласыс, копиясын ала аласыс",
+       "tooltip-ca-history": "Питне пашҡартыу күцермәлеге",
+       "tooltip-ca-protect": "Питне пашҡартыулартан саҡлағалы",
+       "tooltip-ca-delete": "Пы питне йуҡ иткәле",
+       "tooltip-ca-move": "Питкә йаңа исем пиргәле",
+       "tooltip-ca-watch": "Пы питне сеснең көсәтеү күцермәлеккә өстәгәле",
+       "tooltip-ca-unwatch": "Пы питне көсәтеү күцермәлегемнән алып ташлағалы",
+       "tooltip-search": "{{SITENAME}} эцтә эстәгәле",
+       "tooltip-search-go": "Шалай уҡ аталған питкә күцкәле",
+       "tooltip-search-fulltext": "Ошонтайын эцтәлекле питләрне тапҡалы",
+       "tooltip-p-logo": "Паш питкә күцкәле",
+       "tooltip-n-mainpage": "Паш питкә күцкәле",
+       "tooltip-n-mainpage-description": "Паш питкә күцкәле",
+       "tooltip-n-portal": "Проект турлы, мынта нимә ҡылғалы йарағаны турлы, ҡайта нимә урнашҡан турлы",
+       "tooltip-n-currentevents": "Ҡәсер пулып йатҡан пулған ҡәлләр турлы пелешмә",
+       "tooltip-n-recentchanges": "Суңҡы төсәтеүләрнең күцермәлеге",
+       "tooltip-n-randompage": "Көтөлмәгән питне ҡарап паҡҡалы",
+       "tooltip-n-help": "Пелешмәне алырлыҡ урын",
+       "tooltip-t-whatlinkshere": "Пы питкә тапшырған пөтөн питләрнең күцермәлеге",
+       "tooltip-t-recentchangeslinked": "Пы питтән тапшырылған питләрнең суңҡы пашҡартыулары",
+       "tooltip-feed-atom": "Пы пит өцөн Atom-ҡа трансляция",
+       "tooltip-t-contributions": "{{GENDER:$1|Пы ҡулланыуцы ҡылған}} пашҡартыуларның күцермәлеге",
+       "tooltip-t-emailuser": "{{GENDER:$1|пы ҡулланыуцыға}} ҡат йебәргәле",
+       "tooltip-t-upload": "Файлларны төйәгәле",
+       "tooltip-t-specialpages": "Ҡесмәт питләрнең күцермәлеге",
+       "tooltip-t-print": "Пы питнең пасма аңышы",
+       "tooltip-t-permalink": "Пы питнең версиясына тапшырған келәң ссылка",
+       "tooltip-ca-nstab-main": "Мәҡәләнеңке эцтәлеге",
+       "tooltip-ca-nstab-user": "Ҡулланыуцыныңҡы үсенең пите",
+       "tooltip-ca-nstab-special": "Пы ҡесмәт пит, аны төсәткәле пулмайты",
+       "tooltip-ca-nstab-project": "Проектның пите",
+       "tooltip-ca-nstab-image": "Файлның пите",
+       "tooltip-ca-nstab-mediawiki": "MediaWiki-ның ҡәбәрләре пите",
+       "tooltip-ca-nstab-template": "Ҡалыпның пите",
+       "tooltip-ca-nstab-category": "Категорияның пите",
+       "tooltip-minoredit": "Пы пашҡартыуны пайтаҡ түгел итеп пилгеләгәле",
+       "tooltip-save": "Пашҡартыуларығысны ҡалтырғалы",
+       "tooltip-preview": "Питне алттан ҡарап цығыу; төсәтеүләрегесне ҡалтырыр алттан ҡулланың!",
+       "tooltip-diff": "Текстта эшләнкән үсегеснең пашҡартыуларығысны генә күргәскәле",
+       "tooltip-compareselectedversions": "Пы питнең ике сайланҡан аңышылары арасынтағы айырманы ҡарағалы.",
+       "tooltip-watch": "Пы питне көсәтеү күцермәлегемә өстәгәле",
+       "tooltip-rollback": "Пер пасыу пелән суңҡы пашҡартыуларны алып уйғалы",
+       "tooltip-undo": "Пулмайын итеүнең сәбәбен күргәсә алыу мөмкинцелеге пелән кергеселгән төсәтеүне уйып ташлағалы.",
+       "tooltip-summary": "Ҡысҡаца сүрәтләмә кергесең",
+       "simpleantispam-label": "Спамҡа ҡаршы тикшереү. Мыны <strong>ТУЛТЫРМАҢ</strong>!",
+       "pageinfo-title": "«$1» турлы ҡәбәр-пелем",
+       "pageinfo-header-basic": "Төп ҡәбәр-пелемнәр",
+       "pageinfo-header-edits": "Төсәтеүләр тариғы",
+       "pageinfo-header-restrictions": "Питне саҡлау",
+       "pageinfo-header-properties": "Питнең үслекләре",
+       "pageinfo-display-title": "Күренкән исем",
+       "pageinfo-default-sort": "Тик сайлау ацҡыц",
+       "pageinfo-length": "Питнеңке оссонноғо (байтларта)",
+       "pageinfo-article-id": "Питнең аңныштырғыцы",
+       "pageinfo-language": "Пы питнең теле",
+       "pageinfo-content-model": "Пит эцтәлекнең моделе",
+       "pageinfo-robot-policy": "Эстәйтеген роботлар индексацялағанннар",
+       "pageinfo-robot-index": "Рөҡсәт ителгән",
+       "pageinfo-robot-noindex": "Рөҡсәт ителмәгән",
+       "pageinfo-watchers": "Көсәткәннәрнең исәбе",
+       "pageinfo-few-watchers": "$1 асыраҡ {{PLURAL:$1|көсәтеүце}}",
+       "pageinfo-redirects-name": "Пы питкә йусыҡлауларның исәбе",
+       "pageinfo-subpages-name": "Пы питнең эцке питләре",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|йусыҡлау}}; $3 {{PLURAL:$3|тик}})",
+       "pageinfo-firstuser": "Питне ҡылыуцы",
+       "pageinfo-firsttime": "Пит ҡылыуның датасы",
+       "pageinfo-lastuser": "Суңҡы төсәткер",
+       "pageinfo-lasttime": "Суңҡы төсәтеүнең датасы",
+       "pageinfo-edits": "Пөттөрә төсәтеүләр исәбе",
+       "pageinfo-authors": "Төрлө авторларның тулы исәбе",
+       "pageinfo-recent-edits": "Суңҡы уаҡыттағы төсәтеүләр ($1 уаҡытны)",
+       "pageinfo-recent-authors": "Суңҡы уаҡыттағы авторлар",
+       "pageinfo-magic-words": "{{PLURAL:$1|1=Сиғерле сүс|Сиғерле сүсләр}} ($1)",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|1=Йәшерен категория|Йәшерен категориялар}} ($1)",
+       "pageinfo-templates": "{{PLURAL:$1|1=Ҡалып|Ҡалыплар}} ($1)",
+       "pageinfo-toolboxlink": "\tПит турлы ҡәбәр-пелемнәр",
+       "pageinfo-contentpage": "Эцтәлекле пит тип исәпләнәте",
+       "pageinfo-contentpage-yes": "Әйә",
+       "patrol-log-page": "Тикшереү журнал",
+       "previousdiff": "← Алттағы төсәтеү",
+       "nextdiff": "Мынан суңҡы төсәтеү →",
+       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3 пит}}",
+       "file-info-size": "$1 × $2 {{PLURAL:$2|пиксель}}, файлның сурлығы: $3, MIME төр: $4",
+       "file-info-size-pages": "$1 × $2 пиксель, файл үлцәме: $3, MIME-тибы: $4, $5 {{PLURAL:$5|пит|питләр}}",
+       "file-nohires": "Йуғары ацыҡлыҡлы аңыш йуҡ",
+       "svg-long-desc": "SVG-файл, номиналь $1 × $2 {{PLURAL:$2|пиксель|}}, файлның сурлығы: $3",
+       "show-big-image": "Пашлапҡы файл",
+       "show-big-image-preview": "Алттан ҡарау уаҡыттағы сурлыҡ: $1.",
+       "show-big-image-other": "{{PLURAL:$2|1=Пашҡа сурлыҡ|Пашҡа сурлыҡлар}}: $1.",
+       "show-big-image-size": "$1 × $2 пиксель",
+       "metadata": "Метапиремнәр",
+       "metadata-help": "Пы файлта тик цифралы камера йә сканер пелән өстәлгән мәғлүмәтләр пар. Әгәр пы файл ҡороу уаҡытынан суң алмашынҡан пулса, аныңҡы ҡәйбер параметрлары төрөс пулмасҡа мөмкин.",
+       "metadata-fields": "Пы күцермәлеккә кергән метапиремнәрнең ҡырлары сүрәт питтә күрсәтелер. Ҡалғаннары килешеү пуйынца йәшерелер. * make * model * datetimeoriginal * exposuretime * fnumber * isospeedratings * focallength * artist * copyright * imagedescription * gpslatitude * gpslongitude * gpsaltitude",
+       "exif-orientation": "Кадрныңҡы утысылыуы",
+       "exif-xresolution": "Горизонталь сурлыҡ",
+       "exif-yresolution": "Вертикаль сурлыҡ",
+       "exif-datetime": "Файлны пашҡартыу көнө пелән уаҡыты",
+       "exif-make": "Камераны йасауцы",
+       "exif-model": "Камераның төрө",
+       "exif-software": "Ҡулланҡан программа",
+       "exif-exifversion": "Exif-ныңҡы версиясы",
+       "exif-colorspace": "Төсләрнең киңнеге",
+       "exif-datetimeoriginal": "Цын көнө пелән уаҡыты",
+       "exif-datetimedigitized": "Саннаштырыуның көнө пелән уаҡыты",
+       "exif-orientation-1": "Нормаль",
+       "namespacesall": "парысы",
+       "monthsall": "пөттөрә",
+       "imgmultipagenext": "алттағы пит →",
+       "imgmultigo": "Күцкәле!",
+       "imgmultigoto": "$1 питкә күцкәле",
+       "watchlisttools-clear": "Төсәтеү күцермәлекне тасартҡалы",
+       "watchlisttools-view": "Күцермәлектәге питләртә пашҡарыулар",
+       "watchlisttools-edit": "Күцермәлекне ҡарағалы та төсәткәле",
+       "watchlisttools-raw": "Тик текст итеп төсәтеү",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|уйлап ҡарау]])",
+       "redirect": "Файлның аңныштырғыцынан, ҡатнашыуцытан, питтән, версиятан йә журналтан йусыҡлау",
+       "redirect-summary": "Пы ҡесмәт пит файлға (файлның исеменән), питкә (версияның аңныштырғыцынан йә питенән), ҡатнашыуцының питен (ҡатнашыуцының исәп аңныштырғыцынан) йә ҡатнашыуцының питенә (аңныштырғыцның журналынан) йебәрәте.",
+       "redirect-submit": "Күцкәле",
+       "redirect-lookup": "Эстәү:",
+       "redirect-value": "Мәғнә:",
+       "redirect-user": "Ҡатнашыуцының аңыштырғыцы",
+       "redirect-page": "Питнең аңныштырғыцы",
+       "redirect-revision": "Питнең аңышы",
+       "redirect-file": "Файлның исеме",
+       "specialpages": "Атайы питләр",
+       "tag-filter": "[[Special:Tags|Пилгеләр]] фильтры:",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тамғалар}}]]: $2)",
+       "tags-active-yes": "Әйә",
+       "tags-active-no": "Йуҡ",
+       "tags-hitcount": "$1 {{PLURAL:$1|1=пашҡартыу|пашҡартыулар|пашҡартыуларны}}",
+       "logentry-delete-delete": "$1 $3 питне {{GENDER:$2|пөтөрөп ҡуйты}}",
+       "logentry-delete-restore": "$1 $3 ($4) питен {{GENDER:$2|йаңатан торғосто}}",
+       "logentry-delete-revision": "Ҡатнашыуцы $1 $3: $4 питенең {{PLURAL:$5|$5 версиялары|$5 версияларының|1=версиялар}} күренешен {{GENDER:$2|пашҡартты|пашҡартты}}.",
+       "revdelete-content-hid": "эцтәлеге йәшерелшән",
+       "logentry-move-move": "$1 $3 питнең исемен {{GENDER:$2| алмаштырты}}. Йаңа исеме: $4",
+       "logentry-move-move-noredirect": "$1 йусыҡ ҡалтырмайын $3 питне $4 итеп йаңа исем пирте",
+       "logentry-move-move_redir": "$1 йусыҡлап $3 питне $4 итеп күцерте",
+       "logentry-patrol-patrol-auto": "$1 $3 питнең $4 версиясын автоматиклап {{GENDER:$2|тикшерте}}",
+       "logentry-newusers-create": "{{GENDER:$2|ҡатнашыуцы}} $1 исәп йасманы ҡылты.",
+       "logentry-newusers-autocreate": "Автоматик килеш {{GENDER:$2| ҡатнашыуцының}} $1 исәп йасмасы ҡылынты",
+       "logentry-upload-upload": "$1 $3 {{GENDER:$2|төйәте}}",
+       "logentry-upload-overwrite": "$1 йаңа аңыш{{GENDER:$2||тейәте}} $3",
+       "searchsuggest-search": "{{SITENAME}}та эстәгәле‎",
+       "duration-days": "$1 {{PLURAL:$1|көн}}",
+       "randomrootpage": "Көтөлмәгән тамыр пит"
+}
index a838dbf..d0d8ceb 100644 (file)
        "nosuchusershort": "\"$1\" adında bir kullanıcı bulunmamaktadır. Yazılışı kontrol edin.",
        "nouserspecified": "Bir kullanıcı adı belirtmek zorundasınız.",
        "login-userblocked": "Bu kullanıcı engellenmiş. Giriş yapmaya izin verilmiyor.",
-       "wrongpassword": "Parolayı yanlış girdiniz. Lütfen tekrar deneyiniz.",
+       "wrongpassword": "Hatalı kullanıcı adı ya da parola girildi. Lütfen tekrar deneyin.",
        "wrongpasswordempty": "Boş parola girdiniz. Lütfen tekrar deneyiniz.",
        "passwordtooshort": "Parolalar en az {{PLURAL:$1|1 karakter|$1 karakter}} uzunluğunda olmalı.",
        "passwordtoolong": "Parolalar $1 karakterden uzun olamaz.",
        "grant-editmywatchlist": "İzleme listeni düzenle",
        "grant-editprotected": "Korumalı sayfaları Düzenle",
        "grant-patrol": "Sayfadaki değişiklikleri incele",
+       "grant-sendemail": "Diğer kullanıcılara e-posta gönder",
+       "grant-uploadeditmovefile": "Dosya yükle, yenisiyle değiştir ve taşı",
        "grant-uploadfile": "Dosya yükle",
        "grant-basic": "Basit haklar",
        "grant-viewdeleted": "Silinen dosya ve sayfaları görüntüle",
        "grant-viewmywatchlist": "İzleme listeni gör",
+       "grant-viewrestrictedlogs": "Kısıtlanmış günlük girdilerini görüntüle",
        "newuserlogpage": "Kullanıcı oluşturma günlüğü",
        "newuserlogpagetext": "Bu bir kullanıcı oluşturma günlüğüdür.",
        "rightslog": "Kullanıcı hakları günlüğü",
        "rightslogtext": "Bu, kullanıcı hakları değişiklikleri için bir günlüktür.",
        "action-read": "bu sayfayı okumaya",
        "action-edit": "bu sayfayı değiştirmeye",
-       "action-createpage": "sayfa oluşturmaya",
-       "action-createtalk": "tartışma sayfası oluşturmaya",
+       "action-createpage": "sayfayı oluştur",
+       "action-createtalk": "tartışma sayfasını oluştur",
        "action-createaccount": "bu kullanıcı hesabını oluşturmaya",
+       "action-autocreateaccount": "bu harici kullanıcı hesabını otomatik olarak oluştur",
        "action-history": "sayfa geçmişini görüntüle",
        "action-minoredit": "bu değişikliği küçük olarak işaretlemeye",
        "action-move": "bu sayfayı taşımaya",
        "action-upload_by_url": "bir URL adresinden bu dosyayı yüklemeye",
        "action-writeapi": "API yaz kullanmaya",
        "action-delete": "bu sayfayı silmeye",
-       "action-deleterevision": "bu revizyonu silmeye",
-       "action-deletedhistory": "bu sayfanın silinme geçmişini görmeye",
+       "action-deleterevision": "revizyonları sil",
+       "action-deletelogentry": "günlük girdilerini sil",
+       "action-deletedhistory": "sayfanın silme geçmişini görüntüle",
+       "action-deletedtext": "silinmiş revizyon metnini görüntüle",
        "action-browsearchive": "silinen sayfaları aramaya",
        "action-undelete": "sayfaları geri getir",
        "action-suppressrevision": "gizli sürümleri gözden geçir ve geri getir",
        "rcfilters-activefilters": "Etkin süzgeçler",
        "rcfilters-advancedfilters": "Gelişmiş süzgeçler",
        "rcfilters-limit-title": "Gösterilecek sonuçlar",
+       "rcfilters-limit-and-date-label": "$1 değişiklik, $2",
        "rcfilters-days-title": "Son günler",
        "rcfilters-hours-title": "Son saatler",
+       "rcfilters-days-show-days": "$1 gün",
+       "rcfilters-days-show-hours": "$1 saat",
        "rcfilters-quickfilters": "Kaydedilmiş süzgeçler",
        "rcfilters-quickfilters-placeholder-title": "Henüz hiçbir süzgeç kaydedilmedi",
        "rcfilters-quickfilters-placeholder-description": "Süzgeç ayarlarınızı kaydetmek ve sonrasında bunları kullanmak için, aşağıda Aktif Süzgeçler alanındaki yer imi simgesine tıklayın.",
        "rcfilters-filtergroup-userExpLevel": "Deneyim düzeyi (yalnızca kayıtlı kullanıcılar için)",
        "rcfilters-filter-user-experience-level-registered-label": "Kayıtlı",
        "rcfilters-filter-user-experience-level-registered-description": "Oturum açmış editörler.",
-       "rcfilters-filter-user-experience-level-unregistered-label": "Kayıtsız",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Oturum açmamış editörler.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Kayıtlı olmayan",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Oturum açmamış kullanıcılar.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Yeni gelenler",
        "rcfilters-filter-user-experience-level-newcomer-description": "10'dan az düzenlemesi veya 4 günden az etkinliği olan kayıtlı kullanıcılar.",
        "rcfilters-filter-user-experience-level-learner-label": "Öğreniciler",
        "rcfilters-filter-minor-description": "Yazarın küçük olarak etiketlediği düzenlemeler.",
        "rcfilters-filter-major-label": "Küçük olmayan düzenlemeler",
        "rcfilters-filter-major-description": "Küçük olarak etiketlenmemiş düzenlemeler.",
+       "rcfilters-filtergroup-watchlistactivity": "İzleme listesi faaliyetleri",
+       "rcfilters-filter-watchlistactivity-unseen-label": "Görülmemiş değişiklikler",
+       "rcfilters-filter-watchlistactivity-seen-label": "Görülmüş değişiklikler",
        "rcfilters-filtergroup-changetype": "Değişiklik türü",
        "rcfilters-filter-pageedits-label": "Sayfa düzenlemeleri",
        "rcfilters-filter-pageedits-description": "Viki içeriği, tartışmalar, kategori açıklamalarındaki düzenlemeler...",
index f1e7dbb..e1eef07 100644 (file)
@@ -100,7 +100,7 @@ abstract class Benchmarker extends Maintenance {
                                'name' => $name,
                                'count' => $stat->getCount(),
                                // Get rate per second from mean (in ms)
-                               'rate' => 1.0 / ( $stat->getMean() / 1000.0 ),
+                               'rate' => $stat->getMean() == 0 ? INF : ( 1.0 / ( $stat->getMean() / 1000.0 ) ),
                                'total' => $stat->getMean() * $stat->getCount(),
                                'mean' => $stat->getMean(),
                                'max' => $stat->max,
index 799af4c..d1e6496 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/bash -eu
 
-# This script generates a commit that updates our copy of OOjs UI
+# This script generates a commit that updates our copy of OOUI
 
 if [ -n "${2:-}" ]
 then
@@ -20,7 +20,7 @@ git checkout composer.json
 git reset -- $TARGET_DIR
 git checkout -- $TARGET_DIR
 git fetch origin
-git checkout -B upstream-oojs-ui origin/master
+git checkout -B upstream-ooui origin/master
 
 # Fetch upstream version
 cd $NPM_DIR
@@ -31,10 +31,10 @@ else
        npm install oojs-ui
 fi
 
-OOJSUI_VERSION=$(node -e 'console.log(require("./node_modules/oojs-ui/package.json").version);')
-if [ "$OOJSUI_VERSION" == "" ]
+OOUI_VERSION=$(node -e 'console.log(require("./node_modules/oojs-ui/package.json").version);')
+if [ "$OOUI_VERSION" == "" ]
 then
-       echo 'Could not find OOjs UI version'
+       echo 'Could not find OOUI version'
        exit 1
 fi
 
@@ -68,15 +68,15 @@ rm -rf "$NPM_DIR"
 cd $REPO_DIR
 
 COMMITMSG=$(cat <<END
-Update OOjs UI to v$OOJSUI_VERSION
+Update OOUI to v$OOUI_VERSION
 
 Release notes:
- https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v$OOJSUI_VERSION
+ https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v$OOUI_VERSION
 END
 )
 
 # Update composer.json as well
-composer require oojs/oojs-ui $OOJSUI_VERSION --no-update
+composer require oojs/oojs-ui $OOUI_VERSION --no-update
 
 # Stage deletion, modification and creation of files. Then commit.
 git add --update $TARGET_DIR
index 1c9294c..afa7799 100644 (file)
@@ -3,7 +3,7 @@
                "Á", "á", "À", "à", "Â", "â", "Ä", "ä", "Ã", "ã", "Ǎ", "ǎ", "Ā", "ā", "Ă", "ă", "Ą", "ą", "Å", "å", "Ć", "ć", "Ĉ", "ĉ", "Ç", "ç", "Č", "č", "Ċ", "ċ", "Đ", "đ", "Ď", "ď", "É", "é", "È", "è", "Ê", "ê", "Ë", "ë", "Ě", "ě", "Ē", "ē", "Ĕ", "ĕ", "Ė", "ė", "Ę", "ę", "Ĝ", "ĝ", "Ģ", "ģ", "Ğ", "ğ", "Ġ", "ġ", "Ĥ", "ĥ", "Ħ", "ħ", "Í", "í", "Ì", "ì", "Î", "î", "Ï", "ï", "Ĩ", "ĩ", "Ǐ", "ǐ", "Ī", "ī", "Ĭ", "ĭ", "İ", "ı", "Į", "į", "Ĵ", "ĵ", "Ķ", "ķ", "Ĺ", "ĺ", "Ļ", "ļ", "Ľ", "ľ", "Ł", "ł", "Ń", "ń", "Ñ", "ñ", "Ņ", "ņ", "Ň", "ň", "Ó", "ó", "Ò", "ò", "Ô", "ô", "Ö", "ö", "Õ", "õ", "Ǒ", "ǒ", "Ō", "ō", "Ŏ", "ŏ", "Ǫ", "ǫ", "Ő", "ő", "Ŕ", "ŕ", "Ŗ", "ŗ", "Ř", "ř", "Ś", "ś", "Ŝ", "ŝ", "Ş", "ş", "Š", "š", "Ș", "ș", "Ț", "ț", "Ť", "ť", "Ú", "ú", "Ù", "ù", "Û", "û", "Ü", "ü", "Ũ", "ũ", "Ů", "ů", "Ǔ", "ǔ", "Ū", "ū", "ǖ", "ǘ", "ǚ", "ǜ", "Ŭ", "ŭ", "Ų", "ų", "Ű", "ű", "Ŵ", "ŵ", "Ý", "ý", "Ŷ", "ŷ", "Ÿ", "ÿ", "Ȳ", "ȳ", "Ź", "ź", "Ž", "ž", "Ż", "ż", "Æ", "æ", "Ǣ", "ǣ", "Ø", "ø", "Œ", "œ", "ß", "Ð", "ð", "Þ", "þ", "Ə", "ə"
        ],
        "latinextended": [
-               "Ḁ", "ḁ", "ẚ", "Ạ", "ạ", "Ả", "ả", "Ấ", "ấ", "Ầ", "ầ", "Ẩ", "ẩ", "Ẫ", "ẫ", "Ậ", "ậ", "Ắ", "ắ", "Ằ", "ằ", "Ẳ", "ẳ", "Ẵ", "ẵ", "Ặ", "ặ", "Ḃ", "ḃ", "Ḅ", "ḅ", "Ḇ", "ḇ", "Ḉ", "ḉ", "Ḋ", "ḋ", "Ḍ", "ḍ", "Ḏ", "ḏ", "Ḑ", "ḑ", "Ḓ", "ḓ", "Ḕ", "ḕ", "Ḗ", "ḗ", "Ḙ", "ḙ", "Ḛ", "ḛ", "Ḝ", "ḝ", "Ẹ", "ẹ", "Ẻ", "ẻ", "Ẽ", "ẽ", "Ế", "ế", "Ề", "ề", "Ể", "ể", "Ễ", "ễ", "Ệ", "ệ", "Ḟ", "ḟ", "Ḡ", "ḡ", "Ḣ", "ḣ", "Ḥ", "ḥ", "Ḧ", "ḧ", "Ḩ", "ḩ", "Ḫ", "ḫ", "ẖ", "Ḭ", "ḭ", "Ḯ", "ḯ", "Ỉ", "ỉ", "Ị", "ị", "Ḱ", "ḱ", "Ḳ", "ḳ", "Ḵ", "ḵ", "Ḷ", "ḷ", "Ḹ", "ḹ", "Ḻ", "ḻ", "Ḽ", "ḽ", "Ỻ", "ỻ", "Ḿ", "ḿ", "Ṁ", "ṁ", "Ṃ", "ṃ", "Ṅ", "ṅ", "Ṇ", "ṇ", "Ṉ", "ṉ", "Ṋ", "ṋ", "Ṍ", "ṍ", "Ṏ", "ṏ", "Ṑ", "ṑ", "Ṓ", "ṓ", "Ọ", "ọ", "Ỏ", "ỏ", "Ố", "ố", "Ồ", "ồ", "Ổ", "ổ", "Ỗ", "ỗ", "Ộ", "ộ", "Ớ", "ớ", "Ờ", "ờ", "Ở", "ở", "Ỡ", "ỡ", "Ợ", "ợ", "Ǿ", "ǿ", "Ơ", "ơ", "Ṕ", "ṕ", "Ṗ", "ṗ", "Ṙ", "ṙ", "Ṛ", "ṛ", "Ṝ", "ṝ", "Ṟ", "ṟ", "Ṡ", "ṡ", "ẛ", "Ṣ", "ṣ", "Ṥ", "ṥ", "Ṧ", "ṧ", "Ṩ", "ṩ", "ẜ", "ẝ", "Ṫ", "ṫ", "Ṭ", "ṭ", "Ṯ", "ṯ", "Ṱ", "ṱ", "ẗ", "Ṳ", "ṳ", "Ṵ", "ṵ", "Ṷ", "ṷ", "Ṹ", "ṹ", "Ṻ", "ṻ", "Ụ", "ụ", "Ủ", "ủ", "Ứ", "ứ", "Ừ", "ừ", "Ử", "ử", "Ữ", "ữ", "Ự", "ự", "Ư", "ư", "Ǖ", "Ǘ", "Ǚ", "Ǜ", "Ṽ", "ṽ", "Ṿ", "ṿ", "Ỽ", "ỽ", "Ẁ", "ẁ", "Ẃ", "ẃ", "Ẅ", "ẅ", "Ẇ", "ẇ", "Ẉ", "ẉ", "ẘ", "Ẋ", "ẋ", "Ẍ", "ẍ", "Ẏ", "ẏ", "ẙ", "Ỳ", "ỳ", "Ỵ", "ỵ", "Ỷ", "ỷ", "Ỹ", "ỹ", "Ỿ", "ỿ", "Ẑ", "ẑ", "Ẓ", "ẓ", "Ẕ", "ẕ", "Ǽ", "ǽ", "ẞ", "ẟ"
+               "Ḁ", "ḁ", "ẚ", "Ạ", "ạ", "Ả", "ả", "Ấ", "ấ", "Ầ", "ầ", "Ẩ", "ẩ", "Ẫ", "ẫ", "Ậ", "ậ", "Ắ", "ắ", "Ằ", "ằ", "Ẳ", "ẳ", "Ẵ", "ẵ", "Ặ", "ặ", "Ḃ", "ḃ", "Ḅ", "ḅ", "Ḇ", "ḇ", "Ḉ", "ḉ", "Ḋ", "ḋ", "Ḍ", "ḍ", "Ḏ", "ḏ", "Ḑ", "ḑ", "Ḓ", "ḓ", "Ḕ", "ḕ", "Ḗ", "ḗ", "Ḙ", "ḙ", "Ḛ", "ḛ", "Ḝ", "ḝ", "Ẹ", "ẹ", "Ẻ", "ẻ", "Ẽ", "ẽ", "Ế", "ế", "Ề", "ề", "Ể", "ể", "Ễ", "ễ", "Ệ", "ệ", "Ḟ", "ḟ", "Ḡ", "ḡ", "Ḣ", "ḣ", "Ḥ", "ḥ", "Ḧ", "ḧ", "Ḩ", "ḩ", "Ḫ", "ḫ", "ẖ", "Ḭ", "ḭ", "Ḯ", "ḯ", "Ỉ", "ỉ", "Ị", "ị", "Ḱ", "ḱ", "Ḳ", "ḳ", "Ḵ", "ḵ", "ĸ", "Ḷ", "ḷ", "Ḹ", "ḹ", "Ḻ", "ḻ", "Ḽ", "ḽ", "Ỻ", "ỻ", "Ḿ", "ḿ", "Ṁ", "ṁ", "Ṃ", "ṃ", "Ṅ", "ṅ", "Ṇ", "ṇ", "Ṉ", "ṉ", "Ṋ", "ṋ", "Ṍ", "ṍ", "Ṏ", "ṏ", "Ṑ", "ṑ", "Ṓ", "ṓ", "Ọ", "ọ", "Ỏ", "ỏ", "Ố", "ố", "Ồ", "ồ", "Ổ", "ổ", "Ỗ", "ỗ", "Ộ", "ộ", "Ớ", "ớ", "Ờ", "ờ", "Ở", "ở", "Ỡ", "ỡ", "Ợ", "ợ", "Ǿ", "ǿ", "Ơ", "ơ", "Ṕ", "ṕ", "Ṗ", "ṗ", "Ṙ", "ṙ", "Ṛ", "ṛ", "Ṝ", "ṝ", "Ṟ", "ṟ", "Ṡ", "ṡ", "ẛ", "Ṣ", "ṣ", "Ṥ", "ṥ", "Ṧ", "ṧ", "Ṩ", "ṩ", "ẜ", "ẝ", "Ṫ", "ṫ", "Ṭ", "ṭ", "Ṯ", "ṯ", "Ṱ", "ṱ", "ẗ", "Ṳ", "ṳ", "Ṵ", "ṵ", "Ṷ", "ṷ", "Ṹ", "ṹ", "Ṻ", "ṻ", "Ụ", "ụ", "Ủ", "ủ", "Ứ", "ứ", "Ừ", "ừ", "Ử", "ử", "Ữ", "ữ", "Ự", "ự", "Ư", "ư", "Ǖ", "Ǘ", "Ǚ", "Ǜ", "Ṽ", "ṽ", "Ṿ", "ṿ", "Ỽ", "ỽ", "Ẁ", "ẁ", "Ẃ", "ẃ", "Ẅ", "ẅ", "Ẇ", "ẇ", "Ẉ", "ẉ", "ẘ", "Ẋ", "ẋ", "Ẍ", "ẍ", "Ẏ", "ẏ", "ẙ", "Ỳ", "ỳ", "Ỵ", "ỵ", "Ỷ", "ỷ", "Ỹ", "ỹ", "Ỿ", "ỿ", "Ẑ", "ẑ", "Ẓ", "ẓ", "Ẕ", "ẕ", "Ǽ", "ǽ", "ẞ", "ẟ"
        ],
        "ipa": [
                "p", "t̪", "t", "ʈ", "c", "k", "q", "ʡ", "ʔ", "b", "d̪", "d", "ɖ", "ɟ", "ɡ", "ɢ", "ɓ", "ɗ", "ʄ", "ɠ", "ʛ", "t͡s", "t͡ʃ", "t͡ɕ", "d͡z", "d͡ʒ", "d͡ʑ", "ɸ", "f", "θ", "s", "ʃ", "ʅ", "ʆ", "ʂ", "ɕ", "ç", "ɧ", "x", "χ", "ħ", "ʜ", "h", "β", "v", "ʍ", "ð", "z", "ʒ", "ʓ", "ʐ", "ʑ", "ʝ", "ɣ", "ʁ", "ʕ", "ʖ", "ʢ", "ɦ", "ɬ", "ɮ", "m", "m̩", "ɱ", "ɱ̩", "ɱ̍", "n̪", "n̪̍", "n", "n̩", "ɳ", "ɳ̩", "ɲ", "ɲ̩", "ŋ", "ŋ̍", "ŋ̩", "ɴ", "ɴ̩", "ʙ", "ʙ̩", "r", "r̩", "ʀ", "ʀ̩", "ɾ", "ɽ", "ɿ", "ɺ", "l̪", "l̪̩", "l", "l̩", "ɫ", "ɫ̩", "ɭ", "ɭ̩", "ʎ", "ʎ̩", "ʟ", "ʟ̩", "w", "ɥ", "ʋ", "ɹ", "ɻ", "j", "ɰ", "ʘ", "ǂ", "ǀ", "!", "ǁ", "ʰ", "ʱ", "ʷ", "ʸ", "ʲ", "ʳ", "ⁿ", "ˡ", "ʴ", "ʵ", "ˢ", "ˣ", "ˠ", "ʶ", "ˤ", "ˁ", "ˀ", "ʼ", "i", "i̯", "ĩ", "y", "y̯", "ỹ", "ɪ", "ɪ̯", "ɪ̃", "ʏ", "ʏ̯", "ʏ̃", "ɨ", "ɨ̯", "ɨ̃", "ʉ", "ʉ̯", "ʉ̃", "ɯ", "ɯ̯", "ɯ̃", "u", "u̯", "ũ", "ʊ", "ʊ̯", "ʊ̃", "e", "e̯", "ẽ", "ø", "ø̯", "ø̃", "ɘ", "ɘ̯", "ɘ̃", "ɵ", "ɵ̯", "ɵ̃", "ɤ", "ɤ̯", "ɤ̃", "o", "o̯", "õ", "ɛ", "ɛ̯", "ɛ̃", "œ", "œ̯", "œ̃", "ɜ", "ɜ̯", "ɜ̃", "ə", "ə̯", "ə̃", "ɞ", "ɞ̯", "ɞ̃", "ʌ", "ʌ̯", "ʌ̃", "ɔ", "ɔ̯", "ɔ̃", "æ", "æ̯", "æ̃", "ɶ", "ɶ̯", "ɶ̃", "a", "a̯", "ã", "ɐ", "ɐ̯", "ɐ̃", "ɑ", "ɑ̯", "ɑ̃", "ɒ", "ɒ̯", "ɒ̃", "ˈ", "ˌ", "ː", "ˑ", "˘", ".", "‿", "|", "‖", "ɚ", "ɝ"
index db439e3..413d45b 100644 (file)
                display: none;
        }
 
+       #jump-to-nav {
+               margin-top: -0.5em;
+               margin-bottom: 0.5em;
+       }
+
        // Make the watchlist-details message display while loading, but make it not take up any
        // space. This makes the min-height trick work better.
        .watchlistDetails {
index cf77a96..fe013bc 100644 (file)
@@ -8,7 +8,7 @@
        font-weight: bold;
 }
 
-/* Login Button, following `ButtonWidget (progressive)‎` from OOjs UI */
+/* Login Button, following 'ButtonWidget (progressive)' from OOUI */
 #mw-createaccount-join {
        background-color: #f8f9fa;
        color: #36c;
index 9233eef..05180fd 100644 (file)
@@ -3,7 +3,7 @@
        /**
         * DateTimeInputWidgets can be used to input a date, a time, or a date and
         * time, in either UTC or the user's local timezone.
-        * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
+        * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples.
         *
         * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
         *
@@ -12,7 +12,7 @@
         *     var dateTimeInput = new mw.widgets.datetime.DateTimeInputWidget( {} )
         *     $( 'body' ).append( dateTimeInput.$element );
         *
-        * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+        * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
         *
         * @class
         * @extends OO.ui.InputWidget
index fa45d5a..a9c2dd2 100644 (file)
@@ -1,6 +1,6 @@
 /*!
- * OOJS-UI defines used by the existing CSS (will make it easier to put this
- * widget in OOJS-UI once OOJS-UI is capable of handling it)
+ * OOUI defines used by the existing CSS (will make it easier to put this
+ * widget in OOUI once OOUI is capable of handling it)
  */
 
 .oo-ui-box-sizing( @type: border-box ) {
index f2e0f4d..c39e43a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * HTMLForm enhancements:
- * Infuse some OOjs UI HTMLForm fields (those which benefit from always being infused).
+ * Infuse some OOUI HTMLForm fields (those which benefit from always being infused).
  */
 ( function ( mw, $ ) {
 
index 4f672fc..01108e6 100644 (file)
@@ -3,7 +3,7 @@
        mw.htmlform = {};
 
        /**
-        * Allows custom data specific to HTMLFormField to be set for OOjs UI forms. This picks up the
+        * Allows custom data specific to HTMLFormField to be set for OOUI forms. This picks up the
         * extra config from a matching PHP widget (defined in HTMLFormElement.php) when constructed using
         * OO.ui.infuse().
         *
index 148c4c7..b98ba13 100644 (file)
@@ -1,4 +1,4 @@
-/* HACK: Set sane font-size for OOjs UI dialogs (and menus/popups inside the default overlay), in
+/* HACK: Set sane font-size for OOUI dialogs (and menus/popups inside the default overlay), in
    the most common case. This should be skin's responsibility, but alas our skins tend to have the
    weirdest font-sizes on body. This shall be removed when we make the MediaWiki skins bundled with
    tarball sane. (T91152) */
index dffa863..0c65512 100644 (file)
@@ -1,9 +1,9 @@
 ( function ( mw ) {
        var isMobile;
-       // Connect OOjs UI to MediaWiki's localisation system
+       // Connect OOUI to MediaWiki's localisation system
        OO.ui.getUserLanguages = mw.language.getFallbackLanguageChain;
        OO.ui.msg = mw.msg;
-       // Connect OOjs UI's deprecation warnings to MediaWiki's logging system
+       // Connect OOUI's deprecation warnings to MediaWiki's logging system
        OO.ui.warnDeprecation = function ( message ) {
                mw.track( 'mw.deprecate', 'oojs-ui' );
                mw.log.warn( message );
index 3b02e28..cc769d7 100644 (file)
@@ -195,6 +195,11 @@ abstract class MWHttpRequestTestCase extends PHPUnit_Framework_TestCase {
                $this->assertSame( 401, $request->getStatus() );
        }
 
+       public function testFactoryDefaults() {
+               $request = MWHttpRequest::factory( 'http://acme.test' );
+               $this->assertInstanceOf( MWHttpRequest::class, $request );
+       }
+
        // --------------------
 
        /**
@@ -242,4 +247,5 @@ abstract class MWHttpRequestTestCase extends PHPUnit_Framework_TestCase {
                $this->assertArrayNotHasKey( strtolower( $name ),
                        array_change_key_case( $cookieJar->cookie, CASE_LOWER ) );
        }
+
 }
index 9b5897c..4dd4bc6 100644 (file)
@@ -811,10 +811,6 @@ class ParserTestRunner {
                $options = ParserOptions::newFromContext( $context );
                $options->setTimestamp( $this->getFakeTimestamp() );
 
-               if ( !isset( $opts['wrap'] ) ) {
-                       $options->setWrapOutputClass( false );
-               }
-
                if ( isset( $opts['tidy'] ) ) {
                        if ( !$this->tidySupport->isEnabled() ) {
                                $this->recorder->skipped( $test, 'tidy extension is not installed' );
@@ -854,7 +850,8 @@ class ParserTestRunner {
                } else {
                        $output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
                        $out = $output->getText( [
-                               'allowTOC' => !isset( $opts['notoc'] )
+                               'allowTOC' => !isset( $opts['notoc'] ),
+                               'unwrap' => !isset( $opts['wrap'] ),
                        ] );
                        if ( isset( $opts['tidy'] ) ) {
                                $out = preg_replace( '/\s+$/', '', $out );
index aaa135d..75ebd31 100644 (file)
@@ -26,7 +26,6 @@ class ExtraParserTest extends MediaWikiTestCase {
                // FIXME: This test should pass without setting global content language
                $this->options = ParserOptions::newFromUserAndLang( new User, $contLang );
                $this->options->setTemplateCallback( [ __CLASS__, 'statelessFetchTemplate' ] );
-               $this->options->setWrapOutputClass( false );
                $this->parser = new Parser;
 
                MagicWord::clearCache();
@@ -41,9 +40,8 @@ class ExtraParserTest extends MediaWikiTestCase {
 
                $title = Title::newFromText( 'Unit test' );
                $options = ParserOptions::newFromUser( new User() );
-               $options->setWrapOutputClass( false );
                $this->assertEquals( "<p>$longLine</p>",
-                       $this->parser->parse( $longLine, $title, $options )->getText() );
+                       $this->parser->parse( $longLine, $title, $options )->getText( [ 'unwrap' => true ] ) );
        }
 
        /**
@@ -55,7 +53,7 @@ class ExtraParserTest extends MediaWikiTestCase {
                $parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options );
                $this->assertEquals(
                        "<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>",
-                       $parserOutput->getText()
+                       $parserOutput->getText( [ 'unwrap' => true ] )
                );
        }
 
index 088ab4f..6977aef 100644 (file)
@@ -29,8 +29,54 @@ class MWCallableUpdateTest extends PHPUnit_Framework_TestCase {
                // Emulate rollback
                $db->rollback( __METHOD__ );
 
+               $update->doUpdate();
+
+               // Ensure it was cancelled
+               $this->assertSame( 0, $ran );
+       }
+
+       public function testCancelSome() {
+               // Prepare update and DB
+               $db1 = new DatabaseTestHelper( __METHOD__ );
+               $db1->begin( __METHOD__ );
+               $db2 = new DatabaseTestHelper( __METHOD__ );
+               $db2->begin( __METHOD__ );
+               $ran = 0;
+               $update = new MWCallableUpdate( function () use ( &$ran ) {
+                       $ran++;
+               }, __METHOD__, [ $db1, $db2 ] );
+
+               // Emulate rollback
+               $db1->rollback( __METHOD__ );
+
+               $update->doUpdate();
+
+               // Prevents: "Notice: DB transaction writes or callbacks still pending"
+               $db2->rollback( __METHOD__ );
+
                // Ensure it was cancelled
+               $this->assertSame( 0, $ran );
+       }
+
+       public function testCancelAll() {
+               // Prepare update and DB
+               $db1 = new DatabaseTestHelper( __METHOD__ );
+               $db1->begin( __METHOD__ );
+               $db2 = new DatabaseTestHelper( __METHOD__ );
+               $db2->begin( __METHOD__ );
+               $ran = 0;
+               $update = new MWCallableUpdate( function () use ( &$ran ) {
+                       $ran++;
+               }, __METHOD__, [ $db1, $db2 ] );
+
+               // Emulate rollbacks
+               $db1->rollback( __METHOD__ );
+               $db2->rollback( __METHOD__ );
+
                $update->doUpdate();
+
+               // Ensure it was cancelled
                $this->assertSame( 0, $ran );
        }
+
 }
index a2c3bdc..ca78f65 100644 (file)
@@ -1481,7 +1481,10 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
                $wanCache = new WANObjectCache( [
                        'cache' => $localBag,
                        'pool' => 'testcache-hash',
-                       'relayer' => new EventRelayerNull( [] )
+                       'relayer' => new EventRelayerNull( [] ),
+                       'mcrouterAware' => true,
+                       'region' => 'pmtpa',
+                       'cluster' => 'mw-wan'
                ] );
                $valFunc = function () {
                        return 1;
@@ -1498,6 +1501,60 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
                $wanCache->reap( 'zzz', time() - 300 );
        }
 
+       public function testMcRouterSupportBroadcastDelete() {
+               $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
+                       ->setMethods( [ 'set' ] )->getMock();
+               $wanCache = new WANObjectCache( [
+                       'cache' => $localBag,
+                       'pool' => 'testcache-hash',
+                       'relayer' => new EventRelayerNull( [] ),
+                       'mcrouterAware' => true,
+                       'region' => 'pmtpa',
+                       'cluster' => 'mw-wan'
+               ] );
+
+               $localBag->expects( $this->once() )->method( 'set' )
+                       ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" );
+
+               $wanCache->delete( 'test' );
+       }
+
+       public function testMcRouterSupportBroadcastTouchCK() {
+               $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
+                       ->setMethods( [ 'set' ] )->getMock();
+               $wanCache = new WANObjectCache( [
+                       'cache' => $localBag,
+                       'pool' => 'testcache-hash',
+                       'relayer' => new EventRelayerNull( [] ),
+                       'mcrouterAware' => true,
+                       'region' => 'pmtpa',
+                       'cluster' => 'mw-wan'
+               ] );
+
+               $localBag->expects( $this->once() )->method( 'set' )
+                       ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
+
+               $wanCache->touchCheckKey( 'test' );
+       }
+
+       public function testMcRouterSupportBroadcastResetCK() {
+               $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
+                       ->setMethods( [ 'delete' ] )->getMock();
+               $wanCache = new WANObjectCache( [
+                       'cache' => $localBag,
+                       'pool' => 'testcache-hash',
+                       'relayer' => new EventRelayerNull( [] ),
+                       'mcrouterAware' => true,
+                       'region' => 'pmtpa',
+                       'cluster' => 'mw-wan'
+               ] );
+
+               $localBag->expects( $this->once() )->method( 'delete' )
+                       ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
+
+               $wanCache->resetCheckKey( 'test' );
+       }
+
        /**
         * @dataProvider provideAdaptiveTTL
         * @covers WANObjectCache::adaptiveTTL()
index 54706d5..25613fe 100644 (file)
@@ -12,7 +12,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
 
        public function testAffected() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
-               $logger->expects( $this->exactly( 3 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 3 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -27,7 +27,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
        public function testReadTime() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
                // 1 per query
-               $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -42,7 +42,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
        public function testWriteTime() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
                // 1 per query, 1 per trx, and one "sub-optimal trx" entry
-               $logger->expects( $this->exactly( 4 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 4 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -56,7 +56,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
 
        public function testAffectedTrx() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
-               $logger->expects( $this->exactly( 1 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 1 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -69,7 +69,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
        public function testWriteTimeTrx() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
                // 1 per trx, and one "sub-optimal trx" entry
-               $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -81,7 +81,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
 
        public function testConns() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
-               $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -95,7 +95,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
 
        public function testMasterConns() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
-               $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -112,7 +112,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
 
        public function testReadQueryCount() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
-               $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
@@ -126,7 +126,7 @@ class TransactionProfilerTest extends PHPUnit_Framework_TestCase {
 
        public function testWriteQueryCount() {
                $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
-               $logger->expects( $this->exactly( 2 ) )->method( 'info' );
+               $logger->expects( $this->exactly( 2 ) )->method( 'warning' );
 
                $tp = new TransactionProfiler();
                $tp->setLogger( $logger );
index 93ab35c..232b0bb 100644 (file)
@@ -62,7 +62,7 @@ class ParserOptionsTest extends MediaWikiTestCase {
                        'No overrides' => [ true, [] ],
                        'In-key options are ok' => [ true, [
                                'thumbsize' => 1e100,
-                               'wrapclass' => false,
+                               'printable' => false,
                        ] ],
                        'Non-in-key options are not ok' => [ false, [
                                'removeComments' => false,
@@ -102,7 +102,7 @@ class ParserOptionsTest extends MediaWikiTestCase {
        }
 
        public static function provideOptionsHash() {
-               $used = [ 'wrapclass', 'printable' ];
+               $used = [ 'thumbsize', 'printable' ];
 
                $classWrapper = TestingAccessWrapper::newFromClass( ParserOptions::class );
                $classWrapper->getDefaults();
@@ -116,9 +116,9 @@ class ParserOptionsTest extends MediaWikiTestCase {
                        'Canonical options, used some options' => [ $used, 'canonical', [] ],
                        'Used some options, non-default values' => [
                                $used,
-                               'printable=1!wrapclass=foobar',
+                               'printable=1!thumbsize=200',
                                [
-                                       'wrapclass' => 'foobar',
+                                       'thumbsize' => 200,
                                        'printable' => true,
                                ]
                        ],
index 9642bbc..efcc4e0 100644 (file)
@@ -105,6 +105,8 @@ class ParserOutputTest extends MediaWikiTestCase {
                        'wgScriptPath' => '/w',
                        'wgScript' => '/w/index.php',
                ] );
+               $this->hideDeprecated( 'ParserOutput stateful allowTOC' );
+               $this->hideDeprecated( 'ParserOutput stateful enableSectionEditLinks' );
 
                $po = new ParserOutput( $text );
 
@@ -125,7 +127,7 @@ class ParserOutputTest extends MediaWikiTestCase {
        public static function provideGetText() {
                // phpcs:disable Generic.Files.LineLength
                $text = <<<EOF
-<p>Test document.
+<div class="mw-parser-output"><p>Test document.
 </p>
 <mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
 <ul>
@@ -150,13 +152,13 @@ class ParserOutputTest extends MediaWikiTestCase {
 </p>
 <h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
 <p>Three
-</p>
+</p></div>
 EOF;
 
                return [
                        'No stateless options, default state' => [
                                [], [], $text, <<<EOF
-<p>Test document.
+<div class="mw-parser-output"><p>Test document.
 </p>
 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
 <ul>
@@ -181,12 +183,12 @@ EOF;
 </p>
 <h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <p>Three
-</p>
+</p></div>
 EOF
                        ],
                        'No stateless options, TOC statefully disabled' => [
                                [], [ 'mTOCEnabled' => false ], $text, <<<EOF
-<p>Test document.
+<div class="mw-parser-output"><p>Test document.
 </p>
 
 <h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
@@ -200,12 +202,12 @@ EOF
 </p>
 <h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <p>Three
-</p>
+</p></div>
 EOF
                        ],
                        'No stateless options, section edits statefully disabled' => [
                                [], [ 'mEditSectionTokens' => false ], $text, <<<EOF
-<p>Test document.
+<div class="mw-parser-output"><p>Test document.
 </p>
 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
 <ul>
@@ -230,14 +232,14 @@ EOF
 </p>
 <h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
 <p>Three
-</p>
+</p></div>
 EOF
                        ],
                        'Stateless options override stateful settings' => [
                                [ 'allowTOC' => true, 'enableSectionEditLinks' => true ],
                                [ 'mTOCEnabled' => false, 'mEditSectionTokens' => false ],
                                $text, <<<EOF
-<p>Test document.
+<div class="mw-parser-output"><p>Test document.
 </p>
 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
 <ul>
@@ -262,12 +264,12 @@ EOF
 </p>
 <h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <p>Three
-</p>
+</p></div>
 EOF
                        ],
                        'Statelessly disable section edit links' => [
                                [ 'enableSectionEditLinks' => false ], [], $text, <<<EOF
-<p>Test document.
+<div class="mw-parser-output"><p>Test document.
 </p>
 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
 <ul>
@@ -292,13 +294,43 @@ EOF
 </p>
 <h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
 <p>Three
-</p>
+</p></div>
 EOF
                        ],
                        'Statelessly disable TOC' => [
                                [ 'allowTOC' => false ], [], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
+</p>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p></div>
+EOF
+                       ],
+                       'Statelessly unwrap text' => [
+                               [ 'unwrap' => true ], [], $text, <<<EOF
 <p>Test document.
 </p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
 
 <h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <p>One
@@ -314,6 +346,9 @@ EOF
 </p>
 EOF
                        ],
+                       'Unwrap without a mw-parser-output wrapper' => [
+                               [ 'unwrap' => true ], [], '<div class="foobar">Content</div>', '<div class="foobar">Content</div>'
+                       ],
                ];
                // phpcs:enable
        }
index 7e31cba..2fdaa18 100644 (file)
@@ -46,7 +46,6 @@ class TagHooksTest extends MediaWikiTestCase {
        private function getParserOptions() {
                global $wgContLang;
                $popt = ParserOptions::newFromUserAndLang( new User, $wgContLang );
-               $popt->setWrapOutputClass( false );
                return $popt;
        }
 
@@ -63,7 +62,7 @@ class TagHooksTest extends MediaWikiTestCase {
                        Title::newFromText( 'Test' ),
                        $this->getParserOptions()
                );
-               $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() );
+               $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText( [ 'unwrap' => true ] ) );
 
                $parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
        }
@@ -98,7 +97,7 @@ class TagHooksTest extends MediaWikiTestCase {
                        Title::newFromText( 'Test' ),
                        $this->getParserOptions()
                );
-               $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() );
+               $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText( [ 'unwrap' => true ] ) );
 
                $parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
        }
index 5943294..06c146c 100644 (file)
@@ -6,18 +6,21 @@
 
        /**
         * Make a safe copy of localEnv:
-        * - Creates a copy so that when the same object reference to module hooks is
-        *   used by multipe test hooks, our QUnit.module extension will not wrap the
-        *   callbacks multiple times. Instead, they wrap using a new object.
-        * - Normalise setup/teardown to avoid having to repeat this in each extension
+        * - Creates a new object that inherits, instead of modifying the original.
+        *   This prevents recursion in the event that a test suite stores inherits
+        *   hooks object statically and passes it to multiple QUnit.module() calls.
+        * - Supporting QUnit 1.x 'setup' and 'teardown' hooks
         *   (deprecated in QUnit 1.16, removed in QUnit 2).
-        * - Strip any other properties.
         */
        function makeSafeEnv( localEnv ) {
-               return {
-                       beforeEach: localEnv.setup || localEnv.beforeEach,
-                       afterEach: localEnv.teardown || localEnv.afterEach
-               };
+               var wrap = localEnv ? Object.create( localEnv ) : {};
+               if ( wrap.setup ) {
+                       wrap.beforeEach = wrap.beforeEach || wrap.setup;
+               }
+               if ( wrap.teardown ) {
+                       wrap.afterEach = wrap.afterEach || wrap.teardown;
+               }
+               return wrap;
        }
 
        /**
                useFakeTimers: false,
                useFakeServer: false
        };
-       // Extend QUnit.module to provide a Sinon sandbox.
+       // Extend QUnit.module with:
+       // - Add support for QUnit 1.x 'setup' and 'teardown' hooks
+       // - Add a Sinon sandbox to the test context.
+       // - Add a test fixture to the test context.
        ( function () {
                var orgModule = QUnit.module;
                QUnit.module = function ( name, localEnv, executeNow ) {
-                       var orgBeforeEach, orgAfterEach, orgExecute;
+                       var orgExecute, orgBeforeEach, orgAfterEach;
                        if ( nested ) {
-                               // In a nested module, don't re-run our handlers.
+                               // In a nested module, don't re-add our hooks, QUnit does that already.
                                return orgModule.apply( this, arguments );
                        }
                        if ( arguments.length === 2 && typeof localEnv === 'function' ) {
                                };
                        }
 
-                       localEnv = localEnv || {};
+                       localEnv = makeSafeEnv( localEnv );
                        orgBeforeEach = localEnv.beforeEach;
                        orgAfterEach = localEnv.afterEach;
+
                        localEnv.beforeEach = function () {
+                               // Sinon sandbox
                                var config = sinon.getConfig( sinon.config );
                                config.injectInto = this;
                                sinon.sandbox.create( config );
 
-                               if ( orgBeforeEach ) {
-                                       return orgBeforeEach.apply( this, arguments );
-                               }
-                       };
-                       localEnv.afterEach = function () {
-                               var ret;
-                               if ( orgAfterEach ) {
-                                       ret = orgAfterEach.apply( this, arguments );
-                               }
-
-                               this.sandbox.verifyAndRestore();
-                               return ret;
-                       };
-                       return orgModule( name, localEnv, executeNow );
-               };
-       }() );
-
-       // Extend QUnit.module to provide a fixture element.
-       ( function () {
-               var orgModule = QUnit.module;
-               QUnit.module = function ( name, localEnv, executeNow ) {
-                       var orgBeforeEach, orgAfterEach;
-                       if ( nested ) {
-                               // In a nested module, don't re-run our handlers.
-                               return orgModule.apply( this, arguments );
-                       }
-                       if ( arguments.length === 2 && typeof localEnv === 'function' ) {
-                               executeNow = localEnv;
-                               localEnv = undefined;
-                       }
-
-                       localEnv = localEnv || {};
-                       orgBeforeEach = localEnv.beforeEach;
-                       orgAfterEach = localEnv.afterEach;
-                       localEnv.beforeEach = function () {
+                               // Fixture element
                                this.fixture = document.createElement( 'div' );
                                this.fixture.id = 'qunit-fixture';
                                document.body.appendChild( this.fixture );
                                if ( orgAfterEach ) {
                                        ret = orgAfterEach.apply( this, arguments );
                                }
-
+                               this.sandbox.verifyAndRestore();
                                this.fixture.parentNode.removeChild( this.fixture );
                                return ret;
                        };
-                       return orgModule( name, localEnv, executeNow );
-               };
-       }() );
 
-       // Extend QUnit.module to normalise localEnv.
-       // NOTE: This MUST be the last QUnit.module extension so that the above extensions
-       // may safely modify the object and assume beforeEach/afterEach.
-       ( function () {
-               var orgModule = QUnit.module;
-               QUnit.module = function ( name, localEnv, executeNow ) {
-                       if ( typeof localEnv === 'object' ) {
-                               localEnv = makeSafeEnv( localEnv );
-                       }
                        return orgModule( name, localEnv, executeNow );
                };
        }() );
                }
 
                return function ( orgEnv ) {
-                       var localEnv = orgEnv ? makeSafeEnv( orgEnv ) : {};
-                       // MediaWiki env testing
-                       localEnv.config = orgEnv && orgEnv.config || {};
-                       localEnv.messages = orgEnv && orgEnv.messages || {};
-
-                       return {
-                               beforeEach: function () {
-                                       // Greetings, mock environment!
-                                       mw.config = new MwMap();
-                                       mw.config.set( freshConfigCopy( localEnv.config ) );
-                                       mw.messages = new MwMap();
-                                       mw.messages.set( freshMessagesCopy( localEnv.messages ) );
-                                       // Update reference to mw.messages
-                                       mw.jqueryMsg.setParserDefaults( {
-                                               messages: mw.messages
-                                       } );
-
-                                       this.suppressWarnings = suppressWarnings;
-                                       this.restoreWarnings = restoreWarnings;
+                       var localEnv, orgBeforeEach, orgAfterEach;
 
-                                       // Start tracking ajax requests
-                                       $( document ).on( 'ajaxSend', trackAjax );
-
-                                       if ( localEnv.beforeEach ) {
-                                               return localEnv.beforeEach.apply( this, arguments );
-                                       }
-                               },
+                       localEnv = makeSafeEnv( orgEnv );
+                       // MediaWiki env testing
+                       localEnv.config = localEnv.config || {};
+                       localEnv.messages = localEnv.messages || {};
 
-                               afterEach: function () {
-                                       var timers, pending, $activeLen, ret;
+                       orgBeforeEach = localEnv.beforeEach;
+                       orgAfterEach = localEnv.afterEach;
 
-                                       if ( localEnv.afterEach ) {
-                                               ret = localEnv.afterEach.apply( this, arguments );
-                                       }
+                       localEnv.beforeEach = function () {
+                               // Greetings, mock environment!
+                               mw.config = new MwMap();
+                               mw.config.set( freshConfigCopy( localEnv.config ) );
+                               mw.messages = new MwMap();
+                               mw.messages.set( freshMessagesCopy( localEnv.messages ) );
+                               // Update reference to mw.messages
+                               mw.jqueryMsg.setParserDefaults( {
+                                       messages: mw.messages
+                               } );
+
+                               this.suppressWarnings = suppressWarnings;
+                               this.restoreWarnings = restoreWarnings;
+
+                               // Start tracking ajax requests
+                               $( document ).on( 'ajaxSend', trackAjax );
 
-                                       // Stop tracking ajax requests
-                                       $( document ).off( 'ajaxSend', trackAjax );
+                               if ( orgBeforeEach ) {
+                                       return orgBeforeEach.apply( this, arguments );
+                               }
+                       };
+                       localEnv.afterEach = function () {
+                               var timers, pending, $activeLen, ret;
 
-                                       // As a convenience feature, automatically restore warnings if they're
-                                       // still suppressed by the end of the test.
-                                       restoreWarnings();
+                               if ( orgAfterEach ) {
+                                       ret = orgAfterEach.apply( this, arguments );
+                               }
 
-                                       // Farewell, mock environment!
-                                       mw.config = liveConfig;
-                                       mw.messages = liveMessages;
-                                       // Restore reference to mw.messages
-                                       mw.jqueryMsg.setParserDefaults( {
-                                               messages: liveMessages
+                               // Stop tracking ajax requests
+                               $( document ).off( 'ajaxSend', trackAjax );
+
+                               // As a convenience feature, automatically restore warnings if they're
+                               // still suppressed by the end of the test.
+                               restoreWarnings();
+
+                               // Farewell, mock environment!
+                               mw.config = liveConfig;
+                               mw.messages = liveMessages;
+                               // Restore reference to mw.messages
+                               mw.jqueryMsg.setParserDefaults( {
+                                       messages: liveMessages
+                               } );
+
+                               // Tests should use fake timers or wait for animations to complete
+                               // Check for incomplete animations/requests/etc and throw if there are any.
+                               if ( $.timers && $.timers.length !== 0 ) {
+                                       timers = $.timers.length;
+                                       $.each( $.timers, function ( i, timer ) {
+                                               var node = timer.elem;
+                                               mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' +
+                                                       mw.html.element( node.nodeName.toLowerCase(), $( node ).getAttrs() )
+                                               );
                                        } );
+                                       // Force animations to stop to give the next test a clean start
+                                       $.timers = [];
+                                       $.fx.stop();
 
-                                       // Tests should use fake timers or wait for animations to complete
-                                       // Check for incomplete animations/requests/etc and throw if there are any.
-                                       if ( $.timers && $.timers.length !== 0 ) {
-                                               timers = $.timers.length;
-                                               $.each( $.timers, function ( i, timer ) {
-                                                       var node = timer.elem;
-                                                       mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' +
-                                                               mw.html.element( node.nodeName.toLowerCase(), $( node ).getAttrs() )
-                                                       );
-                                               } );
-                                               // Force animations to stop to give the next test a clean start
-                                               $.timers = [];
-                                               $.fx.stop();
-
-                                               throw new Error( 'Unfinished animations: ' + timers );
-                                       }
+                                       throw new Error( 'Unfinished animations: ' + timers );
+                               }
 
-                                       // Test should use fake XHR, wait for requests, or call abort()
-                                       $activeLen = $.active;
-                                       if ( $activeLen !== undefined && $activeLen !== 0 ) {
-                                               pending = ajaxRequests.filter( function ( ajax ) {
-                                                       return ajax.xhr.state() === 'pending';
-                                               } );
-                                               if ( pending.length !== $activeLen ) {
-                                                       mw.log.warn( 'Pending requests does not match jQuery.active count' );
-                                               }
-                                               // Force requests to stop to give the next test a clean start
-                                               ajaxRequests.forEach( function ( ajax, i ) {
-                                                       mw.log.warn(
-                                                               'AJAX request #' + i + ' (state: ' + ajax.xhr.state() + ')',
-                                                               ajax.options
-                                                       );
-                                                       ajax.xhr.abort();
-                                               } );
-                                               ajaxRequests = [];
-
-                                               throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
+                               // Test should use fake XHR, wait for requests, or call abort()
+                               $activeLen = $.active;
+                               if ( $activeLen !== undefined && $activeLen !== 0 ) {
+                                       pending = ajaxRequests.filter( function ( ajax ) {
+                                               return ajax.xhr.state() === 'pending';
+                                       } );
+                                       if ( pending.length !== $activeLen ) {
+                                               mw.log.warn( 'Pending requests does not match jQuery.active count' );
                                        }
+                                       // Force requests to stop to give the next test a clean start
+                                       ajaxRequests.forEach( function ( ajax, i ) {
+                                               mw.log.warn(
+                                                       'AJAX request #' + i + ' (state: ' + ajax.xhr.state() + ')',
+                                                       ajax.options
+                                               );
+                                               ajax.xhr.abort();
+                                       } );
+                                       ajaxRequests = [];
 
-                                       return ret;
+                                       throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
                                }
+
+                               return ret;
                        };
+                       return localEnv;
                };
        }() );
 
                } );
        } );
 
+       QUnit.module( 'testrunner-hooks-outer', function () {
+               var beforeHookWasExecuted = false,
+                       afterHookWasExecuted = false;
+               QUnit.module( 'testrunner-hooks', {
+                       before: function () {
+                               beforeHookWasExecuted = true;
+
+                               // This way we can be sure that module `testrunner-hook-after` will always
+                               // be executed after module `testrunner-hooks`
+                               QUnit.module( 'testrunner-hooks-after' );
+                               QUnit.test(
+                                       '`after` hook for module `testrunner-hooks` was executed',
+                                       function ( assert ) {
+                                               assert.ok( afterHookWasExecuted );
+                                       }
+                               );
+                       },
+                       after: function () {
+                               afterHookWasExecuted = true;
+                       }
+               } );
+
+               QUnit.test( '`before` hook was executed', function ( assert ) {
+                       assert.ok( beforeHookWasExecuted );
+               } );
+       } );
+
 }( jQuery, mediaWiki, QUnit ) );
index 02ac0b0..c4b40dc 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -337,7 +337,16 @@ function wfStreamThumb( array $params ) {
                return;
        }
 
-       list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName, $thumbPath );
+       $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
+
+       if ( strlen( $thumbProxyUrl ) ) {
+               wfProxyThumbnailRequest( $img, $thumbName );
+               // No local fallback when in proxy mode
+               return;
+       } else {
+               // Generate the thumbnail locally
+               list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName, $thumbPath );
+       }
 
        /** @var MediaTransformOutput|MediaTransformError|bool $thumb */
 
@@ -377,6 +386,43 @@ function wfStreamThumb( array $params ) {
        }
 }
 
+/**
+ * Proxies thumbnail request to a service that handles thumbnailing
+ *
+ * @param File $img
+ * @param string $thumbName
+ */
+function wfProxyThumbnailRequest( $img, $thumbName ) {
+       $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
+
+       // Instead of generating the thumbnail ourselves, we proxy the request to another service
+       $thumbProxiedUrl = $thumbProxyUrl . $img->getThumbRel( $thumbName );
+
+       $req = MWHttpRequest::factory( $thumbProxiedUrl );
+       $secret = $img->getRepo()->getThumbProxySecret();
+
+       // Pass a secret key shared with the proxied service if any
+       if ( strlen( $secret ) ) {
+               $req->setHeader( 'X-Swift-Secret', $secret );
+       }
+
+       // Send request to proxied service
+       $status = $req->execute();
+
+       // Simply serve the response from the proxied service as-is
+       header( 'HTTP/1.1 ' . $req->getStatus() );
+
+       $headers = $req->getResponseHeaders();
+
+       foreach ( $headers as $key => $values ) {
+               foreach ( $values as $value ) {
+                       header( $key . ': ' . $value, false );
+               }
+       }
+
+       echo $req->getContent();
+}
+
 /**
  * Actually try to generate a new thumbnail
  *